Repository: ponfee/commons-core Branch: master Commit: dfd2f363adc9 Files: 670 Total size: 3.3 MB Directory structure: gitextract_2bawa1vq/ ├── .editorconfig ├── .github/ │ └── workflows/ │ └── build-with-maven.yml ├── .gitignore ├── .mvn/ │ └── wrapper/ │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── LICENSE ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src/ ├── main/ │ ├── java/ │ │ └── cn/ │ │ └── ponfee/ │ │ └── commons/ │ │ ├── base/ │ │ │ ├── Initializable.java │ │ │ ├── NoArgMethodInvoker.java │ │ │ ├── Predicates.java │ │ │ ├── PrimitiveTypes.java │ │ │ ├── Releasable.java │ │ │ ├── Symbol.java │ │ │ ├── TimestampProvider.java │ │ │ └── tuple/ │ │ │ ├── Tuple.java │ │ │ ├── Tuple0.java │ │ │ ├── Tuple1.java │ │ │ ├── Tuple2.java │ │ │ ├── Tuple3.java │ │ │ ├── Tuple4.java │ │ │ ├── Tuple5.java │ │ │ ├── Tuple6.java │ │ │ ├── Tuple7.java │ │ │ ├── Tuple8.java │ │ │ └── Tuple9.java │ │ ├── collect/ │ │ │ ├── ArrayHashKey.java │ │ │ ├── ByteArrayComparator.java │ │ │ ├── ByteArrayTrait.java │ │ │ ├── ByteArrayWrapper.java │ │ │ ├── Collects.java │ │ │ ├── Comparators.java │ │ │ ├── DelegatedIntSpliterator.java │ │ │ ├── DoubleListViewer.java │ │ │ ├── FilterableIterator.java │ │ │ ├── ImmutableArrayList.java │ │ │ ├── ImmutableHashList.java │ │ │ ├── LRUCache.java │ │ │ ├── Maps.java │ │ │ ├── StreamForker.java │ │ │ └── ValueSortedMap.java │ │ ├── concurrent/ │ │ │ ├── AsyncBatchProcessor.java │ │ │ ├── AsyncDelayedExecutor.java │ │ │ ├── DelayedData.java │ │ │ ├── MultithreadExecutors.java │ │ │ ├── NamedThreadFactory.java │ │ │ ├── SingleThreadShutdownHook.java │ │ │ ├── ThreadPoolExecutors.java │ │ │ ├── ThreadPoolMonitor.java │ │ │ ├── Threads.java │ │ │ └── TracedRunnable.java │ │ ├── constrain/ │ │ │ ├── ConstrainParam.java │ │ │ ├── Constraint.java │ │ │ ├── Constraints.java │ │ │ ├── FailFastValidatorFactoryBean.java │ │ │ ├── FieldValidator.java │ │ │ ├── Jsr303Validator.java │ │ │ ├── MethodValidator.java │ │ │ └── ParamValidator.java │ │ ├── dag/ │ │ │ ├── DAGEdge.java │ │ │ ├── DAGExpressionParser.java │ │ │ └── DAGNode.java │ │ ├── data/ │ │ │ ├── DataSourceFactory.java │ │ │ ├── DataSourceNaming.java │ │ │ ├── DruidDataSourceFactory.java │ │ │ ├── MultipleDataSourceAdvisor.java │ │ │ ├── MultipleDataSourceAspect.java │ │ │ ├── NamedDataSource.java │ │ │ └── lookup/ │ │ │ ├── DataSourceLookup.java │ │ │ ├── MultipleCachedDataSource.java │ │ │ ├── MultipleDataSourceContext.java │ │ │ ├── MultipleFixedDataSource.java │ │ │ └── MultipleScalableDataSource.java │ │ ├── date/ │ │ │ ├── CustomLocalDateTimeDeserializer.java │ │ │ ├── DatePeriods.java │ │ │ ├── Dates.java │ │ │ ├── JavaUtilDateFormat.java │ │ │ └── LocalDateTimeFormat.java │ │ ├── exception/ │ │ │ ├── BaseCheckedException.java │ │ │ ├── BaseUncheckedException.java │ │ │ ├── ServerException.java │ │ │ ├── Throwables.java │ │ │ ├── UnauthorizedException.java │ │ │ └── UnimplementedException.java │ │ ├── export/ │ │ │ ├── AbstractCsvExporter.java │ │ │ ├── AbstractDataExporter.java │ │ │ ├── AbstractSplitExporter.java │ │ │ ├── CellStyleOptions.java │ │ │ ├── ConsoleExporter.java │ │ │ ├── CsvFileExporter.java │ │ │ ├── CsvStringExporter.java │ │ │ ├── CsvWriteExporter.java │ │ │ ├── DataExporter.java │ │ │ ├── ExcelExporter.java │ │ │ ├── HtmlExporter.java │ │ │ ├── SplitCsvFileExporter.java │ │ │ ├── SplitExcelExporter.java │ │ │ ├── Table.java │ │ │ ├── Thead.java │ │ │ └── Tmeta.java │ │ ├── extract/ │ │ │ ├── CsvExtractor.java │ │ │ ├── DataExtractor.java │ │ │ ├── DataExtractorBuilder.java │ │ │ ├── ExcelExtractor.java │ │ │ ├── ExtractableDataSource.java │ │ │ ├── ValidateResult.java │ │ │ └── streaming/ │ │ │ ├── StreamingExcelExtractor.java │ │ │ └── xls/ │ │ │ ├── HSSFStreamingCell.java │ │ │ ├── HSSFStreamingReader.java │ │ │ ├── HSSFStreamingRow.java │ │ │ ├── HSSFStreamingSheet.java │ │ │ └── HSSFStreamingWorkbook.java │ │ ├── http/ │ │ │ ├── ContentType.java │ │ │ ├── Http.java │ │ │ ├── HttpException.java │ │ │ ├── HttpParams.java │ │ │ ├── HttpRequest.java │ │ │ └── HttpStatus.java │ │ ├── io/ │ │ │ ├── ByteOrderMarks.java │ │ │ ├── CharsetDetector.java │ │ │ ├── Closeables.java │ │ │ ├── ExtendedGZIPOutputStream.java │ │ │ ├── FileTransformer.java │ │ │ ├── Files.java │ │ │ ├── GzipProcessor.java │ │ │ ├── HumanReadables.java │ │ │ ├── PrereadInputStream.java │ │ │ ├── StringPrintWriter.java │ │ │ ├── WrappedBufferedReader.java │ │ │ ├── WrappedBufferedWriter.java │ │ │ └── charset/ │ │ │ ├── BytesDetector.java │ │ │ ├── CodepageDetector.java │ │ │ ├── JchardetDetector.java │ │ │ └── TikaDetector.java │ │ ├── jce/ │ │ │ ├── CryptoProvider.java │ │ │ ├── DigestAlgorithms.java │ │ │ ├── ECParameters.java │ │ │ ├── HmacAlgorithms.java │ │ │ ├── Providers.java │ │ │ ├── RSACipherPaddings.java │ │ │ ├── RSASignAlgorithms.java │ │ │ ├── cert/ │ │ │ │ ├── CertPKCS1Verifier.java │ │ │ │ ├── CertPKCS7Verifier.java │ │ │ │ ├── CertSignedVerifier.java │ │ │ │ ├── ObjectIdentifiers.java │ │ │ │ ├── RepairX500Principal.java │ │ │ │ ├── X509CertGenerator.java │ │ │ │ ├── X509CertInfo.java │ │ │ │ └── X509CertUtils.java │ │ │ ├── digest/ │ │ │ │ ├── DigestUtils.java │ │ │ │ └── HmacUtils.java │ │ │ ├── implementation/ │ │ │ │ ├── Cryptor.java │ │ │ │ ├── Key.java │ │ │ │ ├── NoopCryptor.java │ │ │ │ ├── digest/ │ │ │ │ │ ├── RipeMD160Digest.java │ │ │ │ │ └── SHA1Digest.java │ │ │ │ ├── ecc/ │ │ │ │ │ ├── ECCryptor.java │ │ │ │ │ ├── ECKey.java │ │ │ │ │ ├── ECPoint.java │ │ │ │ │ ├── EllipticCurve.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ ├── rsa/ │ │ │ │ │ ├── AbstractRSACryptor.java │ │ │ │ │ ├── RSAHashCryptor.java │ │ │ │ │ ├── RSAKey.java │ │ │ │ │ ├── RSANoPaddingCryptor.java │ │ │ │ │ ├── RSAPKCS1PaddingCryptor.java │ │ │ │ │ ├── RSASigner.java │ │ │ │ │ └── package-info.java │ │ │ │ └── symmetric/ │ │ │ │ └── RC4.java │ │ │ ├── package-info.java │ │ │ ├── passwd/ │ │ │ │ ├── BCrypt.java │ │ │ │ ├── Crypt.java │ │ │ │ ├── PBKDF2.java │ │ │ │ └── SCrypt.java │ │ │ ├── pkcs/ │ │ │ │ ├── CryptoMessageSyntax.java │ │ │ │ ├── PKCS1Signature.java │ │ │ │ └── PKCS7Signature.java │ │ │ ├── security/ │ │ │ │ ├── DHKeyExchanger.java │ │ │ │ ├── DSASigner.java │ │ │ │ ├── ECDHKeyExchanger.java │ │ │ │ ├── ECDSASigner.java │ │ │ │ ├── KeyStoreResolver.java │ │ │ │ ├── RSACryptor.java │ │ │ │ ├── RSAPrivateKeys.java │ │ │ │ └── RSAPublicKeys.java │ │ │ ├── sm/ │ │ │ │ ├── SM2.java │ │ │ │ ├── SM2KeyExchanger.java │ │ │ │ ├── SM3Digest.java │ │ │ │ └── SM4.java │ │ │ └── symmetric/ │ │ │ ├── Algorithm.java │ │ │ ├── Mode.java │ │ │ ├── PBECryptor.java │ │ │ ├── PBECryptorBuilder.java │ │ │ ├── Padding.java │ │ │ ├── SymmetricCryptor.java │ │ │ └── SymmetricCryptorBuilder.java │ │ ├── json/ │ │ │ ├── FastjsonMoney.java │ │ │ ├── FastjsonPropertyFilter.java │ │ │ ├── FastjsonTypeReferences.java │ │ │ ├── JacksonCurrencyUnit.java │ │ │ ├── JacksonDate.java │ │ │ ├── JacksonMoney.java │ │ │ ├── JacksonTypeReferences.java │ │ │ └── Jsons.java │ │ ├── limit/ │ │ │ ├── current/ │ │ │ │ ├── CurrentLimiter.java │ │ │ │ └── GuavaCurrentLimiter.java │ │ │ └── request/ │ │ │ ├── ConcurrentMapRequestLimiter.java │ │ │ ├── HttpSessionRequestLimiter.java │ │ │ ├── RequestLimitException.java │ │ │ └── RequestLimiter.java │ │ ├── log/ │ │ │ ├── LogAnnotation.java │ │ │ ├── LogInfo.java │ │ │ └── LogRecorder.java │ │ ├── math/ │ │ │ ├── FailureRatioActuary.java │ │ │ ├── Maths.java │ │ │ ├── Numbers.java │ │ │ └── WrappedBigDecimal.java │ │ ├── model/ │ │ │ ├── AbstractDataConverter.java │ │ │ ├── BaseEntity.java │ │ │ ├── CodeMsg.java │ │ │ ├── Form.java │ │ │ ├── MapDataConverter.java │ │ │ ├── Null.java │ │ │ ├── Page.java │ │ │ ├── PageBoundsResolver.java │ │ │ ├── PageHandler.java │ │ │ ├── PageParameter.java │ │ │ ├── PaginationHtmlBuilder.java │ │ │ ├── RemovableTypedKeyValue.java │ │ │ ├── RemovableTypedMap.java │ │ │ ├── Result.java │ │ │ ├── ResultCode.java │ │ │ ├── SearchAfter.java │ │ │ ├── SortField.java │ │ │ ├── SortOrder.java │ │ │ ├── ToJsonString.java │ │ │ ├── TypedHashMap.java │ │ │ ├── TypedKeyValue.java │ │ │ ├── TypedLinkedHashMap.java │ │ │ ├── TypedLinkedMultiValueMap.java │ │ │ ├── TypedMap.java │ │ │ ├── TypedMapWrapper.java │ │ │ └── TypedParameter.java │ │ ├── mybatis/ │ │ │ ├── MultipleSqlSessionTemplate.java │ │ │ ├── PackagesSqlSessionFactoryBean.java │ │ │ ├── SqlHelper.java │ │ │ └── SqlMapper.java │ │ ├── parser/ │ │ │ ├── DateUDF.java │ │ │ └── ELParser.java │ │ ├── pdf/ │ │ │ ├── PdfWaterMark.java │ │ │ └── sign/ │ │ │ ├── PdfSignature.java │ │ │ ├── Signer.java │ │ │ └── Stamp.java │ │ ├── reflect/ │ │ │ ├── BeanConverts.java │ │ │ ├── BeanCopiers.java │ │ │ ├── BeanMaps.java │ │ │ ├── ClassUtils.java │ │ │ ├── Fields.java │ │ │ └── GenericUtils.java │ │ ├── resource/ │ │ │ ├── ClassPathResourceLoader.java │ │ │ ├── FileSystemResourceLoader.java │ │ │ ├── Resource.java │ │ │ ├── ResourceLoaderFacade.java │ │ │ ├── ResourceScanner.java │ │ │ └── WebappResourceLoader.java │ │ ├── schema/ │ │ │ ├── DataColumn.java │ │ │ ├── DataStructure.java │ │ │ ├── DataStructures.java │ │ │ ├── DataTable.java │ │ │ ├── DataType.java │ │ │ ├── GridTable.java │ │ │ ├── NormalStructure.java │ │ │ ├── PlainStructure.java │ │ │ ├── TableStructure.java │ │ │ └── json/ │ │ │ ├── JsonExtractUtils.java │ │ │ ├── JsonId.java │ │ │ └── JsonTree.java │ │ ├── serial/ │ │ │ ├── ByteArraySerializer.java │ │ │ ├── ByteArrayTraitSerializer.java │ │ │ ├── FstSerializer.java │ │ │ ├── HessianSerializer.java │ │ │ ├── JdkSerializer.java │ │ │ ├── JsonSerializer.java │ │ │ ├── KryoSerializer.java │ │ │ ├── NullSerializer.java │ │ │ ├── ProtostuffSerializer.java │ │ │ ├── SerializationException.java │ │ │ ├── Serializer.java │ │ │ ├── StringSerializer.java │ │ │ ├── ToStringSerializer.java │ │ │ └── WrappedSerializer.java │ │ ├── spring/ │ │ │ ├── BaseController.java │ │ │ ├── JdbcTemplateWrapper.java │ │ │ ├── LocalizedMethodArgumentResolver.java │ │ │ ├── LocalizedMethodArguments.java │ │ │ ├── MarkRpcController.java │ │ │ ├── PageMethodArgumentResolver.java │ │ │ ├── ProxyUtils.java │ │ │ ├── RpcController.java │ │ │ ├── SpringContextHolder.java │ │ │ ├── SpringUtils.java │ │ │ ├── TransactionUtils.java │ │ │ ├── TypedMapMethodArgumentResolver.java │ │ │ ├── YamlProperties.java │ │ │ └── YamlPropertySourceFactory.java │ │ ├── tree/ │ │ │ ├── BaseNode.java │ │ │ ├── FlatNode.java │ │ │ ├── MapTreeTrait.java │ │ │ ├── NodeId.java │ │ │ ├── NodePath.java │ │ │ ├── PlainNode.java │ │ │ ├── SiblingNodesComparator.java │ │ │ ├── TreeNode.java │ │ │ ├── TreeNodeBuilder.java │ │ │ ├── TreeTrait.java │ │ │ └── print/ │ │ │ ├── BinaryTreePrinter.java │ │ │ ├── BinaryTreePrinterBuilder.java │ │ │ └── MultiwayTreePrinter.java │ │ ├── util/ │ │ │ ├── Asserts.java │ │ │ ├── Base58.java │ │ │ ├── Base64UrlSafe.java │ │ │ ├── Bytes.java │ │ │ ├── CRC16.java │ │ │ ├── Captchas.java │ │ │ ├── Colors.java │ │ │ ├── ConsistentHash.java │ │ │ ├── CurrencyEnum.java │ │ │ ├── Enums.java │ │ │ ├── ExtendMethodHandles.java │ │ │ ├── FailRetryTemplate.java │ │ │ ├── Holder.java │ │ │ ├── IdcardResolver.java │ │ │ ├── ImageUtils.java │ │ │ ├── LazyLoader.java │ │ │ ├── MavenProjects.java │ │ │ ├── MessageFormats.java │ │ │ ├── Money.java │ │ │ ├── Networks.java │ │ │ ├── ObjectUtils.java │ │ │ ├── PropertiesUtils.java │ │ │ ├── RegexUtils.java │ │ │ ├── SecureRandoms.java │ │ │ ├── Snowflake.java │ │ │ ├── SqlUtils.java │ │ │ ├── Strings.java │ │ │ ├── SynchronizedCaches.java │ │ │ ├── TimingWheel.java │ │ │ ├── URLCodes.java │ │ │ ├── UuidUtils.java │ │ │ ├── Wechats.java │ │ │ └── ZipUtils.java │ │ ├── web/ │ │ │ ├── AbstractWebExceptionHandler.java │ │ │ ├── DevicePlatform.java │ │ │ ├── DeviceType.java │ │ │ ├── GlobalExceptionHandler.java │ │ │ ├── GlobalExceptionResolver.java │ │ │ ├── LiteDevice.java │ │ │ ├── LiteDeviceResolver.java │ │ │ ├── WebContext.java │ │ │ └── WebUtils.java │ │ ├── ws/ │ │ │ ├── JAXWS.java │ │ │ └── adapter/ │ │ │ ├── ListMapAdapter.java │ │ │ ├── ListMapNormalAdapter.java │ │ │ ├── MapAdapter.java │ │ │ ├── MapNormalAdapter.java │ │ │ ├── MarshalJsonAdapter.java │ │ │ ├── MarshalJsonResult.java │ │ │ ├── MarshalJsonXml.java │ │ │ ├── ResultDataJsonAdapter.java │ │ │ ├── ResultDataJsonPageAdapter.java │ │ │ ├── ResultListAdapter.java │ │ │ ├── ResultListMapAdapter.java │ │ │ ├── ResultListMapNormalAdapter.java │ │ │ ├── ResultListObjectAdapter.java │ │ │ ├── ResultListObjectArrayAdapter.java │ │ │ ├── ResultListStringAdapter.java │ │ │ ├── ResultMapAdapter.java │ │ │ ├── ResultMapNormalAdapter.java │ │ │ ├── ResultPageAdapter.java │ │ │ ├── ResultPageMapAdapter.java │ │ │ ├── ResultPageMapAdapter.java.bak │ │ │ ├── ResultPageMapNormalAdapter.java │ │ │ ├── ResultPageObjectAdapter.java │ │ │ ├── ResultPageObjectArrayAdapter.java │ │ │ ├── ResultSetAdapter.java │ │ │ ├── ResultSetStringAdapter.java │ │ │ ├── model/ │ │ │ │ ├── ArrayItem.java │ │ │ │ ├── MapEntry.java │ │ │ │ ├── MapItem.java │ │ │ │ ├── MapItemArray.java │ │ │ │ ├── TransitPage.java │ │ │ │ └── package-info.java │ │ │ └── package-info.java │ │ └── xml/ │ │ ├── SimpleXmlHandler.java │ │ ├── XmlException.java │ │ ├── XmlMap.java │ │ ├── XmlReader.java │ │ └── XmlWriter.java │ └── resources/ │ ├── log4j2.xml.template │ └── mybatis-conf.xml.template └── test/ ├── java/ │ ├── cn/ │ │ └── ponfee/ │ │ └── commons/ │ │ ├── Options.java │ │ ├── SpringBaseTest.java │ │ ├── SpringBootTest.java │ │ ├── WebServiceCxfTest.java │ │ ├── WebServiceJaxTest.java │ │ ├── base/ │ │ │ ├── MethodInvokerTest.java │ │ │ └── TupleTest.java │ │ ├── boolm/ │ │ │ ├── GuavaBloomFilterTest.java │ │ │ ├── JdkBloomFilter.java │ │ │ ├── RedisBloomFilterTest.java │ │ │ └── VisitedFrontier.java │ │ ├── cache/ │ │ │ ├── Cache.java │ │ │ ├── CacheBuilder.java │ │ │ ├── CacheValue.java │ │ │ ├── RemovalListener.java │ │ │ └── RemovalNotification.java │ │ ├── collects/ │ │ │ ├── AbstractArrayList.java │ │ │ ├── ByteArrayList.java │ │ │ ├── ByteArrayListTest.java │ │ │ ├── DoubleListViewerTest.java │ │ │ ├── ImmutableArrayList1.java │ │ │ ├── ImmutableArrayListTest.java │ │ │ ├── IntArrayList.java │ │ │ ├── ListTest.java │ │ │ └── LongArrayList.java │ │ ├── concurrent/ │ │ │ ├── ThreadPoolTest.java │ │ │ └── ThreadPoolTestUtils.java │ │ ├── dag/ │ │ │ └── DAGExpressionParserTest.java │ │ ├── data/ │ │ │ ├── ExtendedDruidPasswordCallback.java │ │ │ └── JSONExtractUtilsTest.java │ │ ├── date/ │ │ │ ├── DateFormatTest.java │ │ │ ├── DatePeriodCalculatorTest.java │ │ │ ├── DatePeriodsTest.java │ │ │ ├── DatesTest.java │ │ │ ├── JavaUtilDateFormatTest.java │ │ │ └── LocalDateTimeFormatTest.java │ │ ├── event/ │ │ │ └── EventBusTest.java │ │ ├── exception/ │ │ │ └── ThrowablesTest.java │ │ ├── innerclass/ │ │ │ ├── MyInterface.java │ │ │ └── TryUsingAnonymousClass.java │ │ ├── io/ │ │ │ ├── BeforeReadInputStreamTest.java │ │ │ ├── CopyrightTest.java │ │ │ ├── FileTransformerTest.java │ │ │ ├── FileTypeDetector.java │ │ │ ├── FilesTest.java │ │ │ ├── WindowsBomTest.java │ │ │ ├── WrappedBufferedReaderTest.java │ │ │ └── file/ │ │ │ ├── ASCII.txt │ │ │ ├── Big5.txt │ │ │ ├── EUC-KR.txt │ │ │ ├── GB18030.txt │ │ │ ├── GB2312.txt │ │ │ ├── GBK.txt │ │ │ ├── KOI8-R.txt │ │ │ ├── Shift_JIS.txt │ │ │ ├── UTF-8-BOM.txt │ │ │ └── UTF-8.txt │ │ ├── jce/ │ │ │ ├── DesgitTest.java │ │ │ ├── PBECryptorTest.java │ │ │ ├── PasswdTest.java │ │ │ ├── SCryptTester.java │ │ │ └── security/ │ │ │ ├── DHKeyExchangerTest.java │ │ │ ├── DSASignerTest.java │ │ │ ├── ECDHKeyExchangerTest.java │ │ │ ├── ECDSASignerTest.java │ │ │ └── RSAPrivateKeysTest.java │ │ ├── json/ │ │ │ ├── BooleanPojoTest.java │ │ │ ├── FastJsonUtils.java │ │ │ ├── JacksonIgnore.java │ │ │ └── JsonsTest.java │ │ ├── loadbalance/ │ │ │ ├── AbstractLoadBalance.java │ │ │ ├── HashedLoadBalance.java │ │ │ ├── LeastActiveLoadBalance.java │ │ │ ├── RandomLoadBalance.java │ │ │ ├── RoundRobinLoadBalance.java │ │ │ ├── WeightRandomLoadBalance.java │ │ │ ├── WeightRoundRobinLoadBalance.java │ │ │ └── package-info.java │ │ ├── log/ │ │ │ ├── JclLogger.java │ │ │ ├── JulLogger.java │ │ │ ├── Log4jLogger.java │ │ │ └── Slf4jLogger.java │ │ ├── model/ │ │ │ └── ParamsTest.java │ │ ├── mybatis/ │ │ │ └── SQLMapperTest.java │ │ ├── reflect/ │ │ │ ├── ClassA.java │ │ │ ├── FieldsTest.java │ │ │ ├── GenericExtendsTest.java │ │ │ ├── GenericTest.java │ │ │ └── GenericTest2.java │ │ ├── serial/ │ │ │ ├── JacksonObjectMapperTest.java │ │ │ ├── PersonProtobuf.java │ │ │ ├── ProtobufClient.java │ │ │ ├── ProtobufServer.java │ │ │ ├── SerializerTester.java │ │ │ └── person.proto │ │ └── util/ │ │ ├── BitSetTest.java │ │ ├── BloomFilterTest.java │ │ ├── ELParserTest.java │ │ ├── EscapeRegexTest.java │ │ ├── FibonacciTest.java │ │ ├── ForEachTest.java │ │ ├── IdcardResolverTest.java │ │ ├── MathsTest.java │ │ ├── MoneyTest.java │ │ ├── ObjectUtilsTest.java │ │ ├── ProxyTest.java │ │ ├── RegexUtilsTest.java │ │ ├── SqlUtilsTest.java │ │ ├── StreamForkerTest.java │ │ ├── StringsTest.java │ │ └── TestSerialize.java │ └── test/ │ ├── Cat.java │ ├── CsvWrappedCharTest.java │ ├── CustomClassLoader.java │ ├── GuavaCacheRefreshTest.java │ ├── Test1.java │ ├── Test2.java │ ├── Test3.java │ ├── TestBean.java │ ├── TestSynthetic.java │ ├── ThrowEggsTest.java │ ├── concurrent/ │ │ ├── AsnycBatchProcessorTest.java │ │ ├── ForkJoinPoolTest1.java │ │ ├── ForkJoinPoolTest2.java │ │ ├── InheritableThreadLocalTest.java │ │ ├── InheritableThreadLocalTest2.java │ │ ├── ReadWriteLock.java │ │ ├── TestThread.java │ │ ├── TheadPoolExecTester.java │ │ └── TtlTest.java │ ├── constraint/ │ │ └── TestConstraint.java │ ├── disruptor/ │ │ ├── InParkingDataEvent.java │ │ ├── Main.java │ │ ├── ParkingDataInDbHandler.java │ │ ├── ParkingDataSmsHandler.java │ │ ├── ParkingDataToKafkaHandler.java │ │ └── Sequence.java │ ├── elasticsearch/ │ │ ├── CateEsDaoImpl.java │ │ ├── EsClientFactory.java │ │ ├── EsDbUtils.java │ │ └── EsQueryObj.java │ ├── export/ │ │ ├── ConsoleExportTest.java │ │ ├── ExportTester.java │ │ └── ExportTester2.java │ ├── extract/ │ │ ├── ExampleEventUserModel.java │ │ ├── ExcelExtractorTest.java │ │ ├── TestHSSFStreaming.java │ │ ├── XLSEventTest.java │ │ ├── XLSX2CSV.java │ │ ├── XLSXEventTest.java │ │ ├── advices_export.xls │ │ └── writeTest2.xls │ ├── http/ │ │ ├── HttpClientUtils.java │ │ ├── HttpParamsTest.java │ │ ├── HttpPostTester.java │ │ ├── HttpTester.java │ │ ├── NewApiTester.java │ │ ├── OldApiTester.java │ │ ├── TestHttpUploadFile.java │ │ ├── TestJsoup.java │ │ ├── TestOpenApi.java │ │ ├── WSClientTester.java │ │ ├── jdk/ │ │ │ ├── HTTPServerSample.java │ │ │ └── WSProvider.java │ │ └── ssl/ │ │ ├── HttpsCert.java │ │ ├── HttpsClient.java │ │ ├── SSLClient.java │ │ └── SSLServer.java │ ├── jce/ │ │ ├── Argon2Test.java │ │ ├── CryptoProviderTest.java │ │ ├── DigestTest.java │ │ ├── Paillier.java │ │ ├── cert/ │ │ │ ├── CryptoMessageSyntaxTester.java │ │ │ ├── KeyStoreResolverTester.java │ │ │ ├── SM2CertTest.java │ │ │ ├── TestPem.java │ │ │ ├── X500NameTest.java │ │ │ └── X509CertUtilsTester.java │ │ ├── crypto/ │ │ │ ├── EncryptTester.java │ │ │ └── RSACryptoTester.java │ │ ├── demo/ │ │ │ ├── CertService.java │ │ │ ├── CreateCert.java │ │ │ └── GenX509Cert.java │ │ ├── ecc0/ │ │ │ ├── CryptoInputStream.java │ │ │ ├── CryptoOutputStream.java │ │ │ ├── ECCryptor.java.bak │ │ │ ├── ECCryptorTest.java │ │ │ ├── EllipticCurveTest.java │ │ │ ├── Login.java │ │ │ ├── Main.java │ │ │ ├── Screen.java │ │ │ └── View.java │ │ ├── ecc2/ │ │ │ ├── BaseConvert.java │ │ │ ├── CurveParameters.java │ │ │ ├── PrivateKey.java │ │ │ ├── PrivateKeyTest.java │ │ │ ├── PublicKey.java │ │ │ ├── PublicKeyTest.java │ │ │ ├── UnsupportedBaseException.java │ │ │ └── Utils.java │ │ ├── rsa/ │ │ │ ├── RSAKeyTest.java │ │ │ ├── RSASignerTest.java │ │ │ └── RSAryptorTest.java │ │ ├── sha1/ │ │ │ └── SHA1BrokenTest.java │ │ └── sm/ │ │ ├── SM2KeyExchangeTest.java │ │ ├── SM2Test.java │ │ ├── SM3DigestTest.java │ │ └── SM4Test.java │ ├── log4j/ │ │ └── TestLog4j.java │ ├── model/ │ │ ├── PageInfo.java │ │ └── PagePlugin.java │ ├── pdf/ │ │ ├── ItextUtil.java │ │ ├── PdfP7Sign.java │ │ └── TestPdfSign.java │ ├── qrcode/ │ │ ├── Qrcode.java │ │ └── QrcodeTest.java │ ├── reflect/ │ │ ├── ClassUtilsTest.java │ │ └── ProxyTest.java │ ├── swing/ │ │ ├── SM2Crypto.java │ │ └── WebBrowser.java │ ├── tree/ │ │ ├── NodePathSerialTest.java │ │ ├── NodePathTest.java │ │ ├── NodeTreeTest.java │ │ └── TreeNodePrinterTest.java │ └── utils/ │ ├── AtomicStampedReferenceTest.java │ ├── Base58Test.java │ ├── Base64.java │ ├── BytesTest.java │ ├── FloatContent.java │ ├── GuavaCacheRefreshTest.java │ ├── GuavaCacheTest.java │ ├── Java8DateTimeTester.java │ ├── MapToObjTest.java │ ├── NumbersTest.java │ ├── ObjectUtilsTest.java │ ├── OptionalTest.java │ ├── ProjectFileUtilsTester.java │ ├── RepeatableAnn.java │ ├── SimpleXmlHandlerTest.java │ ├── TempTest.java │ ├── Test1.java │ ├── Test2.java │ ├── TestBeanCopy.java │ ├── TestCache.java │ ├── TestCost.java │ ├── TestInterrupt.java │ ├── TestLock.java │ ├── TestXmlReader.java │ └── Ztzip.java └── resources/ ├── abc.xlsx ├── ca.pfx ├── cacert.pem ├── cas_test.pfx ├── copy-right.txt ├── csv.csv ├── log/ │ ├── log4j.properties │ ├── log4j.properties.bak │ ├── log4j2.xml │ └── logback.xml ├── signer.xsd ├── signers.xml ├── sm2-1.cer ├── sm2-2.cer ├── sm2-crypto.cer ├── sm2-root.cer ├── subject.pfx └── test.txt ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # http://editorconfig.org root = true [*] indent_style = space indent_size = 4 charset = utf-8 end_of_line = lf trim_trailing_whitespace = true insert_final_newline = true [*.{json,yml,yaml,xml}] indent_size = 2 [*.md] trim_trailing_whitespace = false ================================================ FILE: .github/workflows/build-with-maven.yml ================================================ # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven name: build-with-maven on: push: branches: [ "master" ] pull_request: branches: [ "master" ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up JDK 8 uses: actions/setup-java@v3 with: java-version: '8' distribution: 'temurin' cache: maven - name: Build with Maven run: ./mvnw clean package -DskipTests -Dcheckstyle.skip=true -Dmaven.javadoc.skip=true -B -V -U --file pom.xml ================================================ FILE: .gitignore ================================================ # Eclipse /**/.classpath /**/.project /**/.settings/ /**/.myeclipse/ # Intellij /**/.idea/ /**/*.iml /**/*.iws /**/*.ipr # NetBeans nbproject/private/ nbbuild/ nbdist/ /**/.nb-gradle/ # Mac /**/.DS_Store # Maven /**/target/ # Gradle /**/.gradle/ # SBT: Simple Build Tool. Is a build tool for Scala, Java, and more. dist/* target/ lib_managed/ src_managed/ project/boot/ project/plugins/project/ .history .cache .lib/ # STS: Spring Tool Suit /**/.apt_generated /**/.factorypath /**/.springBeans # Webapp /**/src/main/webapp/WEB-INF/classes/ # Others /**/.svn/ /**/.externalToolBuilders/ /**/.recommenders/ /**/.metadata/ /**/node_modules/ out/ build/ bin/ logs/ dist/ # rebel.xml /**/rebel.xml /**/.cache-main /**/.cache-tests /**/dependency-reduced-pom.xml ================================================ FILE: .mvn/wrapper/maven-wrapper.properties ================================================ # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar ================================================ FILE: LICENSE ================================================ 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 (c) 2017-2023 Ponfee Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 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 ================================================ [![Blog](https://img.shields.io/badge/blog-@Ponfee-informational.svg?logo=Pelican)](http://www.ponfee.cn) [![License](https://img.shields.io/badge/license-Apache--2.0-green.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) [![JDK](https://img.shields.io/badge/jdk-8+-green.svg)](https://www.oracle.com/java/technologies/downloads/#java8) [![Build status](https://github.com/ponfee/commons-core/workflows/build-with-maven/badge.svg)](https://github.com/ponfee/commons-core/actions) [![Maven Central](https://img.shields.io/badge/maven--central-1.4-orange.svg?style=plastic&logo=apachemaven)](https://central.sonatype.com/artifact/cn.ponfee/commons-core/1.4) # Commons Core A commons java tool lib ## ⬇️ [Download From Maven Central](https://central.sonatype.com/artifact/cn.ponfee/commons-core/1.4) ```xml cn.ponfee commons-core 1.4 ``` ## 🔄 Build From Source ```bash ./mvnw clean package -DskipTests -Dcheckstyle.skip=true -U ``` ## 🛠️ Functions | **function** | **description** | | ------------ | -------------------------------------------------------------------------------------------------------- | | base | 基础类:Tuple数据类型、原始与包装类型等 | | collect | 集合工具类 | | concurrent | 并发相关的工具类:异步批处理、延时消费、线程池创建与监控等 | | constrain | 方法参数、实体字段等数据校验 | | data | 多数据源组件,动态增加数据源 | | date | 时间工具类(支持各种时间格式的解析,时间周期处理) | | exception | 异常工具类 | | export | 数据导出为Excel(支持复杂表头及切分多个文件)、HTML(支持复杂表头)、CSV(支持切分多个文件)、Console(类似SQL命令行查询结果) | | extract | 数据文件导入:支持XLS/XLSX/CSV格式的文件,支持大文件 | | http | HTTP工具类(轻量级,不依赖第三方库) | | io | IO操作工具类(如文件UTF编码BOM头处理、文件编码探测、文件编码转换及内容替换、数字格式化为KB/MB/GB/TB/PB、Gzip等) | | jce | 加解密工具(对称加解密、非对称加解密、签名/验签、数字信封、ECC算法、哈希算法、国密算法、根证创建与CA证书签发、密码处理等) | | model | 数据模型相关公用类(带类型的Map操作、定义返回结果的结构体、分页实体等) | | reflect | 反射工具类(泛型解析、实体与Map互转、实体字段拷贝、实体字段获取、方法调用、Unsafe工具等) | | schema | 表格数据结构定义,任意JSON格式数据转二维表等 | | serial | 序列化工具类(JDK、JSON、FST、Hessian、Kryo、Protostuff) | | spring | Spring相关工具类 | | tree | 强大的树型数据结构组件,构建复杂表头的基础(多路树构造及解析、类似`tree -N`命令的多路树打印、二叉树打印等) | | util | 常用工具类(Zip、时间轮、Snowflake id生成算法、Money/币种、一致性Hash算法、Base58编码、高效的字节处理等) | ================================================ FILE: mvnw ================================================ #!/bin/sh # ---------------------------------------------------------------------------- # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- # Apache Maven Wrapper startup batch script, version 3.1.1 # # Required ENV vars: # ------------------ # JAVA_HOME - location of a JDK home dir # # Optional ENV vars # ----------------- # MAVEN_OPTS - parameters passed to the Java VM when running Maven # e.g. to debug Maven itself, use # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 # MAVEN_SKIP_RC - flag to disable loading of mavenrc files # ---------------------------------------------------------------------------- if [ -z "$MAVEN_SKIP_RC" ] ; then if [ -f /usr/local/etc/mavenrc ] ; then . /usr/local/etc/mavenrc fi if [ -f /etc/mavenrc ] ; then . /etc/mavenrc fi if [ -f "$HOME/.mavenrc" ] ; then . "$HOME/.mavenrc" fi fi # OS specific support. $var _must_ be set to either true or false. cygwin=false; darwin=false; mingw=false case "`uname`" in CYGWIN*) cygwin=true ;; MINGW*) mingw=true;; Darwin*) darwin=true # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home # See https://developer.apple.com/library/mac/qa/qa1170/_index.html if [ -z "$JAVA_HOME" ]; then if [ -x "/usr/libexec/java_home" ]; then JAVA_HOME="`/usr/libexec/java_home`"; export JAVA_HOME else JAVA_HOME="/Library/Java/Home"; export JAVA_HOME fi fi ;; esac if [ -z "$JAVA_HOME" ] ; then if [ -r /etc/gentoo-release ] ; then JAVA_HOME=`java-config --jre-home` fi fi # For Cygwin, ensure paths are in UNIX format before anything is touched if $cygwin ; then [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` [ -n "$CLASSPATH" ] && CLASSPATH=`cygpath --path --unix "$CLASSPATH"` fi # For Mingw, ensure paths are in UNIX format before anything is touched if $mingw ; then [ -n "$JAVA_HOME" ] && JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" fi if [ -z "$JAVA_HOME" ]; then javaExecutable="`which javac`" if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then # readlink(1) is not available as standard on Solaris 10. readLink=`which readlink` if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then if $darwin ; then javaHome="`dirname \"$javaExecutable\"`" javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" else javaExecutable="`readlink -f \"$javaExecutable\"`" fi javaHome="`dirname \"$javaExecutable\"`" javaHome=`expr "$javaHome" : '\(.*\)/bin'` JAVA_HOME="$javaHome" export JAVA_HOME fi fi fi if [ -z "$JAVACMD" ] ; then if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi else JAVACMD="`\\unset -f command; \\command -v java`" fi fi if [ ! -x "$JAVACMD" ] ; then echo "Error: JAVA_HOME is not defined correctly." >&2 echo " We cannot execute $JAVACMD" >&2 exit 1 fi if [ -z "$JAVA_HOME" ] ; then echo "Warning: JAVA_HOME environment variable is not set." fi # traverses directory structure from process work directory to filesystem root # first directory with .mvn subdirectory is considered project base directory find_maven_basedir() { if [ -z "$1" ] then echo "Path not specified to find_maven_basedir" return 1 fi basedir="$1" wdir="$1" while [ "$wdir" != '/' ] ; do if [ -d "$wdir"/.mvn ] ; then basedir=$wdir break fi # workaround for JBEAP-8937 (on Solaris 10/Sparc) if [ -d "${wdir}" ]; then wdir=`cd "$wdir/.."; pwd` fi # end of workaround done printf '%s' "$(cd "$basedir"; pwd)" } # concatenates all lines of a file concat_lines() { if [ -f "$1" ]; then echo "$(tr -s '\n' ' ' < "$1")" fi } BASE_DIR=$(find_maven_basedir "$(dirname $0)") if [ -z "$BASE_DIR" ]; then exit 1; fi MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR if [ "$MVNW_VERBOSE" = true ]; then echo $MAVEN_PROJECTBASEDIR fi ########################################################################################## # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central # This allows using the maven wrapper in projects that prohibit checking in binary data. ########################################################################################## if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then if [ "$MVNW_VERBOSE" = true ]; then echo "Found .mvn/wrapper/maven-wrapper.jar" fi else if [ "$MVNW_VERBOSE" = true ]; then echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." fi if [ -n "$MVNW_REPOURL" ]; then wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" else wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" fi while IFS="=" read key value; do case "$key" in (wrapperUrl) wrapperUrl="$value"; break ;; esac done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" if [ "$MVNW_VERBOSE" = true ]; then echo "Downloading from: $wrapperUrl" fi wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" if $cygwin; then wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` fi if command -v wget > /dev/null; then QUIET="--quiet" if [ "$MVNW_VERBOSE" = true ]; then echo "Found wget ... using wget" QUIET="" fi if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" else wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" fi [ $? -eq 0 ] || rm -f "$wrapperJarPath" elif command -v curl > /dev/null; then QUIET="--silent" if [ "$MVNW_VERBOSE" = true ]; then echo "Found curl ... using curl" QUIET="" fi if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L else curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L fi [ $? -eq 0 ] || rm -f "$wrapperJarPath" else if [ "$MVNW_VERBOSE" = true ]; then echo "Falling back to using Java to download" fi javaSource="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" # For Cygwin, switch paths to Windows format before running javac if $cygwin; then javaSource=`cygpath --path --windows "$javaSource"` javaClass=`cygpath --path --windows "$javaClass"` fi if [ -e "$javaSource" ]; then if [ ! -e "$javaClass" ]; then if [ "$MVNW_VERBOSE" = true ]; then echo " - Compiling MavenWrapperDownloader.java ..." fi # Compiling the Java class ("$JAVA_HOME/bin/javac" "$javaSource") fi if [ -e "$javaClass" ]; then # Running the downloader if [ "$MVNW_VERBOSE" = true ]; then echo " - Running MavenWrapperDownloader.java ..." fi ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") fi fi fi fi ########################################################################################## # End of extension ########################################################################################## MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" # For Cygwin, switch paths to Windows format before running java if $cygwin; then [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` [ -n "$CLASSPATH" ] && CLASSPATH=`cygpath --path --windows "$CLASSPATH"` [ -n "$MAVEN_PROJECTBASEDIR" ] && MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` fi # Provide a "standardized" way to retrieve the CLI args that will # work with both Windows and non-Windows executions. MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" export MAVEN_CMD_LINE_ARGS WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain exec "$JAVACMD" \ $MAVEN_OPTS \ $MAVEN_DEBUG_OPTS \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" ================================================ FILE: mvnw.cmd ================================================ @REM ---------------------------------------------------------------------------- @REM Licensed to the Apache Software Foundation (ASF) under one @REM or more contributor license agreements. See the NOTICE file @REM distributed with this work for additional information @REM regarding copyright ownership. The ASF licenses this file @REM to you under the Apache License, Version 2.0 (the @REM "License"); you may not use this file except in compliance @REM with the License. You may obtain a copy of the License at @REM @REM http://www.apache.org/licenses/LICENSE-2.0 @REM @REM Unless required by applicable law or agreed to in writing, @REM software distributed under the License is distributed on an @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @REM KIND, either express or implied. See the License for the @REM specific language governing permissions and limitations @REM under the License. @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- @REM Apache Maven Wrapper startup batch script, version 3.1.1 @REM @REM Required ENV vars: @REM JAVA_HOME - location of a JDK home dir @REM @REM Optional ENV vars @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven @REM e.g. to debug Maven itself, use @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files @REM ---------------------------------------------------------------------------- @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' @echo off @REM set title of command window title %0 @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% @REM set %HOME% to equivalent of $HOME if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") @REM Execute a user defined script before this one if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre @REM check for pre script, once with legacy .bat ending and once with .cmd ending if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* :skipRcPre @setlocal set ERROR_CODE=0 @REM To isolate internal variables from possible post scripts, we use another setlocal @setlocal @REM ==== START VALIDATION ==== if not "%JAVA_HOME%" == "" goto OkJHome echo. echo Error: JAVA_HOME not found in your environment. >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 echo. goto error :OkJHome if exist "%JAVA_HOME%\bin\java.exe" goto init echo. echo Error: JAVA_HOME is set to an invalid directory. >&2 echo JAVA_HOME = "%JAVA_HOME%" >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 echo. goto error @REM ==== END VALIDATION ==== :init @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". @REM Fallback to current working directory if not found. set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir set EXEC_DIR=%CD% set WDIR=%EXEC_DIR% :findBaseDir IF EXIST "%WDIR%"\.mvn goto baseDirFound cd .. IF "%WDIR%"=="%CD%" goto baseDirNotFound set WDIR=%CD% goto findBaseDir :baseDirFound set MAVEN_PROJECTBASEDIR=%WDIR% cd "%EXEC_DIR%" goto endDetectBaseDir :baseDirNotFound set MAVEN_PROJECTBASEDIR=%EXEC_DIR% cd "%EXEC_DIR%" :endDetectBaseDir IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig @setlocal EnableExtensions EnableDelayedExpansion for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% :endReadAdditionalConfig SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B ) @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central @REM This allows using the maven wrapper in projects that prohibit checking in binary data. if exist %WRAPPER_JAR% ( if "%MVNW_VERBOSE%" == "true" ( echo Found %WRAPPER_JAR% ) ) else ( if not "%MVNW_REPOURL%" == "" ( SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" ) if "%MVNW_VERBOSE%" == "true" ( echo Couldn't find %WRAPPER_JAR%, downloading it ... echo Downloading from: %WRAPPER_URL% ) powershell -Command "&{"^ "$webclient = new-object System.Net.WebClient;"^ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ "}"^ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ "}" if "%MVNW_VERBOSE%" == "true" ( echo Finished downloading %WRAPPER_JAR% ) ) @REM End of extension @REM Provide a "standardized" way to retrieve the CLI args that will @REM work with both Windows and non-Windows executions. set MAVEN_CMD_LINE_ARGS=%* %MAVEN_JAVA_EXE% ^ %JVM_CONFIG_MAVEN_PROPS% ^ %MAVEN_OPTS% ^ %MAVEN_DEBUG_OPTS% ^ -classpath %WRAPPER_JAR% ^ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* if ERRORLEVEL 1 goto error goto end :error set ERROR_CODE=1 :end @endlocal & set ERROR_CODE=%ERROR_CODE% if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost @REM check for post script, once with legacy .bat ending and once with .cmd ending if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" :skipRcPost @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' if "%MAVEN_BATCH_PAUSE%"=="on" pause if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% cmd /C exit /B %ERROR_CODE% ================================================ FILE: pom.xml ================================================ 4.0.0 cn.ponfee commons-core 1.5-SNAPSHOT Commons core A commons tool java lib https://github.com/ponfee/commons-core The Apache Software License, Version 2.0 https://www.apache.org/licenses/LICENSE-2.0.txt Ponfee ponfee.cn@gmail.com ponfee.cn http://www.ponfee.cn/ scm:git:https://github.com/ponfee/commons-core.git scm:git:https://github.com/ponfee/commons-core.git https://github.com/ponfee/commons-core HEAD ossrh https://s01.oss.sonatype.org/content/repositories/snapshots/ ossrh https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ UTF-8 ${file.encoding} ${file.encoding} 1.8 ${java.version} ${java.version} ${java.version} 5.3.24 1.7.36 2.19.0 1.72 2.14.1 unix unix : windows windows ; develop release org.apache.maven.plugins maven-javadoc-plugin 3.4.1 attach-javadocs jar none org.apache.maven.plugins maven-gpg-plugin 3.0.1 sign-artifacts verify sign org.apache.tika tika-bom 2.6.0 pom import org.projectlombok lombok 1.18.24 true provided org.slf4j slf4j-api ${slf4j.version} org.slf4j jcl-over-slf4j ${slf4j.version} org.slf4j jul-to-slf4j ${slf4j.version} org.slf4j log4j-over-slf4j ${slf4j.version} org.apache.logging.log4j log4j-slf4j-impl ${log4j2.version} runtime org.apache.logging.log4j log4j-web ${log4j2.version} runtime org.apache.commons commons-lang3 3.12.0 org.apache.commons commons-text 1.10.0 commons-io commons-io 2.11.0 org.apache.commons commons-collections4 4.4 org.apache.commons commons-math3 3.6.1 org.apache.commons commons-pool2 2.11.1 org.apache.commons commons-csv 1.9.0 org.dom4j dom4j 2.1.3 com.google.guava guava 31.1-jre joda-time joda-time 2.10.13 net.lingala.zip4j zip4j 2.11.2 org.apache.tika tika-parsers-standard-package commons-logging commons-logging org.apache.tika tika-core com.itextpdf itextpdf 5.5.13.3 javax.money money-api 1.1 true org.javamoney moneta 1.4.2 pom true jakarta.servlet jakarta.servlet-api 4.0.4 true provided jakarta.annotation jakarta.annotation-api 1.3.5 jakarta.validation jakarta.validation-api 2.0.2 org.hibernate.validator hibernate-validator 6.2.5.Final org.springframework spring-core ${spring-framework.version} commons-logging commons-logging org.springframework spring-context-support ${spring-framework.version} org.springframework spring-web ${spring-framework.version} org.springframework spring-aspects ${spring-framework.version} org.springframework spring-jdbc ${spring-framework.version} org.apache.poi poi-ooxml 5.2.3 org.apache.logging.log4j log4j-api com.monitorjbl xlsx-streamer 2.2.0 org.apache.poi * org.slf4j * org.apache.logging.log4j * com.esotericsoftware kryo 5.3.0 com.fasterxml.jackson.core jackson-databind ${jackson.version} com.fasterxml.jackson.datatype jackson-datatype-jsr310 ${jackson.version} com.caucho hessian 4.0.66 de.ruedigermoeller fst 2.57 com.fasterxml.jackson.core jackson-core org.objenesis objenesis io.protostuff protostuff-runtime 1.8.0 io.protostuff protostuff-core 1.8.0 com.alibaba fastjson 1.2.83 com.alibaba druid 1.2.15 true org.mybatis mybatis 3.5.11 org.mybatis mybatis-spring 2.1.0 com.github.pagehelper pagehelper 5.3.2 org.bouncycastle bcmail-jdk18on ${bouncycastle.version} org.bouncycastle bcpg-jdk18on ${bouncycastle.version} org.bouncycastle bctls-jdk18on ${bouncycastle.version} org.bouncycastle bcprov-ext-jdk18on ${bouncycastle.version} commons-codec commons-codec 1.15 junit junit 4.13.2 test org.springframework spring-test ${spring-framework.version} test org.zeroturnaround zt-zip 1.14 test jar org.slf4j * org.jsoup jsoup 1.13.1 test com.lmax disruptor 3.4.2 test org.apache.httpcomponents httpclient 4.5.14 test commons-logging commons-logging org.testng testng 6.14.3 test com.google.protobuf protobuf-java 3.11.4 test com.alibaba transmittable-thread-local 2.11.4 test de.mkammerer argon2-jvm 2.6 test org.openjdk.jol jol-core 0.16 test com.beust jcommander 1.82 test info.picocli picocli 4.7.0 test ${project.artifactId}-${project.version} src/main/resources false src/main/java false **/*.java src/test/resources false org.codehaus.mojo versions-maven-plugin 2.13.0 false org.apache.maven.plugins maven-resources-plugin 3.2.0 ${file.encoding} org.apache.maven.plugins maven-compiler-plugin 3.10.1 ${maven.compiler.source} ${maven.compiler.target} ${maven.compiler.compilerVersion} ${file.encoding} true true -bootclasspath ${java.home}/lib/rt.jar${system.separator}${java.home}/lib/jce.jar${system.separator}${java.home}/lib/jsse.jar -parameters -Xlint:unchecked,deprecation node_modules/** org.apache.maven.plugins maven-source-plugin 3.2.1 attach-sources verify jar-no-fork ================================================ FILE: src/main/java/cn/ponfee/commons/base/Initializable.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.base; /** * Initialize resources * * @author Ponfee */ @FunctionalInterface public interface Initializable { NoArgMethodInvoker INITIATOR = new NoArgMethodInvoker("open", "init", "initialize"); void init(); static void init(Object caller) { if (caller == null) { return; } if (caller instanceof Initializable) { ((Initializable) caller).init(); } else { INITIATOR.invoke(caller); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/base/NoArgMethodInvoker.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.base; import cn.ponfee.commons.reflect.ClassUtils; import org.apache.commons.lang3.StringUtils; import javax.annotation.Nonnull; import java.util.Arrays; /** * Specifies multiple non-arg method names, find the first and invoke it * * @author Ponfee */ public final class NoArgMethodInvoker { private final String[] methodNames; /** * @param methodNames the no-arg method list */ public NoArgMethodInvoker(@Nonnull String... methodNames) { if (methodNames == null || methodNames.length == 0) { throw new IllegalArgumentException("Must be specified least once no-arg method name."); } this.methodNames = methodNames; } public void invoke(Object caller) { if (caller == null) { return; } if (caller instanceof Class) { throw new IllegalArgumentException("Invalid caller object " + caller); } Arrays.stream(methodNames) .filter(StringUtils::isNotBlank) .map(name -> ClassUtils.getMethod(caller, name)) .findAny() .ifPresent(method -> ClassUtils.invoke(caller, method)); } } ================================================ FILE: src/main/java/cn/ponfee/commons/base/Predicates.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.base; import java.util.function.Predicate; /** * Representing a boolean status * *
 *  异或(XOR , ⊕) = A ^ B
 *  同或(XNOR, ⊙) = 异或 ^ 1 = (A ^ B) ^ 1
 *  (0, 1)间的切换可以使用异或:1 ^ n,其中n ∈ (0, 1)
 * 
* * @author Ponfee */ public enum Predicates { Y(1, "是"), // N(0, "否"), // ; private final int value; private final char code; private final String desc; Predicates(int value, String desc) { this.value = value; this.code = name().charAt(0); // 'Y' or 'N' this.desc = desc; } public int value() { return value; } public char code() { return code; } public boolean state() { return this == Y; } public String desc() { return this.desc; } // ------------------------------------------------ equals methods public boolean equals(Integer value) { return equals(value == null ? N.value : value); } public boolean equals(int value) { if (value != Y.value && value != N.value) { throw new IllegalArgumentException("Invalid int value '" + value + "'"); } return this.value == value; } public boolean equals(String code) { char c; if (code == null) { c = N.code; } else if (code.length() == 1) { c = code.charAt(0); } else { throw new IllegalArgumentException("Invalid string code '" + code + "'"); } return equals(c); } public boolean equals(Character code) { return equals(code == null ? N.code : code); } public boolean equals(char code) { code = Character.toUpperCase(code); if (code != Y.code && code != N.code) { throw new IllegalArgumentException("Invalid char code '" + code + "'"); } return this.code == code; } public boolean equals(Boolean state) { return equals(state == null ? N.state() : state); } public boolean equals(boolean state) { return state() == state; } public boolean equals(Predicates other) { return this == (other == null ? N : other); } // ------------------------------------------------ check whether the value is yes public static boolean yes(Integer value) { return Y.equals(value); } public static boolean yes(int value) { return Y.equals(value); } public static boolean yes(String code) { return Y.equals(code); } public static boolean yes(Character code) { return Y.equals(code); } public static boolean yes(char code) { return Y.equals(code); } public static boolean yes(Boolean state) { return Y.equals(state); } public static boolean yes(boolean state) { return Y.equals(state); } public static boolean yes(Predicates other) { return Y.equals(other); } // ------------------------------------------------ check whether the value is no public static boolean no(Integer value) { return N.equals(value); } public static boolean no(int value) { return N.equals(value); } public static boolean no(String code) { return N.equals(code); } public static boolean no(Character code) { return N.equals(code); } public static boolean no(char code) { return N.equals(code); } public static boolean no(Boolean state) { return N.equals(state); } public static boolean no(boolean state) { return N.equals(state); } public static boolean no(Predicates other) { return N.equals(other); } // ------------------------------------------------ of methods public static Predicates of(Integer value) { return Y.equals(value) ? Y : N; } public static Predicates of(int value) { return Y.equals(value) ? Y : N; } public static Predicates of(String code) { return Y.equals(code) ? Y : N; } public static Predicates of(Character code) { return Y.equals(code) ? Y : N; } public static Predicates of(char code) { return Y.equals(code) ? Y : N; } public static Predicates of(Boolean state) { return Y.equals(state) ? Y : N; } public static Predicates of(boolean state) { return Y.equals(state) ? Y : N; } public static Predicate not(Predicate target) { return target.negate(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/base/PrimitiveTypes.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.base; import cn.ponfee.commons.reflect.Fields; import cn.ponfee.commons.util.Enums; import java.util.HashMap; import java.util.Map; import java.util.Set; /** *
 * 基本数据类型及其包装类型工具类(不包含 {@link Void}),以及这些数据类型间的转换规则
 *
 * +----------+-------+------+-----+-------+------+------+---------+
 * |  double  | float | long | int | short | char | byte | boolean |
 * +----------+-------+------+-----+-------+------+------+---------+
 * 
* * @author Ponfee */ public enum PrimitiveTypes { DOUBLE (Double.class , (byte) 0B1_0_0_0_0_0_0_0, (byte) 0B1_0_0_0_0_0_0_0 ), FLOAT (Float.class , (byte) 0B0_1_0_0_0_0_0_0, (byte) 0B1_1_0_0_0_0_0_0 ), LONG (Long.class , (byte) 0B0_0_1_0_0_0_0_0, (byte) 0B1_1_1_0_0_0_0_0 ), INT (Integer.class , (byte) 0B0_0_0_1_0_0_0_0, (byte) 0B1_1_1_1_0_0_0_0 ), SHORT (Short.class , (byte) 0B0_0_0_0_1_0_0_0, (byte) 0B1_1_1_1_1_0_0_0 ), // short与char不能互相转换 CHAR (Character.class, (byte) 0B0_0_0_0_0_1_0_0, (byte) 0B1_1_1_1_0_1_0_0 ), // short与char不能互相转换 BYTE (Byte.class , (byte) 0B0_0_0_0_0_0_1_0, (byte) 0B1_1_1_1_1_0_1_0 ), // byte不能转为char BOOLEAN(Boolean.class , (byte) 0B0_0_0_0_0_0_0_1, (byte) 0B0_0_0_0_0_0_0_1, 1), // boolean只能转boolean ; private final Class wrapper; private final byte value; // 类型值 private final byte castable; // 支持转换到的目标类型 private final Class primitive; private final int size; private static final Map, PrimitiveTypes> PRIMITIVE_MAPPING = Enums.toMap(PrimitiveTypes.class, PrimitiveTypes::primitive); private static final Map, PrimitiveTypes> WRAPPER_MAPPING = Enums.toMap(PrimitiveTypes.class, PrimitiveTypes::wrapper); PrimitiveTypes(Class wrapper, byte value, byte castable) { this(wrapper, value, castable, (int) Fields.get(wrapper, "SIZE")); } PrimitiveTypes(Class wrapper, byte value, byte castable, int size) { this.wrapper = wrapper; this.value = value; this.castable = castable; this.primitive = (Class) Fields.get(wrapper, "TYPE"); this.size = size; Hide.PRIMITIVE_OR_WRAPPER_MAPPING.put(primitive, this); Hide.PRIMITIVE_OR_WRAPPER_MAPPING.put(wrapper, this); } public Class primitive() { return primitive; } public Class wrapper() { return wrapper; } public int size() { return size; } /** * 用于判断传入方法真实的参数类型(this)是否能转换到方法定义的参数类型(target) * * @param target 目标参数类型 * @return {@code true}是,{@code false}否 */ public boolean isCastable(PrimitiveTypes target) { return (this.castable & target.value) == target.value; } public static PrimitiveTypes ofPrimitive(Class primitive) { return PRIMITIVE_MAPPING.get(primitive); } public static PrimitiveTypes ofWrapper(Class wrapper) { return WRAPPER_MAPPING.get(wrapper); } public static PrimitiveTypes ofPrimitiveOrWrapper(Class primitive) { return Hide.PRIMITIVE_OR_WRAPPER_MAPPING.get(primitive); } public static Set> allPrimitiveTypes() { return PRIMITIVE_MAPPING.keySet(); } public static Set> allWrapperTypes() { return WRAPPER_MAPPING.keySet(); } public static boolean isWrapperType(Class primitive) { return ofWrapper(primitive) != null; } public static Class wrap(Class type) { PrimitiveTypes pt = ofPrimitiveOrWrapper(type); return pt == null ? type : (Class) pt.wrapper; } public static Class unwrap(Class type) { PrimitiveTypes pt = ofPrimitiveOrWrapper(type); return pt == null ? type : (Class) pt.primitive; } private static class Hide { private static final Map, PrimitiveTypes> PRIMITIVE_OR_WRAPPER_MAPPING = new HashMap<>(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/base/Releasable.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.base; import cn.ponfee.commons.exception.ServerException; /** * Release resources * * @author Ponfee */ @FunctionalInterface public interface Releasable { NoArgMethodInvoker RELEASER = new NoArgMethodInvoker("close", "destroy", "release"); /** * 释放资源 */ void release(); static void release(Object caller) { if (caller == null) { return; } try { if (caller instanceof AutoCloseable) { ((AutoCloseable) caller).close(); } else if (caller instanceof Releasable) { Releasable releasable = (Releasable) caller; if (!releasable.isReleased()) { ((Releasable) caller).release(); } } else { RELEASER.invoke(caller); } } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new ServerException(e); } } /** * 是否已经释放,true为已经释放,false未释放 * * @return {@code true}已经释放 */ default boolean isReleased() { return false; } } ================================================ FILE: src/main/java/cn/ponfee/commons/base/Symbol.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.base; /** * Symbol definitions. * * @author Ponfee */ public final class Symbol { public interface Str { /** * Zero symbol */ String ZERO = "\u0000"; /** * Colon symbol */ String COLON = ":"; /** * Comma symbol */ String COMMA = ","; /** * Dot symbol */ String DOT = "."; /** * Hyphen symbol */ String HYPHEN = "-"; /** * Slash symbol */ String SLASH = "/"; /** * Space symbol */ String SPACE = " "; /** * Tab symbol */ String TAB = " "; /** * Backslash symbol */ String BACKSLASH = "\\"; /** * CR symbol */ String CR = "\r"; /** * LF symbol */ String LF = "\n"; /** * Underscore symbol */ String UNDERSCORE = "_"; /** * Asterisk symbol */ String ASTERISK = "*"; /** * Semicolon symbol */ String SEMICOLON = ";"; /** * Ampersand symbol */ String AMPERSAND = "&"; /** * Open symbol */ String OPEN = "("; /** * Close symbol */ String CLOSE = ")"; } public interface Char { /** * Zero char symbol, equals '\0' */ char ZERO = '\u0000'; /** * Colon symbol */ char COLON = ':'; /** * Comma symbol */ char COMMA = ','; /** * Dot symbol */ char DOT = '.'; /** * Hyphen symbol */ char HYPHEN = '-'; /** * Slash symbol */ char SLASH = '/'; /** * Space symbol */ char SPACE = ' '; /** * Tab symbol */ char TAB = ' '; /** * Backslash symbol */ char BACKSLASH = '\\'; /** * CR symbol */ char CR = '\r'; /** * LF symbol */ char LF = '\n'; /** * Underscore symbol */ char UNDERSCORE = '_'; /** * Asterisk symbol */ char ASTERISK = '*'; /** * Semicolon symbol */ char SEMICOLON = ';'; /** * Ampersand symbol */ char AMPERSAND = '&'; /** * Open symbol */ char OPEN = '('; /** * Close symbol */ char CLOSE = ')'; } } ================================================ FILE: src/main/java/cn/ponfee/commons/base/TimestampProvider.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.base; /** * 时间戳服务提供 * * @author Ponfee */ @FunctionalInterface public interface TimestampProvider { TimestampProvider EARLIEST = () -> Long.MIN_VALUE; TimestampProvider CURRENT = System::currentTimeMillis; TimestampProvider LATEST = () -> Long.MAX_VALUE; long get(); } ================================================ FILE: src/main/java/cn/ponfee/commons/base/tuple/Tuple.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.base.tuple; import cn.ponfee.commons.collect.Comparators; import cn.ponfee.commons.collect.DelegatedIntSpliterator; import cn.ponfee.commons.collect.ImmutableArrayList; import cn.ponfee.commons.util.ObjectUtils; import java.io.Serializable; import java.util.*; import java.util.function.Function; /** * Abstract Tuple type. * * @author Ponfee */ public abstract class Tuple implements Comparable, Iterable, Serializable { private static final long serialVersionUID = -3292038317953347997L; /** * Get the object at the given index. * * @param index The index of the object to retrieve. Starts at 0. * @return The object or {@literal null} if out of bounds. */ public abstract T get(int index); /** * Set the value at the given index. * * @param value The object value. * @param index The index of the object to retrieve. Starts at 0. */ public abstract void set(T value, int index); /** * Returns a copy of this instance. * * @return a copy of this instance. */ public abstract T copy(); /** * Returns int value of this tuple elements count. * * @return int value of this tuple elements count. */ public abstract int length(); /** * Turn this {@code Tuple} into a plain {@code Object[]}. * The array isn't tied to this Tuple but is a copy. * * @return A copy of the tuple as a new {@link Object Object[]}. */ public final Object[] toArray() { int len = length(); Object[] array = new Object[len]; for (int i = 0; i < len; i++) { array[i] = get(i); } return array; } /** * Returns a string representation of the object. * * @return a string representation of the Tuple. */ @Override public final String toString() { return join(", ", String::valueOf, "(", ")"); } /** * Returns a hash code value for the object. * * @return a hash code value for this object. */ @Override public final int hashCode() { int len = length(); if (len < 1) { return 0; } int hash = Objects.hashCode(get(0)); for (int i = 1; i < len; i++) { hash = 31 * hash + Objects.hashCode(get(i)); } return hash; } /** * Indicates whether some other object is "equal to" this one. * * @param obj the reference object with which to compare. * @return {@code true} if this object equals the other. */ @Override public final boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || obj.getClass() != this.getClass()) { return false; } Tuple other = (Tuple) obj; for (int i = 0, len = length(); i < len; i++) { if (!Objects.equals(this.get(i), other.get(i))) { return false; } } return true; } /** * Returns tuple elements are equals array elements. * * @param elements the elements * @return {@code true} if elements equals. */ public final boolean equals(Object... elements) { int len; if (elements == null || elements.length != (len = length())) { return false; } for (int i = 0; i < len; i++) { if (!Objects.equals(get(i), elements[i])) { return false; } } return true; } @Override public final int compareTo(Object o) { if (this == o) { return Comparators.EQ; } if (!(o instanceof Tuple)) { return ObjectUtils.compare(this, o); } Tuple other = (Tuple) o; for (int c, i = 0, n = this.length(); i < n; i++) { c = ObjectUtils.compare(this.get(i), other.get(i)); if (c != Comparators.EQ) { return c; } } return Comparators.EQ; } /** * Turn this {@code Tuple} into a {@link List List<Object>}. * The list isn't tied to this Tuple but is a copy with limited * mutability ({@code add} and {@code remove} are not supported, but {@code set} is). * * @return A copy of the tuple as a new {@link List List<Object>}. */ public List toList() { return ImmutableArrayList.of(toArray()); } /** * Return an immutable {@link Iterator Iterator<Object>} around * the content of this {@code Tuple}. * * @return An unmodifiable {@link Iterator} over the elements in this Tuple. * @implNote As an {@link Iterator} is always tied to its {@link Iterable} source by * definition, the iterator cannot be mutable without the iterable also being mutable. */ @Override public Iterator iterator() { // Also use: toList().iterator(); return new TupleIterator<>(); } @Override public Spliterator spliterator() { return new DelegatedIntSpliterator<>(0, length(), this::get); } /** * Returns string of joined the tuple elements. * * @param delimiter the delimiter * @param valueMapper the valueMapper for each element to string function * @param prefix the prefix * @param suffix the suffix * @return string of joined the tuple elements */ public final String join(CharSequence delimiter, Function valueMapper, CharSequence prefix, CharSequence suffix) { StringBuilder builder = new StringBuilder(prefix); for (int i = 0, n = length() - 1; i <= n; i++) { builder.append(valueMapper.apply(get(i))); if (i < n) { builder.append(delimiter); } } return builder.append(suffix).toString(); } /** * Tuple Iterator * * @param element type */ private class TupleIterator implements Iterator { private int position = 0; private final int size = length(); @Override public boolean hasNext() { return position < size; } @Override public T next() { if (!hasNext()) { throw new NoSuchElementException(); } return get(position++); } @Override public void remove() { throw new UnsupportedOperationException(); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/base/tuple/Tuple0.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.base.tuple; import java.util.*; /** * Tuple0 consisting of empty element. * * @author Ponfee */ public final class Tuple0 extends Tuple { private static final long serialVersionUID = -3627925720098458172L; private static final Tuple0 INSTANCE = new Tuple0(); public Tuple0() { } public static Tuple0 of() { return INSTANCE; } @Override public T get(int index) { throw new IndexOutOfBoundsException("Index: " + index); } @Override public void set(T value, int index) { throw new IndexOutOfBoundsException("Index: " + index); } @Override public int length() { return 0; } @Override public Tuple0 copy() { return INSTANCE; } @Override public List toList() { return Collections.emptyList(); } @Override public Iterator iterator() { return Collections.emptyIterator(); } @Override public Spliterator spliterator() { return Spliterators.emptySpliterator(); } private Object readResolve() { return INSTANCE; } } ================================================ FILE: src/main/java/cn/ponfee/commons/base/tuple/Tuple1.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.base.tuple; /** * Tuple1 consisting of one element. * * @author Ponfee */ public final class Tuple1 extends Tuple { private static final long serialVersionUID = -3627925720098458172L; public A a; public Tuple1(A a) { this.a = a; } public static Tuple1 of(A a) { return new Tuple1<>(a); } @Override public T get(int index) { if (index == 0) { return (T) a; } else { throw new IndexOutOfBoundsException("Index: " + index); } } @Override public void set(T value, int index) { if (index == 0) { a = (A) value; } else { throw new IndexOutOfBoundsException("Index: " + index); } } @Override public int length() { return 1; } @Override public Tuple1 copy() { return new Tuple1<>(a); } } ================================================ FILE: src/main/java/cn/ponfee/commons/base/tuple/Tuple2.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.base.tuple; /** * Tuple2 consisting of two elements. * * @author Ponfee */ public final class Tuple2 extends Tuple { private static final long serialVersionUID = -3627925720098458172L; public A a; public B b; public Tuple2(A a, B b) { this.a = a; this.b = b; } public static Tuple2 of(A a, B b) { return new Tuple2<>(a, b); } @Override public T get(int index) { switch (index) { case 0: return (T) a; case 1: return (T) b; default: throw new IndexOutOfBoundsException("Index: " + index); } } @Override public void set(T value, int index) { switch (index) { case 0: a = (A) value; break; case 1: b = (B) value; break; default: throw new IndexOutOfBoundsException("Index: " + index); } } @Override public int length() { return 2; } @Override public Tuple2 copy() { return new Tuple2<>(a, b); } /** * Returns a Tuple2 Object of this instance swapped values. * * @return a Tuple2 Object of this instance swapped values */ public Tuple2 swap() { return new Tuple2<>(b, a); } } ================================================ FILE: src/main/java/cn/ponfee/commons/base/tuple/Tuple3.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.base.tuple; /** * Tuple3 consisting of three elements. * * @author Ponfee */ public final class Tuple3 extends Tuple { private static final long serialVersionUID = -8101132015890693468L; public A a; public B b; public C c; public Tuple3(A a, B b, C c) { this.a = a; this.b = b; this.c = c; } public static Tuple3 of(A a, B b, C c) { return new Tuple3<>(a, b, c); } @Override public T get(int index) { switch (index) { case 0: return (T) a; case 1: return (T) b; case 2: return (T) c; default: throw new IndexOutOfBoundsException("Index: " + index); } } @Override public void set(T value, int index) { switch (index) { case 0: a = (A) value; break; case 1: b = (B) value; break; case 2: c = (C) value; break; default: throw new IndexOutOfBoundsException("Index: " + index); } } @Override public int length() { return 3; } @Override public Tuple3 copy() { return new Tuple3<>(a, b, c); } } ================================================ FILE: src/main/java/cn/ponfee/commons/base/tuple/Tuple4.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.base.tuple; /** * Tuple4 consisting of four elements. * * @author Ponfee */ public final class Tuple4 extends Tuple { private static final long serialVersionUID = -4282006520880127762L; public A a; public B b; public C c; public D d; public Tuple4(A a, B b, C c, D d) { this.a = a; this.b = b; this.c = c; this.d = d; } public static Tuple4 of(A a, B b, C c, D d) { return new Tuple4<>(a, b, c, d); } @Override public T get(int index) { switch (index) { case 0: return (T) a; case 1: return (T) b; case 2: return (T) c; case 3: return (T) d; default: throw new IndexOutOfBoundsException("Index: " + index); } } @Override public void set(T value, int index) { switch (index) { case 0: a = (A) value; break; case 1: b = (B) value; break; case 2: c = (C) value; break; case 3: d = (D) value; break; default: throw new IndexOutOfBoundsException("Index: " + index); } } @Override public int length() { return 4; } @Override public Tuple4 copy() { return new Tuple4<>(a, b, c, d); } } ================================================ FILE: src/main/java/cn/ponfee/commons/base/tuple/Tuple5.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.base.tuple; /** * Tuple5 consisting of five elements. * * @author Ponfee */ public final class Tuple5 extends Tuple { private static final long serialVersionUID = -528096819207260665L; public A a; public B b; public C c; public D d; public E e; public Tuple5(A a, B b, C c, D d, E e) { this.a = a; this.b = b; this.c = c; this.d = d; this.e = e; } public static Tuple5 of(A a, B b, C c, D d, E e) { return new Tuple5<>(a, b, c, d, e); } @Override public T get(int index) { switch (index) { case 0: return (T) a; case 1: return (T) b; case 2: return (T) c; case 3: return (T) d; case 4: return (T) e; default: throw new IndexOutOfBoundsException("Index: " + index); } } @Override public void set(T value, int index) { switch (index) { case 0: a = (A) value; break; case 1: b = (B) value; break; case 2: c = (C) value; break; case 3: d = (D) value; break; case 4: e = (E) value; break; default: throw new IndexOutOfBoundsException("Index: " + index); } } @Override public int length() { return 5; } @Override public Tuple5 copy() { return new Tuple5<>(a, b, c, d, e); } } ================================================ FILE: src/main/java/cn/ponfee/commons/base/tuple/Tuple6.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.base.tuple; /** * Tuple6 consisting of six elements. * * @author Ponfee */ public final class Tuple6 extends Tuple { private static final long serialVersionUID = 8697978867751048118L; public A a; public B b; public C c; public D d; public E e; public F f; public Tuple6(A a, B b, C c, D d, E e, F f) { this.a = a; this.b = b; this.c = c; this.d = d; this.e = e; this.f = f; } public static Tuple6 of(A a, B b, C c, D d, E e, F f) { return new Tuple6<>(a, b, c, d, e, f); } @Override public T get(int index) { switch (index) { case 0: return (T) a; case 1: return (T) b; case 2: return (T) c; case 3: return (T) d; case 4: return (T) e; case 5: return (T) f; default: throw new IndexOutOfBoundsException("Index: " + index); } } @Override public void set(T value, int index) { switch (index) { case 0: a = (A) value; break; case 1: b = (B) value; break; case 2: c = (C) value; break; case 3: d = (D) value; break; case 4: e = (E) value; break; case 5: f = (F) value; break; default: throw new IndexOutOfBoundsException("Index: " + index); } } @Override public int length() { return 6; } @Override public Tuple6 copy() { return new Tuple6<>(a, b, c, d, e, f); } } ================================================ FILE: src/main/java/cn/ponfee/commons/base/tuple/Tuple7.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.base.tuple; /** * Tuple7 consisting of seven elements. * * @author Ponfee */ public final class Tuple7 extends Tuple { private static final long serialVersionUID = 4235194450172178770L; public A a; public B b; public C c; public D d; public E e; public F f; public G g; public Tuple7(A a, B b, C c, D d, E e, F f, G g) { this.a = a; this.b = b; this.c = c; this.d = d; this.e = e; this.f = f; this.g = g; } public static Tuple7 of(A a, B b, C c, D d, E e, F f, G g) { return new Tuple7<>(a, b, c, d, e, f, g); } @Override public T get(int index) { switch (index) { case 0: return (T) a; case 1: return (T) b; case 2: return (T) c; case 3: return (T) d; case 4: return (T) e; case 5: return (T) f; case 6: return (T) g; default: throw new IndexOutOfBoundsException("Index: " + index); } } @Override public void set(T value, int index) { switch (index) { case 0: a = (A) value; break; case 1: b = (B) value; break; case 2: c = (C) value; break; case 3: d = (D) value; break; case 4: e = (E) value; break; case 5: f = (F) value; break; case 6: g = (G) value; break; default: throw new IndexOutOfBoundsException("Index: " + index); } } @Override public int length() { return 7; } @Override public Tuple7 copy() { return new Tuple7<>(a, b, c, d, e, f, g); } } ================================================ FILE: src/main/java/cn/ponfee/commons/base/tuple/Tuple8.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.base.tuple; /** * Tuple8 consisting of eight elements. * * @author Ponfee */ public final class Tuple8 extends Tuple { private static final long serialVersionUID = 3607273779775623549L; public A a; public B b; public C c; public D d; public E e; public F f; public G g; public H h; public Tuple8(A a, B b, C c, D d, E e, F f, G g, H h) { this.a = a; this.b = b; this.c = c; this.d = d; this.e = e; this.f = f; this.g = g; this.h = h; } public static Tuple8 of(A a, B b, C c, D d, E e, F f, G g, H h) { return new Tuple8<>(a, b, c, d, e, f, g, h); } @Override public T get(int index) { switch (index) { case 0: return (T) a; case 1: return (T) b; case 2: return (T) c; case 3: return (T) d; case 4: return (T) e; case 5: return (T) f; case 6: return (T) g; case 7: return (T) h; default: throw new IndexOutOfBoundsException("Index: " + index); } } @Override public void set(T value, int index) { switch (index) { case 0: a = (A) value; break; case 1: b = (B) value; break; case 2: c = (C) value; break; case 3: d = (D) value; break; case 4: e = (E) value; break; case 5: f = (F) value; break; case 6: g = (G) value; break; case 7: h = (H) value; break; default: throw new IndexOutOfBoundsException("Index: " + index); } } @Override public int length() { return 8; } @Override public Tuple8 copy() { return new Tuple8<>(a, b, c, d, e, f, g, h); } } ================================================ FILE: src/main/java/cn/ponfee/commons/base/tuple/Tuple9.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.base.tuple; /** * Tuple9 consisting of nine elements. * * @author Ponfee */ public final class Tuple9 extends Tuple { private static final long serialVersionUID = 3462929449266307061L; public A a; public B b; public C c; public D d; public E e; public F f; public G g; public H h; public I i; public Tuple9(A a, B b, C c, D d, E e, F f, G g, H h, I i) { this.a = a; this.b = b; this.c = c; this.d = d; this.e = e; this.f = f; this.g = g; this.h = h; this.i = i; } public static Tuple9 of(A a, B b, C c, D d, E e, F f, G g, H h, I i) { return new Tuple9<>(a, b, c, d, e, f, g, h, i); } @Override public T get(int index) { switch (index) { case 0: return (T) a; case 1: return (T) b; case 2: return (T) c; case 3: return (T) d; case 4: return (T) e; case 5: return (T) f; case 6: return (T) g; case 7: return (T) h; case 8: return (T) i; default: throw new IndexOutOfBoundsException("Index: " + index); } } @Override public void set(T value, int index) { switch (index) { case 0: a = (A) value; break; case 1: b = (B) value; break; case 2: c = (C) value; break; case 3: d = (D) value; break; case 4: e = (E) value; break; case 5: f = (F) value; break; case 6: g = (G) value; break; case 7: h = (H) value; break; case 8: i = (I) value; break; default: throw new IndexOutOfBoundsException("Index: " + index); } } @Override public int length() { return 9; } @Override public Tuple9 copy() { return new Tuple9<>(a, b, c, d, e, f, g, h, i); } } ================================================ FILE: src/main/java/cn/ponfee/commons/collect/ArrayHashKey.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.collect; import org.apache.commons.lang3.builder.CompareToBuilder; import java.util.Arrays; /** * The class use in Object array as hash map key *

Use for HashMap key * * @author Ponfee */ public final class ArrayHashKey implements java.io.Serializable, Comparable { private static final long serialVersionUID = -8749483734287105153L; private final Object[] key; public ArrayHashKey(Object... key) { this.key = key; } public static ArrayHashKey of(Object... key) { return new ArrayHashKey(key); } @Override public boolean equals(Object other) { if (other == this) { return true; } return other instanceof ArrayHashKey && Arrays.equals(key, ((ArrayHashKey) other).key); } @Override public int hashCode() { return Arrays.hashCode(key); } @Override public int compareTo(ArrayHashKey o) { return new CompareToBuilder().append(key, o.key).toComparison(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/collect/ByteArrayComparator.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.collect; /** * The utility for compare two byte array * * For compare * * @author Ponfee */ public final class ByteArrayComparator { public static int compareTo(final byte[] left, final byte[] right) { return compareTo(left, 0, left.length, right, 0, right.length); } public static int compareTo(byte[] buffer1, int offset1, int length1, byte[] buffer2, int offset2, int length2) { // Short circuit equal case if (buffer1 == buffer2 && offset1 == offset2 && length1 == length2) { return 0; } // Bring WritableComparator code local int end1 = offset1 + length1; int end2 = offset2 + length2; for (int i = offset1, j = offset2; i < end1 && j < end2; i++, j++) { int a = (buffer1[i] & 0xff); int b = (buffer2[j] & 0xff); if (a != b) { return a - b; } } return length1 - length2; } } ================================================ FILE: src/main/java/cn/ponfee/commons/collect/ByteArrayTrait.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.collect; /** * Represents the class has an byte[] array args constructor and a toByteArray method * * For serialize * * @author Ponfee */ public abstract class ByteArrayTrait { public ByteArrayTrait(byte[] array) {} /** * Returns byte array * * @return byte array */ public abstract byte[] toByteArray(); } ================================================ FILE: src/main/java/cn/ponfee/commons/collect/ByteArrayWrapper.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.collect; import org.apache.commons.lang3.ArrayUtils; import java.util.Arrays; /** * The class use in byte array as hash map key * * For HashMap key * * @author Ponfee * @see org.springframework.data.redis.connection.util.ByteArrayWrapper */ public final class ByteArrayWrapper implements java.io.Serializable, Comparable { private static final long serialVersionUID = -8749483734287105153L; private final byte[] array; private final int hashCode; public ByteArrayWrapper(Byte... array) { this(ArrayUtils.toPrimitive(array)); } public ByteArrayWrapper(byte... array) { this.array = array; this.hashCode = Arrays.hashCode(array); } public static ByteArrayWrapper of(byte... array) { return new ByteArrayWrapper(array); } @Override public boolean equals(Object other) { if (other == this) { return true; } if (other instanceof ByteArrayWrapper) { return Arrays.equals(array, ((ByteArrayWrapper) other).array); } return false; } @Override public int hashCode() { return hashCode; } @Override public int compareTo(ByteArrayWrapper o) { return ByteArrayComparator.compareTo(array, o.array); //return Bytes.toBigInteger(array).compareTo(Bytes.toBigInteger(o.array)); } /** * Returns the byte array * * @return a byte array */ public byte[] getArray() { return array; } @Override public String toString() { return "ByteArrayWrapper[" + (array == null ? "null" : array.length) + "]"; } } ================================================ FILE: src/main/java/cn/ponfee/commons/collect/Collects.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.collect; import cn.ponfee.commons.base.Predicates; import cn.ponfee.commons.math.Numbers; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.ArrayUtils; import org.springframework.util.Assert; import java.lang.reflect.Array; import java.util.*; import java.util.Map.Entry; import java.util.function.*; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; /** * 集合工具类 * * @author Ponfee */ public final class Collects { /** * 转数组 * @param args * @return */ @SuppressWarnings("unchecked") public static T[] toArray(T... args) { return args; } /** * object to list * @param obj of elements * @return list with the same elements */ public static List toList(Object obj) { if (obj == null) { return null; } if (obj.getClass().isArray()) { int length = Array.getLength(obj); List result = new ArrayList<>(length); for (int i = 0; i < length; i++) { result.add(Array.get(obj, i)); } return result; } if (obj instanceof Collection) { return new ArrayList<>((Collection) obj); } return Collections.singletonList(obj); } public static LinkedList newLinkedList(E element) { LinkedList list = new LinkedList<>(); list.add(element); return list; } /** * Gets the first element for values * * @param values the values * @param the values element type * @return first element of values */ public static T getFirst(Collection values) { if (values == null || values.isEmpty()) { return null; } if (values instanceof Deque) { return ((Deque) values).getFirst(); } if (values instanceof List) { return ((List) values).get(0); } return values.iterator().next(); } /** * Gets the last element for values * * @param values the values * @param the values element type * @return last element of values */ public static T getLast(Collection values) { if (values == null || values.isEmpty()) { return null; } if (values instanceof Deque) { return ((Deque) values).getLast(); } if (values instanceof List) { return ((List) values).get(values.size() - 1); } return values.stream().reduce((a, b) -> b).orElse(null); } public static T get(T[] array, int index) { if (array == null || index < 0 || index >= array.length) { return null; } return index < array.length ? array[index] : null; } public static T get(List list, int index) { if (list == null || index < 0 || index >= list.size()) { return null; } return list.get(index); } // -----------------------------the collection of intersect, union and different operations /** * two Collection intersect * intersect([1,2,3], [2,3,4]) = [2,3] * * @param coll1 the collection 1 * @param coll2 the collection 2 * @return a list of the two collection intersect result */ public static List intersect(Collection coll1, Collection coll2) { return coll1.stream().filter(coll2::contains).collect(Collectors.toList()); } /** * two array intersect * * @param array1 * @param array2 * @return */ @SuppressWarnings({ "unchecked" }) public static T[] intersect(T[] array1, T[] array2) { List list = Stream.of(array1) .filter(t -> ArrayUtils.contains(array2, t)) .collect(Collectors.toList()); Class type = array1.getClass().getComponentType(); return list.toArray((T[]) Array.newInstance(type, list.size())); } /** * two Collection union result * * @param coll1 * @param coll2 * @return */ public static List union(Collection coll1, Collection coll2) { int max = coll1.size(), min = coll2.size(); if (max < min) { int tmp = max; max = min; min = tmp; } List res = new ArrayList<>(max + (min >> 1)); res.addAll(coll1); coll2.stream().filter(Predicates.not(coll1::contains)).forEach(res::add); return res; } /** * list差集 * different([1,2,3], [2,3,4]) = [1,4] * * @param list1 * @param list2 * @return */ public static List different(List list1, List list2) { List res = new ArrayList<>(); list1.stream().filter(Predicates.not(list2::contains)).forEach(res::add); list2.stream().filter(Predicates.not(list1::contains)).forEach(res::add); return res; } /** * The two set different elements * * @param set1 * @param set2 * @return */ public static Set different(Set set1, Set set2) { Set res = new HashSet<>(); set1.stream().filter(Predicates.not(set2::contains)).forEach(res::add); set2.stream().filter(Predicates.not(set1::contains)).forEach(res::add); return res; } /** * map差集 * * @param map1 * @param map2 * @return */ public static Map different(Map map1, Map map2) { Map res = new HashMap<>(Math.max(map1.size(), map2.size())); map1.entrySet() .stream() .filter(e -> !map2.containsKey(e.getKey())) .forEach(e -> res.put(e.getKey(), e.getValue())); map2.entrySet() .stream() .filter(e -> !map1.containsKey(e.getKey())) .forEach(e -> res.put(e.getKey(), e.getValue())); return res; } public static List duplicate(Collection list) { return duplicate(list, Function.identity()); } /** * Returns the duplicates elements for list * * @param list the list * @return a set of duplicates elements for list */ public static List duplicate(Collection list, Function mapper) { if (CollectionUtils.isEmpty(list)) { return Collections.emptyList(); } return list.stream() .map(mapper) .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())) .entrySet() .stream() .filter(e -> e.getValue() > 1) .map(Entry::getKey) .collect(Collectors.toList()); } /** * Returns a new array for merged the generic array generator * * @see org.apache.commons.lang3.ArrayUtils#addAll(T[] array1, T... array2) * @param generator the generic array generator * @param arrays the multiple generic object array * @return a new array of merged */ @SuppressWarnings({ "unchecked" }) public static T[] concat(IntFunction generator, T[]... arrays) { // component type maybe not correct //Class type = arrays[0].getClass().getComponentType(); //return list.toArray((T[]) Array.newInstance(type, list.size())); // [Ljava.lang.Object; cannot be cast to [Ljava.lang.String; //return list.toArray((T[]) new Object[list.size()]); if (ArrayUtils.isEmpty(arrays)) { return null; } return Arrays.stream(arrays) .filter(Objects::nonNull) .flatMap(Arrays::stream) .toArray(generator); } /** * Puts the element to list specified index * * @param list a list * @param index spec index * @param obj the element */ public static void set(List list, int index, T obj) { for (int i = list.size(); i <= index; i++) { list.add(null); } list.set(index, obj); } /** * Expand the list size * * @param list the list * @param size the target size * @param supplier element provider * @param the element type */ public static void expand(List list, int size, Supplier supplier) { for (int i = list.size(); i <= size; i++) { list.add(supplier.get()); } } /** * Returns consecutive sub array of an array, * each of the same size (the final list may be smaller). * *
     *  Collects.partition(new int[]{1,1,2,5,3}, 1)    ->  [1, 1, 2, 5, 3]
     *  Collects.partition(new int[]{1,1,2,5,3}, 3)    ->  [1, 1]; [2, 5]; [3]
     *  Collects.partition(new int[]{1,1,2,5,3}, 5)    ->  [1]; [1]; [2]; [5]; [3]
     *  Collects.partition(new int[]{1,1,2,5,3}, 6)    ->  [1]; [1]; [2]; [5]; [3]
     *  Collects.partition(new int[]{1,1,2,5,3}, 100)  ->  [1]; [1]; [2]; [5]; [3]
     * 
* * @param array the array * @param size the size * @return a list of consecutive sub sets */ public static List partition(int[] array, int size) { Assert.isTrue(size > 0, "Size must be greater than 0."); if (array == null || array.length == 0) { return null; } size = Math.min(size, array.length); if (size == 1) { return Collections.singletonList(array); } List result = new ArrayList<>(size); int pos = 0; for (int number : Numbers.slice(array.length, size)) { if (number == 0) { break; } result.add(Arrays.copyOfRange(array, pos, pos = pos + number)); } return result; } /** * Compute cartesian product * * [1, 2, 3] x [4, 5, 6] = [[4, 5, 6], [8, 10, 12], [12, 15, 18]] * * @param x the list of type A * @param y the list of type B * @param fun convert A and B to T * @return a list of type T */ public static List> cartesian(List x, List y, BiFunction fun) { List> product = new ArrayList<>(x.size()); for (A a : x) { List row = new ArrayList<>(y.size()); for (B b : y) { row.add(fun.apply(a, b)); } product.add(row); } return product; } /** * Rotate list array data * * [[a,b,c,d],[1,2,3,4]] -> [[a,1],[b,2],[c,3],[d,4]] * * @param list the list * @return a list array result */ public static List rotate(List list) { if (list == null || list.isEmpty()) { return null; } int length = list.get(0).length, size = list.size(); List result = new ArrayList<>(length); for (int i = 0; i < length; i++) { Object[] array = new Object[size]; for (int j = 0; j < size; j++) { array[j] = list.get(j)[i]; } result.add(array); } return result; } public static int[] sortAndGetIndexSwapMapping(int[] array) { int[] indexSwapMapping = IntStream.range(0, array.length).toArray(); for (int n = array.length - 1, i = 0; i < n; i++) { int minimumIndex = i; for (int j = i + 1; j <= n; j++) { if (array[minimumIndex] > array[j]) { minimumIndex = j; } } if (minimumIndex != i) { ArrayUtils.swap(array, i, minimumIndex); ArrayUtils.swap(indexSwapMapping, i, minimumIndex); } } return indexSwapMapping; } /** * Checks that the specified array reference is not null and not empty, * throws a customized {@link IllegalStateException} if it is. * * @param array the array * @param the type of the array element * @return {@code array} if not null and not empty */ public static T[] requireNonEmpty(T[] array) { if (ArrayUtils.isEmpty(array)) { throw new IllegalStateException("The array cannot be empty."); } return array; } /** * Checks that the specified list reference is not null and not empty, * throws a customized {@link IllegalStateException} if it is. * * @param list the list * @param the type of the list element * @return {@code list} if not null and not empty */ public static List requireNonEmpty(List list) { if (CollectionUtils.isEmpty(list)) { throw new IllegalStateException("The list cannot be empty."); } return list; } public static List convert(Collection collection, Function mapper) { return convert(collection, null, mapper); } public static List convert(Collection collection, Predicate predicate, Function mapper) { if (collection == null) { return null; } if (collection.isEmpty()) { return Collections.emptyList(); } List result = new ArrayList<>(collection.size()); Stream stream = collection.stream(); if (predicate != null) { stream = stream.filter(predicate); } stream.map(mapper).forEach(result::add); return result; } @SafeVarargs public static List concat(List list, T... array) { if (list == null) { return array == null ? Collections.emptyList() : Arrays.asList(array); } if (array == null || array.length == 0) { return list; } List result = new ArrayList<>(list.size() + array.length); result.addAll(list); Collections.addAll(result, array); return result; } public static T[] newArray(Class arrayType, int length) { return arrayType.equals(Object[].class) ? (T[]) new Object[length] : (T[]) Array.newInstance(arrayType.getComponentType(), length); } public static Stream stream(Collection collection) { return CollectionUtils.isEmpty(collection) ? Stream.empty() : collection.stream(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/collect/Comparators.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.collect; import java.util.Comparator; /** * For collection order * * @author Ponfee */ public final class Comparators { public static final int EQ = 0; public static final int GT = 1; public static final int LT = -1; public static > Comparator asc() { return Comparator.naturalOrder(); } public static > Comparator desc() { return Comparator.reverseOrder(); } public static > Comparator order(boolean asc) { return asc ? asc() : desc(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/collect/DelegatedIntSpliterator.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.collect; import java.util.Comparator; import java.util.Spliterator; import java.util.function.Consumer; import java.util.function.IntConsumer; import java.util.function.IntFunction; import java.util.stream.IntStream; /** * Delegated int spliterator * * @param elelement type * @author Ponfee */ public class DelegatedIntSpliterator implements Spliterator { private static final int CHARACTERISTICS = Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.IMMUTABLE; private final Spliterator.OfInt delegate; private final IntFunction mapper; public DelegatedIntSpliterator(int startInclusive, int endExclusive, IntFunction mapper) { this.delegate = IntStream.range(startInclusive, endExclusive).spliterator(); this.mapper = mapper; } public DelegatedIntSpliterator(Spliterator.OfInt delegate, IntFunction mapper) { this.delegate = delegate; this.mapper = mapper; } @Override public boolean tryAdvance(Consumer action) { return delegate.tryAdvance((IntConsumer) i -> action.accept(mapper.apply(i))); } @Override public void forEachRemaining(Consumer action) { delegate.forEachRemaining((IntConsumer) i -> action.accept(mapper.apply(i))); } @Override public Spliterator trySplit() { Spliterator.OfInt split = delegate.trySplit(); return (split == null) ? null : new DelegatedIntSpliterator<>(split, mapper); } @Override public long estimateSize() { return delegate.estimateSize(); } @Override public int characteristics() { return CHARACTERISTICS; } @Override public Comparator getComparator() { // inner elements unsupported sortable throw new IllegalStateException(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/collect/DoubleListViewer.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.collect; import org.springframework.util.Assert; import java.util.*; /** * Double(tow-dimensional) list viewer * * @param the element type * @author Ponfee */ public class DoubleListViewer implements List, RandomAccess { private final Collection> list; private final int size; public DoubleListViewer(Collection> list) { Assert.notNull(list, "Origin list cannot be null."); this.list = list; this.size = list.stream().mapToInt(e -> e == null ? 0 : e.size()).sum(); } @Override public int size() { return size; } @Override public boolean isEmpty() { return size() == 0; } @Override public boolean contains(Object o) { return indexOf(o) >= 0; } @Override public boolean containsAll(Collection c) { for (Object e : c) { if (!contains(e)) { return false; } } return false; } @Override public Object[] toArray() { return list.stream().filter(Objects::nonNull).flatMap(List::stream).toArray(); } @Override public T[] toArray(T[] a) { return list.stream() .filter(Objects::nonNull) .flatMap(List::stream) .toArray(length -> (T[]) Collects.newArray(a.getClass(), length)); } @Override public E get(int index) { Assert.isTrue(index >= 0, "Index must greater than zero."); if (index >= size()) { throw new IndexOutOfBoundsException("Index out of bounds: " + index); } int count = 0; for (List sub : list) { if (sub == null || sub.isEmpty()) { continue; } if (index >= count + sub.size()) { count += sub.size(); continue; } return sub.get(index - count); } // cannot happen throw new IllegalStateException(); } @Override public int indexOf(Object o) { int index = 0; if (o == null) { for (List sub : list) { if (sub == null || sub.isEmpty()) { continue; } for (E e : sub) { if (e == null) { return index; } index++; } } } else { for (List sub : list) { if (sub == null || sub.isEmpty()) { continue; } for (E e : sub) { if (o.equals(e)) { return index; } index++; } } } return -1; } @Override public int lastIndexOf(Object o) { int index = size() - 1; if (o == null) { for (List sub : list) { if (sub == null || sub.isEmpty()) { continue; } for (int i = sub.size() - 1; i >= 0; i--) { if (sub.get(i) == null) { return index; } index--; } } } else { for (List sub : list) { if (sub == null || sub.isEmpty()) { continue; } for (int i = sub.size() - 1; i >= 0; i--) { if (o.equals(sub.get(i))) { return index; } index--; } } } return -1; } @Override public Iterator iterator() { return new UnmodifiableIterator(0, size()); } @Override public ListIterator listIterator() { return new UnmodifiableListIterator(0, size()); } @Override public ListIterator listIterator(int index) { return new UnmodifiableListIterator(index, size()); } @Override public List subList(int fromIndex, int toIndex) { throw new UnsupportedOperationException(); } @Override public String toString() { Iterator it = iterator(); if (!it.hasNext()) { return "[]"; } StringBuilder sb = new StringBuilder(); sb.append('['); for (; ; ) { E e = it.next(); sb.append(e == this ? "(this Collection)" : e); if (!it.hasNext()) { return sb.append(']').toString(); } sb.append(',').append(' '); } } @Override public int hashCode() { int hashCode = 1; for (E e : this) { hashCode = 31 * hashCode + (e == null ? 0 : e.hashCode()); } return hashCode; } @Override public boolean equals(Object o) { if (o == this) { return true; } if (!(o instanceof List)) { return false; } ListIterator a = listIterator(); ListIterator b = ((List) o).listIterator(); while (a.hasNext() && b.hasNext()) { if (!(Objects.equals(a.next(), b.next()))) { return false; } } return !(a.hasNext() || b.hasNext()); } // ----------------------------------------------------Unsupported operations @Override public E set(int index, E element) { throw new UnsupportedOperationException(); } @Override public void add(int index, E element) { throw new UnsupportedOperationException(); } @Override public E remove(int index) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection c) { throw new UnsupportedOperationException(); } @Override public boolean addAll(int index, Collection c) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection c) { throw new UnsupportedOperationException(); } @Override public boolean retainAll(Collection c) { throw new UnsupportedOperationException(); } @Override public void clear() { throw new UnsupportedOperationException(); } @Override public boolean add(E e) { throw new UnsupportedOperationException(); } @Override public boolean remove(Object o) { throw new UnsupportedOperationException(); } // ----------------------------------------------------Static class private class UnmodifiableListIterator extends UnmodifiableIterator implements ListIterator { UnmodifiableListIterator(int position, int end) { super(position, end); } @Override public boolean hasPrevious() { return !isEmpty() && position > 0; } @Override public E previous() { if (!hasPrevious()) { throw new NoSuchElementException(); } return get(--position); } @Override public int nextIndex() { return position; } @Override public int previousIndex() { return position - 1; } @Override public void set(E e) { throw new UnsupportedOperationException(); } @Override public void add(E e) { throw new UnsupportedOperationException(); } } private class UnmodifiableIterator implements Iterator { protected int position; protected final int end; UnmodifiableIterator(int position, int end) { this.position = position; this.end = end; } @Override public boolean hasNext() { return !isEmpty() && position < end; } @Override public E next() { if (!hasNext()) { throw new NoSuchElementException(); } return get(position++); } @Override public void remove() { throw new UnsupportedOperationException(); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/collect/FilterableIterator.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.collect; import java.util.Iterator; import java.util.Objects; import java.util.function.Predicate; /** * 遍历集合,选取符合条件的元素,用于增强for循环场景 *
{@code
 *  for (String s : FilterableIterator.of("", null, "a")) {
 *    System.out.println(s);
 *  }
 * }
* * @param Parameterized Type * @author Ponfee */ public class FilterableIterator implements Iterable, Iterator { private final Predicate predicate; private final Iterator iterator; private T current; private FilterableIterator(Iterator iterator) { this(Objects::nonNull, iterator); } private FilterableIterator(Predicate predicate, Iterator iterator) { this.predicate = predicate; this.iterator = iterator; } public static FilterableIterator of(Iterator iterator) { return new FilterableIterator<>(iterator); } public static FilterableIterator of(Predicate predicate, Iterator iterator) { return new FilterableIterator<>(predicate, iterator); } public static FilterableIterator of(Iterable iterable) { return new FilterableIterator<>(iterable.iterator()); } public static FilterableIterator of(Predicate predicate, Iterable iterable) { return new FilterableIterator<>(predicate, iterable.iterator()); } @SafeVarargs public static FilterableIterator of(T... array) { return new FilterableIterator<>(new ArrayIterator<>(array)); } @SafeVarargs public static FilterableIterator of(Predicate predicate, T... array) { return new FilterableIterator<>(predicate, new ArrayIterator<>(array)); } @Override public Iterator iterator() { return this; } @Override public boolean hasNext() { while (iterator.hasNext()) { if (predicate.test(current = iterator.next())) { return true; } } return false; } @Override public T next() { return current; } private static class ArrayIterator implements Iterator { private final T[] array; private int cursor = 0; @SafeVarargs private ArrayIterator(T... array) { this.array = array; } @Override public boolean hasNext() { return array != null && cursor != array.length; } @Override public T next() { return array[cursor++]; } } } ================================================ FILE: src/main/java/cn/ponfee/commons/collect/ImmutableArrayList.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.collect; import cn.ponfee.commons.model.ToJsonString; import com.google.common.base.Preconditions; import org.apache.commons.lang3.ArrayUtils; import java.lang.reflect.Array; import java.util.*; import java.util.function.Predicate; import java.util.function.UnaryOperator; /** * Representing immutable List * * @param the element type * @author Ponfee */ public class ImmutableArrayList extends ToJsonString implements List, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 7013120001220709229L; public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; private final E[] elements; public ImmutableArrayList() { this.elements = (E[]) EMPTY_OBJECT_ARRAY; } public ImmutableArrayList(Object[] elements) { // 为了节省时间及空间,此处认为外部环境不会修改数组,故不做拷贝操作 //this.elements = (T[]) Arrays.copyOf(elements, elements.length); this.elements = (E[]) Objects.requireNonNull(elements); } /** * Returns an empty list * * @return empty list */ public static ImmutableArrayList of() { return new ImmutableArrayList<>(); } @SafeVarargs public static ImmutableArrayList of(T... array) { return new ImmutableArrayList<>(array); } public static ImmutableArrayList of(T[] array, T last) { return of(ArrayUtils.addAll(Objects.requireNonNull(array), last)); } public static ImmutableArrayList of(List list) { return of((T[]) (list.isEmpty() ? EMPTY_OBJECT_ARRAY : list.toArray())); } public static ImmutableArrayList of(List list, T last) { return of((T[]) list.toArray(), last); } // ------------------------------------------------------------------------- methods protected int offset() { return 0; } @Override public int size() { return elements.length; } @Override public final boolean isEmpty() { return size() == 0; } @Override public final Object[] toArray() { if (isEmpty()) { return elements.length == 0 ? elements : EMPTY_OBJECT_ARRAY; } return Arrays.copyOfRange(elements, offset(), offset() + size()); } @Override public final T[] toArray(T[] a) { if (isEmpty()) { if (a.length > 0) { a[0] = null; } return a; } else if (a.length < size()) { return (T[]) Arrays.copyOfRange(elements, offset(), offset() + size(), a.getClass()); } else { System.arraycopy(elements, offset(), a, 0, size()); if (a.length > size()) { a[size()] = null; } return a; } } @Override public final E get(int index) { if (index >= size()) { throw new IndexOutOfBoundsException("Index: " + index + ", size: " + size()); } return elements[offset() + index]; } @Override public final int indexOf(Object o) { if (o == null) { for (int i = 0, n = size(); i < n; i++) { if (elements[offset() + i] == null) { return i; } } } else { for (int i = 0, n = size(); i < n; i++) { if (o.equals(elements[offset() + i])) { return i; } } } return -1; } @Override public final int lastIndexOf(Object o) { if (o == null) { for (int i = size() - 1; i >= 0; i--) { if (elements[offset() + i] == null) { return i; } } } else { for (int i = size() - 1; i >= 0; i--) { if (o.equals(elements[offset() + i])) { return i; } } } return -1; } @Override public final boolean contains(Object o) { return indexOf(o) >= 0; } @Override public final boolean containsAll(Collection c) { for (Object e : c) { if (!contains(e)) { return false; } } return true; } @Override public final Iterator iterator() { return new UnmodifiableIterator(offset(), offset() + size()); } @Override public final ListIterator listIterator() { return new UnmodifiableListIterator(offset(), offset() + size()); } @Override public final ListIterator listIterator(int index) { return new UnmodifiableListIterator(offset() + index, offset() + size()); } @Override public final ImmutableArrayList subList(int fromIndex, int toIndex) { Preconditions.checkPositionIndexes(fromIndex, toIndex, size()); int length = toIndex - fromIndex; if (length == size()) { return this; } else if (length == 0) { return of((E[]) EMPTY_OBJECT_ARRAY); } else { return new SubList(offset() + fromIndex, offset() + toIndex); } } @Override public final Spliterator spliterator() { return new DelegatedIntSpliterator<>(offset(), offset() + size(), i -> elements[i]); } @Override public final int hashCode() { int hashCode = 1; for (E e : this) { hashCode = 31 * hashCode + (e == null ? 0 : e.hashCode()); } return hashCode; } @Override public boolean equals(Object o) { if (o == this) { return true; } if (!(o instanceof List)) { return false; } ListIterator a = listIterator(); ListIterator b = ((List) o).listIterator(); while (a.hasNext() && b.hasNext()) { if (!Objects.equals(a.next(), b.next())) { return false; } } return !(a.hasNext() || b.hasNext()); } @Override public Object clone() { return this; } public final E[] join(E last) { if (isEmpty()) { // t.getClass().getComponentType() return last == null ? (E[]) new Object[]{null} : Collects.toArray(last); } Class arrayType = (Class) elements.getClass(); E[] array = arrayType.equals(Object[].class) ? (E[]) new Object[size() + 1] : (E[]) Array.newInstance(arrayType.getComponentType(), size() + 1); System.arraycopy(elements, offset(), array, 0, size()); array[size()] = last; return array; } public final ImmutableArrayList concat(E last) { return of(join(last)); } // ---------------------------------------------------- unsupported operation @Override public final boolean add(E e) { throw new UnsupportedOperationException(); } @Override public final boolean remove(Object o) { throw new UnsupportedOperationException(); } @Override public final boolean addAll(Collection c) { throw new UnsupportedOperationException(); } @Override public final boolean addAll(int index, Collection c) { throw new UnsupportedOperationException(); } @Override public final boolean removeAll(Collection c) { throw new UnsupportedOperationException(); } @Override public final boolean removeIf(Predicate filter) { throw new UnsupportedOperationException(); } @Override public final boolean retainAll(Collection c) { throw new UnsupportedOperationException(); } @Override public final void replaceAll(UnaryOperator operator) { throw new UnsupportedOperationException(); } @Override public final void sort(Comparator c) { throw new UnsupportedOperationException(); } @Override public final void clear() { throw new UnsupportedOperationException(); } @Override public final E set(int index, E element) { throw new UnsupportedOperationException(); } @Override public final void add(int index, E element) { throw new UnsupportedOperationException(); } @Override public final E remove(int index) { throw new UnsupportedOperationException(); } private class UnmodifiableIterator implements Iterator { protected int position; protected final int end; UnmodifiableIterator(int position, int end) { this.position = position; this.end = end; } @Override public boolean hasNext() { return !isEmpty() && position < end; } @Override public E next() { if (!hasNext()) { throw new NoSuchElementException(); } return elements[position++]; } @Override public void remove() { throw new UnsupportedOperationException(); } } private class UnmodifiableListIterator extends UnmodifiableIterator implements ListIterator { UnmodifiableListIterator(int position, int end) { super(position, end); } @Override public boolean hasPrevious() { return !isEmpty() && position > 0; } @Override public E previous() { if (!hasPrevious()) { throw new NoSuchElementException(); } return elements[--position]; } @Override public int nextIndex() { return position; } @Override public int previousIndex() { return position - 1; } @Override public void set(E e) { throw new UnsupportedOperationException(); } @Override public void add(E e) { throw new UnsupportedOperationException(); } } private class SubList extends ImmutableArrayList { private static final long serialVersionUID = 8017446305586649188L; final int offset; final int size; SubList(int from, int to) { super(elements); this.offset = from; this.size = to - from; } @Override protected int offset() { return offset; } @Override public int size() { return size; } } } ================================================ FILE: src/main/java/cn/ponfee/commons/collect/ImmutableHashList.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.collect; import org.apache.commons.collections4.CollectionUtils; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; /** * Immutable hash map & list structure * * @param the key type * @param the val type * @author Ponfee */ public class ImmutableHashList, V> { private static final ImmutableHashList EMPTY = new ImmutableHashList<>(); private final Function mapper; private final Set keys; private final List values; private ImmutableHashList() { this.mapper = v -> null; this.keys = Collections.emptySet(); this.values = Collections.emptyList(); } private ImmutableHashList(List values, Function mapper) { // sort values.sort(Comparator.comparing(mapper)); this.mapper = mapper; this.keys = values.stream().map(mapper).collect(Collectors.toSet()); this.values = Collections.unmodifiableList(values); } public final List values() { return values; } public final boolean contains(V value) { return keys.contains(mapper.apply(value)); } public final boolean isEmpty() { return values.isEmpty(); } public static , V> ImmutableHashList of(List values, Function mapper) { return CollectionUtils.isEmpty(values) ? EMPTY : new ImmutableHashList<>(values, mapper); } public static , V> ImmutableHashList empty() { return EMPTY; } } ================================================ FILE: src/main/java/cn/ponfee/commons/collect/LRUCache.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.collect; import java.util.LinkedHashMap; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * LRU cache based LinkedHashMap * * @author Ponfee * * @param the key type * @param the val type */ public class LRUCache extends LinkedHashMap { private static final long serialVersionUID = 3943991140850259837L; private final ReadWriteLock lock = new ReentrantReadWriteLock(); private volatile int maxSize; public LRUCache() { this(1024); // default maximum size 1024 } public LRUCache(int maxCapacity) { super(16, 0.75f, true); // default initial capacity 16 this.maxSize = maxCapacity; } @Override protected boolean removeEldestEntry(java.util.Map.Entry eldest) { return this.size() > maxSize; } @Override public boolean containsKey(Object key) { lock.readLock().lock(); try { return super.containsKey(key); } finally { lock.readLock().unlock(); } } @Override public V get(Object key) { lock.readLock().lock(); try { return super.get(key); } finally { lock.readLock().unlock(); } } @Override public V put(K key, V value) { lock.writeLock().lock(); try { return super.put(key, value); } finally { lock.writeLock().unlock(); } } @Override public V remove(Object key) { lock.writeLock().lock(); try { return super.remove(key); } finally { lock.writeLock().unlock(); } } @Override public int size() { lock.readLock().lock(); try { return super.size(); } finally { lock.readLock().unlock(); } } @Override public void clear() { lock.writeLock().lock(); try { super.clear(); } finally { lock.writeLock().unlock(); } } public int getMaxSize() { return maxSize; } public void setMaxSize(int maxSize) { this.maxSize = maxSize; } } ================================================ FILE: src/main/java/cn/ponfee/commons/collect/Maps.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.collect; import cn.ponfee.commons.model.Page; import cn.ponfee.commons.model.Result; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; /** * Map Utilities * * @author Ponfee */ public final class Maps { /** * Returns a map contains key specified key * * @param map the map * @param key the string of key * @return {@code true} means constains */ public static boolean hasKey(Map map, String key) { return map != null && map.containsKey(key); } // ----------------------------------------------------------------map to array /** * map转数组 * * @param map the map * @param fields the string fields * @return array of fields mapped values */ public static Object[] toArray(Map map, String... fields) { return Stream.of(fields).map(map::get).toArray(); } /** * List>转List * @param data * @param fields * @return */ public static List toArray(List> data, String... fields) { if (data == null) { return null; } return data.stream() .map(map -> toArray(map, fields)) .collect(Collectors.toList()); } /** * LinkedHashMap转Object[] * * @param data * @return */ public static Object[] toArray(LinkedHashMap data) { if (data == null) { return null; } return data.values().stream().toArray(); } /** * List> -> List * @param data * @return */ public static List toArray(List> data) { if (data == null) { return null; } return data.stream() .map(Maps::toArray) .collect(Collectors.toList()); } /** * Result>>转Result> * @param source * @return */ public static Result> toArray(Result>> source) { return source.from(source.getData().map(Maps::toArray)); } /** * Result>>转Result> * @param source * @param fields * @return */ public static Result> toArray(Result>> source, String... fields) { return source.from(source.getData().map(map -> toArray(map, fields))); } // ----------------------------------------------------------------to List, Map and Array /** * Converts array to map * * @param kv the key value array * @return a map */ public static Map toMap(Object... kv) { if (kv == null) { return null; } int length = kv.length; // length % 2 if ((length & 0x01) != 0) { throw new IllegalArgumentException("args must be pair."); } Map map = new LinkedHashMap<>(length); for (int i = 0; i < length; i += 2) { map.put((String) kv[i], kv[i + 1]); } return map; } } ================================================ FILE: src/main/java/cn/ponfee/commons/collect/StreamForker.java ================================================ package cn.ponfee.commons.collect; import cn.ponfee.commons.exception.ServerException; import java.util.*; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Stream; import java.util.stream.StreamSupport; /** * The class use in fork {@link Stream}, * from book "Java 8 In Action"

* * Usage: *

 {@code
 *   Stream stream = Stream.of(1, 2, 3, 4, 4, 5, 5);
 *   StreamForker.Results results = new StreamForker<>(stream)
 *     .fork(1, s -> s.max(Integer::compareTo)) // 直接聚合
 *     .fork(2, s -> s.distinct().reduce(0, Integer::sum))
 *     .getResults();
 * } 
* * @param * @author Java 8 In Action */ public class StreamForker { private final Stream stream; private final Map, ?>> forks = new HashMap<>(); public StreamForker(Stream stream) { this.stream = stream; } public StreamForker fork(Object key, Function, ?> f) { forks.put(key, f); return this; } public Results getResults() { ForkingStreamConsumer consumer = build(); try { stream.sequential().forEach(consumer); } finally { consumer.finish(); } return consumer; } private ForkingStreamConsumer build() { List> queues = new ArrayList<>(); Map> actions = forks.entrySet().stream().reduce( new HashMap<>(), (map, e) -> { map.put(e.getKey(), getOperationResult(queues, e.getValue())); return map; }, (m1, m2) -> { m1.putAll(m2); return m1; } ); return new ForkingStreamConsumer<>(queues, actions); } private Future getOperationResult(List> queues, Function, ?> f) { LinkedBlockingQueue queue = new LinkedBlockingQueue<>(); queues.add(queue); Stream source = StreamSupport.stream(new BlockingQueueSpliterator<>(queue), false); return CompletableFuture.supplyAsync(() -> f.apply(source)); } public interface Results { R get(Object key); } @SuppressWarnings("unchecked") private static class ForkingStreamConsumer implements Consumer, Results { private static final Object END_OF_STREAM = new Object(); private final List> queues; private final Map> actions; ForkingStreamConsumer(List> queues, Map> actions) { this.queues = queues; this.actions = actions; } @Override public void accept(T t) { queues.forEach(q -> q.add(t)); } @Override public R get(Object key) { try { return ((Future) actions.get(key)).get(); } catch (Exception e) { throw new RuntimeException(e); } } void finish() { accept((T) END_OF_STREAM); } } private static class BlockingQueueSpliterator implements Spliterator { private final BlockingQueue q; BlockingQueueSpliterator(BlockingQueue q) { this.q = q; } @Override public boolean tryAdvance(Consumer action) { T t; while (true) { // if occur exception, then keep take try { t = q.take(); break; } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new ServerException(e); } } if (t != ForkingStreamConsumer.END_OF_STREAM) { action.accept(t); return true; } return false; } @Override public Spliterator trySplit() { return null; } @Override public long estimateSize() { return 0; } @Override public int characteristics() { return 0; } } } ================================================ FILE: src/main/java/cn/ponfee/commons/collect/ValueSortedMap.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.collect; import java.util.Comparator; import java.util.Map; import java.util.TreeMap; import java.util.function.BiFunction; import java.util.function.Function; /** *
 * Extends TreeMap sort by value: {@code
 *   ValueSortedMap valueSortedMap = ValueSortedMap.nullsFirst(a map);
 * }
 *
 * Also use like this: {@code
 *    Map originMap = ImmutableMap.of("b", 2, "a", 1);
 *    TreeMap sortedByValueMap = new TreeMap<>(Comparator.comparing(originMap::get));
 *    sortedByValueMap.putAll(originMap);
 * }
 * 
* * @author Ponfee */ public class ValueSortedMap extends TreeMap { private static final long serialVersionUID = -6242175050718596776L; private ValueSortedMap(Map map, Comparator comparator) { super(new MapValueComparator<>(map, comparator)); map.forEach(super::put); } public static > ValueSortedMap nullsFirst( Map map) { return nullsFirst(map, Comparators.asc()); } public static ValueSortedMap nullsFirst( Map map, Comparator comparator) { return new ValueSortedMap<>(map, Comparator.nullsFirst(comparator)); } public static > ValueSortedMap nullsLast( Map map) { return nullsLast(map, Comparators.asc()); } public static ValueSortedMap nullsLast( Map map, Comparator comparator) { return new ValueSortedMap<>(map, Comparator.nullsLast(comparator)); } // ------------------------------------------------------------Comparator private static class MapValueComparator implements Comparator { private final Map data; private final Comparator comparator; private MapValueComparator(Map data, Comparator comparator) { this.data = data; this.comparator = comparator; } @Override public int compare(K k1, K k2) { int n = comparator.compare(data.get(k1), data.get(k2)); return n != 0 ? n : k1.toString().compareTo(k2.toString()); } } // ------------------------------------------------------------Deprecated Methods @Deprecated @Override public final V put(K k, V v) { throw new UnsupportedOperationException(); } @Deprecated @Override public final V putIfAbsent(K key, V value) { throw new UnsupportedOperationException(); } @Deprecated @Override public final boolean replace(K key, V oldValue, V newValue) { throw new UnsupportedOperationException(); } @Deprecated @Override public final V replace(K key, V value) { throw new UnsupportedOperationException(); } @Deprecated @Override public final V computeIfAbsent(K key, Function mappingFunction) { throw new UnsupportedOperationException(); } @Deprecated @Override public final V computeIfPresent( K key, BiFunction remappingFunction) { throw new UnsupportedOperationException(); } @Deprecated @Override public final V compute(K key, BiFunction remappingFunction) { throw new UnsupportedOperationException(); } @Deprecated @Override public final V merge( K key, V value, BiFunction remappingFunction) { throw new UnsupportedOperationException(); } @Deprecated @Override public final void putAll(Map map) { throw new UnsupportedOperationException(); } @Deprecated @Override public final void replaceAll(BiFunction function) { throw new UnsupportedOperationException(); } @Deprecated @Override public final V remove(Object o) { throw new UnsupportedOperationException(); } @Deprecated @Override public final boolean remove(Object key, Object value) { throw new UnsupportedOperationException(); } @Deprecated @Override public final void clear() { throw new UnsupportedOperationException(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/concurrent/AsyncBatchProcessor.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.concurrent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.Assert; import java.util.ArrayList; import java.util.List; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.atomic.AtomicBoolean; /** *
* * @param * @author Ponfee * @see Thread#setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler) */ public final class AsyncBatchProcessor { private final static Logger LOG = LoggerFactory.getLogger(AsyncBatchProcessor.class); private final AsyncBatchThread async; public AsyncBatchProcessor(BatchProcessor processor) { this(processor, 100, 200, 2); } /** * @param processor 批处理器 * @param periodTimeMillis 处理周期(毫秒) * @param batchSize 批量大小 * @param maximumPoolSize 最大线程数 */ public AsyncBatchProcessor(BatchProcessor processor, int periodTimeMillis, int batchSize, int maximumPoolSize) { this.async = new AsyncBatchThread<>( processor, periodTimeMillis, batchSize, maximumPoolSize ); } /** * Puts an element to queue * * @param element the element */ public boolean put(T element) { return !async.stopped.get() && async.queue.offer(element); } /** * Batch put elements to queue. * * @param elements the elements */ public boolean put(T[] elements) { if (async.stopped.get() || elements == null || elements.length == 0) { return false; } for (T element : elements) { if (!async.queue.offer(element)) { return false; } } return true; } /** * Batch put elements to queue. * * @param elements the list of elements */ public boolean put(List elements) { if (async.stopped.get() || elements == null || elements.isEmpty()) { return false; } for (T element : elements) { if (!async.queue.offer(element)) { return false; } } return true; } /** * Do stop * * @return {@code true} if stop success */ public boolean stop() { return async.stopped.compareAndSet(false, true); } public void stopAndAwait() throws InterruptedException { stop(); while (!Threads.isStopped(async)) { Thread.sleep(async.periodTimeMillis); } } /** * Async batch consume into this alone thread */ private static class AsyncBatchThread extends Thread { private static final int MINIMUM_PERIOD_TIME_MILLIS = 9; // 单消费者用LinkedBlockingQueue,多消费者用ConcurrentLinkedQueue private final LinkedBlockingQueue queue = new LinkedBlockingQueue<>(); private final AtomicBoolean stopped = new AtomicBoolean(false); private final BatchProcessor processor; // 处理器 private final int periodTimeMillis; // 消费周期(毫秒) private final int sleepTimeMillis; // 休眠时间 private final int batchSize; // 批量大小 private final int asyncExecuteThreshold; // 启用异步执行器的阈值 private final int maximumPoolSize; // 最大线程数 private long nextRefreshTimeMillis = 0L; // 下一次刷新时间 /** * @param processor 批处理器 * @param periodTimeMillis 处理周期(毫秒) * @param batchSize 批量大小 * @param maximumPoolSize 最大线程数 */ private AsyncBatchThread(BatchProcessor processor, int periodTimeMillis, int batchSize, int maximumPoolSize) { Assert.isTrue( periodTimeMillis >= MINIMUM_PERIOD_TIME_MILLIS, () -> "Period time millis must greater than " + MINIMUM_PERIOD_TIME_MILLIS + ", but actual " + periodTimeMillis ); Assert.isTrue(batchSize > 0, "Batch size cannot negative number."); Assert.isTrue(maximumPoolSize > 0, "Maximum pool size cannot negative number."); this.processor = processor; this.periodTimeMillis = periodTimeMillis; this.sleepTimeMillis = (periodTimeMillis >>> 1); this.batchSize = batchSize; this.asyncExecuteThreshold = batchSize + (batchSize >>> 1); this.maximumPoolSize = maximumPoolSize; super.setName("async-batch-processor-thread-" + Integer.toHexString(hashCode())); super.setDaemon(false); super.start(); } /** * thread inner run, don't to direct call this method * it is a thread and the alone thread */ @Override public void run() { // async thread pool executor ThreadPoolExecutor asyncExecutor = null; ArrayList list = new ArrayList<>(batchSize); for (int left = batchSize; ; ) { if (isEnd()) { if (asyncExecutor != null) { // wait a moment if async execute try { Thread.sleep(periodTimeMillis); } catch (InterruptedException e) { LOG.error("Thread#sleep occur error.", e); Thread.currentThread().interrupt(); } } // double check if (isEnd()) { if (asyncExecutor != null) { // destroy the async executor ThreadPoolExecutors.shutdown(asyncExecutor, 3); } // exit for loop if end break; } } // 尽量不要使用queue.size(),时间复杂度O(n) if (!queue.isEmpty() && left > 0) { left -= queue.drainTo(list, left); } long currentTimeMillis = System.currentTimeMillis(); if (left == 0 || (!list.isEmpty() && (stopped.get() || currentTimeMillis >= nextRefreshTimeMillis))) { if (asyncExecutor == null && left == 0 && queue.size() > asyncExecuteThreshold) { asyncExecutor = ThreadPoolExecutors.builder() .corePoolSize(1) .maximumPoolSize(maximumPoolSize) .workQueue(new LinkedBlockingQueue<>(2)) .keepAliveTimeSeconds(300) .threadFactory(NamedThreadFactory.builder().prefix("async-batch-processor-worker").build()) .rejectedHandler(ThreadPoolExecutors.CALLER_RUNS) .build(); LOG.info("Asnyc batch processor created thread pool executor: {}", new ThreadPoolMonitor(asyncExecutor)); } if (asyncExecutor != null) { // async thread pool execute // task抛异常后: // execute输出错误信息,线程结束,后续任务会创建新线程执行,会抛出异常 // submit不输出错误信息,线程继续分配执行其它任务,不会抛出异常,除非你调用Future.get() final List data = list; asyncExecutor.submit(() -> processor.process(data, isEnd())); list = new ArrayList<>(left = batchSize); } else { // current thread execute processor.process(list, isEnd()); // reuse the list object list.clear(); left = batchSize; } nextRefreshTimeMillis = currentTimeMillis + periodTimeMillis; } else if (!stopped.get()) { try { // to sleep for prevent endless loop Thread.sleep(sleepTimeMillis); } catch (InterruptedException e) { LOG.error("Thread#sleep occur error.", e); stopped.compareAndSet(false, true); Thread.currentThread().interrupt(); break; } } } } private boolean isEnd() { return stopped.get() && queue.isEmpty(); } } @FunctionalInterface public interface BatchProcessor { void process(List t, boolean stopped); } } ================================================ FILE: src/main/java/cn/ponfee/commons/concurrent/AsyncDelayedExecutor.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.concurrent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.DelayQueue; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; /** * Async delayed executor * * @param the element type * @author Ponfee */ public final class AsyncDelayedExecutor extends Thread { private final static Logger LOG = LoggerFactory.getLogger(AsyncDelayedExecutor.class); private final Consumer processor; // 数据处理器 private final ThreadPoolExecutor asyncExecutor; // 异步执行器 private final DelayQueue> queue = new DelayQueue<>(); private final AtomicBoolean stopped = new AtomicBoolean(false); public AsyncDelayedExecutor(Consumer processor) { this(1, processor); } /** * @param maximumPoolSize the maximumPoolSize * @param processor the data processor */ public AsyncDelayedExecutor(int maximumPoolSize, Consumer processor) { this.processor = processor; ThreadPoolExecutor executor = null; if (maximumPoolSize > 1) { executor = ThreadPoolExecutors.builder() .corePoolSize(1) .maximumPoolSize(maximumPoolSize) .workQueue(new SynchronousQueue<>()) .keepAliveTimeSeconds(300) .threadFactory(NamedThreadFactory.builder().prefix("async_delayed_worker").build()) .rejectedHandler(ThreadPoolExecutors.CALLER_RUNS) .build(); } this.asyncExecutor = executor; super.setName("async_delayed_executor-" + Integer.toHexString(hashCode())); super.setDaemon(false); super.start(); } /** * Puts an element to queue * * @param delayedData the delayed data */ public boolean put(DelayedData delayedData) { if (stopped.get()) { return false; } return queue.offer(delayedData); } public void doStop() { if (stopped.compareAndSet(false, true)) { Threads.stopThread(this, 0, 0, 1000); } } @Override public void run() { while (!stopped.get()) { DelayedData delayed; try { delayed = queue.poll(3000, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { LOG.error("Delayed queue pool occur interrupted.", e); stopped.compareAndSet(false, true); Thread.currentThread().interrupt(); break; } if (delayed != null) { E data = delayed.getData(); if (asyncExecutor != null) { asyncExecutor.submit(() -> processor.accept(data)); } else { processor.accept(data); } } } if (asyncExecutor != null) { // destroy the async executor ThreadPoolExecutors.shutdown(asyncExecutor, 3); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/concurrent/DelayedData.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.concurrent; import com.google.common.primitives.Ints; import java.util.Objects; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; /** * Delayed data * * @author Ponfee */ public class DelayedData implements Delayed { private final long fireTime; private final E data; private DelayedData(E data, long delayInMilliseconds) { this.data = Objects.requireNonNull(data); this.fireTime = System.currentTimeMillis() + delayInMilliseconds; } public static DelayedData of(E data, long delayInMilliseconds) { return new DelayedData<>(data, delayInMilliseconds); } @Override public long getDelay(TimeUnit unit) { long diff = fireTime - System.currentTimeMillis(); return unit.convert(diff, TimeUnit.MILLISECONDS); } @Override public int compareTo(Delayed o) { return Ints.saturatedCast(this.fireTime - ((DelayedData) o).fireTime); } public E getData() { return data; } } ================================================ FILE: src/main/java/cn/ponfee/commons/concurrent/MultithreadExecutors.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.concurrent; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; /** * Multi Thread executor * *

{@code Thread#stop()} will occur "java.lang.ThreadDeath: null" if try...catch wrapped in Throwable * * @author Ponfee */ public class MultithreadExecutors { public static void run(Collection coll, Consumer action, Executor executor) { run(coll, action, executor, 2); } /** * Run async, action the T collection * * @param coll the T collection * @param action the T action * @param executor thread executor service */ public static void run(Collection coll, Consumer action, Executor executor, int dataSizeThreshold) { if (coll == null || coll.isEmpty()) { return; } if (dataSizeThreshold <= 0 || coll.size() < dataSizeThreshold) { coll.forEach(action); return; } coll.stream() .map(e -> CompletableFuture.runAsync(() -> action.accept(e), executor)) .collect(Collectors.toList()) .forEach(CompletableFuture::join); } public static List call(Collection coll, Function mapper, Executor executor) { return call(coll, mapper, executor, 2); } /** * Call async, mapped T to U * * @param coll the T collection * @param mapper the mapper of T to U * @param executor thread executor service * @return the U collection */ public static List call(Collection coll, Function mapper, Executor executor, int dataSizeThreshold) { if (coll == null) { return null; } if (coll.isEmpty()) { return Collections.emptyList(); } if (dataSizeThreshold <= 0 || coll.size() < dataSizeThreshold) { return coll.stream().map(mapper).collect(Collectors.toList()); } return coll.stream() .map(e -> CompletableFuture.supplyAsync(() -> mapper.apply(e), executor)) .collect(Collectors.toList()) .stream() .map(CompletableFuture::join) .collect(Collectors.toList()); } } ================================================ FILE: src/main/java/cn/ponfee/commons/concurrent/NamedThreadFactory.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.concurrent; import org.apache.commons.lang3.StringUtils; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; /** * Implementation of thread factory. * * @author Ponfee */ public class NamedThreadFactory implements ThreadFactory { private static final AtomicInteger POOL_SEQ = new AtomicInteger(1); private final AtomicInteger threadNo = new AtomicInteger(1); private final String prefix; private final Boolean daemon; private final Integer priority; private final Thread.UncaughtExceptionHandler uncaughtExceptionHandler; private final ThreadGroup group; public NamedThreadFactory(String prefix, Boolean daemon, Integer priority, Thread.UncaughtExceptionHandler uncaughtExceptionHandler) { if (StringUtils.isBlank(prefix)) { prefix = "pool-" + POOL_SEQ.getAndIncrement(); } SecurityManager sm = System.getSecurityManager(); this.prefix = prefix + "-thread-"; this.daemon = daemon; this.priority = priority; this.uncaughtExceptionHandler = uncaughtExceptionHandler; this.group = sm != null ? sm.getThreadGroup() : Thread.currentThread().getThreadGroup(); } @Override public Thread newThread(Runnable runnable) { Thread thread = new Thread(group, runnable, prefix + threadNo.getAndIncrement(), 0); thread.setDaemon(daemon != null ? daemon : Thread.currentThread().isDaemon()); if (priority != null) { thread.setPriority(priority); } if (uncaughtExceptionHandler != null) { thread.setUncaughtExceptionHandler(uncaughtExceptionHandler); } return thread; } public ThreadGroup getThreadGroup() { return group; } public static Builder builder() { return new Builder(); } public static class Builder { private String prefix; private Boolean daemon; private Integer priority; private Thread.UncaughtExceptionHandler uncaughtExceptionHandler; private Builder() { } public Builder prefix(String prefix) { this.prefix = prefix; return this; } public Builder daemon(boolean daemon) { this.daemon = daemon; return this; } public Builder priority(Integer priority) { this.priority = priority; return this; } public Builder uncaughtExceptionHandler(Thread.UncaughtExceptionHandler uncaughtExceptionHandler) { this.uncaughtExceptionHandler = uncaughtExceptionHandler; return this; } public NamedThreadFactory build() { return new NamedThreadFactory(prefix, daemon, priority, uncaughtExceptionHandler); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/concurrent/SingleThreadShutdownHook.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.concurrent; import cn.ponfee.commons.exception.Throwables.ThrowingRunnable; import java.util.LinkedList; import java.util.List; /** * Single thread runtime shutdown hook. * * @author Ponfee */ public class SingleThreadShutdownHook { private static final Object LOCK = new Object(); private static HookThread thread = null; public static void addHook(Runnable hook) { synchronized (LOCK) { if (thread == null) { thread = new HookThread(); Runtime.getRuntime().addShutdownHook(thread); } thread.hooks.add(hook); } } private static class HookThread extends Thread { private final List hooks = new LinkedList<>(); @Override public void run() { synchronized (LOCK) { hooks.forEach(e -> ThrowingRunnable.doCaught(e::run)); } } } } ================================================ FILE: src/main/java/cn/ponfee/commons/concurrent/ThreadPoolExecutors.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.concurrent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.Assert; import java.util.concurrent.*; import java.util.concurrent.ThreadPoolExecutor.AbortPolicy; import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy; import java.util.concurrent.ThreadPoolExecutor.DiscardOldestPolicy; import java.util.concurrent.ThreadPoolExecutor.DiscardPolicy; /** * Thread pool executor utility * Multiple Thread * * @author Ponfee */ public final class ThreadPoolExecutors { private final static Logger LOG = LoggerFactory.getLogger(ThreadPoolExecutors.class); public static final int MAX_CAP = 0x7FFF; // max #workers - 1 // ----------------------------------------------------------build-in rejected policy /** * abort and throw RejectedExecutionException */ public static final RejectedExecutionHandler ABORT = new AbortPolicy(); /** * discard the task */ public static final RejectedExecutionHandler DISCARD = new DiscardPolicy(); /** * if not shutdown then run */ public static final RejectedExecutionHandler CALLER_RUNS = new CallerRunsPolicy(); /** * if not shutdown then discard oldest and execute the new */ public static final RejectedExecutionHandler DISCARD_OLDEST = new DiscardOldestPolicy(); /** * if not shutdown then put queue until enqueue */ public static final RejectedExecutionHandler CALLER_BLOCKS = (task, executor) -> { if (!executor.isShutdown()) { try { executor.getQueue().put(task); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("Put a task to queue occur error: BLOCK_PRODUCER", e); } } }; /** * anyway always run */ public static final RejectedExecutionHandler ALWAYS_CALLER_RUNS = (task, executor) -> task.run(); public static Builder builder() { return new Builder(); } public enum PrestartCoreThreadType { NONE, ONE, ALL } public static class Builder { private int corePoolSize; private int maximumPoolSize; private BlockingQueue workQueue; private long keepAliveTimeSeconds; private RejectedExecutionHandler rejectedHandler; private ThreadFactory threadFactory; private boolean allowCoreThreadTimeOut = true; private PrestartCoreThreadType prestartCoreThreadType = PrestartCoreThreadType.NONE; private Builder() { } public Builder corePoolSize(int corePoolSize) { this.corePoolSize = corePoolSize; return this; } public Builder maximumPoolSize(int maximumPoolSize) { this.maximumPoolSize = maximumPoolSize; return this; } public Builder workQueue(BlockingQueue workQueue) { this.workQueue = workQueue; return this; } public Builder keepAliveTimeSeconds(long keepAliveTimeSeconds) { this.keepAliveTimeSeconds = keepAliveTimeSeconds; return this; } public Builder rejectedHandler(RejectedExecutionHandler rejectedHandler) { this.rejectedHandler = rejectedHandler; return this; } public Builder threadFactory(ThreadFactory threadFactory) { this.threadFactory = threadFactory; return this; } public Builder allowCoreThreadTimeOut(boolean allowCoreThreadTimeOut) { this.allowCoreThreadTimeOut = allowCoreThreadTimeOut; return this; } public Builder prestartCoreThreadType(PrestartCoreThreadType prestartCoreThreadType) { this.prestartCoreThreadType = prestartCoreThreadType; return this; } public ThreadPoolExecutor build() { Assert.isTrue(maximumPoolSize > 0, () -> String.format("Maximum pool size %d must greater than 0.", maximumPoolSize)); Assert.isTrue(maximumPoolSize <= MAX_CAP, () -> String.format("Maximum pool size %d cannot greater than %d.", maximumPoolSize, MAX_CAP)); Assert.isTrue(corePoolSize > 0, () -> String.format("Core pool size %d must greater than 0.", corePoolSize)); Assert.isTrue(corePoolSize <= maximumPoolSize, () -> String.format("Core pool size %d cannot greater than maximum pool size %d.", corePoolSize, maximumPoolSize)); Assert.notNull(workQueue, "Worker queue cannot be null."); // create ThreadPoolExecutor instance ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( corePoolSize, maximumPoolSize, keepAliveTimeSeconds, TimeUnit.SECONDS, workQueue, threadFactory != null ? threadFactory : Executors.defaultThreadFactory(), rejectedHandler != null ? rejectedHandler : CALLER_RUNS ); threadPoolExecutor.allowCoreThreadTimeOut(allowCoreThreadTimeOut); if (prestartCoreThreadType == PrestartCoreThreadType.ONE) { threadPoolExecutor.prestartCoreThread(); } else if (prestartCoreThreadType == PrestartCoreThreadType.ALL) { threadPoolExecutor.prestartAllCoreThreads(); } return threadPoolExecutor; } } // ---------------------------------------------------------- /** * Shutdown the ExecutorService safe * * @param executorService the executorService * @return is safe shutdown */ public static boolean shutdown(ExecutorService executorService) { executorService.shutdown(); try { while (!executorService.awaitTermination(1, TimeUnit.SECONDS)) { // do nothing } return true; } catch (Exception e) { LOG.error("Shutdown ExecutorService occur error.", e); executorService.shutdownNow(); Threads.interruptIfNecessary(e); return false; } } /** * Shutdown the executorService max wait time * * @param executorService the executorService * @param awaitSeconds await time seconds * @return {@code true} if safe terminate */ public static boolean shutdown(ExecutorService executorService, int awaitSeconds) { executorService.shutdown(); boolean isSafeTerminated = false, hasCallShutdownNow = false; try { isSafeTerminated = executorService.awaitTermination(awaitSeconds, TimeUnit.SECONDS); if (!isSafeTerminated) { hasCallShutdownNow = true; executorService.shutdownNow(); } } catch (Exception e) { LOG.error("Shutdown ExecutorService occur error.", e); if (!hasCallShutdownNow) { executorService.shutdownNow(); } Threads.interruptIfNecessary(e); } return isSafeTerminated; } } ================================================ FILE: src/main/java/cn/ponfee/commons/concurrent/ThreadPoolMonitor.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.concurrent; import cn.ponfee.commons.model.ToJsonString; import lombok.Getter; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * 线程池监控信息 * * @author Ponfee */ @Getter public class ThreadPoolMonitor extends ToJsonString implements java.io.Serializable { private static final long serialVersionUID = 3890678647435855868L; private final long keepAliveTime; private final int corePoolSize; private final int maximumPoolSize; private final int largestPoolSize; private final int poolSize; private final long taskCount; private final long completedTaskCount; private final int queueSize; private final int activeCount; private final boolean shutdown; private final boolean terminated; public ThreadPoolMonitor(ThreadPoolExecutor pool) { this.keepAliveTime = pool.getKeepAliveTime(TimeUnit.MILLISECONDS); this.corePoolSize = pool.getCorePoolSize(); this.maximumPoolSize = pool.getMaximumPoolSize(); this.largestPoolSize = pool.getLargestPoolSize(); this.poolSize = pool.getPoolSize(); this.taskCount = pool.getTaskCount(); this.completedTaskCount = pool.getCompletedTaskCount(); this.queueSize = pool.getQueue().size(); this.activeCount = pool.getActiveCount(); this.shutdown = pool.isShutdown(); this.terminated = pool.isTerminated(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/concurrent/Threads.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.concurrent; import cn.ponfee.commons.util.ObjectUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Thread Utilities * * @author Ponfee */ public final class Threads { private final static Logger LOG = LoggerFactory.getLogger(ThreadPoolExecutors.class); /** * Returns the thread is whether stopped * * @param thread the thread * @return {@code true} if the thread is stopped */ public static boolean isStopped(Thread thread) { return thread.getState() == Thread.State.TERMINATED; } /** * Stop the thread, and return boolean result of has called java.lang.Thread#stop() * * @param thread the thread * @param sleepCount the sleepCount * @param sleepMillis the sleepMillis * @param joinMillis the joinMillis * @return {@code true} if called java.lang.Thread#stop() */ public static boolean stopThread(Thread thread, int sleepCount, long sleepMillis, long joinMillis) { if (isStopped(thread)) { return false; } if (Thread.currentThread() == thread) { LOG.warn("Call stop on self thread: {}\n{}", thread.getName(), ObjectUtils.getStackTrace()); thread.interrupt(); return stopThread(thread); } LOG.info("Thread stopping: {}", thread.getName()); while (sleepCount-- > 0 && sleepMillis > 0 && !isStopped(thread)) { try { // Wait some time Thread.sleep(sleepMillis); } catch (InterruptedException e) { LOG.error("Waiting thread terminal interrupted: " + thread.getName(), e); thread.interrupt(); Thread.currentThread().interrupt(); } } if (!isStopped(thread)) { // interrupt and wait joined thread.interrupt(); if (joinMillis > 0) { try { thread.join(joinMillis); } catch (InterruptedException e) { LOG.error("Join thread terminal interrupted: " + thread.getName(), e); thread.interrupt(); Thread.currentThread().interrupt(); } } } return stopThread(thread); } public static void interruptIfNecessary(Throwable t) { if (t instanceof InterruptedException) { Thread.currentThread().interrupt(); } } /** * Stop the thread, and return boolean result of has called java.lang.Thread#stop() * * @param thread the thread * @return {@code true} if called java.lang.Thread#stop() */ private static boolean stopThread(Thread thread) { if (isStopped(thread)) { return false; } synchronized (thread) { if (isStopped(thread)) { return false; } try { thread.stop(); // cannot catch Throwable, because it will occur "java.lang.ThreadDeath: null" } catch (Exception e) { LOG.error("Invoke thread stop occur error: " + thread.getName(), e); } LOG.warn("Invoked java.lang.Thread#stop() method: {}", thread.getName()); } return true; } } ================================================ FILE: src/main/java/cn/ponfee/commons/concurrent/TracedRunnable.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.concurrent; import org.slf4j.MDC; import java.util.Map; /** * Traced runnable * * @author Ponfee */ public final class TracedRunnable implements Runnable { private final Runnable runnable; private TracedRunnable(Runnable runnable) { this.runnable = runnable; } public static TracedRunnable of(Runnable runnable) { return new TracedRunnable(runnable); } @Override public void run() { Map ctx = MDC.getCopyOfContextMap(); if (ctx != null) { MDC.setContextMap(ctx); } try { runnable.run(); } finally { MDC.clear(); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/constrain/ConstrainParam.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.constrain; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** *

 * 方法参数验证,用于方法参数内,e.g.
 * public void method(@ConstrainParam SomeBean param);
 * 
* * @author Ponfee */ @Target({ ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) public @interface ConstrainParam { } ================================================ FILE: src/main/java/cn/ponfee/commons/constrain/Constraint.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.constrain; import java.lang.annotation.*; /** *
 *  `@Constraints({
 *      `@Constraint(field = "name", notBlank = true, maxLen = 64),
 *      `@Constraint(field = "type", series = { 1, 2 })
 *  })
 *
 *  or
 *
 *  `@Constraint(notBlank = true, maxLen = 64)
 *  private String name;
 * 
* * 参数约束 * * @author Ponfee */ @Target({ElementType.FIELD, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Repeatable(Constraints.class) // 当只有一个Constraint时要包上Constraints @Documented public @interface Constraint { /** * 参数索引位置(第一个参数为0,第二个参数为1,...,第N个参数为N-1) */ int index() default 0; /** * 基本数据类型参数不用设值,对象类型参数为字段名 */ String field() default ""; // can not use in class filed /** * 校验失败时的提示信息(不设置时自动拼装为如:orderNo{null}:not allow blank;) */ String msg() default ""; /** * 是否不能为null , 为true表示不能为空 , false表示能够为空(对所有类型有效,默认不能为空) */ boolean notNull() default true; /** * 是否不能为空(只针对CharSequence,Collection,Map,Dictionary,Array) */ boolean notEmpty() default false; /** * 是否不为空白串(只针对CharSequence) */ boolean notBlank() default false; /** * 最大长度(只针对CharSequence) */ int maxLen() default -1; /** * 最小长度(只针对CharSequence) */ int minLen() default -1; /** * 正则验证(只针对CharSequence) */ String regExp() default ""; /** * 最大值(只针对整数) * the max value cannot supported Long.MAX_VALUE */ long max() default Long.MAX_VALUE; /** * 最小值(只针对整数) * the max value cannot supported Long.MIN_VALUE */ long min() default Long.MIN_VALUE; /** * 日期格式(只针对CharSequence) */ String datePattern() default ""; /** * 时态(只针对CharSequence[datePattern],Date) */ Tense tense() default Tense.ANY; /** * 数列 */ long[] series() default {}; /** * 最大值(只针对浮点数) * the decimalMax value cannot supported Double.POSITIVE_INFINITY */ double decimalMax() default Double.POSITIVE_INFINITY; /** * 最小值(只针对浮点数) * the decimalMin value cannot supported Double.NEGATIVE_INFINITY */ double decimalMin() default Double.NEGATIVE_INFINITY; /** * 时态(过去或将来) */ enum Tense { PAST("过去"), FUTURE("将来"), ANY("任意"); private final String desc; Tense(String desc) { this.desc = desc; } public String desc() { return this.desc; } } } ================================================ FILE: src/main/java/cn/ponfee/commons/constrain/Constraints.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.constrain; import java.lang.annotation.*; /** *
 *  `@Constraints({
 *      `@Constraint(field = "name", notBlank = true, maxLen = 64),
 *      `@Constraint(field = "type", series = { 1, 2 }),
 *  })
 * 
* * 方法参数校验器 * * @author Ponfee */ @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Constraints { Constraint[] value() default {}; } ================================================ FILE: src/main/java/cn/ponfee/commons/constrain/FailFastValidatorFactoryBean.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.constrain; import org.hibernate.validator.HibernateValidator; import org.springframework.beans.factory.FactoryBean; import javax.validation.Validation; import javax.validation.Validator; /** * For fail fast validator factory bean * * @author Ponfee */ public class FailFastValidatorFactoryBean implements FactoryBean { private final Validator validator; private final Class validatorType; public FailFastValidatorFactoryBean() { this.validator = Validation .byProvider(HibernateValidator.class) .configure() .failFast(true) //.addProperty("hibernate.validator.fail_fast", "true") .buildValidatorFactory() .getValidator(); this.validatorType = this.validator.getClass(); } @Override public Validator getObject() { return this.validator; } @Override public Class getObjectType() { return this.validatorType; } @Override public boolean isSingleton() { return true; } } ================================================ FILE: src/main/java/cn/ponfee/commons/constrain/FieldValidator.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.constrain; import cn.ponfee.commons.base.PrimitiveTypes; import cn.ponfee.commons.base.tuple.Tuple2; import cn.ponfee.commons.date.Dates; import cn.ponfee.commons.model.Result; import cn.ponfee.commons.reflect.Fields; import cn.ponfee.commons.reflect.GenericUtils; import cn.ponfee.commons.util.ObjectUtils; import cn.ponfee.commons.util.RegexUtils; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.time.DateFormatUtils; import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.Field; import java.lang.reflect.GenericDeclaration; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.text.ParseException; import java.util.*; import static cn.ponfee.commons.model.ResultCode.BAD_REQUEST; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; /** *
 *   校验bean实体中含@Constraint注解的属性
 *   e.g.:FieldValidator.newInstance().constrain(bean);
 * 
* * GAV: net.sf.oval:oval:1.90 * * 字段校验 * * @author Ponfee */ public class FieldValidator { private static final Logger LOG = LoggerFactory.getLogger(FieldValidator.class); static final int MAX_MSG_SIZE = 500; private static final String CFG_ERR = "约束配置错误["; private static final String EMPTY = ""; // Method equals & same hashCode static final Cache METHOD_ARGSNAME = CacheBuilder.newBuilder().build(); private static final Cache, Pair> META_CFG_CACHE = CacheBuilder.newBuilder().build(); protected FieldValidator() {} public static FieldValidator newInstance() { return new FieldValidator(); } /** * 约束验证 * @param bean */ public final void constrain(Object bean) { Class clazz = bean.getClass(); if (clazz.isInterface()) { throw new UnsupportedOperationException("unsupported interface constrain."); } StringBuilder builder = new StringBuilder(); while (!clazz.equals(Object.class)) { for (Field field : clazz.getDeclaredFields()) { Constraint cst = field.getAnnotation(Constraint.class); if (cst == null || Modifier.isStatic(field.getModifiers())) { continue; } builder.append( constrain(clazz, field.getName(), Fields.get(bean, field), cst, GenericUtils.getFieldActualType(clazz, field)) ); } clazz = clazz.getSuperclass(); } if (builder.length() > 0) { throw new IllegalArgumentException(builder.toString()); } } protected Object processError(StringBuilder errorMsgBuilder, Method method, Object[] args) { // 校验失败,不调用方法,进入失败处理 if (errorMsgBuilder.length() > MAX_MSG_SIZE) { errorMsgBuilder.setLength(MAX_MSG_SIZE - 3); errorMsgBuilder.append("..."); } String errMsg = errorMsgBuilder.toString(); if (LOG.isInfoEnabled()) { LOG.info( "[args check not pass]-[{}]-{}-[{}]", method.toGenericString(), ObjectUtils.toString(args), errMsg ); } return handleFailure(method.getReturnType(), errMsg); } protected Object handleFailure(Class returnType, String errMsg) { if (returnType == Result.class) { return Result.failure(BAD_REQUEST.getCode(), errMsg); } else { throw new IllegalArgumentException(errMsg); } } protected final String constrain(GenericDeclaration classOrMethod, String field, Object value, Constraint cst, Class type) { Tuple2 key = Tuple2.of(classOrMethod, field); Pair result = META_CFG_CACHE.getIfPresent(key); if (result == null) { synchronized (META_CFG_CACHE) { if ((result = META_CFG_CACHE.getIfPresent(key)) == null) { try { verifyMeta(field, cst, type); result = ImmutablePair.of(true, null); META_CFG_CACHE.put(key, result); } catch (Exception e) { result = ImmutablePair.of(false, e.getMessage()); META_CFG_CACHE.put(key, result); throw e; } } } } // 配置验证 if (!result.getLeft()) { throw new UnsupportedOperationException(result.getRight()); } // 参数验证 return verifyValue(field, value, cst); } /** * Constrain without cache, * only use in method parameter is Map or Dictionary type * * @param name * @param value * @param cst * @param type * @return the constrain result, if empty string means success */ protected final String constrain(String name, Object value, Constraint cst, Class type) { // 配置验证 verifyMeta(name, cst, type); // 参数验证 return verifyValue(name, value, cst); } /** * 配置元数据验证 * @param name * @param c * @param type */ private void verifyMeta(String name, Constraint c, Class type) { if (type == null) { return; } // 基本类型转包装类型(如果是) type = PrimitiveTypes.ofPrimitive(type).wrapper(); // 字串类型验证 if ( (isNotBlank(c.regExp()) || isNotBlank(c.datePattern()) || c.notBlank() || c.maxLen() > -1 || c.minLen() > -1) && !CharSequence.class.isAssignableFrom(type) ) { throw new UnsupportedOperationException(CFG_ERR + name + "]:非字符类型不支持字符规则验证"); } // 整数类型验证 if ( !(c.max() == Long.MAX_VALUE && c.min() == Long.MIN_VALUE) // meet not all the conditions && !(Long.class.isAssignableFrom(type) || Integer.class.isAssignableFrom(type)) ) { throw new UnsupportedOperationException(CFG_ERR + name + "]:非整数类型不支持整数数值验证"); } // 数列类型验证 if ( (c.series() != null && c.series().length > 0) && !(Long.class.isAssignableFrom(type) || Integer.class.isAssignableFrom(type)) ) { throw new UnsupportedOperationException(CFG_ERR + name + "]:非整数类型不支持数列验证"); } // 小数类型验证 if ( !(c.decimalMax() == Double.POSITIVE_INFINITY && c.decimalMin() == Double.NEGATIVE_INFINITY) && !(Double.class.isAssignableFrom(type) || Float.class.isAssignableFrom(type)) ) { throw new UnsupportedOperationException(CFG_ERR + name + "]:非浮点数类型不支持浮点数值验证"); } // 时间类型验证 if ( (c.tense() != Constraint.Tense.ANY && isBlank(c.datePattern())) && !Date.class.isAssignableFrom(type) ) { throw new UnsupportedOperationException(CFG_ERR + name + "]:非日期类型不支持时态验证"); } // 字串、集合类型验证 if (c.notEmpty() && !isEmptiable(type)) { throw new UnsupportedOperationException(CFG_ERR + name + "非集合/字符类型不支持非空验证"); } } /** * Returns the type instance value can with empty verifyValue * * @param type the class type * @return {@code true} means should with empty verifyValue */ private boolean isEmptiable(Class type) { return CharSequence.class.isAssignableFrom(type) || Collection.class.isAssignableFrom(type) || type.isArray() || Map.class.isAssignableFrom(type) || Dictionary.class.isAssignableFrom(type); } private String verifyValue(String str, Object value, Constraint cst) { String error = verify(str, value, cst); if (isNotBlank(error)) { // verify result is not pass return isNotBlank(cst.msg()) ? cst.msg() + ";" : error; } else { return EMPTY; } } /** * 参数验证 * @param n * @param v * @param c * @return */ private String verify(String n, Object v, Constraint c) { // 可以为null且值为null,则跳过验证 if (!c.notNull() && v == null) { return EMPTY; } // 是否不能为blank if (c.notBlank() && isBlank((CharSequence) v)) { return n + "{" + v + "}:不能为空串;"; } // 是否不能为empty if (c.notEmpty() && ObjectUtils.isEmpty(v)) { return n + "{" + v + "}:不能为empty;"; } // 是否不能为null if (c.notNull() && v == null) { return n + "{null}:不能为null;"; } // 正则校验 if (!(!c.notNull() && v == null) && isNotBlank(c.regExp()) && (v == null || !RegexUtils.matches(v.toString(), c.regExp()))) { return n + "{" + v + "}:格式不匹配" + c.regExp() + ";"; } // 最大字符长度 if (c.maxLen() > -1 && v != null && ((CharSequence) v).length() > c.maxLen()) { return n + "{" + v + "}:不能大于" + c.maxLen() + "个字符;"; } // 最小字符长度 if (c.minLen() > -1 && (v == null || ((CharSequence) v).length() < c.minLen())) { return n + "{" + v + "}:不能小于" + c.minLen() + "个字符;"; } // 整数值限制 if (c.max() != Long.MAX_VALUE && v != null && Long.parseLong(v.toString()) > c.max()) { return n + "{" + v + "}:不能大于" + c.max() + ";"; } if (c.min() != Long.MIN_VALUE && (v == null || Long.parseLong(v.toString()) < c.min())) { return n + "{" + v + "}:不能小于" + c.min() + ";"; } // 数列验证 long[] seqs = c.series(); if ((seqs != null && seqs.length > 0) && (v == null || !ArrayUtils.contains(seqs, Long.parseLong(v.toString())))) { return n + "{" + v + "}:不属于数列" + Arrays.toString(seqs) + ";"; } // 浮点数限制 if (c.decimalMax() != Double.POSITIVE_INFINITY && v != null && Double.parseDouble(v.toString()) > c.decimalMax()) { return n + "{" + v + "}:不能大于" + c.decimalMax() + ";"; } if (c.decimalMin() != Double.NEGATIVE_INFINITY && (v == null || Double.parseDouble(v.toString()) < c.decimalMin())) { return n + "{" + v + "}:不能小于" + c.decimalMin() + ";"; } // 时间格式 Date date = null; if (isNotBlank(c.datePattern()) && !(!c.notNull() && (v == null || ObjectUtils.isEmpty(v)))) { try { date = DateUtils.parseDateStrictly((String) v, c.datePattern()); //date = FastDateFormat.getInstance(c.datePattern()).parse((String) v); } catch (ParseException e) { return n + "{" + v + "}:日期格式不匹配" + c.datePattern() + ";"; } } // 时态校验 if (c.tense() != Constraint.Tense.ANY) { if (date == null) { date = (Date) v; } String pattern = c.datePattern(); if (isBlank(pattern)) { pattern = Dates.DATETIME_PATTERN; } if (date == null) { return n + "{null}:日期不能为空;"; } else if (c.tense() == Constraint.Tense.FUTURE && date.before(new Date())) { return n + "{" + DateFormatUtils.format(date, pattern) + "}:不为将来时间;"; } else if (c.tense() == Constraint.Tense.PAST && date.after(new Date())) { return n + "{" + DateFormatUtils.format(date, pattern) + "}:不为过去时间;"; } } return EMPTY; } } ================================================ FILE: src/main/java/cn/ponfee/commons/constrain/Jsr303Validator.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.constrain; import cn.ponfee.commons.model.Result; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.validation.BindingResult; import org.springframework.validation.ObjectError; import java.util.stream.Collectors; import static cn.ponfee.commons.model.ResultCode.BAD_REQUEST; /** * 基于JSR303的Web端参数校验统一处理 * * Controller的方法中有BindingResult参数,则spring框架会进入Controller的方法内 * public Result testValidate(@Valid Article article, BindingResult result) {} * * @author Ponfee */ //@ControllerAdvice //@Aspect //@Order(Ordered.HIGHEST_PRECEDENCE) public abstract class Jsr303Validator { //@Around("execution(public * cn.ponfee.xxx.controller..*Controller..*(..)) && args(..,bindingResult)") public Object verify(ProceedingJoinPoint pjp, BindingResult bindingResult) throws Throwable { if (bindingResult.hasErrors()) { return handleFailure( ((MethodSignature) pjp.getSignature()).getMethod().getReturnType(), bindingResult ); } return pjp.proceed(); } protected Object handleFailure(Class returnType, BindingResult bindingResult) { String errorMsg = bindingResult.getAllErrors() .stream() .map(ObjectError::getDefaultMessage) .collect(Collectors.joining(",", "[", "]")); if (returnType == Result.class) { return Result.failure(BAD_REQUEST.getCode(), BAD_REQUEST.getMsg() + ": " + errorMsg); } else { throw new IllegalArgumentException(errorMsg); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/constrain/MethodValidator.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.constrain; import cn.ponfee.commons.reflect.ClassUtils; import cn.ponfee.commons.reflect.Fields; import cn.ponfee.commons.reflect.GenericUtils; import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.*; /** *
 * 方法参数校验:拦截方法中包含@Constraints注解的方法
 * e.g.:
 *    1.开启spring切面特性:
 *    2.编写子类:
 *        @Component @Aspect
 *        public class TestMethodValidator extends MethodValidator {
 *
 *            @Around(
 *                value = "execution(public * cn.ponfee.xxx.service.impl..*Impl..*(..)) && @annotation(cst)", 
 *                argNames = "pjp,cst"
 *            )
 *            @Override
 *            public Object constrain(ProceedingJoinPoint pjp, Constraints cst) throws Throwable {
 *                return super.constrain(pjp, cst);
 *            }
 *
 *            @Override
 *            protected Object handleFailure(Class returnType, String errMsg) {
 *                if (BaseResult.class.isAssignableFrom(returnType)) {
 *                    return BaseResult.failure(errMsg);
 *                }
 *                return super.handleFailure(returnType, errMsg);
 *            }
 *
 *        }
 * 
* * 参数校验 * * @author Ponfee */ public abstract class MethodValidator extends FieldValidator { private static final Logger LOG = LoggerFactory.getLogger(MethodValidator.class); /** * @param pjp * @param validator * @return * @throws Throwable */ @SuppressWarnings("unchecked") public Object constrain(ProceedingJoinPoint pjp, Constraints validator) throws Throwable { Object[] args = pjp.getArgs(); if (args == null || args.length == 0) { return pjp.proceed(); } Method method = ((MethodSignature) pjp.getSignature()).getMethod(); String[] argsName = METHOD_ARGSNAME.getIfPresent(method); if (argsName == null) { // 要用到asm字节码操作,消耗性能,所以缓存 argsName = ClassUtils.getMethodParamNames(method); METHOD_ARGSNAME.put(method, argsName); } // 校验开始 StringBuilder builder = new StringBuilder(); Class[] paramTypes = method.getParameterTypes(); Constraint cst; String fieldName; Object fieldVal; Class fieldType; Constraint[] csts = validator.value(); try { boolean[] argsNullable = argsNullable(args, csts); for (int len = csts.length, i = 0; i < len; i++) { cst = csts[i]; fieldVal = args[cst.index()]; // 参数对象校验 fieldType = paramTypes[cst.index()]; if (argsNullable[cst.index()] && fieldVal == null) { continue; // 参数可为空,则跳过校验 } else if (StringUtils.isEmpty(cst.field())) { // 验证参数对象 fieldName = argsName[cst.index()]; builder.append(constrain(method, fieldName, fieldVal, cst, fieldType)); } else if (fieldVal == null) { // 不可为空,则抛出异常 String msg; if (args.length == 1) { msg = "参数不能为空;"; } else { msg = "参数{" + argsName[cst.index()] + "}不能为空;"; } throw new IllegalArgumentException(msg); } else if (fieldVal instanceof Map) { /*Method get = fieldVal.getClass().getMethod("get", Object.class); get.setAccessible(true); // ImmutableMap must be set accessible true fieldVal = get.invoke(fieldVal, cst.field());*/ fieldVal = ((Map) fieldVal).get(cst.field()); fieldType = fieldVal == null ? null : fieldVal.getClass(); fieldName = argsName[cst.index()] + "[" + cst.field() + "]"; builder.append(constrain(fieldName, fieldVal, cst, fieldType)); // cannot cache } else if (fieldVal instanceof Dictionary) { fieldVal = ((Dictionary) fieldVal).get(cst.field()); fieldType = fieldVal == null ? null : fieldVal.getClass(); fieldName = argsName[cst.index()] + "[" + cst.field() + "]"; builder.append(constrain(fieldName, fieldVal, cst, fieldType)); // cannot cache } else { // 验证java bean String[] ognl = cst.field().split("\\."); Field field; for (String s : ognl) { field = ClassUtils.getField(fieldType, s); fieldType = GenericUtils.getFieldActualType(fieldType, field); if (fieldVal != null) { fieldVal = Fields.get(fieldVal, field); } } fieldName = argsName[cst.index()] + "." + cst.field(); builder.append(constrain(method, fieldName, fieldVal, cst, fieldType)); } if (builder.length() > MAX_MSG_SIZE) { break; } } } catch (UnsupportedOperationException | IllegalArgumentException e) { builder.append(e.getMessage()); } catch (Exception e) { LOG.error("参数约束校验异常", e); builder.append("参数约束校验异常:").append(e.getMessage()); } return builder.length() == 0 ? pjp.proceed() : processError(builder, method, args); } // -------------------------------------------------------------------------private methods private boolean[] argsNullable(Object[] args, Constraint[] csts) { Set set = new HashSet<>(csts.length); boolean[] isArgsNullable = new boolean[args.length]; Arrays.fill(isArgsNullable, false); for (Constraint cst : csts) { String key = "index=" + cst.index() + ", field=\"" + cst.field() + "\""; if (!set.add(key)) { throw new RuntimeException("配置错误,重复校验[" + key + "]"); } if (cst.index() > args.length - 1) { throw new RuntimeException("配置错误,下标超出[index=" + cst.index() + "]"); } if (StringUtils.isEmpty(cst.field()) && !cst.notNull()) { isArgsNullable[cst.index()] = true; // 该参数可为空 } } return isArgsNullable; } } ================================================ FILE: src/main/java/cn/ponfee/commons/constrain/ParamValidator.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.constrain; import cn.ponfee.commons.exception.Throwables; import cn.ponfee.commons.reflect.ClassUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.annotation.Annotation; import java.lang.reflect.Method; /** *
 * 方法参数校验:拦截参数中包含@ConstrainParam注解的方法
 * @Component
 * @Aspect
 * public class TestParamValidator extends ParamValidator {
 *    @Around(value = "execution(public * cn.ponfee.xxx.service.impl.*Impl.*(
 *         @cn.ponfee.commons.constrain.ConstrainParam (*)
 *    ))")
 *    public @Override Object constrain(ProceedingJoinPoint joinPoint) throws Throwable {
 *      return super.constrain(joinPoint);
 *    }
 * }
 * 
* * @author Ponfee */ public abstract class ParamValidator extends FieldValidator { private static final Logger LOG = LoggerFactory.getLogger(ParamValidator.class); /** * @param joinPoint * @return * @throws Throwable */ public Object constrain(ProceedingJoinPoint joinPoint) throws Throwable { Object[] args = joinPoint.getArgs(); if (args == null || args.length == 0) { return joinPoint.proceed(); } // 参数校验 StringBuilder builder = new StringBuilder(); String[] argsName; Method method = null; try { // 缓存方法参数名 MethodSignature mSign = (MethodSignature) joinPoint.getSignature(); method = joinPoint.getTarget().getClass() .getMethod(mSign.getName(), mSign.getParameterTypes()); argsName = METHOD_ARGSNAME.getIfPresent(method); if (argsName == null) { argsName = ClassUtils.getMethodParamNames(method); METHOD_ARGSNAME.put(method, argsName); } // 方法参数注解校验 Annotation[][] anns = method.getParameterAnnotations(); outer: // this is the label for the outer loop for (int i = 0; i < args.length; i++) { for (Annotation ann : anns[i]) { if (ann instanceof ConstrainParam) { try { constrain(args[i]); } catch (IllegalArgumentException e) { builder.append("[").append(argsName[i]).append("]").append(e.getMessage()); } } if (builder.length() > MAX_MSG_SIZE) { break outer; } } } } catch (UnsupportedOperationException e) { builder.append(e.getMessage()); } catch (NoSuchMethodException e) { LOG.error("reflect exception", e); builder.append(Throwables.getRootCauseStackTrace(e)); } return builder.length() == 0 ? joinPoint.proceed() : processError(builder, method, args); } } ================================================ FILE: src/main/java/cn/ponfee/commons/dag/DAGEdge.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.dag; import cn.ponfee.commons.model.ToJsonString; import com.google.common.graph.EndpointPair; import java.io.Serializable; import java.util.Objects; /** * DAG Edge * * @author Ponfee */ public final class DAGEdge extends ToJsonString implements Serializable { private static final long serialVersionUID = 2292231888365728538L; private final DAGNode source; private final DAGNode target; private DAGEdge(DAGNode source, DAGNode target) { this.source = Objects.requireNonNull(source, "DAG source node cannot be null."); this.target = Objects.requireNonNull(target, "DAG target node cannot be null."); } public static DAGEdge of(DAGNode source, DAGNode target) { return new DAGEdge(source, target); } public static DAGEdge of(String source, String target) { Objects.requireNonNull(source, "DAG source text cannot be blank."); Objects.requireNonNull(target, "DAG target text cannot be blank."); return new DAGEdge(DAGNode.fromString(source), DAGNode.fromString(target)); } public static DAGEdge of(EndpointPair pair) { Objects.requireNonNull(pair, "DAG node pair cannot be blank."); return new DAGEdge(pair.source(), pair.target()); } public DAGNode getSource() { return source; } public DAGNode getTarget() { return target; } @Override public int hashCode() { return Objects.hash(source, target); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof DAGEdge)) { return false; } DAGEdge other = (DAGEdge) obj; return this.source.equals(other.source) && this.target.equals(other.target); } } ================================================ FILE: src/main/java/cn/ponfee/commons/dag/DAGExpressionParser.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.dag; import cn.ponfee.commons.base.Predicates; import cn.ponfee.commons.base.Symbol.Char; import cn.ponfee.commons.base.Symbol.Str; import cn.ponfee.commons.base.tuple.Tuple2; import cn.ponfee.commons.collect.Collects; import cn.ponfee.commons.json.Jsons; import cn.ponfee.commons.tree.BaseNode; import cn.ponfee.commons.tree.PlainNode; import cn.ponfee.commons.tree.TreeNode; import com.fasterxml.jackson.core.type.TypeReference; import com.google.common.collect.ImmutableList; import com.google.common.graph.Graph; import com.google.common.graph.GraphBuilder; import com.google.common.graph.Graphs; import com.google.common.graph.ImmutableGraph; import lombok.Getter; import lombok.Setter; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.util.Assert; import java.io.Serializable; import java.util.*; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; /** * Parse DAG expression to graph * *
 * 解析:new DAGParser("A->((B->C->D),(A->F))->(G,H,X)->J; A->Y").parse();
 * 结果:
 *  "A->((B->C->D),(A->F))->(G,H,X)->J"
 *    <0:0:Start -> 1:1:A>
 *    <1:1:A -> 1:1:B>
 *    <1:1:A -> 1:2:A>
 *    <1:1:B -> 1:1:C>
 *    <1:1:C -> 1:1:D>
 *    <1:2:A -> 1:1:F>
 *    <1:1:D -> 1:1:G>
 *    <1:1:D -> 1:1:H>
 *    <1:1:D -> 1:1:X>
 *    <1:1:F -> 1:1:G>
 *    <1:1:F -> 1:1:H>
 *    <1:1:F -> 1:1:X>
 *    <1:1:G -> 1:1:J>
 *    <1:1:H -> 1:1:J>
 *    <1:1:X -> 1:1:J>
 *    <1:1:J -> 0:0:End>
 *
 *  "A->Y"
 *    <0:0:Start -> 2:3:A>
 *    <2:3:A -> 2:1:Y>
 *    <2:1:Y -> 0:0:End>
 *
 * ---------------------------------------------------
 *
 * 无法用expression来表达的场景:[A->C, A->D, B->D, B->E]
 * ┌─────────────────────────────────┐
 * │               ┌─────>C──┐       │
 * │        ┌──>A──┤         │       │
 * │        │      └──┐      │       │
 * │ Start──┤         ├──>D──┼──>End │
 * │        │      ┌──┘      │       │
 * │        └──>B──┤         │       │
 * │               └─────>E──┘       │
 * └─────────────────────────────────┘
 * 但可通过json graph来表达:
 *   [
 *     {"source": "1:1:A", "target": "1:1:C"},
 *     {"source": "1:1:A", "target": "1:1:D"},
 *     {"source": "1:1:B", "target": "1:1:D"},
 *     {"source": "1:1:B", "target": "1:1:E"}
 *   ]
 * 
* * @author Ponfee */ public class DAGExpressionParser { /** *
     * 1、(?i) 开启大小写忽略模式,但是只适用于ASCII字符
     * 2、(?u) 开启utf-8编码模式
     * 3、(?s) 单行模式,“.”匹配任意字符(包括空白字符)
     * 4、(?m) 开启多行匹配模式,“.”不匹配空白字符
     * 5、(?d) 单行模式,“.”不匹配空白字符
     *
     * Match json array: [{...}]
     * 有两种方式:
     *   1、(?s)^\s*\[\s*\{.+}\s*]\s*$
     *   2、(?m)^\s*\[\s*\{(\s*\S+\s*)+}\s*]\s*$
     * 
*/ private static final Pattern JSON_GRAPH_PATTERN = Pattern.compile("(?s)^\\s*\\[\\s*\\{.+}\\s*]\\s*$"); private static final String SEP_STAGE = "->"; private static final String SEP_UNION = Str.COMMA; private static final List SEP_SYMBOLS = ImmutableList.of(SEP_STAGE, SEP_UNION); private static final List ALL_SYMBOLS = ImmutableList.of(SEP_STAGE, SEP_UNION, Str.CLOSE, Str.OPEN); private static final char[] SINGLE_SYMBOLS = {Char.OPEN, Char.CLOSE, Char.COMMA}; private final String expression; /** * Identity cache of expression wrapped '()' */ private final Map wrappedCache = new IdentityHashMap<>(); /** * Identity cache of partition key */ private final Map partitionCache = new HashMap<>(); /** * Map>> */ private final Map>> incrementer = new HashMap<>(); public DAGExpressionParser(String text) { Assert.hasText(text, "Expression cannot be blank."); this.expression = text.trim(); } public Graph parse() { ImmutableGraph.Builder graphBuilder = GraphBuilder.directed().allowsSelfLoops(false).immutable(); List edges; if (JSON_GRAPH_PATTERN.matcher(expression).matches() && (edges = GraphEdge.fromJson(expression)) != null) { parseJsonGraph(graphBuilder, edges); } else { parsePlainExpr(graphBuilder); } ImmutableGraph graph = graphBuilder.build(); Assert.state(graph.nodes().size() > 2, () -> "Expression not any name: " + expression); Assert.state(graph.successors(DAGNode.START).stream().noneMatch(DAGNode::isEnd), () -> "Expression name cannot direct end: " + expression); Assert.state(graph.predecessors(DAGNode.END).stream().noneMatch(DAGNode::isStart), () -> "Expression name cannot direct start: " + expression); Assert.state(!Graphs.hasCycle(graph), () -> "Expression name section has cycle: " + expression); return graph; } /** * [{"source":"1:1:A","target":"1:1:C"},{"source":"1:1:A","target":"1:1:D"},{"source":"1:1:B","target":"1:1:D"},{"source":"1:1:B","target":"1:1:E"}] * * @param graphBuilder the graph builder * @param edges the edges */ private void parseJsonGraph(ImmutableGraph.Builder graphBuilder, List edges) { Assert.notEmpty(edges, "Graph edges cannot be empty."); Set allNode = new HashSet<>(); Set nonHead = new HashSet<>(); Set nonTail = new HashSet<>(); for (GraphEdge graphEdge : edges) { DAGEdge dagEdge = graphEdge.toDAGEdge(); DAGNode source = dagEdge.getSource(); DAGNode target = dagEdge.getTarget(); Assert.isTrue(!source.isStartOrEnd(), () -> "Graph edge cannot be start or end: " + source); Assert.isTrue(!target.isStartOrEnd(), () -> "Graph edge cannot be start or end: " + target); graphBuilder.putEdge(source, target); allNode.add(source); allNode.add(target); nonHead.add(target); nonTail.add(source); } allNode.stream().filter(Predicates.not(nonHead::contains)).forEach(e -> graphBuilder.putEdge(DAGNode.START, e)); allNode.stream().filter(Predicates.not(nonTail::contains)).forEach(e -> graphBuilder.putEdge(e, DAGNode.END)); } /** * A->((B->C->D),(A->F))->(G,H,X)->J; A->Y * * @param graphBuilder the graph builder */ private void parsePlainExpr(ImmutableGraph.Builder graphBuilder) { Assert.isTrue(checkParenthesis(expression), () -> "Invalid expression parenthesis: " + expression); List sections = Stream.of(expression.split(";")).filter(StringUtils::isNotBlank).map(String::trim).collect(Collectors.toList()); Assert.notEmpty(sections, () -> "Invalid split with ';' expression: " + expression); for (int i = 0, n = sections.size(); i < n; i++) { String section = sections.get(i); Assert.isTrue(checkParenthesis(section), () -> "Invalid expression parenthesis: " + section); String expr = completeParenthesis(section); buildGraph(i + 1, Collections.singletonList(expr), graphBuilder, DAGNode.START, DAGNode.END); } } private void buildGraph(int section, List expressions, ImmutableGraph.Builder graphBuilder, DAGNode prev, DAGNode next) { // 划分第一个stage Tuple2, List> tuple = divideFirstStage(expressions); if (tuple == null) { return; } List first = tuple.a, remains = tuple.b; for (int i = 0, n = first.size() - 1; i <= n; i++) { List list = resolve(first.get(i)); Assert.notEmpty(list, () -> "Invalid expression: " + String.join("", expressions)); if (list.size() == 1) { String name = list.get(0); DAGNode node = DAGNode.of(section, increment(name), name); graphBuilder.putEdge(prev, node); if (remains == null) { graphBuilder.putEdge(node, next); } else { buildGraph(section, remains, graphBuilder, node, next); } } else { buildGraph(section, concat(list, remains), graphBuilder, prev, next); } } } private List resolve(String text) { String expr = text.trim(); if (ALL_SYMBOLS.stream().noneMatch(expr::contains)) { // unnecessary resolve return Collections.singletonList(expr); } if (!expr.startsWith(Str.OPEN) || !expr.endsWith(Str.CLOSE)) { return resolve(wrappedCache.computeIfAbsent(expr, DAGExpressionParser::wrap)); } List> groups = group(expr); // 取被"()"包裹的最外层表达式 List> outermost = groups.stream().filter(e -> e.b == 1).collect(Collectors.toList()); if (outermost.size() == 2) { // 首尾括号,如:(A,B -> C,D) Assert.isTrue(outermost.get(0).a == 0 && outermost.get(1).a == expr.length() - 1, () -> "Invalid expression: " + text); } else if (outermost.size() > 2) { // 多组括号情况,需要在外层再包层括号,如: // 1)“(A,B) -> (C,D)” => “((A,B) -> (C,D))” // 2)“(B->C->D),(A->F)” => “((B->C->D),(A->F))” return resolve(wrappedCache.computeIfAbsent(expr, DAGExpressionParser::wrap)); } else { throw new IllegalArgumentException("Invalid expression: " + expr); } TreeNode root = buildTree(groups); List list = new ArrayList<>(root.getChildrenCount() * 2 + 2); list.add(root.getNid().open); root.forEachChild(child -> { list.add(child.getNid().open); list.add(child.getNid().close); }); list.add(root.getNid().close); return partition(expr, list); } private List partition(String expr, List groups) { List result = new ArrayList<>(groups.size()); for (int i = 0, n = groups.size() - 1; i < n; i++) { PartitionIdentityKey key = new PartitionIdentityKey(expr, groups.get(i) + 1, groups.get(i + 1)); // if twice open “((”,then str is empty content String str = partitionCache.computeIfAbsent(key, PartitionIdentityKey::partition); if (StringUtils.isNotBlank(str)) { result.add(str); } } return result; } private int increment(String name) { List> list = incrementer.computeIfAbsent(name, k -> new LinkedList<>()); Tuple2 tuple = list.stream().filter(e -> name == e.a).findAny().orElse(null); if (tuple == null) { // increment name ordinal list.add(tuple = Tuple2.of(name, list.size() + 1)); } return tuple.b; } // ------------------------------------------------------------------------------------static methods private static Tuple2, List> divideFirstStage(List list) { if (CollectionUtils.isEmpty(list)) { return null; } Assert.isTrue(!SEP_SYMBOLS.contains(Collects.getFirst(list)), () -> "Invalid expression: " + String.join("", list)); Assert.isTrue(!SEP_SYMBOLS.contains(Collects.getLast(list)), () -> "Invalid expression: " + String.join("", list)); if (list.size() == 1) { return Tuple2.of(list, null); } List head = new ArrayList<>(); for (int i = 0, n = list.size() - 1; i <= n; ) { head.add(list.get(i++)); if (i > n) { return Tuple2.of(head, null); } switch (list.get(i++)) { case SEP_STAGE: return Tuple2.of(head, list.subList(i, list.size())); case SEP_UNION: // skip “,” break; default: throw new IllegalArgumentException("Invalid expression: " + String.join("", list)); } } return Tuple2.of(head, null); } static TreeNode buildTree(List> groups) { List> nodes = new ArrayList<>(groups.size() + 1); buildTree(groups, TreeNodeId.ROOT_ID, 1, 0, nodes); // create a dummy root node TreeNode dummyRoot = TreeNode.builder(TreeNodeId.ROOT_ID).build(); // mount nodes dummyRoot.mount(nodes); // gets the actual root Assert.state(dummyRoot.getChildrenCount() == 1, "Build tree root node must be has a single child."); return dummyRoot.getChildren().get(0); } private static void buildTree(List> groups, TreeNodeId pid, int level, int start, List> nodes) { int open = -1; for (int i = start, n = groups.size(); i < n; i++) { if (groups.get(i).b < level) { return; } if (groups.get(i).b == level) { if (open == -1) { open = i; } else { // find "()" position TreeNodeId nid = TreeNodeId.of(groups.get(open).a, groups.get(i).a); nodes.add(new PlainNode<>(nid, pid, null)); buildTree(groups, nid, level + 1, open + 1, nodes); open = -1; } } } } private static List concat(List left, List right) { if (CollectionUtils.isEmpty(right)) { return left; } List result = new ArrayList<>(left.size() + 1 + right.size()); result.addAll(left); result.add(SEP_STAGE); result.addAll(right); return result; } /** * Checks the text is wrapped '()' is valid. * * @param text the text string * @return {@code true} if valid */ static boolean checkParenthesis(String text) { int openCount = 0; for (int i = 0, n = text.length(); i < n; i++) { char c = text.charAt(i); if (c == Char.OPEN) { openCount++; } else if (c == Char.CLOSE) { openCount--; } if (openCount < 0) { // For example "())(" return false; } } return openCount == 0; } /** * Complete the text wrapped with '()' * * @param text the text string * @return wrapped text string */ static String completeParenthesis(String text) { List list = new ArrayList<>(); int mark = 0, position = 0; for (int len = text.length() - 1; position <= len; ) { char ch = text.charAt(position++); Assert.isTrue(ch != '>', () -> "Invalid '" + ch + "': " + text); if (ArrayUtils.contains(SINGLE_SYMBOLS, ch)) { list.add(text.substring(mark, position - 1).trim()); list.add(Character.toString(ch)); mark = position; } else if (ch == '-') { // position not equals len, because expression cannot end with '>' Assert.isTrue(position <= len && text.charAt(position) == '>', () -> "Invalid '->' :" + text); list.add(text.substring(mark, position - 1).trim()); list.add(SEP_STAGE); mark = ++position; } } if (position > mark) { list.add(text.substring(mark, position).trim()); } StringBuilder builder = new StringBuilder(); for (int i = 0, n = list.size() - 1; i <= n; i++) { String item = list.get(i); if (StringUtils.isBlank(item)) { // skip empty string } else if (ALL_SYMBOLS.contains(item)) { builder.append(item); } else if (Str.OPEN.equals(Collects.get(list, i - 1)) && Str.CLOSE.equals(Collects.get(list, i + 1))) { builder.append(item); } else { builder.append(Str.OPEN).append(item).append(Str.CLOSE); } } return builder.toString(); } /** * Group expression by "()" * * @param expr the expression * @return groups of "()" */ static List> group(String expr) { Assert.isTrue(checkParenthesis(expr), () -> "Invalid expression parenthesis: " + expr); int depth = 0; // Tuple2 List> list = new ArrayList<>(); for (int i = 0, n = expr.length(); i < n; i++) { if (expr.charAt(i) == Char.OPEN) { ++depth; if (depth <= 2) { // 只取两层 list.add(Tuple2.of(i, depth)); } } else if (expr.charAt(i) == Char.CLOSE) { if (depth <= 2) { list.add(Tuple2.of(i, depth)); } --depth; } } Assert.isTrue((list.size() & 0x01) == 0, () -> "Expression not pair with '()': " + expr); return list; } private static String wrap(String text) { return Str.OPEN + text + Str.CLOSE; } @Getter @Setter private static final class GraphEdge implements Serializable { private static final long serialVersionUID = 7881441757444058390L; private static final TypeReference> LIST_TYPE = new TypeReference>() {}; private String source; private String target; private DAGEdge toDAGEdge() { return DAGEdge.of(source, target); } private static List fromJson(String text) { try { return Jsons.fromJson(text, LIST_TYPE); } catch (Exception e) { return null; } } } static final class TreeNodeId implements Serializable, Comparable { private static final long serialVersionUID = -468548698179536500L; private static final TreeNodeId ROOT_ID = new TreeNodeId(-1, -1); /** * position of "(" */ private final int open; /** * position of ")" */ private final int close; private TreeNodeId(int open, int close) { this.open = open; this.close = close; } private static TreeNodeId of(int open, int close) { Assert.isTrue(open > -1, "Tree node id open must be greater than -1: " + open); Assert.isTrue(close > 0, "Tree node id close must be greater than 0: " + close); return new TreeNodeId(open, close); } @Override public boolean equals(Object obj) { if (!(obj instanceof TreeNodeId)) { return false; } TreeNodeId other = (TreeNodeId) obj; return this.open == other.open && this.close == other.close; } @Override public int hashCode() { return open + close; } @Override public int compareTo(TreeNodeId other) { int n = this.open - other.open; return n != 0 ? n : (this.close - other.close); } @Override public String toString() { return "(" + open + "," + close + ")"; } } private static final class PartitionIdentityKey { private final String expr; private final int open; private final int close; private PartitionIdentityKey(String expr, int open, int close) { Assert.hasText(expr, () -> "Partition expression cannot be blank: " + expr); Assert.isTrue(open > -1, () -> "Partition key open must be greater than -1: " + open); Assert.isTrue(close > 0, () -> "Partition key close must be greater than 0: " + close); this.expr = expr; this.open = open; this.close = close; } @Override public boolean equals(Object obj) { if (!(obj instanceof PartitionIdentityKey)) { return false; } PartitionIdentityKey other = (PartitionIdentityKey) obj; // 比较对象地址 return this.expr == other.expr && this.open == other.open && this.close == other.close; } @Override public int hashCode() { return System.identityHashCode(expr) + open + close; } private String partition() { return expr.substring(open, close).trim(); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/dag/DAGNode.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.dag; import org.springframework.util.Assert; import java.beans.Transient; import java.io.Serializable; import java.util.Objects; import static cn.ponfee.commons.base.Symbol.Str.COLON; /** * DAG Node * * @author Ponfee */ public final class DAGNode implements Serializable { private static final long serialVersionUID = 7413110685194391605L; public static final DAGNode START = new DAGNode(0, 0, "Start"); public static final DAGNode END = new DAGNode(0, 0, "End"); /** *
     *  任务链的编号,用来区分不同的任务链
     *  如[A -> B; C -> D],表达式用“;”分隔成两个不同的任务链
     *  section=1: A -> B
     *  section=2: C -> D
     * 
*/ private final int section; /** * 名称相同时通过顺序来区分,如[A -> B -> A],两个A是不同的 *

实际结果为 [1:1:A -> 1:1:B -> 1:2:A] */ private final int ordinal; /** * 名称 */ private final String name; private DAGNode(int section, int ordinal, String name) { this.section = section; this.ordinal = ordinal; this.name = name; } public static DAGNode of(int section, int ordinal, String name) { Assert.isTrue(section > 0, () -> "Graph node section must be greater than 0: " + section); Assert.isTrue(ordinal > 0, () -> "Graph node ordinal must be greater than 0: " + ordinal); Assert.hasText(name, () -> "Graph node name cannot be blank: " + name); return new DAGNode(section, ordinal, name); } public int getSection() { return section; } public int getOrdinal() { return ordinal; } public String getName() { return name; } @Transient public boolean isStart() { return this.equals(START); } @Transient public boolean isEnd() { return this.equals(END); } @Transient public boolean isStartOrEnd() { return isStart() || isEnd(); } @Override public int hashCode() { return Objects.hash(section, ordinal, name); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof DAGNode)) { return false; } DAGNode other = (DAGNode) obj; return this.section == other.section && this.ordinal == other.ordinal && this.name.equals(other.name); } public boolean equals(int section, int ordinal, String name) { return this.section == section && this.ordinal == ordinal && this.name.equals(name); } @Override public String toString() { return section + COLON + ordinal + COLON + name; } public static DAGNode fromString(String str) { int pos = -1; int section = Integer.parseInt(str.substring(pos += 1, pos = str.indexOf(COLON, pos))); int ordinal = Integer.parseInt(str.substring(pos += 1, pos = str.indexOf(COLON, pos))); String name = str.substring(pos + 1); if (START.equals(section, ordinal, name)) { return START; } if (END.equals(section, ordinal, name)) { return END; } return DAGNode.of(section, ordinal, name); } } ================================================ FILE: src/main/java/cn/ponfee/commons/data/DataSourceFactory.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.data; import cn.ponfee.commons.reflect.BeanMaps; import javax.sql.DataSource; import java.util.Map; import java.util.Properties; /** * DataSource factory interface * * @author Ponfee */ @SuppressWarnings({"unchecked", "rawtypes"}) public interface DataSourceFactory { default void configure(T dataSource, Properties commonConfig) { BeanMaps.PROPS.copyFromMap((Map) commonConfig, dataSource); } default T create(String dataSourceClassName, Properties basicConfig, Properties commonConfig) { T dataSource; try { dataSource = (T) Class.forName(dataSourceClassName).newInstance(); } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } BeanMaps.PROPS.copyFromMap((Map) basicConfig, dataSource); configure(dataSource, commonConfig); return dataSource; } DataSourceFactory COMMON_FACTORY = new DataSourceFactory() {}; } ================================================ FILE: src/main/java/cn/ponfee/commons/data/DataSourceNaming.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.data; import java.lang.annotation.*; /** * 多数据源注解,指定要切换的数据源名称,支持Spring SPEL,上下文为方法参数(数组) * * @author Ponfee */ @Documented @Inherited @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) public @interface DataSourceNaming { /** * Specifiy string of the dataSource name * Spring EL expression */ String value() default ""; } ================================================ FILE: src/main/java/cn/ponfee/commons/data/DruidDataSourceFactory.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.data; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.wall.WallConfig; import com.alibaba.druid.wall.WallFilter; import org.apache.commons.lang3.StringUtils; import java.sql.SQLException; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Properties; import java.util.stream.Collectors; /** * Druid DataSource factory * * @author Ponfee */ public class DruidDataSourceFactory implements DataSourceFactory { @Override public void configure(DruidDataSource ds, Properties props) { configureFilters(ds, props.getProperty("filters")); } private void configureFilters(DruidDataSource ds, String filters) { if (StringUtils.isBlank(filters)) { return; } filters = filters.trim(); boolean force = filters.startsWith("!"); if (force) { filters = filters.substring(1); } List list = Arrays.stream(filters.split(",")) .map(String::trim) .collect(Collectors.toList()); boolean hasWall = list.remove("wall"); if (hasWall) { WallConfig wallConfig = new WallConfig(); wallConfig.setCommentAllow(true); wallConfig.setMultiStatementAllow(true); WallFilter wallFilter = new WallFilter(); wallFilter.setConfig(wallConfig); ds.setProxyFilters(Collections.singletonList(wallFilter)); } filters = (force ? "!" : "") + String.join(",", list); try { ds.setFilters(filters); } catch (SQLException e) { throw new IllegalArgumentException(e); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/data/MultipleDataSourceAdvisor.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.data; import cn.ponfee.commons.data.lookup.MultipleDataSourceContext; import cn.ponfee.commons.exception.Throwables.ThrowingCallable; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import java.lang.reflect.Method; import java.util.concurrent.Callable; /** * 多数据源切换,用于Spring XML配置文件形式的切面拦截多数据源切换处理 * * @author Ponfee */ public class MultipleDataSourceAdvisor implements MethodInterceptor { private static final ExpressionParser PARSER = new SpelExpressionParser(); /** * 基于Spring XML <aop:aspect />的配置方式 * *

     * {@code
     *   
     *   
     *   
     *     
     *     
     *     
     *       
     *     
     *   
     * }
     *
     *  1、transaction-xml:
     *    MultipleDataSourceAdvisor.doAround:数据源切换正常,事务正常√
     *
     *  2、transaction-xml:
     *    MultipleDataSourceAdvisor.doAround:数据源切换无效,事务正常×
     *
     *  3、
     *    MultipleDataSourceAdvisor.doAround:数据源切换正常,事务正常√
     *
     *  4、
     *    MultipleDataSourceAdvisor.doAround:数据源切换正常,事务正常√
     *
     *  5、
     *    MultipleDataSourceAdvisor.doAround:数据源切换正常,事务正常√
     *
     *  6、
     *    MultipleDataSourceAdvisor.doAround:数据源切换无效,事务正常×
     * 
* * @param pjp the ProceedingJoinPoint * @return target method return result * @throws Throwable if occur error */ public Object doAround(ProceedingJoinPoint pjp) throws Throwable { /*MethodSignature ms = (MethodSignature) pjp.getSignature(); // DatabaseQueryServiceImpl.query4page(PageRequestParams) System.out.println(ms.getMethod()); // DatabaseQueryServiceImpl$$EnhancerBySpringCGLIB$$ca6c0b12.query4page(PageRequestParams) System.out.println(pjp.getTarget().getClass().getMethod(ms.getName(), ms.getParameterTypes()));*/ return around( ((MethodSignature) pjp.getSignature()).getMethod(), pjp.getArgs(), ThrowingCallable.toChecked(pjp::proceed) ); } /** * 基于Spring XML <aop:advisor />的配置方式 *
     * {@code
     *   
     *   
     *     
     *     
     *     
     *   
     * }

     *  1、transaction-xml:
     *    MultipleDataSourceAdvisor.invoke  :数据源切换正常,事务无效×
     *
     *  2、transaction-xml:
     *    MultipleDataSourceAdvisor.invoke  :数据源切换无效,事务无效×
     *
     *  3、
     *    MultipleDataSourceAdvisor.invoke  :数据源切换正常,事务无效×
     *
     *  4、
     *    MultipleDataSourceAdvisor.invoke  :数据源切换正常,事务无效×
     *
     *  5、
     *    MultipleDataSourceAdvisor.invoke  :数据源切换正常,事务无效×
     *
     *  6、
     *    MultipleDataSourceAdvisor.invoke  :数据源切换无效,事务无效×
     * 
* * @param invocation the MethodInvocation * @return target method return result * @throws Throwable if occur error * * @deprecated 此方式事务失效(去掉此数据源切面,则事务正常) */ @Override @Deprecated public Object invoke(MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); Object[] args = invocation.getArguments(); return around(method, args, () -> method.invoke(invocation.getThis(), args)); } private static Object around(Method method, Object[] args, Callable call) throws Throwable { return around(method, args, method.getAnnotation(DataSourceNaming.class), call); } static Object around(Method method, Object[] args, DataSourceNaming dsn, Callable call) throws Throwable { String name = null; if (dsn != null && StringUtils.isNotBlank(dsn.value())) { name = PARSER.parseExpression(dsn.value()) .getValue(new StandardEvaluationContext(args), String.class); } boolean changed = false; try { if (StringUtils.isNotBlank(name)) { MultipleDataSourceContext.set(name); changed = true; } return call.call(); } finally { if (changed) { MultipleDataSourceContext.clear(); } } } } ================================================ FILE: src/main/java/cn/ponfee/commons/data/MultipleDataSourceAspect.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.data; import cn.ponfee.commons.exception.Throwables.ThrowingCallable; import cn.ponfee.commons.math.Maths; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.core.Ordered; /** *
 *  1、开启spring切面特性:
 *
 *  2、编写子类:
 *  {@code
 *
 *    `@Component `@Aspect
 *    public class MultipleDataSourceChanger extends MultipleDataSourceAspect {
 *      `@Around(
 *        value = "execution(public * cn.ponfee..*.service.impl..*Impl..*(..)) && `@annotation(dsn)",
 *        argNames = "pjp,dsn"
 *      )
 *      `@Override
 *      public Object doAround(ProceedingJoinPoint pjp, DataSourceNaming dsn) throws Throwable {
 *        return super.doAround(pjp, dsn);
 *      }
 *    }
 *
 *  }
 *
 *  1、transaction-xml:
 *    MultipleDataSourceAspect.doAround :数据源切换无效,事务正常×
 *
 *  2、transaction-xml:
 *    MultipleDataSourceAspect.doAround :数据源切换正常,事务正常√
 *
 *  3、
 *    MultipleDataSourceAspect.doAround :数据源切换正常,事务正常√
 *
 *  4、
 *    MultipleDataSourceAspect.doAround :数据源切换正常,事务正常√
 * 
* * @author Ponfee */ public abstract class MultipleDataSourceAspect implements Ordered { private static final int ORDER = Maths.minus(Ordered.LOWEST_PRECEDENCE, 1); public Object doAround(ProceedingJoinPoint pjp, DataSourceNaming dsn) throws Throwable { return MultipleDataSourceAdvisor.around( ((MethodSignature) pjp.getSignature()).getMethod(), pjp.getArgs(), dsn, ThrowingCallable.toChecked(pjp::proceed) ); } @Override public int getOrder() { return ORDER; } } ================================================ FILE: src/main/java/cn/ponfee/commons/data/NamedDataSource.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.data; import cn.ponfee.commons.util.Asserts; import cn.ponfee.commons.util.PropertiesUtils; import cn.ponfee.commons.util.Strings; import org.apache.commons.lang3.StringUtils; import javax.sql.DataSource; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Named Data Source * * @author Ponfee */ public class NamedDataSource { private static final Pattern PATTERN_DBURL_KEY = Pattern.compile("^(\\w+)\\.url$"); private final String name; private final DataSource dataSource; public NamedDataSource(String name, DataSource dataSource) { this.name = name; this.dataSource = dataSource; } public static NamedDataSource of(String name, DataSource dataSource) { return new NamedDataSource(name, dataSource); } public String getName() { return name; } public DataSource getDataSource() { return dataSource; } /** * * @param prefix * @param props * @return */ public static NamedDataSource[] build(String prefix, Properties props) { prefix = StringUtils.isBlank(prefix) ? "" : prefix.trim() + "."; props = PropertiesUtils.filterProperties(props, prefix); Set names = new LinkedHashSet<>(); props.forEach((k, v) -> { Matcher matcher = PATTERN_DBURL_KEY.matcher(k.toString()); if (matcher.matches()) { String name = matcher.group(1); if (!names.add(name)) { throw new IllegalStateException("Duplicated datasource name '" + name + "'."); } } }); Asserts.isTrue(!names.isEmpty(), "Not configured datasource 'name' option."); Pattern pattern = Pattern.compile("^(?!(" + Strings.join(names, "|") + ")\\.).*"); Properties commonConfig = new Properties(); // commons properties props.entrySet() .stream() .filter(e -> pattern.matcher(e.toString()).matches()) .forEach(e -> commonConfig.put(e.getKey(), e.getValue())); String defaultDsName = (String) commonConfig.remove("default"); // default datasource name String defaultType = (String) commonConfig.remove("type"); // default datasource type List dataSources = new LinkedList<>(); for (String name : names) { // specify "{name}.type" or default "type" for datasource type String dsType = Strings.ifEmpty((String) props.remove(name + ".type"), defaultType); Properties basicConfig = PropertiesUtils.filterProperties(props, name + "."); DataSource dataSource = DataSourceFactory.COMMON_FACTORY.create(dsType, basicConfig, commonConfig); NamedDataSource namedDs = NamedDataSource.of(name, dataSource); if (name.equals(defaultDsName)) { dataSources.add(0, namedDs); // default ds at index 0 } else { dataSources.add(namedDs); } } return dataSources.toArray(new NamedDataSource[0]); } } ================================================ FILE: src/main/java/cn/ponfee/commons/data/lookup/DataSourceLookup.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.data.lookup; import javax.sql.DataSource; /** * Looking up DataSources by name. * * @author Ponfee */ public interface DataSourceLookup { DataSource lookupDataSource(String name); } ================================================ FILE: src/main/java/cn/ponfee/commons/data/lookup/MultipleCachedDataSource.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.data.lookup; import cn.ponfee.commons.base.Initializable; import cn.ponfee.commons.base.Releasable; import cn.ponfee.commons.data.NamedDataSource; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.collect.ImmutableMap; import org.apache.commons.lang3.ArrayUtils; import org.springframework.jdbc.datasource.AbstractDataSource; import javax.annotation.Nonnull; import javax.sql.DataSource; import java.io.Closeable; import java.sql.Connection; import java.sql.SQLException; import java.time.Duration; import java.util.Map; import java.util.function.Supplier; /** * 可缓存的多数据源类型:可动态增加数据源、可动态移除数据源、数据源自动超时失效 * * @author Ponfee * @see org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource */ public class MultipleCachedDataSource extends AbstractDataSource implements DataSourceLookup, Initializable, Closeable { private final Map naturalDataSources; // original/native private final DataSource defaultDataSource; private final Cache adoptedDataSources; // foreign/stranger public MultipleCachedDataSource(int expireSeconds, NamedDataSource dataSource) { this(expireSeconds, dataSource.getName(), dataSource.getDataSource()); } public MultipleCachedDataSource(int expireSeconds, NamedDataSource... dataSources) { this( expireSeconds, dataSources[0].getName(), dataSources[0].getDataSource(), ArrayUtils.subarray(dataSources, 1, dataSources.length) ); } public MultipleCachedDataSource(int expireSeconds, String defaultName, DataSource defaultDataSource, NamedDataSource... othersDataSource) { // set the default data source this.defaultDataSource = defaultDataSource; this.naturalDataSources = ImmutableMap.copyOf( MultipleDataSourceContext.process(defaultName, defaultDataSource, othersDataSource) ); this.adoptedDataSources = CacheBuilder.newBuilder() .expireAfterAccess(Duration.ofSeconds(expireSeconds)) .maximumSize(8192) .removalListener(notification -> { try { Releasable.release(notification.getValue()); } catch (Exception ignored) { ignored.printStackTrace(); } }) .build(); } // -----------------------------------------------------------------add/remove public synchronized boolean addIfAbsent(String dataSourceName, Supplier supplier) { if (existsDatasourceName(dataSourceName)) { return false; } this.add(dataSourceName, supplier.get()); return true; } public synchronized boolean addIfAbsent(String dataSourceName, DataSource datasource) { if (existsDatasourceName(dataSourceName)) { return false; } this.add(dataSourceName, datasource); return true; } public synchronized void add(NamedDataSource ds) { this.add(ds.getName(), ds.getDataSource()); } public synchronized void add(@Nonnull String dataSourceName, @Nonnull DataSource datasource) { if (existsDatasourceName(dataSourceName)) { // check the datasource name not exists throw new IllegalArgumentException("Duplicated datasource name: " + dataSourceName); } this.adoptedDataSources.put(dataSourceName, datasource); MultipleDataSourceContext.add(dataSourceName); } public synchronized void remove(String dataSourceName) { if (this.naturalDataSources.containsKey(dataSourceName)) { throw new UnsupportedOperationException("Local datasource cannot remove: " + dataSourceName); } this.adoptedDataSources.invalidate(dataSourceName); MultipleDataSourceContext.remove(dataSourceName); } // -----------------------------------------------------------------override methods @Override public Connection getConnection() throws SQLException { return determineTargetDataSource().getConnection(); } @Override public Connection getConnection(String username, String password) throws SQLException { return determineTargetDataSource().getConnection(username, password); } @Override @SuppressWarnings("unchecked") public T unwrap(Class iface) throws SQLException { if (iface.isInstance(this)) { return (T) this; } return determineTargetDataSource().unwrap(iface); } @Override public boolean isWrapperFor(Class iface) throws SQLException { return iface.isInstance(this) || determineTargetDataSource().isWrapperFor(iface); } @Override public DataSource lookupDataSource(String name) { DataSource dataSource = this.naturalDataSources.get(name); return dataSource != null ? dataSource : this.adoptedDataSources.getIfPresent(name); } @Override public void init() { naturalDataSources.forEach((name, ds) -> Initializable.init(ds)); } @Override public void close() { naturalDataSources.forEach((name, ds) -> { try { Releasable.release(ds); } catch (Exception ignored) { ignored.printStackTrace(); } }); adoptedDataSources.asMap().forEach((name, ds) -> { try { Releasable.release(ds); } catch (Exception ignored) { ignored.printStackTrace(); } }); } // -----------------------------------------------------------------private methods /** * Retrieve the current target DataSource. Determines the */ private DataSource determineTargetDataSource() { String lookupKey = MultipleDataSourceContext.get(); DataSource dataSource = (lookupKey == null) ? this.defaultDataSource : lookupDataSource(lookupKey); if (dataSource == null) { throw new IllegalStateException("Cannot found DataSource by name [" + lookupKey + "]"); } return dataSource; } private boolean existsDatasourceName(String name) { return this.naturalDataSources.containsKey(name) || this.adoptedDataSources.getIfPresent(name) != null; } } ================================================ FILE: src/main/java/cn/ponfee/commons/data/lookup/MultipleDataSourceContext.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.data.lookup; import cn.ponfee.commons.collect.Collects; import cn.ponfee.commons.data.NamedDataSource; import org.apache.commons.collections4.CollectionUtils; import javax.annotation.Nonnull; import javax.sql.DataSource; import java.util.*; import java.util.stream.Collectors; /** * Multiple DataSource Context * * @author Ponfee */ public final class MultipleDataSourceContext { private static final List KEYS = new LinkedList<>(); // new CopyOnWriteArrayList<>() private static final ThreadLocal CONTEXT = new ThreadLocal<>(); public static void set(String datasourceName) { CONTEXT.set(datasourceName); } public static String get() { return CONTEXT.get(); } public static void clear() { CONTEXT.remove(); } /** * Provides gets the list of data source name to external * * @return a list of data source name string */ public static List listDataSourceNames() { return KEYS.isEmpty() ? Collections.emptyList() : Collections.unmodifiableList(KEYS); } // -----------------------------------------------------datasource keys synchronized static void add(@Nonnull String key) { if (KEYS.contains(key)) { throw new IllegalArgumentException("Duplicate key: " + key); } KEYS.add(key); } synchronized static void addAll(List keys) { if (CollectionUtils.isNotEmpty(keys)) { keys.forEach(MultipleDataSourceContext::add); } } synchronized static void remove(String key) { KEYS.remove(key); } static Map process(String defaultName, DataSource defaultDataSource, NamedDataSource... othersDataSource) { if (othersDataSource == null) { othersDataSource = new NamedDataSource[0]; } List names = Arrays.stream(othersDataSource) .map(NamedDataSource::getName) .collect(Collectors.toList()); names.add(0, defaultName); // default data source at the first // checks whether duplicate datasource name List duplicates = Collects.duplicate(names); if (CollectionUtils.isNotEmpty(duplicates)) { throw new IllegalArgumentException("Duplicated data source name: " + duplicates); } addAll(names); // add data source keys Map dataSources = new LinkedHashMap<>(othersDataSource.length + 1, 1); dataSources.put(defaultName, defaultDataSource); Arrays.stream(othersDataSource) .forEach(ns -> dataSources.put(ns.getName(), ns.getDataSource())); return dataSources; } } ================================================ FILE: src/main/java/cn/ponfee/commons/data/lookup/MultipleFixedDataSource.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.data.lookup; import cn.ponfee.commons.base.Initializable; import cn.ponfee.commons.base.Releasable; import cn.ponfee.commons.data.NamedDataSource; import com.google.common.collect.ImmutableMap; import org.apache.commons.lang3.ArrayUtils; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import javax.sql.DataSource; import java.io.Closeable; import java.util.Map; /** * 固定的多数据源类型:一旦创建则不可再变 * * Multiple DataSource:

* {@linkplain #setTargetDataSources(Map)}:设置数据源集

* {@linkplain #setDefaultTargetDataSource(Object)}:设置默认的数据源

* {@linkplain #determineCurrentLookupKey()}:获取当前数据源, 当返回为空或无对应数据源时会使用defaultTargetDataSource

* * @author Ponfee * @see MultipleScalableDataSource * @see MultipleCachedDataSource */ public class MultipleFixedDataSource extends AbstractRoutingDataSource implements DataSourceLookup, Initializable, Closeable { private final Map dataSources; public MultipleFixedDataSource(NamedDataSource dataSource) { this(dataSource.getName(), dataSource.getDataSource()); } public MultipleFixedDataSource(NamedDataSource... dataSources) { this( dataSources[0].getName(), dataSources[0].getDataSource(), ArrayUtils.subarray(dataSources, 1, dataSources.length) ); } @SuppressWarnings({ "unchecked", "rawtypes" }) public MultipleFixedDataSource(String defaultName, DataSource defaultDataSource, NamedDataSource... othersDataSource) { Map dataSources = MultipleDataSourceContext.process( defaultName, defaultDataSource, othersDataSource ); // if determineCurrentLookupKey not get, then use this default super.setDefaultTargetDataSource(defaultDataSource); // set all the data sources super.setTargetDataSources((Map) dataSources); this.dataSources = ImmutableMap.copyOf(dataSources); } @Override protected Object determineCurrentLookupKey() { return MultipleDataSourceContext.get(); } @Override public DataSource lookupDataSource(String name) { return this.dataSources.get(name); } @Override public void init() { dataSources.forEach((name, ds) -> Initializable.init(ds)); } @Override public void close() { dataSources.forEach((name, ds) -> { try { Releasable.release(ds); } catch (Exception ignored) { ignored.printStackTrace(); } }); } } ================================================ FILE: src/main/java/cn/ponfee/commons/data/lookup/MultipleScalableDataSource.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.data.lookup; import cn.ponfee.commons.base.Initializable; import cn.ponfee.commons.base.Releasable; import cn.ponfee.commons.data.NamedDataSource; import org.apache.commons.lang3.ArrayUtils; import org.springframework.jdbc.datasource.AbstractDataSource; import javax.annotation.Nonnull; import javax.sql.DataSource; import java.io.Closeable; import java.sql.Connection; import java.sql.SQLException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; /** * 可扩展的多数据源类型:可动态增加/移除数据源 * * @author Ponfee * @see org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource */ public class MultipleScalableDataSource extends AbstractDataSource implements DataSourceLookup, Initializable, Closeable { private final Map dataSources = new HashMap<>(); private final DataSource defaultDataSource; public MultipleScalableDataSource(NamedDataSource dataSource) { this(dataSource.getName(), dataSource.getDataSource()); } public MultipleScalableDataSource(NamedDataSource... dataSources) { this( dataSources[0].getName(), dataSources[0].getDataSource(), ArrayUtils.subarray(dataSources, 1, dataSources.length) ); } public MultipleScalableDataSource(String defaultName, DataSource defaultDataSource, NamedDataSource... othersDataSource) { Map dataSources = MultipleDataSourceContext.process( defaultName, defaultDataSource, othersDataSource ); // set the default data source this.defaultDataSource = defaultDataSource; // set all the data sources this.dataSources.putAll(dataSources); } public synchronized void add(NamedDataSource ds) { this.add(ds.getName(), ds.getDataSource()); } public synchronized void add(@Nonnull String dataSourceName, @Nonnull DataSource datasource) { if (dataSources.containsKey(dataSourceName)) { throw new IllegalArgumentException("Duplicated name: " + dataSourceName); } dataSources.put(dataSourceName, datasource); MultipleDataSourceContext.add(dataSourceName); } public synchronized void remove(String dataSourceName) { dataSources.remove(dataSourceName); MultipleDataSourceContext.remove(dataSourceName); } public synchronized void remove(@Nonnull DataSource dataSource) { Objects.requireNonNull(dataSource); for (Iterator> iter = dataSources.entrySet().iterator(); iter.hasNext();) { Entry entry = iter.next(); if (dataSource.equals(entry.getValue())) { iter.remove(); MultipleDataSourceContext.remove(entry.getKey()); } } } @Override public Connection getConnection() throws SQLException { return determineTargetDataSource().getConnection(); } @Override public Connection getConnection(String username, String password) throws SQLException { return determineTargetDataSource().getConnection(username, password); } @Override @SuppressWarnings("unchecked") public T unwrap(Class iface) throws SQLException { if (iface.isInstance(this)) { return (T) this; } return determineTargetDataSource().unwrap(iface); } @Override public boolean isWrapperFor(Class iface) throws SQLException { return iface.isInstance(this) || determineTargetDataSource().isWrapperFor(iface); } @Override public DataSource lookupDataSource(String name) { return this.dataSources.get(name); } @Override public void init() { dataSources.forEach((name, ds) -> Initializable.init(ds)); } @Override public void close() { dataSources.forEach((name, ds) -> { try { Releasable.release(ds); } catch (Exception ignored) { ignored.printStackTrace(); } }); } /** * Retrieve the current target DataSource. Determines the */ private DataSource determineTargetDataSource() { String lookupKey = MultipleDataSourceContext.get(); DataSource dataSource = (lookupKey == null) ? this.defaultDataSource : this.dataSources.get(lookupKey); if (dataSource == null) { throw new IllegalStateException("Cannot found DataSource by name [" + lookupKey + "]"); } return dataSource; } } ================================================ FILE: src/main/java/cn/ponfee/commons/date/CustomLocalDateTimeDeserializer.java ================================================ package cn.ponfee.commons.date; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.JsonTokenId; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.datatype.jsr310.deser.JSR310DateTimeDeserializerBase; import java.io.IOException; import java.time.DateTimeException; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; /** * Deserializer for Java 8 temporal {@link LocalDateTime}s. * * @author Nick Williams * @author Ponfee * @see com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer * @since 2.2.0 */ public class CustomLocalDateTimeDeserializer extends JSR310DateTimeDeserializerBase { private static final long serialVersionUID = 1L; public static final CustomLocalDateTimeDeserializer INSTANCE = new CustomLocalDateTimeDeserializer(); private final LocalDateTimeFormat wrappedFormatter; protected CustomLocalDateTimeDeserializer() { this(Dates.DATETIME_PATTERN); } public CustomLocalDateTimeDeserializer(String pattern) { this(DateTimeFormatter.ofPattern(pattern)); } public CustomLocalDateTimeDeserializer(DateTimeFormatter formatter) { super(LocalDateTime.class, formatter); this.wrappedFormatter = new LocalDateTimeFormat(formatter); } @Override protected JSR310DateTimeDeserializerBase withShape(JsonFormat.Shape shape) { return this; } @Override protected CustomLocalDateTimeDeserializer withDateFormat(DateTimeFormatter formatter) { throw new UnsupportedOperationException(); } @Override protected CustomLocalDateTimeDeserializer withLeniency(Boolean leniency) { throw new UnsupportedOperationException(); } @Override public LocalDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException { if (parser.hasTokenId(JsonTokenId.ID_STRING)) { return _fromString(parser, context, parser.getText()); } // 30-Sep-2020, tatu: New! "Scalar from Object" (mostly for XML) if (parser.isExpectedStartObjectToken()) { return _fromString(parser, context, context.extractScalarFromObject(parser, this, handledType())); } if (parser.isExpectedStartArrayToken()) { JsonToken t = parser.nextToken(); if (t == JsonToken.END_ARRAY) { return null; } if ( (t == JsonToken.VALUE_STRING || t == JsonToken.VALUE_EMBEDDED_OBJECT) && context.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS) ) { final LocalDateTime parsed = deserialize(parser, context); if (parser.nextToken() != JsonToken.END_ARRAY) { handleMissingEndArrayForSingle(parser, context); } return parsed; } if (t == JsonToken.VALUE_NUMBER_INT) { LocalDateTime result; int year = parser.getIntValue(); int month = parser.nextIntValue(-1); int day = parser.nextIntValue(-1); int hour = parser.nextIntValue(-1); int minute = parser.nextIntValue(-1); t = parser.nextToken(); if (t == JsonToken.END_ARRAY) { result = LocalDateTime.of(year, month, day, hour, minute); } else { int second = parser.getIntValue(); t = parser.nextToken(); if (t == JsonToken.END_ARRAY) { result = LocalDateTime.of(year, month, day, hour, minute, second); } else { int partialSecond = parser.getIntValue(); if (partialSecond < 1_000 && !context.isEnabled(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)) { // value is milliseconds, convert it to nanoseconds partialSecond *= 1_000_000; } if (parser.nextToken() != JsonToken.END_ARRAY) { throw context.wrongTokenException(parser, handledType(), JsonToken.END_ARRAY, "Expected array to end"); } result = LocalDateTime.of(year, month, day, hour, minute, second, partialSecond); } } return result; } context.reportInputMismatch(handledType(), "Unexpected token (%s) within Array, expected VALUE_NUMBER_INT", t); } if (parser.hasToken(JsonToken.VALUE_EMBEDDED_OBJECT)) { return (LocalDateTime) parser.getEmbeddedObject(); } if (parser.hasToken(JsonToken.VALUE_NUMBER_INT)) { _throwNoNumericTimestampNeedTimeZone(parser, context); } return _handleUnexpectedToken(context, parser, "Expected array or string."); } protected LocalDateTime _fromString(JsonParser p, DeserializationContext ctxt, String string0) throws IOException { String string = string0.trim(); if (string.length() == 0) { return _fromEmptyString(p, ctxt, string); } try { return wrappedFormatter.parse(string); } catch (DateTimeException e) { return _handleDateTimeException(ctxt, e, string); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/date/DatePeriods.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.date; import org.springframework.util.Assert; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; import java.util.Date; /** *

 * 1990-04-15 00:00:00这天调整了夏令时,即在4月15号0点的时候将表调快了一小时,导致这一天少了一小时。
 *
 * 1986年4月,中国中央有关部门发出“在全国范围内实行夏时制的通知”,具体作法是:每年从四月中旬第一个星
 * 期日的凌晨2时整(北京时间),将时钟拨快一小时,即将表针由2时拨至3时,夏令时开始;到九月中旬第一个
 * 星期日的凌晨2时整(北京夏令时),再将时钟拨回一小时,即将表针由2时拨至1时,夏令时结束。从1986年到
 * 1991年的六个年度,除1986年因是实行夏时制的第一年,从5月4日开始到9月14日结束外,其它年份均按规定的
 * 时段施行。在夏令时开始和结束前几天,新闻媒体均刊登有关部门的通告。1992年起,夏令时暂停实行。
 *
 * 时间周期,计算周期性的时间段
 * 
* * @author Ponfee */ public enum DatePeriods { /** * 每毫秒的 */ PER_MILLIS(ChronoUnit.MILLIS, 1), /** * 每秒钟的 */ PER_SECOND(ChronoUnit.SECONDS, 1), /** * 每分钟的 */ MINUTELY(ChronoUnit.MINUTES, 1), /** * 每小时的 */ HOURLY(ChronoUnit.HOURS, 1), /** * 每天 */ DAILY(ChronoUnit.DAYS, 1), /** * 每周 */ WEEKLY(ChronoUnit.WEEKS, 1), /** * 每月 */ MONTHLY(ChronoUnit.MONTHS, 1), /** * 每季度 */ QUARTERLY(ChronoUnit.MONTHS, 3), /** * 每半年 */ SEMIANNUAL(ChronoUnit.MONTHS, 6), /** * 每年度 */ ANNUAL(ChronoUnit.YEARS, 1), /** * 每十年的 */ DECADES(ChronoUnit.DECADES, 1), /** * 每百年的(世纪) */ CENTURIES(ChronoUnit.CENTURIES, 1), ; private final ChronoUnit unit; private final int multiple; DatePeriods(ChronoUnit unit, int multiple) { this.unit = unit; this.multiple = multiple; } /** * Compute the next segment based original and reference target * * @param original the period original * @param target the target of next reference * @param step the period step * @param next the next of target segment * @return {@code Segment(begin, end)} */ public final Segment next(LocalDateTime original, LocalDateTime target, int step, int next) { Assert.isTrue(step > 0, "Step must be positive number."); Assert.isTrue(!original.isAfter(target), "Original date cannot be after target date."); step *= multiple; long start = (unit.between(original, target) / step + next) * step; LocalDateTime begin = original.plus(start, unit); return new Segment(begin, begin.plus(step, unit)); } public final Segment next(LocalDateTime target, int step, int next) { return next(target, target, step, next); } public final Segment next(LocalDateTime target, int next) { return next(target, target, 1, next); } public final Segment next(Date original, Date target, int step, int next) { return next(Dates.toLocalDateTime(original), Dates.toLocalDateTime(target), step, next); } public final Segment next(Date target, int step, int next) { LocalDateTime original = Dates.toLocalDateTime(target); return next(original, original, step, next); } public final Segment next(Date target, int next) { LocalDateTime original = Dates.toLocalDateTime(target); return next(original, original, 1, next); } public static final class Segment { private final Date begin; private final Date end; private Segment(LocalDateTime begin, LocalDateTime end) { this.begin = Dates.toDate(begin); this.end = Dates.toDate(end.minus(1, ChronoUnit.MILLIS)); } public Date begin() { return begin; } public Date end() { return end; } @Override public String toString() { return JavaUtilDateFormat.PATTERN_51.format(begin) + " ~ " + JavaUtilDateFormat.PATTERN_51.format(end); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/date/Dates.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.date; import cn.ponfee.commons.base.Symbol.Char; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.*; import java.time.temporal.Temporal; import java.time.temporal.TemporalAdjusters; import java.time.temporal.WeekFields; import java.util.Date; import java.util.concurrent.ThreadLocalRandom; /** * Date utility *

Java处理GMT/UTC日期时间 * *

 * 时区:
 *   LocalDateTime:无时区
 *   Date(UTC0):表示自格林威治时间(GMT)1970年1月1日0点经过指定的毫秒数后的时间点
 *   Instant(UTC0):同Date
 *   ZonedDateTime:自带时区
 *
 * ZoneId子类:ZoneRegion、ZoneOffset
 *   ZoneId.of("Etc/GMT-8")                   -->    Etc/GMT-8
 *   ZoneId.of("GMT+8")                       -->    GMT+08:00
 *   ZoneId.of("UTC+8")                       -->    UTC+08:00
 *   ZoneId.of("Asia/Shanghai")               -->    Asia/Shanghai
 *   ZoneId.systemDefault()                   -->    Asia/Shanghai
 *
 * TimeZone子类(不支持UTC):ZoneInfo
 *   TimeZone.getTimeZone("Etc/GMT-8")        -->    Etc/GMT-8
 *   TimeZone.getTimeZone("GMT+8")            -->    GMT+08:00
 *   TimeZone.getTimeZone("Asia/Shanghai")    -->    Asia/Shanghai
 *   TimeZone.getTimeZone(ZoneId.of("GMT+8")) -->    GMT+08:00
 *   TimeZone.getDefault()                    -->    Asia/Shanghai
 * 
* * @author Ponfee */ public class Dates { /** * Date pattern */ public static final String DATE_PATTERN = "yyyy-MM-dd"; /** * Datetime pattern */ public static final String DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss"; /** * Full datetime pattern */ public static final String DATEFULL_PATTERN = "yyyy-MM-dd HH:mm:ss.SSS"; /** * Zero time millis: -62170185600000L */ public static final String ZERO_DATETIME = "0000-00-00 00:00:00"; /** * 简单的日期格式校验(yyyy-MM-dd HH:mm:ss) * * @param dateStr 输入日期 * @return 有效返回true, 反之false */ public static boolean isValidDate(String dateStr) { if (StringUtils.isEmpty(dateStr)) { return false; } try { JavaUtilDateFormat.DEFAULT.parse(dateStr); return true; } catch (Exception ignored) { return false; } } /** * 简单的日期格式校验 * * @param dateStr 输入日期,如(yyyy-MM-dd) * @param pattern 日期格式 * @return 有效返回true, 反之false */ public static boolean isValidDate(String dateStr, String pattern) { if (StringUtils.isEmpty(dateStr)) { return false; } try { new SimpleDateFormat(pattern).parse(dateStr); return true; } catch (Exception ignored) { return false; } } /** * Check the date is whether zero date * * @param date the date * @return is zero if {@code true} */ public static boolean isZeroDate(Date date) { return date != null && date.getTime() == -62170185600000L; } /** * 获取当前日期对象 * * @return 当前日期对象 */ public static Date now() { return new Date(); } /** * 获取当前日期字符串 * * @param pattern 日期格式 * @return 当前日期字符串 */ public static String now(String pattern) { return format(now(), pattern); } /** * 转换日期字符串为日期对象(默认格式: yyyy-MM-dd HH:mm:ss) * * @param dateStr 日期字符串 * @return 日期对象 */ public static Date toDate(String dateStr) { try { return JavaUtilDateFormat.DEFAULT.parse(dateStr); } catch (ParseException e) { return ExceptionUtils.rethrow(e); } } /** * 转换日期即字符串为Date对象 * * @param dateStr 日期字符串 * @param pattern 日期格式 * @return 日期对象 */ public static Date toDate(String dateStr, String pattern) { try { return new SimpleDateFormat(pattern).parse(dateStr); } catch (ParseException e) { return ExceptionUtils.rethrow(e); } } /** * java(毫秒)时间戳 * * @param timeMillis 毫秒时间戳 * @return 日期 */ public static Date ofTimeMillis(long timeMillis) { return new Date(timeMillis); } public static Date ofTimeMillis(Long timeMillis) { return timeMillis == null ? null : new Date(timeMillis); } public static long currentUnixTimestamp() { return System.currentTimeMillis() / 1000; } /** * unix时间戳 * * @param unixTimestamp unix时间戳 * @return 日期 */ public static Date ofUnixTimestamp(long unixTimestamp) { return new Date(unixTimestamp * 1000); } public static Date ofUnixTimestamp(Long unixTimestamp) { return unixTimestamp == null ? null : new Date(unixTimestamp * 1000); } /** * 格式化日期对象 * * @param date 日期对象 * @param pattern 日期格式 * @return 当前日期字符串 */ public static String format(Date date, String pattern) { if (date == null) { return null; } return new SimpleDateFormat(pattern).format(date); } /** * 格式化日期对象,格式为yyyy-MM-dd HH:mm:ss * * @param date 日期对象 * @return 日期字符串 */ public static String format(Date date) { return format(date, DATETIME_PATTERN); } /** * 格式化日期对象 * * @param timeMillis 毫秒 * @param pattern 格式 * @return 日期字符串 */ public static String format(long timeMillis, String pattern) { return format(new Date(timeMillis), pattern); } // ----------------------------------------------------------------plus /** * 增加毫秒数 * * @param date 时间 * @param millis 毫秒数 * @return 时间 */ public static Date plusMillis(Date date, long millis) { return new Date(date.getTime() + millis); } /** * 增加秒数 * * @param date 时间 * @param seconds 秒数 * @return 时间 */ public static Date plusSeconds(Date date, long seconds) { return plusMillis(date, seconds * 1000); } /** * 增加分钟 * * @param date 时间 * @param minutes 分钟数 * @return 时间 */ public static Date plusMinutes(Date date, long minutes) { return plusMillis(date, minutes * 60 * 1000); } /** * 增加小时 * * @param date 时间 * @param hours 小时数 * @return 时间 */ public static Date plusHours(Date date, long hours) { return plusMillis(date, hours * 60 * 60 * 1000); } /** * 增加天数 * * @param date 时间 * @param days 天数 * @return 时间 */ public static Date plusDays(Date date, long days) { return plusMillis(date, days * 24 * 60 * 60 * 1000); } /** * 增加周 * * @param date 时间 * @param weeks 周数 * @return 时间 */ public static Date plusWeeks(Date date, long weeks) { return plusMillis(date, weeks * 7 * 24 * 60 * 60 * 1000); } /** * 增加月份 * * @param date 时间 * @param months 月数 * @return 时间 */ public static Date plusMonths(Date date, long months) { return toDate(toLocalDateTime(date).plusMonths(months)); } /** * 增加年 * * @param date 时间 * @param years 年数 * @return 时间 */ public static Date plusYears(Date date, long years) { return toDate(toLocalDateTime(date).plusYears(years)); } // ----------------------------------------------------------------minus /** * 减少毫秒数 * * @param date 时间 * @param millis 毫秒数 * @return 时间 */ public static Date minusMillis(Date date, long millis) { return plusMillis(date, -millis); } /** * 减少秒数 * * @param date 时间 * @param seconds 秒数 * @return 时间 */ public static Date minusSeconds(Date date, long seconds) { return plusSeconds(date, -seconds); } /** * 减少分钟 * * @param date 时间 * @param minutes 分钟数 * @return 时间 */ public static Date minusMinutes(Date date, long minutes) { return plusMinutes(date, -minutes); } /** * 减少小时 * * @param date 时间 * @param hours 小时数 * @return 时间 */ public static Date minusHours(Date date, long hours) { return plusHours(date, -hours); } /** * 减少天数 * * @param date 时间 * @param days 天数 * @return 时间 */ public static Date minusDays(Date date, long days) { return plusDays(date, -days); } /** * 减少周 * * @param date 时间 * @param weeks 周数 * @return 时间 */ public static Date minusWeeks(Date date, long weeks) { return plusWeeks(date, -weeks); } /** * 减少月份 * * @param date 时间 * @param months 月数 * @return 时间 */ public static Date minusMonths(Date date, long months) { return toDate(toLocalDateTime(date).minusMonths(months)); } /** * 减少年 * * @param date 时间 * @param years 年数 * @return 时间 */ public static Date minusYears(Date date, long years) { return toDate(toLocalDateTime(date).minusYears(years)); } // ----------------------------------------------------------------start/end /** * 获取指定日期所在天的开始时间:yyyy-MM-dd 00:00:00 * * @param date 时间 * @return 时间 */ public static Date startOfDay(Date date) { return toDate(startOfDay0(date)); } /** * 获取指定日期所在天的结束时间:yyyy-MM-dd 23:59:59 * * @param date 时间 * @return 时间 */ public static Date endOfDay(Date date) { return toDate(endOfDay0(date)); } /** * 获取指定日期所在周的开始时间:yyyy-MM-周一 00:00:00 * * @param date 日期 * @return 当前周第一天 */ public static Date startOfWeek(Date date) { return toDate(startOfDay0(date).with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))); } /** * 获取指定日期所在周的结束时间:yyyy-MM-周日 23:59:59 * * @param date 日期 * @return 当前周最后一天 */ public static Date endOfWeek(Date date) { return toDate(endOfDay0(date).with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY))); } /** * 获取指定日期所在月的开始时间:yyyy-MM-01 00:00:00 * * @param date 日期 * @return 当前月的第一天 */ public static Date startOfMonth(Date date) { return toDate(startOfDay0(date).with(TemporalAdjusters.firstDayOfMonth())); } /** * 获取指定日期所在月的结束时间:yyyy-MM-月未 23:59:59 * * @param date 日期 * @return 当前月的最后一天 */ public static Date endOfMonth(Date date) { return toDate(endOfDay0(date).with(TemporalAdjusters.lastDayOfMonth())); } /** * 获取指定日期所在月的开始时间:yyyy-01-01 00:00:00 * * @param date 日期 * @return 当前年的第一天 */ public static Date startOfYear(Date date) { return toDate(startOfDay0(date).with(TemporalAdjusters.firstDayOfYear())); } /** * 获取指定日期所在月的结束时间:yyyy-12-31 23:59:59 * * @param date 日期 * @return 当前年的最后一天 */ public static Date endOfYear(Date date) { return toDate(endOfDay0(date).with(TemporalAdjusters.lastDayOfYear())); } // ----------------------------------------------------------------day of /** * 获取指定时间所在周的周n,1<=day<=7 * * @param date 相对日期 * @param dayOfWeek 1-星期一;2-星期二;... * @return 本周周几的日期对象 */ public static Date withDayOfWeek(Date date, int dayOfWeek) { LocalDateTime dateTime = toLocalDateTime(date).with(WeekFields.of(DayOfWeek.MONDAY, 1).dayOfWeek(), dayOfWeek); //LocalDateTime dateTime = toLocalDateTime(date).with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)).with(TemporalAdjusters.nextOrSame(DayOfWeek.of(dayOfWeek))); return toDate(dateTime); } /** * 获取指定时间所在月的n号,1<=day<=31 * * @param date the date * @param dayOfMonth the day of month * @return date */ public static Date withDayOfMonth(Date date, int dayOfMonth) { return toDate(toLocalDateTime(date).withDayOfMonth(dayOfMonth)); } /** * 获取指定时间所在年的n天,1<=day<=366 * * @param date the date * @param dayOfYear the day of year * @return date */ public static Date withDayOfYear(Date date, int dayOfYear) { return toDate(toLocalDateTime(date).withDayOfYear(dayOfYear)); } // ----------------------------------------------------------------day of public static int dayOfYear(Date date) { return toLocalDateTime(date).getDayOfYear(); } public static int dayOfMonth(Date date) { return toLocalDateTime(date).getDayOfMonth(); } public static int dayOfWeek(Date date) { return toLocalDateTime(date).getDayOfWeek().getValue(); } public static int hourOfDay(Date date) { return toLocalDateTime(date).getHour(); } // ----------------------------------------------------------------others /** * 计算两个日期的时间差(单位:秒) * * @param start 开始时间 * @param end 结束时间 * @return 时间间隔 */ public static long clockDiff(Date start, Date end) { return (end.getTime() - start.getTime()) / 1000; } /** * Returns a days between the two date(end-start) * * @param start the start date * @param end the end date * @return a number of between start to end days * @see java.time.temporal.ChronoUnit#between(Temporal, Temporal) */ public static int daysBetween(Date start, Date end) { return (int) (toLocalDate(end).toEpochDay() - toLocalDate(start).toEpochDay()); } /** * 日期随机 * * @param begin 开发日期 * @param end 结束日期 * @return */ public static Date random(Date begin, Date end) { long beginMills = begin.getTime(), endMills = end.getTime(); if (beginMills >= endMills) { throw new IllegalArgumentException("Date [" + format(begin) + "] must before [" + format(end) + "]"); } return random(beginMills, endMills); } public static Date random(long beginTimeMills, long endTimeMills) { if (beginTimeMills >= endTimeMills) { throw new IllegalArgumentException("Date [" + beginTimeMills + "] must before [" + endTimeMills + "]"); } return new Date(beginTimeMills + ThreadLocalRandom.current().nextLong(endTimeMills - beginTimeMills)); } /** * Returns the smaller of two {@code Date} values. * * @param a the first Date * @param b the second Date * @return the smallest of {@code a} and {@code b} */ public static Date min(Date a, Date b) { return a == null ? b : (b == null || a.before(b)) ? a : b; } /** * Returns the greater of two {@code Date} values. * * @param a the first Date * @param b the second Date * @return the greatest of {@code a} and {@code b} */ public static Date max(Date a, Date b) { return a == null ? b : (b == null || a.after(b)) ? a : b; } // ----------------------------------------------------------------java 8 date public static LocalDateTime toLocalDateTime(Date date) { //return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); } public static Date toDate(LocalDateTime localDateTime) { return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); } public static LocalDate toLocalDate(Date date) { return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); } public static Date toDate(LocalDate localDate) { return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant()); } public static LocalDateTime startOfDay(LocalDateTime dateTime) { return LocalDateTime.of(dateTime.toLocalDate(), LocalTime.MIN); //return dateTime.withHour(0).withMinute(0).withSecond(0).withNano(0); } public static LocalDateTime endOfDay(LocalDateTime dateTime) { // 当毫秒数大于499时,如果Mysql的datetime字段没有毫秒位数,数据会自动加1秒,所以此处毫秒为000 return LocalDateTime.of(dateTime.toLocalDate(), LocalTime.of(23, 59, 59, /*999_999_999*/0)); } /** * 时区转换 * * @param date the date * @param sourceZone the source zone id * @param targetZone the target zone id * @return date of target zone id */ public static Date zoneConvert(Date date, ZoneId sourceZone, ZoneId targetZone) { if (date == null || sourceZone.equals(targetZone)) { return date; } return Date.from( date.toInstant().atZone(targetZone).withZoneSameLocal(sourceZone).toInstant() ); } /** * 时区转换 * * @param localDateTime the localDateTime * @param sourceZone the source zone id * @param targetZone the target zone id * @return localDateTime of target zone id */ public static LocalDateTime zoneConvert(LocalDateTime localDateTime, ZoneId sourceZone, ZoneId targetZone) { if (localDateTime == null || sourceZone.equals(targetZone)) { return localDateTime; } return ZonedDateTime.of(localDateTime, sourceZone).withZoneSameInstant(targetZone).toLocalDateTime(); } public static String toCronExpression(Date date) { return toCronExpression(toLocalDateTime(date)); } /** * Converts date time to cron expression * * @param dateTime the local date time * @return cron expression of the spec date */ public static String toCronExpression(LocalDateTime dateTime) { return new StringBuilder(22) .append(dateTime.getSecond() ).append(Char.SPACE) // second .append(dateTime.getMinute() ).append(Char.SPACE) // minute .append(dateTime.getHour() ).append(Char.SPACE) // hour .append(dateTime.getDayOfMonth()).append(Char.SPACE) // day .append(dateTime.getMonthValue()).append(Char.SPACE) // month .append('?' ).append(Char.SPACE) // week .append(dateTime.getYear() ) // year .toString(); } // -------------------------------------------------------------private methods private static LocalDateTime startOfDay0(Date date) { return startOfDay(toLocalDateTime(date)); } private static LocalDateTime endOfDay0(Date date) { return endOfDay(toLocalDateTime(date)); } } ================================================ FILE: src/main/java/cn/ponfee/commons/date/JavaUtilDateFormat.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.date; import cn.ponfee.commons.base.Symbol.Char; import com.google.common.base.Strings; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.FastDateFormat; import javax.annotation.concurrent.ThreadSafe; import java.text.*; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.regex.Pattern; /** * Convert to {@code java.util.Date}, none zone offset. *

unix timestamp只支持对10位(秒)和13位(毫秒)做解析 * * @author Ponfee * @ThreadSafe */ @ThreadSafe public class JavaUtilDateFormat extends DateFormat { private static final long serialVersionUID = 6837172676882367405L; /** * For {@code java.util.Date#toString} */ private static final DateTimeFormatter DATE_TO_STRING_FORMAT = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss zzz yyyy", Locale.ROOT); /** * For {@link Date#toString()} "EEE MMM dd HH:mm:ss zzz yyyy" format */ static final Pattern DATE_TO_STRING_PATTERN = Pattern.compile("^(Sun|Mon|Tue|Wed|Thu|Fri|Sat) [A-Z][a-z]{2} \\d{2} \\d{2}:\\d{2}:\\d{2} CST \\d{4}$"); /** * 日期时间戳:秒/毫秒 */ static final Pattern DATE_TIMESTAMP_PATTERN = Pattern.compile("^0|[1-9]\\d*$"); static final FastDateFormat PATTERN_11 = FastDateFormat.getInstance("yyyyMM"); static final FastDateFormat PATTERN_12 = FastDateFormat.getInstance("yyyy-MM"); static final FastDateFormat PATTERN_13 = FastDateFormat.getInstance("yyyy/MM"); static final FastDateFormat PATTERN_21 = FastDateFormat.getInstance("yyyyMMdd"); static final FastDateFormat PATTERN_22 = FastDateFormat.getInstance(Dates.DATE_PATTERN); static final FastDateFormat PATTERN_23 = FastDateFormat.getInstance("yyyy/MM/dd"); static final FastDateFormat PATTERN_31 = FastDateFormat.getInstance("yyyyMMddHHmmss"); static final FastDateFormat PATTERN_32 = FastDateFormat.getInstance("yyyyMMddHHmmssSSS"); static final FastDateFormat PATTERN_41 = FastDateFormat.getInstance(Dates.DATETIME_PATTERN); static final FastDateFormat PATTERN_42 = FastDateFormat.getInstance("yyyy/MM/dd HH:mm:ss"); static final FastDateFormat PATTERN_43 = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss"); static final FastDateFormat PATTERN_44 = FastDateFormat.getInstance("yyyy/MM/dd'T'HH:mm:ss"); static final FastDateFormat PATTERN_51 = FastDateFormat.getInstance(Dates.DATEFULL_PATTERN); static final FastDateFormat PATTERN_52 = FastDateFormat.getInstance("yyyy/MM/dd HH:mm:ss.SSS"); static final FastDateFormat PATTERN_53 = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS"); static final FastDateFormat PATTERN_54 = FastDateFormat.getInstance("yyyy/MM/dd'T'HH:mm:ss.SSS"); static final FastDateFormat PATTERN_61 = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss.SSS'Z'"); static final FastDateFormat PATTERN_62 = FastDateFormat.getInstance("yyyy/MM/dd HH:mm:ss.SSS'Z'"); static final FastDateFormat PATTERN_63 = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); static final FastDateFormat PATTERN_64 = FastDateFormat.getInstance("yyyy/MM/dd'T'HH:mm:ss.SSS'Z'"); static final FastDateFormat PATTERN_71 = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss.SSSX"); static final FastDateFormat PATTERN_72 = FastDateFormat.getInstance("yyyy/MM/dd HH:mm:ss.SSSX"); static final FastDateFormat PATTERN_73 = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSX"); static final FastDateFormat PATTERN_74 = FastDateFormat.getInstance("yyyy/MM/dd'T'HH:mm:ss.SSSX"); /** * The default date format with yyyy-MM-dd HH:mm:ss */ public static final JavaUtilDateFormat DEFAULT = new JavaUtilDateFormat(Dates.DATETIME_PATTERN); /** * 兜底解析器 */ private final FastDateFormat backstopFormat; public JavaUtilDateFormat(String pattern) { this(pattern, Locale.getDefault()); } public JavaUtilDateFormat(String pattern, Locale locale) { this(FastDateFormat.getInstance(pattern, locale)); } public JavaUtilDateFormat(FastDateFormat format) { this.backstopFormat = format; super.setCalendar(Calendar.getInstance(format.getTimeZone(), format.getLocale())); NumberFormat numberFormat = NumberFormat.getIntegerInstance(format.getLocale()); numberFormat.setGroupingUsed(false); super.setNumberFormat(numberFormat); } @Override public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) { return backstopFormat.format(date, toAppendTo, fieldPosition); } @Override public Date parse(String source, ParsePosition pos) { Objects.requireNonNull(pos); if (pos.getIndex() < 0) { throw new IllegalArgumentException("Invalid parse position: " + pos.getIndex()); } if (StringUtils.isEmpty(source) || source.length() <= pos.getIndex()) { return null; } String date = source.substring(pos.getIndex()); try { return parse(date); } catch (IllegalArgumentException e) { throw new IllegalArgumentException("Invalid date format: " + source + ", " + pos.getIndex() + ", " + date); } catch (Exception e) { throw new IllegalArgumentException("Invalid date format: " + source + ", " + pos.getIndex() + ", " + date, e); } } @Override public Date parse(String source) throws ParseException { if (StringUtils.isEmpty(source)) { return null; } int length = source.length(); if (length >= 20 && source.endsWith("Z")) { if (length < 24) { source = padding(source) + "Z"; } if (isTSeparator(source)) { return (isCrossbar(source) ? PATTERN_63 : PATTERN_64).parse(source); } else { return (isCrossbar(source) ? PATTERN_61 : PATTERN_62).parse(source); } } switch (length) { case 6: return PATTERN_11.parse(source); case 7: return (isCrossbar(source) ? PATTERN_12 : PATTERN_13).parse(source); case 8: return PATTERN_21.parse(source); case 10: char separator = source.charAt(4); if (separator == Char.HYPHEN) { return PATTERN_22.parse(source); } else if (separator == Char.SLASH) { return PATTERN_23.parse(source); } else if (DATE_TIMESTAMP_PATTERN.matcher(source).matches()) { // long string(length 10) of unix timestamp(e.g. 1640966400) return new Date(Long.parseLong(source) * 1000); } break; case 13: // long string(length 13) of mills unix timestamp(e.g. 1640966400000) if (DATE_TIMESTAMP_PATTERN.matcher(source).matches()) { return new Date(Long.parseLong(source)); } break; case 14: return PATTERN_31.parse(source); case 19: if (isTSeparator(source)) { return (isCrossbar(source) ? PATTERN_43 : PATTERN_44).parse(source); } else { return (isCrossbar(source) ? PATTERN_41 : PATTERN_42).parse(source); } case 17: return PATTERN_32.parse(source); case 23: if (isTSeparator(source)) { return (isCrossbar(source) ? PATTERN_53 : PATTERN_54).parse(source); } else { return (isCrossbar(source) ? PATTERN_51 : PATTERN_52).parse(source); } case 26: case 29: if (isTSeparator(source)) { // 2021-12-31T17:01:01.000+08、2021-12-31T17:01:01.000+08:00 return (isCrossbar(source) ? PATTERN_73 : PATTERN_74).parse(source); } else { // 2021-12-31 17:01:01.000+08、2021-12-31 17:01:01.000+08:00 return (isCrossbar(source) ? PATTERN_71 : PATTERN_72).parse(source); } case 28: if (isCST(source)) { // 以下使用方式会相差14小时: // 1)FastDateFormat.getInstance("EEE MMM dd HH:mm:ss zzz yyyy", Locale.ENGLISH).parse(source); // 2)new Date(source); // 3)Date.from(ZonedDateTime.parse(source, DATE_TO_STRING_FORMAT).toInstant()); return Dates.toDate(LocalDateTime.parse(source, DATE_TO_STRING_FORMAT)); } break; default: break; } return backstopFormat.parse(source); } public LocalDateTime parseToLocalDateTime(String source) throws ParseException { Date date = parse(source); return date == null ? null : Dates.toLocalDateTime(date); } public LocalDateTime parseToLocalDateTime(String source, ParsePosition pos) { Date date = parse(source, pos); return date == null ? null : Dates.toLocalDateTime(date); } @Override public Object parseObject(String source, ParsePosition pos) { return parse(source, pos); } @Override public Object parseObject(String source) throws ParseException { return parse(source); } @Override public int hashCode() { return backstopFormat.hashCode(); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof JavaUtilDateFormat)) { return false; } JavaUtilDateFormat other = (JavaUtilDateFormat) obj; return this.backstopFormat.equals(other.backstopFormat); } @Override public AttributedCharacterIterator formatToCharacterIterator(Object obj) { return backstopFormat.formatToCharacterIterator(obj); } @Override public Object clone() { return this; } // ------------------------------------------------------------------------deprecated methods @Override @Deprecated public void setCalendar(Calendar newCalendar) { if (!Objects.equals(newCalendar, super.getCalendar())) { throw new UnsupportedOperationException(); } } @Override @Deprecated public void setNumberFormat(NumberFormat newNumberFormat) { if (!Objects.equals(newNumberFormat, super.getNumberFormat())) { throw new UnsupportedOperationException(); } } @Override @Deprecated public void setTimeZone(TimeZone zone) { if (!Objects.equals(zone, super.getTimeZone())) { throw new UnsupportedOperationException(); } } @Override @Deprecated public void setLenient(boolean lenient) { if (lenient != super.isLenient()) { throw new UnsupportedOperationException(); } } // ------------------------------------------------------------------------package methods static boolean isCrossbar(String str) { return str.charAt(4) == '-'; } // 'T' literal is the date and time separator static boolean isTSeparator(String str) { return str.charAt(10) == 'T'; } static boolean isCST(String str) { return DATE_TO_STRING_PATTERN.matcher(str).matches(); } static String padding(String source) { // example: 2022/07/18T15:11:11Z, 2022/07/18T15:11:11.Z, 2022/07/18T15:11:11.1Z, 2022/07/18T15:11:11.13Z String[] array = source.split("[.Z]"); return array[0] + "." + (array.length == 1 ? "000" : Strings.padEnd(array[1], 3, '0')); } } ================================================ FILE: src/main/java/cn/ponfee/commons/date/LocalDateTimeFormat.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.date; import cn.ponfee.commons.base.Symbol.Char; import javax.annotation.concurrent.ThreadSafe; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.util.Date; import static cn.ponfee.commons.date.JavaUtilDateFormat.*; /** * Convert to {@code java.time.LocalDateTime}, none zone offset. *

unix timestamp只支持对10位(秒)和13位(毫秒)做解析 *

时区:LocalDateTime[无]、Date[0时区]、Instant[0时区]、ZonedDateTime[自带] * * @author Ponfee * @ThreadSafe * @see JavaUtilDateFormat#parseToLocalDateTime(String) */ @ThreadSafe public class LocalDateTimeFormat { static final DateTimeFormatter PATTERN_01 = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); public static final DateTimeFormatter PATTERN_11 = DateTimeFormatter.ofPattern(Dates.DATETIME_PATTERN); static final DateTimeFormatter PATTERN_12 = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"); static final DateTimeFormatter PATTERN_13 = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"); static final DateTimeFormatter PATTERN_14 = DateTimeFormatter.ofPattern("yyyy/MM/dd'T'HH:mm:ss"); static final DateTimeFormatter PATTERN_21 = DateTimeFormatter.ofPattern(Dates.DATEFULL_PATTERN); static final DateTimeFormatter PATTERN_22 = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss.SSS"); static final DateTimeFormatter PATTERN_23 = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS"); static final DateTimeFormatter PATTERN_24 = DateTimeFormatter.ofPattern("yyyy/MM/dd'T'HH:mm:ss.SSS"); /** * The default date format with yyyy-MM-dd HH:mm:ss */ public static final LocalDateTimeFormat DEFAULT = new LocalDateTimeFormat(Dates.DATETIME_PATTERN); /** * 兜底解析器 */ private final DateTimeFormatter backstopFormat; public LocalDateTimeFormat(String pattern) { this(DateTimeFormatter.ofPattern(pattern)); } public LocalDateTimeFormat(DateTimeFormatter dateTimeFormatter) { this.backstopFormat = dateTimeFormatter; } // --------------------------------------------------------------------------public methods public LocalDateTime parse(String source) { if (source == null || source.length() == 0) { return null; } int length = source.length(); if (length >= 20 && isTSeparator(source) && source.endsWith("Z")) { if (isCrossbar(source)) { // example: 2022-07-18T15:11:11Z, 2022-07-18T15:11:11.Z, 2022-07-18T15:11:11.1Z, 2022-07-18T15:11:11.13Z, 2022-07-18T15:11:11.133Z // 解析会报错:DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ") return LocalDateTime.ofInstant(Instant.parse(source), ZoneOffset.UTC); } else { // example: 2022/07/18T15:11:11Z, 2022/07/18T15:11:11.Z, 2022/07/18T15:11:11.1Z, 2022/07/18T15:11:11.13Z, 2022/07/18T15:11:11.133Z source = length < 24 ? padding(source) : source.substring(0, source.length() - 1); return LocalDateTime.parse(source, PATTERN_24); } } switch (length) { case 8: // yyyyMMdd return LocalDateTime.parse(source + "000000", PATTERN_01); case 10: char c = source.charAt(4); if (c == Char.HYPHEN) { // yyyy-MM-dd return LocalDateTime.parse(source + " 00:00:00", PATTERN_11); } else if (c == Char.SLASH) { // yyyy/MM/dd return LocalDateTime.parse(source + " 00:00:00", PATTERN_12); } else if (JavaUtilDateFormat.DATE_TIMESTAMP_PATTERN.matcher(source).matches()) { // long string(length 10) of second unix timestamp(e.g. 1640966400) return Dates.toLocalDateTime(new Date(Long.parseLong(source) * 1000)); } break; case 13: if (JavaUtilDateFormat.DATE_TIMESTAMP_PATTERN.matcher(source).matches()) { // long string(length 13) of millisecond unix timestamp(e.g. 1640966400000) return Dates.toLocalDateTime(new Date(Long.parseLong(source))); } break; case 14: return LocalDateTime.parse(source, PATTERN_01); case 19: if (isTSeparator(source)) { return LocalDateTime.parse(source, isCrossbar(source) ? PATTERN_13 : PATTERN_14); } else { return LocalDateTime.parse(source, isCrossbar(source) ? PATTERN_11 : PATTERN_12); } case 23: if (isTSeparator(source)) { return LocalDateTime.parse(source, isCrossbar(source) ? PATTERN_23 : PATTERN_24); } else { return LocalDateTime.parse(source, isCrossbar(source) ? PATTERN_21 : PATTERN_22); } default: break; } return LocalDateTime.parse(source, backstopFormat); } public String format(LocalDateTime dateTime) { if (dateTime == null) { return null; } return backstopFormat.format(dateTime); } } ================================================ FILE: src/main/java/cn/ponfee/commons/exception/BaseCheckedException.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.exception; import cn.ponfee.commons.model.CodeMsg; /** * Base checked exception definition * * @author Ponfee */ public abstract class BaseCheckedException extends Exception { private static final long serialVersionUID = -1199930172272040396L; /** * Error code */ private final int code; public BaseCheckedException(int code) { this(code, null, null); } public BaseCheckedException(CodeMsg codeMsg) { this(codeMsg.getCode(), codeMsg.getMsg(), null); } /** * @param code error code * @param message error message */ public BaseCheckedException(int code, String message) { this(code, message, null); } public BaseCheckedException(CodeMsg codeMsg, Throwable cause) { this(codeMsg.getCode(), codeMsg.getMsg(), cause); } /** * @param code error code * @param message error message * @param cause root cause */ public BaseCheckedException(int code, String message, Throwable cause) { super(message, cause); this.code = code; } /** * @param code error code * @param message error message * @param cause root cause * @param enableSuppression the enableSuppression * @param writableStackTrace then writableStackTrace */ public BaseCheckedException(int code, String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); this.code = code; } /** * Returns the error code * * @return int value of error code */ public int getCode() { return code; } } ================================================ FILE: src/main/java/cn/ponfee/commons/exception/BaseUncheckedException.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.exception; import cn.ponfee.commons.model.CodeMsg; /** * Base unchecked exception definition * * @author Ponfee */ public abstract class BaseUncheckedException extends RuntimeException { private static final long serialVersionUID = -54158942051387210L; /** * Error code */ private final int code; public BaseUncheckedException(int code) { this(code, null, null); } public BaseUncheckedException(CodeMsg codeMsg) { this(codeMsg.getCode(), codeMsg.getMsg(), null); } /** * @param code error code * @param message error message */ public BaseUncheckedException(int code, String message) { this(code, message, null); } public BaseUncheckedException(CodeMsg codeMsg, Throwable cause) { this(codeMsg.getCode(), codeMsg.getMsg(), cause); } /** * @param code error code * @param message error message * @param cause root cause */ public BaseUncheckedException(int code, String message, Throwable cause) { super(message, cause); this.code = code; } /** * @param code error code * @param message error message * @param cause root cause * @param enableSuppression the enableSuppression * @param writableStackTrace then writableStackTrace */ public BaseUncheckedException(int code, String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); this.code = code; } /** * Returns the error code * * @return int value of error code */ public int getCode() { return code; } } ================================================ FILE: src/main/java/cn/ponfee/commons/exception/ServerException.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.exception; import cn.ponfee.commons.model.ResultCode; /** * Server exception definition * * @author Ponfee */ public class ServerException extends BaseUncheckedException { private static final long serialVersionUID = -247253152815744553L; private static final int CODE = ResultCode.SERVER_ERROR.getCode(); public ServerException() { super(CODE, null, null); } public ServerException(String message) { super(CODE, message, null); } public ServerException(Throwable cause) { super(CODE, null, cause); } public ServerException(String message, Throwable cause) { super(CODE, message, cause); } public ServerException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(CODE, message, cause, enableSuppression, writableStackTrace); } } ================================================ FILE: src/main/java/cn/ponfee/commons/exception/Throwables.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.exception; import cn.ponfee.commons.concurrent.Threads; import org.apache.commons.lang3.ClassUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; import java.util.concurrent.Callable; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; /** * Throwable utilities. * * @author Ponfee */ public final class Throwables { private static final Logger LOG = LoggerFactory.getLogger(Throwables.class); private static final Supplier EMPTY_MESSAGE = () -> ""; /** * Gets the root cause throwable stack trace * * @param throwable the throwable * @return a string of throwable stack trace information */ public static String getRootCauseStackTrace(Throwable throwable) { if (throwable == null) { return null; } while (throwable.getCause() != null) { throwable = throwable.getCause(); } return ExceptionUtils.getStackTrace(throwable); } public static String getRootCauseMessage(Throwable throwable) { if (throwable == null) { return null; } List list = ExceptionUtils.getThrowableList(throwable); for (int i = list.size() - 1; i >= 0; i--) { String message = list.get(i).getMessage(); if (StringUtils.isNotBlank(message)) { return "error[" + message + "]"; } } return "error[" + ClassUtils.getName(throwable.getClass()) + "]"; } // -------------------------------------------------------------------------------interface definitions @FunctionalInterface public interface ThrowingRunnable { void run() throws T; default ThrowingSupplier toSupplier(R result) { return () -> { run(); return result; }; } default ThrowingCallable toCallable(R result) { return () -> { run(); return result; }; } static void doChecked(ThrowingRunnable runnable) { try { runnable.run(); } catch (Throwable t) { ExceptionUtils.rethrow(t); } } static void doCaught(ThrowingRunnable runnable) { doCaught(runnable, EMPTY_MESSAGE); } static void doCaught(ThrowingRunnable runnable, Supplier message) { try { runnable.run(); } catch (Throwable t) { LOG.error(message.get(), t); Threads.interruptIfNecessary(t); } } static Runnable toChecked(ThrowingRunnable runnable) { return () -> { try { runnable.run(); } catch (Throwable t) { ExceptionUtils.rethrow(t); } }; } static Runnable toCaught(ThrowingRunnable runnable) { return toCaught(runnable, EMPTY_MESSAGE); } static Runnable toCaught(ThrowingRunnable runnable, Supplier message) { return () -> { try { runnable.run(); } catch (Throwable t) { LOG.error(message.get(), t); Threads.interruptIfNecessary(t); } }; } } /** * Lambda function checked exception * * @param the type of results supplied by this supplier * @param the type of the call get method possible occur exception */ @FunctionalInterface public interface ThrowingSupplier { R get() throws T; default ThrowingRunnable toRunnable() { return this::get; } static R doChecked(ThrowingSupplier supplier) { try { return supplier.get(); } catch (Throwable t) { return ExceptionUtils.rethrow(t); } } static R doCaught(ThrowingSupplier supplier) { return doCaught(supplier, null, EMPTY_MESSAGE); } static R doCaught(ThrowingSupplier supplier, R defaultValue, Supplier message) { try { return supplier.get(); } catch (Throwable t) { LOG.error(message.get(), t); Threads.interruptIfNecessary(t); return defaultValue; } } static Supplier toChecked(ThrowingSupplier supplier) { return () -> { try { return supplier.get(); } catch (Throwable t) { return ExceptionUtils.rethrow(t); } }; } static Supplier toCaught(ThrowingSupplier supplier) { return toCaught(supplier, null, EMPTY_MESSAGE); } static Supplier toCaught(ThrowingSupplier supplier, R defaultValue, Supplier message) { return () -> { try { return supplier.get(); } catch (Throwable t) { LOG.error(message.get(), t); Threads.interruptIfNecessary(t); return defaultValue; } }; } } /** * Lambda function checked exception * * @param the result type of method {@code call} * @param the type of the call "call" method possible occur exception */ @FunctionalInterface public interface ThrowingCallable { R call() throws T; default ThrowingRunnable toRunnable() { return this::call; } static R doChecked(ThrowingCallable callable) { try { return callable.call(); } catch (Throwable t) { return ExceptionUtils.rethrow(t); } } static R doCaught(ThrowingCallable callable) { return doCaught(callable, null, EMPTY_MESSAGE); } static R doCaught(ThrowingCallable callable, R defaultValue, Supplier message) { try { return callable.call(); } catch (Throwable t) { LOG.error(message.get(), t); Threads.interruptIfNecessary(t); return defaultValue; } } static Callable toChecked(ThrowingCallable callable) { return () -> { try { return callable.call(); } catch (Throwable t) { return ExceptionUtils.rethrow(t); } }; } static Callable toCaught(ThrowingCallable supplier) { return toCaught(supplier, null, EMPTY_MESSAGE); } static Callable toCaught(ThrowingCallable supplier, R defaultValue, Supplier message) { return () -> { try { return supplier.call(); } catch (Throwable t) { LOG.error(message.get(), t); Threads.interruptIfNecessary(t); return defaultValue; } }; } } /** * Lambda function checked exception * * @param the type of the input to the operation * @param the type of the call accept method possible occur exception */ @FunctionalInterface public interface ThrowingConsumer { void accept(E e) throws T; default ThrowingFunction toFunction(R result) { return x -> { accept(x); return result; }; } static void doChecked(ThrowingConsumer consumer, E arg) { try { consumer.accept(arg); } catch (Throwable t) { ExceptionUtils.rethrow(t); } } static void doCaught(ThrowingConsumer consumer, E arg) { doCaught(consumer, arg, EMPTY_MESSAGE); } static void doCaught(ThrowingConsumer consumer, E arg, Supplier message) { try { consumer.accept(arg); } catch (Throwable t) { LOG.error(message.get(), t); Threads.interruptIfNecessary(t); } } static Consumer toChecked(ThrowingConsumer consumer) { return e -> { try { consumer.accept(e); } catch (Throwable t) { ExceptionUtils.rethrow(t); } }; } static Consumer toCaught(ThrowingConsumer consumer) { return toCaught(consumer, EMPTY_MESSAGE); } static Consumer toCaught(ThrowingConsumer consumer, Supplier message) { return arg -> { try { consumer.accept(arg); } catch (Throwable t) { LOG.error(message.get(), t); Threads.interruptIfNecessary(t); } }; } } /** * Lambda function checked exception * * @param the type of the input to the function * @param the type of the result of the function * @param the type of the call apply method possible occur exception */ @FunctionalInterface public interface ThrowingFunction { R apply(E e) throws T; default ThrowingConsumer toConsumer() { return this::apply; } static R doChecked(ThrowingFunction function, E arg) { try { return function.apply(arg); } catch (Throwable t) { return ExceptionUtils.rethrow(t); } } static R doCaught(ThrowingFunction function, E arg) { return doCaught(function, arg, null, EMPTY_MESSAGE); } static R doCaught(ThrowingFunction function, E arg, R defaultValue, Supplier message) { try { return function.apply(arg); } catch (Throwable t) { LOG.error(message.get(), t); Threads.interruptIfNecessary(t); return defaultValue; } } static Function toChecked(ThrowingFunction function) { return e -> { try { return function.apply(e); } catch (Throwable t) { return ExceptionUtils.rethrow(t); } }; } static Function toCaught(ThrowingFunction function) { return toCaught(function, null, EMPTY_MESSAGE); } static Function toCaught(ThrowingFunction function, R defaultValue, Supplier message) { return arg -> { try { return function.apply(arg); } catch (Throwable t) { LOG.error(message.get(), t); Threads.interruptIfNecessary(t); return defaultValue; } }; } } } ================================================ FILE: src/main/java/cn/ponfee/commons/exception/UnauthorizedException.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.exception; import cn.ponfee.commons.model.ResultCode; /** * Unauthorized exception definition * * @author Ponfee */ public class UnauthorizedException extends BaseUncheckedException { private static final long serialVersionUID = -5678901285130119481L; private static final int CODE = ResultCode.UNAUTHORIZED.getCode(); public UnauthorizedException() { super(CODE, null, null); } public UnauthorizedException(String message) { super(CODE, message, null); } public UnauthorizedException(Throwable cause) { super(CODE, null, cause); } public UnauthorizedException(String message, Throwable cause) { super(CODE, message, cause); } public UnauthorizedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(CODE, message, cause, enableSuppression, writableStackTrace); } } ================================================ FILE: src/main/java/cn/ponfee/commons/exception/UnimplementedException.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.exception; import cn.ponfee.commons.model.ResultCode; /** * Unimplemented exception definition * * @author Ponfee */ public class UnimplementedException extends BaseUncheckedException { private static final long serialVersionUID = -5983398403463732650L; private static final int CODE = ResultCode.SERVER_UNSUPPORTED.getCode(); public UnimplementedException() { super(CODE, null, null); } public UnimplementedException(String message) { super(CODE, message, null); } public UnimplementedException(Throwable cause) { super(CODE, null, cause); } public UnimplementedException(String message, Throwable cause) { super(CODE, message, cause); } public UnimplementedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(CODE, message, cause, enableSuppression, writableStackTrace); } } ================================================ FILE: src/main/java/cn/ponfee/commons/export/AbstractCsvExporter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.export; import cn.ponfee.commons.io.Files; import cn.ponfee.commons.tree.FlatNode; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import java.io.IOException; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; /** * Exports csv * * @author Ponfee */ public abstract class AbstractCsvExporter extends AbstractDataExporter { protected final Appendable csv; private final char csvSeparator; private final AtomicBoolean hasBuild = new AtomicBoolean(false); public AbstractCsvExporter(Appendable csv) { this(csv, ','); } public AbstractCsvExporter(Appendable csv, char csvSeparator) { this.csv = csv; this.csvSeparator = csvSeparator; } @Override public final void build(Table table) { if (hasBuild.getAndSet(true)) { throw new UnsupportedOperationException("Only support single table."); } List> thead = table.getThead(); if (CollectionUtils.isEmpty(thead)) { throw new IllegalArgumentException("Thead cannot be null."); } // build table thead buildComplexThead(thead); // tbody--------------- rollingTbody(table, (data, i) -> { try { for (int m = data.length - 1, j = 0; j <= m; j++) { // escapeCsv(toString(data[j]), csvSeparator); csv.append(toString(data[j])); if (j < m) { csv.append(csvSeparator); } } csv.append(Files.SYSTEM_LINE_SEPARATOR); // 换行 //if ((i & 0xFF) == 0) { // this.flush(); //} } catch (IOException e) { throw new RuntimeException(e); } }); try { if (table.isEmptyTbody()) { csv.append(NO_RESULT_TIP); } else { super.nonEmpty(); } // tfoot--------- if (ArrayUtils.isNotEmpty(table.getTfoot())) { FlatNode root = thead.get(0); if (table.getTfoot().length > root.getTreeLeafCount()) { throw new IllegalStateException("Tfoot length cannot more than total leaf count."); } int n = root.getTreeLeafCount(), m = table.getTfoot().length, mergeNum = n - m; for (int i = 0; i < mergeNum; i++) { if (i == mergeNum - 1) { csv.append("合计"); } csv.append(csvSeparator); } for (int i = mergeNum; i < n; i++) { // escapeCsv(toString((table.getTfoot()[i - mergeNum])), csvSeparator); csv.append(toString(table.getTfoot()[i - mergeNum])); if (i != n - 1) { csv.append(csvSeparator); } } csv.append(Files.SYSTEM_LINE_SEPARATOR); } } catch (IOException e) { throw new RuntimeException(e); } } //protected void flush() {} private void buildComplexThead(List> thead) { List leafs = super.getLeafThead(thead); try { for (int i = 0, n = leafs.size(); i < n; i++) { csv.append(leafs.get(i).getName()); if (i != n - 1) { csv.append(csvSeparator); } } csv.append(Files.SYSTEM_LINE_SEPARATOR); } catch (IOException e) { throw new RuntimeException(e); } } // 创建简单表头 /*private void buildSimpleThead(String[] theadName) { for (String th : theadName) { csv.append(th).append(csvSeparator); } csv.setLength(csv.length() - 1); csv.append(Files.LINE_SEPARATOR); }*/ public static String escapeCsv(String text) { return escapeCsv(text, ','); } public static String escapeCsv(String text, char separator) { if (StringUtils.isEmpty(text)) { return text; } if (text.contains("\"")) { text = text.replace("\"", "\"\""); } if (StringUtils.contains(text, separator)) { //String.format("\"%s\"", text) text = new StringBuilder(text.length() + 2).append('"').append(text).append('"').toString(); } return text; } private static String toString(Object value) { return value == null ? "" : value.toString(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/export/AbstractDataExporter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.export; import cn.ponfee.commons.reflect.Fields; import cn.ponfee.commons.tree.FlatNode; import java.lang.reflect.Array; import java.util.*; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.stream.Collectors; /** * Exports abstract class * * @author Ponfee */ public abstract class AbstractDataExporter implements DataExporter { public static final int AWAIT_TIME_MILLIS = 47; private boolean empty = true; private String name; // report name: non thread safe @Override public boolean isEmpty() { return empty; } @Override public void close() { // nothing to do } public final void nonEmpty() { this.empty = false; } public final AbstractDataExporter setName(String name) { this.name = name; return this; } public final String getName() { return name; } protected final void rollingTbody(Table table, BiConsumer action) { try { E data; Function converter; if ((converter = table.getConverter()) != null) { for (int i = 0; table.isNotEnd();) { if ((data = table.getRow(AWAIT_TIME_MILLIS)) != null) { action.accept(converter.apply(data), i++); } } } else { String[] fields = table.getThead() .stream() .filter(FlatNode::isLeaf) .map(f -> f.getAttach().getField()) .toArray(String[]::new); Object[] array; for (int i = 0; table.isNotEnd();) { if ((data = table.getRow(AWAIT_TIME_MILLIS)) != null) { if (data instanceof Object[]) { array = (Object[]) data; } else if (data.getClass().isArray()) { array = covariantArray(data); } else if (data instanceof Collection) { array = collection2array((Collection) data); } else if (data instanceof Iterable) { array = iterable2array((Iterable) data); } else if (data instanceof Iterator) { array = iterator2array((Iterator) data); } else if (data instanceof Map) { array = map2array((Map) data); } else if (data instanceof Dictionary) { array = dictionary2array((Dictionary) data); } else { array = bean2array(data, fields); } action.accept(array, i++); } } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } } protected final List getLeafThead(List> thead) { return thead.stream() .filter(FlatNode::isLeaf) .map(FlatNode::getAttach) .collect(Collectors.toList()); } private static Object[] collection2array(Collection coll) { Object[] array = new Object[coll.size()]; int i = 0; for (Object obj : coll) { array[i++] = obj; } return array; } private static Object[] iterable2array(Iterable iterable) { List list = new LinkedList<>(); iterable.forEach(list::add); return list.toArray(); } private static Object[] iterator2array(Iterator iter) { List list = new LinkedList<>(); while (iter.hasNext()) { list.add(iter.next()); } return list.toArray(); } private static Object[] covariantArray(Object array0) { int size = Array.getLength(array0); Object[] array = new Object[size]; for (int i = 0; i < size; i++) { array[i] = Array.get(array0, i); } return array; } private static Object[] map2array(Map map) { List list = new LinkedList<>(); map.forEach((k, v) -> list.add(v)); return list.toArray(); } private static Object[] dictionary2array(Dictionary dic) { List list = new LinkedList<>(); Enumeration enu = dic.elements(); while (enu.hasMoreElements()) { list.add(enu.nextElement()); } return list.toArray(); } private static Object[] bean2array(Object bean, String[] fields) { int size = fields.length; Object[] array = new Object[size]; for (int i = 0; i < size; i++) { // must be setting field array[i] = Fields.get(bean, fields[i]); } return array; } } ================================================ FILE: src/main/java/cn/ponfee/commons/export/AbstractSplitExporter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.export; import cn.ponfee.commons.util.Holder; import com.google.common.base.Preconditions; import java.io.IOException; import java.util.LinkedList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; /** * Export multiple file * * @author Ponfee */ public abstract class AbstractSplitExporter extends AbstractDataExporter { private final int batchSize; private final String savingFilePathPrefix; private final String fileSuffix; private final Executor executor; public AbstractSplitExporter(int batchSize, String savingFilePathPrefix, String fileSuffix, Executor executor) { Preconditions.checkArgument(batchSize > 0); this.batchSize = batchSize; this.savingFilePathPrefix = savingFilePathPrefix; this.fileSuffix = fileSuffix; this.executor = executor; } @Override public final void build(Table table) { List> futures = new LinkedList<>(); AtomicInteger count = new AtomicInteger(0); AtomicInteger split = new AtomicInteger(0); Holder> subTable = Holder.of(table.copyOfWithoutTbody(Function.identity())); rollingTbody(table, (data, i) -> { subTable.get().addRow(data); if (count.incrementAndGet() == batchSize) { // sets a new table and return the last Table last = subTable.set(table.copyOfWithoutTbody(Function.identity())); String path = buildFilePath(split.incrementAndGet()); futures.add(CompletableFuture.runAsync(splitExporter(last, path), executor)); count.set(0); // reset count and sub table } }); if (!subTable.get().isEmptyTbody()) { String path = buildFilePath(split.incrementAndGet()); futures.add(CompletableFuture.runAsync(splitExporter(subTable.get(), path), executor)); } if (!futures.isEmpty()) { super.nonEmpty(); futures.forEach(CompletableFuture::join); } } protected abstract AbstractAsyncSplitExporter splitExporter(Table subTable, String savingFilePath); @Override public final Void export() { throw new UnsupportedOperationException(); } private String buildFilePath(int fileNo) { return savingFilePathPrefix + String.format("%04d", fileNo) + fileSuffix; } public static abstract class AbstractAsyncSplitExporter implements Runnable { private final Table subTable; protected final String savingFilePath; public AbstractAsyncSplitExporter(Table subTable, String savingFilePath) { this.subTable = subTable; this.savingFilePath = savingFilePath; } @Override public final void run() { subTable.toEnd(); try (AbstractDataExporter exporter = createExporter()) { exporter.build(subTable); complete(exporter); } catch (IOException e) { throw new RuntimeException(e); } } protected abstract AbstractDataExporter createExporter() throws IOException; protected void complete(AbstractDataExporter exporter) {} } } ================================================ FILE: src/main/java/cn/ponfee/commons/export/CellStyleOptions.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.export; /** * 单元格样式选项 * * @author Ponfee */ public enum CellStyleOptions { HIGHLIGHT, CELL_PROCESS } ================================================ FILE: src/main/java/cn/ponfee/commons/export/ConsoleExporter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.export; import cn.ponfee.commons.math.Numbers; import cn.ponfee.commons.tree.FlatNode; import com.google.common.base.Strings; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.LongAdder; import java.util.stream.Collectors; /** * Console Exporter * * @author Ponfee */ public class ConsoleExporter extends AbstractDataExporter { public static final String HORIZON = "\n\n-------------------------------------------------------\n\n"; public static final String ELLIPSIS_STR = "..."; public static final int ELLIPSIS_LEN = ELLIPSIS_STR.length(); protected final Appendable out; protected final int maxColumnWidth; protected final boolean hasLineSeparator; public ConsoleExporter(Appendable out) { this(out, 36, false); } public ConsoleExporter(Appendable out, int maxColumnWidth, boolean rowSeparator) { this.out = out; this.maxColumnWidth = Math.max(maxColumnWidth, ELLIPSIS_LEN + 1); this.hasLineSeparator = rowSeparator; } /** * 构建html */ @Override public void build(Table table) { List> flats = table.getThead(); if (flats == null || flats.isEmpty()) { throw new IllegalArgumentException("thead can't be null"); } try { // horizon horizon(); // table start List columns = union( new Column(new Thead(("#"))), getLeafThead(table.getThead()).stream().map(Column::new).collect(Collectors.toList()) ); LongAdder rowCount = new LongAdder(); rollingTbody(table, (data, i) -> { // row number Column first = columns.get(0); String rowNumber = Integer.toString(i + 1); first.values.add(rowNumber); first.width = Numbers.bounds(rowNumber.length(), first.width, maxColumnWidth); // each row for (int m = data.length, colIdx = 0; colIdx < m; colIdx++) { Column column = columns.get(colIdx + 1); String value = Objects.toString(data[colIdx], ""); column.values.add(value); column.width = Numbers.bounds(value.length(), column.width, maxColumnWidth); } rowCount.increment(); }); // print caption int rowWidth = columns.stream().mapToInt(e -> e.width + 3).sum() + 1; String caption = Objects.toString(table.getCaption(), ""); append("+-").append('-', rowWidth - 4).append("-+").newLine(); append("| ").center(caption, rowWidth - 4).append(" |").newLine(); String separator = "+-" + columns.stream().map(e -> Strings.repeat("-", e.width)).collect(Collectors.joining("-+-")) + "-+"; // print thead append(separator).newLine(); for (Column col : columns) { append("| ").center(col.getName(), col.width).append(' '); } append('|').newLine(); append(separator).newLine(); // print rows for (int n = rowCount.intValue(), rowIdx = 0; rowIdx < n; rowIdx++) { if (hasLineSeparator && rowIdx > 0) { append(separator).newLine(); } for (Column col : columns) { append("| ").append(col.values.get(rowIdx), col.width).append(' '); } append('|').newLine(); } append(separator).newLine(); nonEmpty(); } catch (IOException e) { throw new RuntimeException(e); } } @Override public String export() { return out.toString(); } // ------------------------------------------------------------private methods private ConsoleExporter horizon() throws IOException { if (!isEmpty()) { out.append(HORIZON); } return this; } private ConsoleExporter append(char c) throws IOException { out.append(c); return this; } private ConsoleExporter append(char c, int count) throws IOException { for (int i = 0; i < count; i++) { out.append(c); } return this; } private ConsoleExporter append(CharSequence text) throws IOException { out.append(text); return this; } private ConsoleExporter append(String text, int width) throws IOException { int padding = width - text.length(); if (padding >= 0) { append(text).append(' ', padding); } else { out.append(text, 0, width - ELLIPSIS_LEN); out.append(ELLIPSIS_STR); } return this; } private ConsoleExporter center(String text, int width) throws IOException { int padding = width - text.length(); if (padding >= 0) { append(' ', padding / 2).append(text).append(' ', (padding + 1) / 2); } else { out.append(text, 0, width - ELLIPSIS_LEN); out.append(ELLIPSIS_STR); } return this; } private void newLine() throws IOException { out.append('\n'); } private static List union(T first, Collection coll) { List list = new ArrayList<>(coll.size() + 1); list.add(first); list.addAll(coll); return list; } private class Column extends Thead { private static final long serialVersionUID = -5764311953058980984L; private final List values = new ArrayList<>(); private int width; public Column(Thead th) { super(th.getName(), th.getTmeta(), th.getField()); this.width = Math.min(th.getName().length(), maxColumnWidth); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/export/CsvFileExporter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.export; import cn.ponfee.commons.io.ByteOrderMarks; import cn.ponfee.commons.io.WrappedBufferedWriter; import java.io.File; import java.io.IOException; import java.io.Writer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; /** * Exports csv file * * @author Ponfee */ public class CsvFileExporter extends CsvWriteExporter { public CsvFileExporter(String filePath, boolean withBom) throws IOException { this(new File(filePath), StandardCharsets.UTF_8, withBom); } public CsvFileExporter(File file, Charset charset, boolean withBom) throws IOException { super(createWriter(file, charset, withBom)); } public CsvFileExporter(File file, Charset charset, boolean withBom, char csvSeparator) throws IOException { super(createWriter(file, charset, withBom), csvSeparator); } private static Writer createWriter(File file, Charset charset, boolean withBom) throws IOException { WrappedBufferedWriter writer = new WrappedBufferedWriter(file, charset); byte[] bom; if (withBom && (bom = ByteOrderMarks.get(charset)) != null) { writer.write(bom); } return writer; } } ================================================ FILE: src/main/java/cn/ponfee/commons/export/CsvStringExporter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.export; import cn.ponfee.commons.io.ByteOrderMarks; import cn.ponfee.commons.io.WrappedBufferedWriter; import java.io.File; import java.io.IOException; import java.nio.charset.Charset; /** * Exports csv string * * @author Ponfee */ public class CsvStringExporter extends AbstractCsvExporter { public CsvStringExporter() { this(0x2000); } public CsvStringExporter(int capacity) { super(new StringBuilder(capacity)); } public CsvStringExporter(int capacity, char csvSeparator) { super(new StringBuilder(capacity), csvSeparator); } @Override public String export() { return csv.toString(); } public void write(String filePath, Charset charset, boolean withBom) { File file = new File(filePath); try (WrappedBufferedWriter writer = new WrappedBufferedWriter(file, charset)) { byte[] bom; if (withBom && (bom = ByteOrderMarks.get(charset)) != null) { writer.write(bom); } writer.append((StringBuilder) super.csv); } catch (IOException e) { throw new RuntimeException(e); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/export/CsvWriteExporter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.export; import java.io.Writer; /** * Exports csv wirte * * @author Ponfee */ public class CsvWriteExporter extends AbstractCsvExporter { public CsvWriteExporter(Writer writer) { super(writer); } public CsvWriteExporter(Writer writer, char csvSeparator) { super(writer, csvSeparator); } @Override public Void export() { throw new UnsupportedOperationException(); } @Override public void close() { try { ((Writer) super.csv).close(); } catch (Exception e) { throw new RuntimeException(e); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/export/DataExporter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.export; import java.io.Closeable; /** * {@link Closeable#close()} 要求幂等 * {@link AutoCloseable#close()} 不要求幂等 * *

数据导出 * * @author Ponfee */ public interface DataExporter extends Closeable { /** 提示无结果 */ String NO_RESULT_TIP = "No results found"; /** * 构建表格 */ void build(Table table); /** * 获取表格 */ T export(); /** * 判断是否为空 */ boolean isEmpty(); } ================================================ FILE: src/main/java/cn/ponfee/commons/export/ExcelExporter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.export; import cn.ponfee.commons.date.Dates; import cn.ponfee.commons.export.Tmeta.Type; import cn.ponfee.commons.math.Numbers; import cn.ponfee.commons.tree.FlatNode; import cn.ponfee.commons.util.Colors; import cn.ponfee.commons.util.ImageUtils; import cn.ponfee.commons.util.ObjectUtils; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.DateUtils; import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.usermodel.ClientAnchor.AnchorType; import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.xssf.streaming.*; import org.apache.poi.xssf.usermodel.*; import java.awt.Color; import java.io.*; import java.text.ParseException; import java.util.*; import java.util.Map.Entry; import java.util.function.Consumer; /** * excel导出 * * SXSSFWorkbook(size>65536),XSSFWorkbook(.xlsx),HSSFWorkbook(.xls) * * @see poi * * |------------|----------------------------------------------------------|--------------| * | model | description | Read & Write | * |------------|----------------------------------------------------------|--------------| * | SXSSF | holding fix rows in memory, write disk when row overflow | R | * |------------|----------------------------------------------------------|--------------| * | eventmodel | based event model, parse with SAX, thrift cup and memory | R | * |------------|----------------------------------------------------------|--------------| * | usermodel | tradition model, need more cpu and memory | R & W | * |------------|----------------------------------------------------------|--------------| * * @author Ponfee */ public class ExcelExporter extends AbstractDataExporter { // 内存最大存放100行数据 超过100自动刷新到硬盘中 // SXSSFSheet.flushRows(100); retain 100 last rows and flush all others public static final int DEFAULT_WINDOW_SIZE = 100; /** row and cell config,默认的列宽和行高大小 */ private static final int DEFAULT_WIDTH = 3200; private static final short DEFAULT_HEIGHT = 350; /** nested image config,office 2013版本 */ private static final double RATE_WIDTH = 70.5; // 图片真实宽度与excel列宽的比例 private static final double RATE_HEIGHT = 18; // 图片真实高度与excel行高的比例 /** 作为分隔符(类似html的


)的合并列数目 */ private static final int MARGIN_ROW_CELL_SIZE = 26; private final IndexedColorMap defaultColorMap = new DefaultIndexedColorMap(); private SXSSFWorkbook workbook; // excel private final XSSFCellStyle titleStyle; // 标题样式 private final XSSFCellStyle headStyle; // 表头样式 private final XSSFCellStyle dataStyle; // 数据样式 private final XSSFCellStyle tfootMergeStyle; // 合计行样式 private final XSSFCellStyle noneStyle; // 无样式 private final XSSFCellStyle tipStyle; // 无样式 private final XSSFDataFormat dataFormat; // 数据格式 private final Map sheets = new HashMap<>(); private final Map images = new HashMap<>(); private final Map freezes = new HashMap<>(); public ExcelExporter() { workbook = new SXSSFWorkbook(DEFAULT_WINDOW_SIZE); //workbook.setCompressTempFiles(true); // temp files will be gzipped dataFormat = (XSSFDataFormat) workbook.createDataFormat(); XSSFFont titleFont = (XSSFFont) workbook.createFont(); titleFont.setBold(true); titleFont.setFontName("黑体"); //titleFont.setFontHeightInPoints((short) 12); // set font 1 to 12 point type XSSFFont headFont = (XSSFFont) workbook.createFont(); headFont.setBold(true); headFont.setFontName("宋体"); XSSFFont redFont = (XSSFFont) workbook.createFont(); redFont.setColor(new XSSFColor(new Color(255, 0, 0), defaultColorMap)); redFont.setFontName("宋体"); XSSFCellStyle baseStyle = (XSSFCellStyle) workbook.createCellStyle(); baseStyle.setBorderLeft(BorderStyle.THIN); baseStyle.setBorderTop(BorderStyle.THIN); baseStyle.setBorderRight(BorderStyle.THIN); baseStyle.setBorderBottom(BorderStyle.THIN); baseStyle.setVerticalAlignment(VerticalAlignment.CENTER); baseStyle.setWrapText(true); baseStyle.setDataFormat(dataFormat.getFormat("@")); titleStyle = (XSSFCellStyle) workbook.createCellStyle(); titleStyle.cloneStyleFrom(baseStyle); titleStyle.setAlignment(HorizontalAlignment.CENTER); titleStyle.setFont(titleFont); titleStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); // 填充样式 titleStyle.setFillForegroundColor(new XSSFColor(new Color(255, 255, 224), defaultColorMap)); // 填充颜色 headStyle = (XSSFCellStyle) workbook.createCellStyle(); headStyle.cloneStyleFrom(baseStyle); headStyle.setAlignment(HorizontalAlignment.CENTER); headStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); headStyle.setFillForegroundColor(new XSSFColor(new Color(192, 192, 192), defaultColorMap)); headStyle.setFont(headFont); headStyle.setWrapText(false); dataStyle = (XSSFCellStyle) workbook.createCellStyle(); dataStyle.cloneStyleFrom(baseStyle); dataStyle.setWrapText(false); tfootMergeStyle = (XSSFCellStyle) workbook.createCellStyle(); tfootMergeStyle.cloneStyleFrom(dataStyle); tfootMergeStyle.setFont(headFont); tfootMergeStyle.setWrapText(false); tfootMergeStyle.setAlignment(HorizontalAlignment.RIGHT); tfootMergeStyle.setBorderLeft(BorderStyle.THIN); tfootMergeStyle.setBorderTop(BorderStyle.THIN); tfootMergeStyle.setBorderRight(BorderStyle.THIN); tfootMergeStyle.setBorderBottom(BorderStyle.THIN); tipStyle = (XSSFCellStyle) workbook.createCellStyle(); tipStyle.cloneStyleFrom(baseStyle); tipStyle.setFont(redFont); noneStyle = (XSSFCellStyle) workbook.createCellStyle(); noneStyle.setVerticalAlignment(VerticalAlignment.CENTER); } /** * 构建excel */ @Override public void build(Table table) { // 1、校验表头是否为空 List> flats = table.getThead(); if (CollectionUtils.isEmpty(flats)) { throw new IllegalArgumentException("thead can't be null"); } // 2、获取工作簿 String name = this.getName(); SXSSFSheet sheet = getSheet(name); // 3、判断工作簿是否已创建过行数据,还没有创建行时sheet.getLastRowNum()返回-1 CursorRowNumber cursorRow = new CursorRowNumber(Math.max(sheet.getLastRowNum(), 0)); if (cursorRow.get() > 0) { // 创建两行空白行 cursorRow.increment(); int i = cursorRow.getAndIncrement(), j = cursorRow.getAndIncrement(); SXSSFRow row1 = sheet.createRow(i); row1.setHeight(DEFAULT_HEIGHT); SXSSFRow row2 = sheet.createRow(j); row2.setHeight(DEFAULT_HEIGHT); for (int k = 0; k < MARGIN_ROW_CELL_SIZE; k++) { createCell(row1, k, noneStyle, null); createCell(row2, k, noneStyle, null); } sheet.addMergedRegion(new CellRangeAddress(i, j, 0, MARGIN_ROW_CELL_SIZE - 1)); } // 4、构建复合表头 buildComplexThead(table, sheet, cursorRow); // 5、冻结窗口配置 Freeze freeze; if ((freeze = freezes.get(name)) != null) { freeze.disable(); } else { freezes.put(name, new Freeze(1, cursorRow.get())); // 叶子节点只占一列,故colSplit=1 } // 6、处理tbody数据 Map options = table.getOptions(); List leafs = getLeafThead(flats); List styles = createStyles(leafs); rollingTbody(table, (data, i) -> { SXSSFRow row = sheet.createRow(cursorRow.getAndIncrement()); //row.setHeight(DEFAULT_HEIGHT); for (int m = data.length, j = 0; j < m; j++) { createCell(row, j, styles.get(j), getTmeta(leafs, j), data[j], i, j, options); } }); // 7、判断是否有数据 SXSSFRow row; int totalLeafCount = flats.get(0).getTreeLeafCount(); if (table.isEmptyTbody()) { createBlankRow(NO_RESULT_TIP, sheet, tipStyle, cursorRow, totalLeafCount); } else { super.nonEmpty(); } // 8、处理tfoot数据 Object[] tfoots = table.getTfoot(); if (ArrayUtils.isNotEmpty(tfoots)) { int rowNum = cursorRow.getAndIncrement(); row = sheet.createRow(rowNum); row.setHeight(DEFAULT_HEIGHT); if (table.getTfoot().length > totalLeafCount) { throw new IllegalStateException("tfoot data length cannot more than total leaf count."); } // 合计单元格 int mergeNum = totalLeafCount - table.getTfoot().length; for (int i = 0; i < mergeNum; i++) { createCell(row, i, tfootMergeStyle, (i == 0) ? "合计" : null); } if (mergeNum > 1) { sheet.addMergedRegion(new CellRangeAddress(rowNum, rowNum, 0, mergeNum - 1)); } // 合计数据 for (int i = 0; i < tfoots.length; i++) { createCell(row, i + mergeNum, styles.get(mergeNum + i), getTmeta(leafs, mergeNum + i), tfoots[i]); } } // 9、文字注释 if (StringUtils.isNotBlank(table.getComment())) { createBlankRow(table.getComment(), sheet, tipStyle, cursorRow, totalLeafCount); } } public void insertImage(byte[] imageBytes) { int[] size = ImageUtils.getImageSize(new ByteArrayInputStream(imageBytes)); insertImage(imageBytes, size[0], size[1]); } /** * excel中嵌入图片 * excel.insertImage(byte[] image, width, cheight); */ public void insertImage(byte[] imageBytes, int width, int height) { if (imageBytes == null || imageBytes.length == 0) { return; } super.nonEmpty(); SXSSFSheet sheet = getSheet(getName()); int startRow = Optional.ofNullable(images.get(getName())).orElse(1), startCol = 1; int endCol = startCol + (int) Math.round(((double) width) / RATE_WIDTH); int endRow = startRow + (int) Math.round(((double) height) / RATE_HEIGHT); images.put(getName(), endRow + 2); SXSSFDrawing drawing = sheet.createDrawingPatriarch(); XSSFClientAnchor anchor = new XSSFClientAnchor( 0, 0, width, height, startCol, startRow, (short) endCol, endRow ); anchor.setAnchorType(AnchorType.DONT_MOVE_AND_RESIZE); drawing.createPicture(anchor, workbook.addPicture(imageBytes, SXSSFWorkbook.PICTURE_TYPE_PNG)); /*int pictureIdx = workbook.addPicture(imageBytes, Workbook.PICTURE_TYPE_PNG); CreationHelper helper = workbook.getCreationHelper(); SXSSFDrawing drawing = sheet.createDrawingPatriarch(); ClientAnchor anchor = helper.createClientAnchor(); anchor.setCol1(0); anchor.setRow1(Math.max(sheet.getLastRowNum(), 0)); Picture pict = drawing.createPicture(anchor, pictureIdx); pict.resize(1);*/ } /** * 输出到输出流 */ public void write(OutputStream out) { try (BufferedOutputStream bos = new BufferedOutputStream(out); SXSSFWorkbook wb = workbook ) { createFreezePane(); wb.write(bos); wb.dispose(); workbook = null; } catch (IOException e) { throw new RuntimeException(e); } } public void write(String filepath) { write(new File(filepath)); } public void write(File file) { try (OutputStream out = new FileOutputStream(file)) { write(out); } catch (IOException e) { throw new RuntimeException(e); } } /** * 导出 */ @Override public byte[] export() { ByteArrayOutputStream out = new ByteArrayOutputStream(8192); write(out); return out.toByteArray(); } /** * 关闭 */ @Override public void close() { if (workbook == null) { return; } try (SXSSFWorkbook wb = workbook) { // nothing to do wb.dispose(); // dispose of temporary files backing this workbook on disk workbook = null; } catch (IOException e) { throw new RuntimeException(e); } finally { sheets.clear(); images.clear(); freezes.clear(); } } //------------------------------------------------------------protected methods protected SXSSFSheet getSheet(String name) { SXSSFSheet sheet = sheets.get(name); if (sheet == null) { sheet = workbook.createSheet(name); sheet.setDisplayGridlines(false); // 不显示网格线 /*sheet.setDefaultColumnWidth(DEFAULT_WIDTH); sheet.setDefaultRowHeight(DEFAULT_HEIGHT);*/ sheets.put(name, sheet); } return sheet; } protected void removeSheet(String name) { workbook.removeSheetAt(workbook.getSheetIndex(sheets.get(name))); sheets.remove(name); } private void createFreezePane() { for (Entry entry : sheets.entrySet()) { Freeze freeze = freezes.get(entry.getKey()); if (freeze != null && freeze.freeze) { entry.getValue().createFreezePane(freeze.colSplit, freeze.rowSplit); } } } // 创建简单表头 /*private void buildSimpleThead(String title, XSSFSheet sheet, CursorRow cursorRow, String[] theadName) { createCell(title, sheet, titleStyle, cursorRow, theadName.length); XSSFRow row = sheet.createRow(cursorRow.getAndIncrement()); row.setHeight(DEFAULT_HEIGHT); for (int i = 0; i < theadName.length; i++) { sheet.setColumnWidth(i, DEFAULT_WIDTH); createCell(row, i, headStyle, theadName[i]); } }*/ // 复合表头 private void buildComplexThead(Table table, SXSSFSheet sheet, CursorRowNumber cursorRow) { List> flats = table.getThead(); FlatNode root = flats.get(0); int totalLeafCount = root.getTreeLeafCount(); List> thead = flats.subList(1, flats.size()); // create caption if (StringUtils.isNotBlank(table.getCaption())) { createBlankRow(table.getCaption(), sheet, titleStyle, cursorRow, totalLeafCount); } //sheet.trackAllColumnsForAutoSizing(); // 设置自动列宽 // 约定非叶子节点不能跨行 Set rows = new HashSet<>(); int beginCol, endRow, endCol, lastLevel = 1; int cellLevel, treeDepth = root.getTreeDepth() - 1; for (int n = thead.size(), i = 0; i < n; i++) { FlatNode flat = thead.get(i); cellLevel = flat.getLevel() - 1; if (cellLevel > lastLevel) { lastLevel = cellLevel; cursorRow.increment(); } beginCol = flat.getLeftLeafCount(); endCol = beginCol + flat.getTreeLeafCount() - 1; if (flat.isLeaf()) { endRow = cursorRow.get() + treeDepth - cellLevel; sheet.setColumnWidth(beginCol, DEFAULT_WIDTH); //sheet.autoSizeColumn(beginCol); // 设置自动列宽,要在autoSizeColumn前使用trackAllColumnsForAutoSizing() } else { endRow = cursorRow.get(); // 约定非子节点不能跨行 } SXSSFRow colRow; if (rows.add(cursorRow.get())) { colRow = sheet.createRow(cursorRow.get()); // 还未创建该行 colRow.setHeight(DEFAULT_HEIGHT); } else { colRow = sheet.getRow(cursorRow.get()); } createCell(colRow, beginCol, headStyle, flat.getAttach().getName()); // -----------------------------设置被合并单元格的样式------------------------------ // for (int b = beginCol + 1; b <= endCol; b++) { // 列 createCell(colRow, b, headStyle, null); } for (int a = cursorRow.get() + 1; a <= endRow; a++) { // 行 if (rows.add(a)) { // 行未创建 colRow = sheet.createRow(a); colRow.setHeight(DEFAULT_HEIGHT); } else { // 行已创建 colRow = sheet.getRow(a); } for (int b = beginCol; b <= endCol; b++) { createCell(colRow, b, headStyle, null); } } if (cursorRow.get() != endRow || beginCol != endCol) { sheet.addMergedRegion(new CellRangeAddress(cursorRow.get(), endRow, beginCol, endCol)); } // -----------------------------设置被合并单元格的样式------------------------------ // } cursorRow.increment(); } private Tmeta getTmeta(List thead, int index) { return thead.get(index).getTmeta(); } /** * 创建空行 * @param text * @param sheet * @param style * @param cursorRow * @param columnLen */ private void createBlankRow(String text, SXSSFSheet sheet, XSSFCellStyle style, CursorRowNumber cursorRow, int columnLen) { SXSSFRow row = sheet.createRow(cursorRow.get()); row.setHeight(DEFAULT_HEIGHT); createCell(row, 0, style, text); for (int i = 1; i < columnLen; i++) { createCell(row, i, style, null); } sheet.addMergedRegion(new CellRangeAddress(cursorRow.get(), cursorRow.get(), 0, columnLen - 1)); cursorRow.increment(); } private void createCell(SXSSFRow row, int colIndex, XSSFCellStyle style, Tmeta tmeta, Object value) { createCell(row, colIndex, style, tmeta, value, -1, -1, null); } private void createCell(SXSSFRow row, int colIndex, XSSFCellStyle style, Object value) { createCell(row, colIndex, style, null, value, -1, -1, null); } /** * 创建单元格 * @param row * @param colIndex * @param style * @param tmeta * @param value * @param tbodyRowIdx * @param tbodyColIdx * @param options */ private void createCell(SXSSFRow row, int colIndex, XSSFCellStyle style, Tmeta tmeta, Object value, int tbodyRowIdx, int tbodyColIdx, Map options) { SXSSFCell cell = row.createCell(colIndex); cell.setCellStyle(style); // 设置单元格格式 if (tmeta == null) { setCellString(cell, value); } else if (tmeta.getType() == Type.NUMERIC) { if (ObjectUtils.isEmpty(value)) { cell.setCellType(CellType.NUMERIC); cell.setCellValue(new XSSFRichTextString()); } else if (value instanceof String && ((String) value).endsWith("%")) { String val = ((String) value).substring(0, ((String) value).length() - 1); cell.setCellValue(Numbers.toDouble(val.replace(",", "")) / 100); } else { cell.setCellValue(Numbers.toDouble(value.toString().replace(",", ""))); } } else if (tmeta.getType() == Type.DATETIME) { if (value == null) { cell.setCellType(CellType.BLANK); } else if (value instanceof Date) { cell.setCellValue((Date) value); } else if (value instanceof Calendar) { cell.setCellValue((Calendar) value); } else { String str = value.toString(); String format = Optional.ofNullable(tmeta.getFormat()).orElse(Dates.DATETIME_PATTERN); try { cell.setCellValue(DateUtils.parseDateStrictly(str, format)); //cell.setCellValue(FastDateFormat.getInstance(format).parse(str)); } catch (ParseException e) { throw new IllegalArgumentException("invalid date str: " + str + ", format: " + format, e); } } } else { setCellString(cell, value); } // 样式自定义处理 processOptions(cell, tbodyRowIdx, tbodyColIdx, options); } /** * 处理其它配置项 * @param cell * @param tbodyRowIdx * @param tbodyColIdx * @param options */ @SuppressWarnings("unchecked") private void processOptions(SXSSFCell cell, int tbodyRowIdx, int tbodyColIdx, Map options) { if (MapUtils.isEmpty(options)) { return; } // 单元格高亮显示 Map highlight = (Map) options.get(CellStyleOptions.HIGHLIGHT); if (MapUtils.isNotEmpty(highlight)) { for (List c : (List>) highlight.get("cells")) { if (c.get(0) == tbodyRowIdx && c.get(1) == tbodyColIdx) { XSSFFont font = (XSSFFont) workbook.createFont(); font.setColor(new XSSFColor( Colors.fromHex((String) highlight.get("color")), defaultColorMap )); XSSFCellStyle style = (XSSFCellStyle) workbook.createCellStyle(); style.cloneStyleFrom(cell.getCellStyle()); style.setFont(font); cell.setCellStyle(style); } } } // 处理 Consumer processor = (Consumer) options.get(CellStyleOptions.CELL_PROCESS); if (processor != null) { processor.accept(new Object[] { workbook, cell, tbodyRowIdx, tbodyColIdx }); } } /** * create cell style, only called once * @param thead * @return */ private List createStyles(List thead) { List styles = new ArrayList<>(thead.size()); for (Thead flat : thead) { XSSFCellStyle style = (XSSFCellStyle) workbook.createCellStyle(); styles.add(style); style.cloneStyleFrom(dataStyle); Tmeta tmeta = flat.getTmeta(); if (tmeta != null) { switch (tmeta.getAlign()) { // 对齐方式 case LEFT: style.setAlignment(HorizontalAlignment.LEFT); break; case CENTER: style.setAlignment(HorizontalAlignment.CENTER); break; case RIGHT: style.setAlignment(HorizontalAlignment.RIGHT); break; default: break; } // 设置单元格格式 if (StringUtils.isNotBlank(tmeta.getFormat())) { //dataFormat.getFormat("0.00%"): 0.00%->0xa; #,###.00%->0xa5; #,##0->xxx; style.setDataFormat(dataFormat.getFormat(tmeta.getFormat())); } // 设置颜色 if (tmeta.getColor() != null) { XSSFFont font = (XSSFFont) workbook.createFont(); font.setColor(new XSSFColor(tmeta.getColor(), defaultColorMap)); style.setFont(font); } } // end of tmeta } return styles; } private static void setCellString(SXSSFCell cell, Object value) { if (value != null) { cell.setCellValue(value.toString()); } else { cell.setCellType(CellType.BLANK); } } /** * 游标行 */ @SuppressWarnings("unused") private static final class CursorRowNumber { int current; CursorRowNumber() { this(0); } CursorRowNumber(int initValue) { this.current = initValue; } int getAndIncrement() { return this.current++; } int incrementAndGet() { return ++this.current; } int getAndDecrement() { return this.current--; } int decrementAndGet() { return --this.current; } void add(int i) { this.current += i; } int addAndGet(int i) { this.current += i; return this.current; } int getAndAdd(int i) { int temp = this.current; this.current += i; return temp; } void set(int i) { this.current = i; } int getAndSet(int i) { int temp = this.current; this.current = i; return temp; } int get() { return this.current; } void increment() { this.current++; } } /** * 窗口冻结 */ @SuppressWarnings("unused") private static final class Freeze { boolean freeze = true; final int colSplit; final int rowSplit; Freeze(int colSplit, int rowSplit) { this.colSplit = colSplit; this.rowSplit = rowSplit; } void enable() { freeze = true; } void disable() { freeze = false; } } /*public void deleteTempFiles() { for (int i = 0, n = workbook.getNumberOfSheets(); i <= n; i++) { SXSSFSheet sheet = workbook.getSheetAt(i); // only support in SXSSFSheet // delete only if the sheet is written by stream SheetDataWriter sdw = (SheetDataWriter) Fields.get(sheet, "_writer"); FileUtils.deleteQuietly((File) Fields.get(sdw, "_fd")); } }*/ } ================================================ FILE: src/main/java/cn/ponfee/commons/export/HtmlExporter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.export; import cn.ponfee.commons.export.Tmeta.Type; import cn.ponfee.commons.math.Numbers; import cn.ponfee.commons.tree.FlatNode; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import java.text.MessageFormat; import java.util.List; import java.util.Map; import java.util.function.Function; /** * html导出 * * @author Ponfee */ public class HtmlExporter extends AbstractDataExporter { //private static final Pattern PATTERN_NEGATIVE = Pattern.compile("^(-(([0-9]+\\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\\.[0-9]+)|([0-9]*[1-9][0-9]*)))(%)?$"); public static final String HORIZON = "
"; private static final String TEMPLATE = new StringBuilder(4096) .append(" \n") .append(" \n") .append(" \n") .append(" \n") .append(" {0} \n") .append(" '' \n") .append(" \n") .append(" {1} \n") .append(" \n") .toString().replaceAll("\\s+\n", "\n"); private final StringBuilder html; // StringBuilder扩容:(value.length << 1) + 2 // 容量如果不够,直接扩充到需要的容量大小 public HtmlExporter() { this.html = new StringBuilder(0x2000); // 初始容量8192 } public HtmlExporter(String initHtml) { this.html = new StringBuilder(initHtml); } /** * 构建html */ @Override public void build(Table table) { List> flats = table.getThead(); if (flats == null || flats.isEmpty()) { throw new IllegalArgumentException("thead can't be null"); } // horizon------- horizon(); // table start------- html.append("
"); if (StringUtils.isNotBlank(table.getCaption())) { html.append(""); } // thead------- buildComplexThead(flats); // tbody------- List leafs = getLeafThead(flats); html.append(""); rollingTbody(table, (data, i) -> { html.append(""); for (int m = data.length, j = 0; j < m; j++) { html.append("").append(formatData(data[j], getTmeta(leafs, j))).append(""); } html.append(""); }); int totalLeafCount = flats.get(0).getTreeLeafCount(); if (table.isEmptyTbody()) { html.append(""); } else { super.nonEmpty(); } html.append(""); // tfoot------- boolean hasTfoot = false; if (ArrayUtils.isNotEmpty(table.getTfoot())) { hasTfoot = true; html.append(""); if (table.getTfoot().length > totalLeafCount) { throw new IllegalStateException("tfoot data length cannot more than total leaf count."); } int mergeNum = totalLeafCount - table.getTfoot().length; if (mergeNum > 0) { html.append(""); } for (int i = 0; i < table.getTfoot().length; i++) { html.append("") .append(formatData(table.getTfoot()[i], getTmeta(leafs, mergeNum + i))) .append(""); } html.append(""); } // comment------ if (StringUtils.isNotBlank(table.getComment())) { String[] comments = table.getComment().split(";"); StringBuilder builder = new StringBuilder(""); if (hasTfoot) { html.insert(html.length() - "".length(), builder); } else { html.append("").append(builder).append(""); } } // table end----- html.append("
") .append(table.getCaption()) .append("
") .append(NO_RESULT_TIP) .append("
合计
") .append("
备注:
"); for (String comment : comments) { builder.append("
") .append(comment) .append("
"); } builder.append("
"); } @Override public String export() { return MessageFormat.format(TEMPLATE, super.getName(), html.toString()); } public String body() { return html.toString(); } public HtmlExporter horizon() { if (html.length() > 0) { html.append(HORIZON); } return this; } //htmlExporter.horizon().append("
"); public HtmlExporter append(String string) { if (StringUtils.isNotBlank(string)) { super.nonEmpty(); html.append(string); } return this; } // public HtmlExporter insertImage(String imageB64) { super.nonEmpty(); html.append(""); return this; } // 创建简单表头 /*private void buildSimpleThead(String[] theadName) { html.append(""); for (String th : theadName) { html.append("").append(th).append(""); } html.append(""); }*/ // 复合表头 private void buildComplexThead(List> flats) { html.append(""); int lastLevel = 1, treeDepth = flats.get(0).getTreeDepth() - 1, cellLevel; for (FlatNode flat : flats.subList(1, flats.size())) { cellLevel = flat.getLevel() - 1; if (lastLevel < cellLevel) { html.append(""); lastLevel = cellLevel; } html.append(" 0) { html.append(" rowspan=\"").append(treeDepth - cellLevel + 1).append("\""); } } else { // 非叶子节点,跨列 if (flat.getTreeLeafCount() > 1) { html.append(" colspan=\"").append(flat.getTreeLeafCount()).append("\""); } } html.append(">").append(flat.getAttach().getName()).append(""); } html.append(""); } private Tmeta getTmeta(List thead, int index) { return thead.get(index).getTmeta(); } private void processMeta(Object value, Tmeta tmeta) { processMeta(value, tmeta, -1, -1, null); } /** * 样式处理 * @param value * @param tmeta * @param tbodyRowIdx * @param tbodyColIdx * @param options */ private final StringBuilder style = new StringBuilder(); private final StringBuilder clazz = new StringBuilder(); private void processMeta(Object value, Tmeta tmeta, int tbodyRowIdx, int tbodyColIdx, Map options) { style.setLength(0); clazz.setLength(0); /*if (PATTERN_NEGATIVE.matcher(Objects.toString(value, "")).matches()) { style.append("color:#006400;font-weight:bold;"); // 负数显示绿色 }*/ if (tmeta != null) { switch (tmeta.getAlign()) { case LEFT: clazz.append("text-left "); break; case CENTER: clazz.append("text-center "); break; case RIGHT: clazz.append("text-right "); break; default: break; } if (tmeta.getColor() != null) { style.append("color:").append(tmeta.getColorHex()).append(";"); } if (tmeta.isNowrap()) { clazz.append("nowrap "); } } processOptions(style, tbodyRowIdx, tbodyColIdx, options); if (style.length() > 0) { html.append(" style=\"").append(style).append("\""); } if (clazz.length() > 0) { clazz.setLength(clazz.length() - 1); html.append(" class=\"").append(clazz).append("\""); } } /** * 格式化 * @param data * @param tmeta * @return */ private static String formatData(Object data, Tmeta tmeta) { if (data == null) { return ""; } else if (tmeta == null) { return data.toString(); } else if (tmeta.getType() == Type.NUMERIC) { return Numbers.format(data); } else { return data.toString(); } } /** * 样式自定义处理 * @param style * @param dataRowIdx * @param dataColIdx * @param options */ @SuppressWarnings("unchecked") private static void processOptions(StringBuilder style, int dataRowIdx, int dataColIdx, Map options) { if (options == null || options.isEmpty()) { return; } Map highlight = (Map) options.get(CellStyleOptions.HIGHLIGHT); if (highlight != null && !highlight.isEmpty()) { String color = "color:" + highlight.get("color") + ";font-weight:bold;"; List> cells = (List>) highlight.get("cells"); for (List cell : cells) { if (cell.get(0).equals(dataRowIdx) && cell.get(1).equals(dataColIdx)) { style.append(color); } } } Function processor = (Function) options.get(CellStyleOptions.CELL_PROCESS); if (processor != null) { style.append(processor.apply(new Object[] { dataRowIdx, dataColIdx })); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/export/SplitCsvFileExporter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.export; import java.io.IOException; import java.util.concurrent.Executor; /** * Export multiple csv file * * @author Ponfee */ public class SplitCsvFileExporter extends AbstractSplitExporter { private final boolean withBom; public SplitCsvFileExporter(int batchSize, String savingFilePathPrefix, boolean withBom, Executor executor) { super(batchSize, savingFilePathPrefix, ".csv", executor); this.withBom = withBom; } @Override protected AbstractAsyncSplitExporter splitExporter(Table subTable, String savingFilePath) { return new AsnycCsvFileExporter(subTable, savingFilePath, withBom); } private static class AsnycCsvFileExporter extends AbstractAsyncSplitExporter { final boolean withBom; AsnycCsvFileExporter(Table subTable, String savingFilePath, boolean withBom) { super(subTable, savingFilePath); this.withBom = withBom; } @Override protected AbstractDataExporter createExporter() throws IOException { return new CsvFileExporter(savingFilePath, withBom); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/export/SplitExcelExporter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.export; import java.util.concurrent.Executor; /** * Export multiple excel file * * @author Ponfee */ public class SplitExcelExporter extends AbstractSplitExporter { public SplitExcelExporter(int batchSize, String savingFilePathPrefix, Executor executor) { super(batchSize, savingFilePathPrefix, ".xlsx", executor); } @Override protected AbstractAsyncSplitExporter splitExporter(Table subTable, String savingFilePath) { return new AsnycExcelExporter(subTable, savingFilePath, super.getName()); } private static class AsnycExcelExporter extends AbstractAsyncSplitExporter { final String sheetName; AsnycExcelExporter(Table subTable, String savingFilePath, String sheetName) { super(subTable, savingFilePath); this.sheetName = sheetName; } @Override protected AbstractDataExporter createExporter() { AbstractDataExporter excel = new ExcelExporter(); excel.setName(sheetName); return excel; } @Override protected void complete(AbstractDataExporter exporter) { ((ExcelExporter) exporter).write(savingFilePath); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/export/Table.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.export; import cn.ponfee.commons.tree.FlatNode; import cn.ponfee.commons.tree.PlainNode; import cn.ponfee.commons.tree.TreeNode; import org.apache.commons.collections4.CollectionUtils; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.function.Function; /** * 表格 * * @author Ponfee */ public class Table implements Serializable { private static final long serialVersionUID = 1600567917100486004L; private static final int ROOT_PID = 0; private final List> thead; // 表头 private final Function converter; // 数据转换 private String caption; // 标题 private Object[] tfoot; // 表尾 private String comment; // 注释说明 private Map options; // 其它特殊配置项,如:{HIGHLIGHT:{\"cells\":[[2,15],[2,16]],\"color\":\"#f00\"}} private final LinkedBlockingQueue tbody = new LinkedBlockingQueue<>(); // 表体:不指定容量,默认为Integer.MAX_VALUE,也就是无界队列 private volatile boolean empty = true; private volatile boolean end = false; public Table(List> thead, Function converter, String caption, Object[] tfoot, String comment, Map options) { this.thead = thead; this.converter = converter; this.caption = caption; this.tfoot = tfoot; this.comment = comment; this.options = options; } public Table(List> list) { this(list, null); } public Table(List> list, Function converter) { this.thead = TreeNode. builder(ROOT_PID).build().mount(list).flatCFS(); this.converter = converter; } public Table(String[] names) { this(names, null); } public Table(String[] names, Function converter) { List> list = new ArrayList<>(names.length); for (int i = 0; i < names.length; i++) { list.add(new PlainNode<>(i + 1, ROOT_PID, new Thead(names[i]))); } this.thead = TreeNode. builder(ROOT_PID).build().mount(list).flatCFS(); this.converter = converter; } public Table copyOfWithoutTbody() { return new Table<>(thead, converter, caption, tfoot, comment, options); } public Table copyOfWithoutTbody(Function converter) { return new Table<>(thead, converter, caption, tfoot, comment, options); } public List> getThead() { return thead; } public Function getConverter() { return converter; } public String getCaption() { return caption; } public void setCaption(String caption) { this.caption = caption; } public Object[] getTfoot() { return tfoot; } public void setTfoot(Object[] tfoot) { this.tfoot = tfoot; } public String getComment() { return comment; } public void setComment(String comment) { this.comment = comment; } public Map getOptions() { return options; } public void setOptions(Map options) { this.options = options; } // -----------------------------------------------add row data public void addRowsAndEnd(List rows) { addRows(rows); toEnd(); } public void addRows(List rows) { if (CollectionUtils.isEmpty(rows)) { return; } try { for (E row : rows) { tbody.put(row); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("Put an element to queue failed.", e); } empty = false; } public void addRowAndEnd(E row) { addRow(row); toEnd(); } public void addRow(E row) { if (row == null) { return; } try { tbody.put(row); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("Put an element to queue failed.", e); } empty = false; } // -----------------------------------------------to end operation public synchronized Table toEnd() { this.end = true; return this; } // -----------------------------------------------data exporter use boolean isEnd() { return end && tbody.isEmpty(); } boolean isNotEnd() { return !isEnd(); } boolean isEmptyTbody() { return empty && isEnd(); } E getRow(long timeoutMillis) throws InterruptedException { return tbody.poll(timeoutMillis, TimeUnit.MILLISECONDS); } } ================================================ FILE: src/main/java/cn/ponfee/commons/export/Thead.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.export; import java.io.Serializable; /** * 表头 * * @author Ponfee */ public class Thead implements Serializable { private static final long serialVersionUID = 1898674740598755648L; private final String name; // 列名 private final Tmeta tmeta; // 列配置信息 private final String field; // 字段(对应类的字段) public Thead(String name) { this(name, null, null); } public Thead(String name, Tmeta tmeta, String field) { this.name = name; this.tmeta = tmeta; this.field = field; } public String getName() { return name; } public Tmeta getTmeta() { return tmeta; } public String getField() { return field; } } ================================================ FILE: src/main/java/cn/ponfee/commons/export/Tmeta.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.export; import cn.ponfee.commons.util.Colors; import java.awt.Color; import java.io.Serializable; /** * 每列的元数据配置 * * @author Ponfee */ public class Tmeta implements Serializable { private static final long serialVersionUID = -7653917777812920043L; private Type type = Type.CHAR; private String format; private Align align = Align.LEFT; private boolean nowrap = false; // only use in html [nowrap="nowrap"] private Color color = null; private String colorHex = null; public Tmeta() {} public Tmeta(Type type, String format, Align align, boolean nowrap, String colorHex) { this.type = type; this.format = format; this.align = align; this.nowrap = nowrap; setColor(colorHex); } public Type getType() { return type; } public void setType(Type type) { this.type = type; } public Align getAlign() { return align; } public void setAlign(Align align) { this.align = align; } public boolean isNowrap() { return nowrap; } public void setNowrap(boolean nowrap) { this.nowrap = nowrap; } public void setColor(Color color) { this.color = color; this.colorHex = Colors.toHex(color); } public void setColor(String color) { this.colorHex = color; this.color = Colors.fromHex(color); } public Color getColor() { return color; } public String getColorHex() { return colorHex; } public String getFormat() { return format; } public void setFormat(String format) { this.format = format; } public enum Type { CHAR, NUMERIC, DATETIME } public enum Align { LEFT, CENTER, RIGHT } } ================================================ FILE: src/main/java/cn/ponfee/commons/extract/CsvExtractor.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.extract; import cn.ponfee.commons.io.ByteOrderMarks; import cn.ponfee.commons.io.CharsetDetector; import cn.ponfee.commons.io.PrereadInputStream; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVRecord; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ObjectUtils; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.nio.charset.Charset; import java.util.function.BiConsumer; /** * Csv file data extractor * * @author Ponfee */ public class CsvExtractor extends DataExtractor { private final CSVFormat csvFormat; private final boolean withHeader; private final int startRow; // start with 0 private final Charset charset; protected CsvExtractor(ExtractableDataSource dataSource, String[] headers, CSVFormat csvFormat, int startRow, Charset charset) { super(dataSource, headers); this.withHeader = ArrayUtils.isNotEmpty(headers); this.startRow = startRow; this.charset = charset; CSVFormat.Builder builder = CSVFormat.Builder.create(ObjectUtils.defaultIfNull(csvFormat, CSVFormat.DEFAULT)); if (this.withHeader) { builder.setHeader(headers); } this.csvFormat = builder.build(); } @Override public void extract(BiConsumer processor) throws IOException { PrereadInputStream bris = new PrereadInputStream( super.dataSource.asInputStream(), CharsetDetector.DEFAULT_DETECT_LENGTH ); // 检测文件编码 Charset encoding = this.charset != null ? this.charset : CharsetDetector.detect(bris.heads()); // 检查是否有BOM ByteOrderMarks bom = ByteOrderMarks.of(encoding, bris.heads()); if (bom != null) { bris.skip(bom.length()); } // Use BOMInputStream maybe occur error(dead loop): UTF-16LE, UTF-16BE, try (Reader reader = new InputStreamReader(/*new BOMInputStream(bris)*/bris, encoding)) { int columnSize = this.withHeader ? this.headers.length : 0; Iterable records = this.csvFormat.parse(reader); int i = 0, j, n, start = this.startRow; String[] data; for (CSVRecord record : records) { if (super.end) { break; } if (start > 0) { start--; continue; } if (!this.withHeader && i == 0) { columnSize = record.size(); // 不指定表头,则取第一行数据为表头 } n = record.size(); data = new String[columnSize]; for (j = 0; j < n && j < columnSize; j++) { data[j] = record.get(j); } for (; j < columnSize; j++) { data[j] = null; } if (isNotEmpty(data)) { processor.accept(i++, data); } } } } } ================================================ FILE: src/main/java/cn/ponfee/commons/extract/DataExtractor.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.extract; import cn.ponfee.commons.util.Holder; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import java.io.IOException; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; /** * The file data extractor * * @author Ponfee */ public abstract class DataExtractor { protected final ExtractableDataSource dataSource; protected final String[] headers; protected volatile boolean end = false; protected DataExtractor(ExtractableDataSource dataSource, String[] headers) { this.dataSource = dataSource; this.headers = headers; } public abstract void extract(BiConsumer processor) throws IOException; public final List extract() throws IOException { List list = new LinkedList<>(); this.extract((rowNumber, data) -> list.add(data)); return list; } /** * Extracts specified count of top rows * * @param count the top rows count * @return a list * @throws IOException if occur io error */ public final List extract(int count) throws IOException { List result = new ArrayList<>(count); this.extract((rowNum, row) -> { if (rowNum >= count) { this.end = true; return; } result.add(row); }); return result; } public final void extract(int batchSize, Consumer> action) throws IOException { Holder> holder = Holder.of(new ArrayList<>(batchSize)); this.extract((rowNumber, data) -> { List list = holder.get(); list.add(data); if (list.size() == batchSize) { action.accept(list); holder.set(new ArrayList<>(batchSize)); } }); if (CollectionUtils.isNotEmpty(holder.get())) { action.accept(holder.get()); } } /** * 验证 * * @param validator * @return * @throws IOException */ public final ValidateResult verify(BiFunction validator) throws IOException { ValidateResult result = new ValidateResult(); this.extract((rowNumber, data) -> { String error = validator.apply(rowNumber, data); if (StringUtils.isBlank(error)) { result.addData(data); } else { result.addError( new StringBuilder(error.length() + 12) .append("第[").append(rowNumber + 1) // rowNumber start 0 .append("]行错误:").append(error).toString() ); } }); return result; } // ---------------------------------------------------------------------------protected methods protected boolean isNotEmpty(String[] data) { if (data == null || data.length == 0) { return false; } for (String str : data) { if (StringUtils.isNotBlank(str)) { return true; } } return false; } } ================================================ FILE: src/main/java/cn/ponfee/commons/extract/DataExtractorBuilder.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.extract; import cn.ponfee.commons.extract.ExcelExtractor.ExcelType; import cn.ponfee.commons.extract.streaming.StreamingExcelExtractor; import cn.ponfee.commons.http.ContentType; import com.google.common.collect.ImmutableList; import org.apache.commons.csv.CSVFormat; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.EnumUtils; import java.io.File; import java.io.InputStream; import java.nio.charset.Charset; import java.util.List; /** * The data extractor builder, facade operator * * @author Ponfee */ public class DataExtractorBuilder { private static final List EXCEL_EXTENSION = ImmutableList.of("xlsx", "xls"); private static final List CSV_EXTENSION = ImmutableList.of("csv", "log", "txt"); private final Object dataSource; // only support such type as File, InputStream private final String fileName; private final String contentType; private String[] headers; private int startRow = 0; // start with 0 // ------------------------------------------excel config private int sheetIndex = 0; // excel work book sheet index: start with 0 private boolean streaming = true; // excel whether streaming read, default true // ------------------------------------------csv config private CSVFormat csvFormat; // csv format private Charset charset; // csv file encoding private DataExtractorBuilder(Object dataSource, String fileName, String contentType) { this.dataSource = dataSource; this.fileName = fileName; this.contentType = contentType; } public static DataExtractorBuilder newBuilder(InputStream dataSource, String fileName, String contentType) { return new DataExtractorBuilder(dataSource, fileName, contentType); } public static DataExtractorBuilder newBuilder(String path) { return newBuilder(new File(path)); } public static DataExtractorBuilder newBuilder(File dataSource) { String fileName = dataSource.getName(); return new DataExtractorBuilder( dataSource, fileName, FilenameUtils.getExtension(fileName) ); } public DataExtractorBuilder headers(String[] headers) { this.headers = headers; return this; } public DataExtractorBuilder startRow(int startRow) { this.startRow = startRow; return this; } public DataExtractorBuilder sheetIndex(int sheetIndex) { this.sheetIndex = sheetIndex; return this; } public DataExtractorBuilder streaming(boolean streaming) { this.streaming = streaming; return this; } public DataExtractorBuilder csvFormat(CSVFormat csvFormat) { this.csvFormat = csvFormat; return this; } public DataExtractorBuilder charset(Charset charset) { this.charset = charset; return this; } public DataExtractor build() { String extension = FilenameUtils.getExtension(fileName).toLowerCase(); if ( ContentType.TEXT_PLAIN.value().equalsIgnoreCase(contentType) || CSV_EXTENSION.contains(extension) ) { // csv, txt文本格式数据 ExtractableDataSource ds = new ExtractableDataSource(dataSource); return new CsvExtractor(ds, headers, csvFormat, startRow, charset); } else if (EXCEL_EXTENSION.contains(extension)) { // Content-Type // xlsx: application/vnd.openxmlformats-officedocument.wordprocessingml.document // application/vnd.openxmlformats-officedocument.spreadsheetml.sheet // // xls: application/vnd.ms-excel // application/x-xls ExtractableDataSource ds = new ExtractableDataSource(dataSource); ExcelType type = EnumUtils.getEnumIgnoreCase(ExcelType.class, extension); return streaming ? new StreamingExcelExtractor(ds, headers, startRow, type, sheetIndex) : new ExcelExtractor(ds, headers, startRow, type, sheetIndex); } else { throw new RuntimeException("File content type not supported: " + fileName); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/extract/ExcelExtractor.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.extract; import cn.ponfee.commons.date.Dates; import org.apache.commons.lang3.ArrayUtils; import org.apache.poi.ss.usermodel.*; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.Iterator; import java.util.function.BiConsumer; import static org.apache.poi.ss.usermodel.Row.MissingCellPolicy.RETURN_NULL_AND_BLANK; /** * Excel file data extractor * * xlsx big file import * * @author Ponfee */ public class ExcelExtractor extends DataExtractor { protected final ExcelType type; protected final int sheetIndex; // start with 0 private final int startRow; // start with 0 protected ExcelExtractor(ExtractableDataSource dataSource, String[] headers, int startRow, ExcelType type, int sheetIndex) { super(dataSource, headers); this.startRow = startRow; this.type = type; this.sheetIndex = sheetIndex; } @Override public final void extract(BiConsumer processor) throws IOException { try (ExtractableDataSource ds = dataSource; Workbook workbook = createWorkbook(ds)) { extract(workbook, processor); } } protected Workbook createWorkbook(ExtractableDataSource dataSource) throws IOException { Object ds = dataSource.getDataSource(); if (ds instanceof File) { return WorkbookFactory.create((File) ds); } else { return WorkbookFactory.create((InputStream) ds); } } private void extract(Workbook workbook, BiConsumer processor) { boolean specHeaders; int columnSize; if (ArrayUtils.isNotEmpty(headers)) { specHeaders = true; columnSize = this.headers.length; } else { specHeaders = false; columnSize = 0; } Row row; String[] data; // sheet.getPhysicalNumberOfRows() Iterator iter = workbook.getSheetAt(sheetIndex).iterator(); for (int i = 0, k = 0, m, j; iter.hasNext(); i++) { if (super.end) { break; } row = iter.next(); // row = sheet.getRow(i); if (row == null || i < startRow) { continue; } if (!specHeaders && i == startRow) { columnSize = row.getLastCellNum(); // 不指定表头则以开始行为表头 } data = new String[columnSize]; for (m = row.getLastCellNum(), j = 0; j <= m && j < columnSize; j++) { // Missing cells are returned as null, Blank cells are returned as normal data[j] = getStringCellValue(row.getCell(j, RETURN_NULL_AND_BLANK)); } for (; j < columnSize; j++) { data[j] = null; // padding } if (isNotEmpty(data)) { processor.accept(k++, data); } } } /** * 获取单元格的值 * * @param cell * @return */ private static String getStringCellValue(Cell cell) { if (cell == null) { return null; } switch (cell.getCellType()) { case NUMERIC: return getNumericAsString(cell); case FORMULA: try { return getNumericAsString(cell); } catch (Exception e) { return cell.getRichStringCellValue().getString(); } case STRING: return cell.getStringCellValue(); case BOOLEAN: return Boolean.toString(cell.getBooleanCellValue()); case ERROR: // 错误 return "Error: " + cell.getErrorCellValue(); default: return cell.getRichStringCellValue().getString(); } } private static String getNumericAsString(Cell cell) { return (DateUtil.isCellDateFormatted(cell) || DateUtil.isCellInternalDateFormatted(cell)) ? Dates.format(cell.getDateCellValue()) : String.valueOf(cell.getNumericCellValue()); } public enum ExcelType { XLS, XLSX } } ================================================ FILE: src/main/java/cn/ponfee/commons/extract/ExtractableDataSource.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.extract; import javax.annotation.Nonnull; import java.io.*; /** * The extractable DataSource is a Inputstream or File * * @author Ponfee */ public class ExtractableDataSource implements Closeable { private final Object dataSource; public ExtractableDataSource(@Nonnull Object dataSource) { if (!(dataSource instanceof InputStream || dataSource instanceof File)) { throw new IllegalArgumentException( "Invalid datasource '" + dataSource.getClass().getName() + "', only support File or InputStream." ); } this.dataSource = dataSource; } @Override public void close() throws IOException { if (dataSource instanceof InputStream) { ((InputStream) dataSource).close(); } } public Object getDataSource() { return dataSource; } public InputStream asInputStream() throws IOException { return dataSource instanceof File ? new FileInputStream((File) dataSource) : (InputStream) dataSource; } } ================================================ FILE: src/main/java/cn/ponfee/commons/extract/ValidateResult.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.extract; import cn.ponfee.commons.io.Files; import com.google.common.collect.Lists; import org.apache.commons.lang3.StringUtils; import java.util.List; /** * Validate result * * @author Ponfee */ public class ValidateResult { private final List data = Lists.newLinkedList(); private final List errors = Lists.newLinkedList(); public boolean hasErrors() { return !errors.isEmpty(); } public boolean isEmpty() { return data.isEmpty(); } public String getErrorsAsString() { return StringUtils.join(errors, Files.UNIX_LINE_SEPARATOR); } public List getData() { return data; } public List getErrors() { return errors; } public void addData(String[] obj) { this.data.add(obj); } public void addError(String error) { this.errors.add(error); } } ================================================ FILE: src/main/java/cn/ponfee/commons/extract/streaming/StreamingExcelExtractor.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.extract.streaming; import cn.ponfee.commons.extract.ExcelExtractor; import cn.ponfee.commons.extract.ExtractableDataSource; import cn.ponfee.commons.extract.streaming.xls.HSSFStreamingReader; import com.monitorjbl.xlsx.StreamingReader; import org.apache.poi.ss.usermodel.Workbook; import java.io.File; import java.io.InputStream; import java.util.concurrent.Executor; /** * Excel file data extractor based streaming * * 在打开一本工作簿时,不管是一个.xls HSSFWorkbook,还是一个.xlsx XSSFWorkbook, * 工作簿都可以从文件或InputStream中加载。使用File对象可以降低内存消耗, * 而InputStream则需要更多的内存,因为它必须缓冲整个文件。 * * // Use a file * Workbook wb = WorkbookFactory.create(new File("MyExcel.xls")); * * // Use an InputStream, needs more memory * Workbook wb = WorkbookFactory.create(new FileInputStream("MyExcel.xlsx")); * * * ======================================================================= * // HSSFWorkbook, File * NPOIFSFileSystem fs = new NPOIFSFileSystem(new File("file.xls")); * HSSFWorkbook wb = new HSSFWorkbook(fs.getRoot(), true); * .... * fs.close(); * * // HSSFWorkbook, InputStream, needs more memory * NPOIFSFileSystem fs = new NPOIFSFileSystem(myInputStream); * HSSFWorkbook wb = new HSSFWorkbook(fs.getRoot(), true); * * * ======================================================================= * // XSSFWorkbook, File * OPCPackage pkg = OPCPackage.open(new File("file.xlsx")); * XSSFWorkbook wb = new XSSFWorkbook(pkg); * .... * pkg.close(); * * // XSSFWorkbook, InputStream, needs more memory * OPCPackage pkg = OPCPackage.open(myInputStream); * XSSFWorkbook wb = new XSSFWorkbook(pkg); * .... * pkg.close(); * * * https://blog.csdn.net/zl_momomo/article/details/80703533 * http://poi.apache.org/components/spreadsheet/how-to.html * https://github.com/monitorjbl/excel-streaming-reader * * * https://github.com/alibaba/easyexcel * https://www.jianshu.com/p/cb9cd9965a63 * * @author Ponfee */ public class StreamingExcelExtractor extends ExcelExtractor { private final Executor executor; public StreamingExcelExtractor(ExtractableDataSource dataSource, String[] headers, int startRow, ExcelType type) { this(dataSource, headers, startRow, type, 0, null); } public StreamingExcelExtractor(ExtractableDataSource dataSource, String[] headers, int startRow, ExcelType type, int sheetIndex) { this(dataSource, headers, startRow, type, sheetIndex, null); } public StreamingExcelExtractor(ExtractableDataSource dataSource, String[] headers, int startRow, ExcelType type, int sheetIndex, Executor executor) { super(dataSource, headers, startRow, type, sheetIndex); this.executor = executor; } @Override protected Workbook createWorkbook(ExtractableDataSource dataSource) { Object ds = dataSource.getDataSource(); switch (type) { case XLS: HSSFStreamingReader reader = HSSFStreamingReader.create(200, sheetIndex); return ds instanceof File ? reader.open((File) ds, executor) : reader.open((InputStream) ds, executor); case XLSX: // only support xlsx StreamingReader.Builder builder = StreamingReader.builder() .rowCacheSize(50) // 缓存到内存中的行数,默认是10 .bufferSize(4096); // 读取资源时,缓存到内存的字节大小,默认是1024 return ds instanceof File ? builder.open((File) ds) : builder.open((InputStream) ds); default: throw new RuntimeException("Unknown excel type: " + type); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/extract/streaming/xls/HSSFStreamingCell.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.extract.streaming.xls; import org.apache.poi.ss.formula.FormulaParseException; import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.util.CellAddress; import org.apache.poi.ss.util.CellRangeAddress; import java.time.LocalDateTime; import java.util.Calendar; import java.util.Date; /** * The version for 2003 or early XSL excel file streaming reader excel cell * * @author Ponfee */ public class HSSFStreamingCell implements Cell { private final String value; public HSSFStreamingCell(String value) { this.value = value; } @Override public String getStringCellValue() { return this.value; } @Override public CellType getCellType() { return CellType.STRING; } // ----------------------------------------------unsupported operation @Override @Deprecated public int getColumnIndex() { throw new UnsupportedOperationException(); } @Override @Deprecated public int getRowIndex() { throw new UnsupportedOperationException(); } @Override @Deprecated public Sheet getSheet() { throw new UnsupportedOperationException(); } @Override @Deprecated public Row getRow() { throw new UnsupportedOperationException(); } @Override @Deprecated public void setCellType(CellType cellType) { throw new UnsupportedOperationException(); } @Override @Deprecated public CellType getCachedFormulaResultType() { throw new UnsupportedOperationException(); } @Override @Deprecated public void setCellValue(double value) { throw new UnsupportedOperationException(); } @Override @Deprecated public void setCellValue(Date value) { throw new UnsupportedOperationException(); } @Override @Deprecated public void setCellValue(Calendar value) { throw new UnsupportedOperationException(); } @Override @Deprecated public void setCellValue(RichTextString value) { throw new UnsupportedOperationException(); } @Override @Deprecated public void setCellValue(String value) { throw new UnsupportedOperationException(); } @Override @Deprecated public void setCellFormula(String formula) throws FormulaParseException { throw new UnsupportedOperationException(); } @Override @Deprecated public String getCellFormula() { throw new UnsupportedOperationException(); } @Override @Deprecated public double getNumericCellValue() { throw new UnsupportedOperationException(); } @Override @Deprecated public Date getDateCellValue() { throw new UnsupportedOperationException(); } @Override @Deprecated public RichTextString getRichStringCellValue() { throw new UnsupportedOperationException(); } @Override @Deprecated public void setCellValue(boolean value) { throw new UnsupportedOperationException(); } @Override @Deprecated public void setCellErrorValue(byte value) { throw new UnsupportedOperationException(); } @Override @Deprecated public boolean getBooleanCellValue() { throw new UnsupportedOperationException(); } @Override @Deprecated public byte getErrorCellValue() { throw new UnsupportedOperationException(); } @Override @Deprecated public void setCellStyle(CellStyle style) { throw new UnsupportedOperationException(); } @Override @Deprecated public CellStyle getCellStyle() { throw new UnsupportedOperationException(); } @Override @Deprecated public void setAsActiveCell() { throw new UnsupportedOperationException(); } @Override @Deprecated public CellAddress getAddress() { throw new UnsupportedOperationException(); } @Override @Deprecated public void setCellComment(Comment comment) { throw new UnsupportedOperationException(); } @Override @Deprecated public Comment getCellComment() { throw new UnsupportedOperationException(); } @Override @Deprecated public void removeCellComment() { throw new UnsupportedOperationException(); } @Override @Deprecated public Hyperlink getHyperlink() { throw new UnsupportedOperationException(); } @Override @Deprecated public void setHyperlink(Hyperlink link) { throw new UnsupportedOperationException(); } @Override @Deprecated public void removeHyperlink() { throw new UnsupportedOperationException(); } @Override @Deprecated public CellRangeAddress getArrayFormulaRange() { throw new UnsupportedOperationException(); } @Override @Deprecated public boolean isPartOfArrayFormulaGroup() { throw new UnsupportedOperationException(); } @Override @Deprecated public void removeFormula() throws IllegalStateException { throw new UnsupportedOperationException(); } @Override @Deprecated public void setBlank() { throw new UnsupportedOperationException(); } @Override @Deprecated public void setCellValue(LocalDateTime value) { throw new UnsupportedOperationException(); } @Override @Deprecated public LocalDateTime getLocalDateTimeCellValue() { throw new UnsupportedOperationException(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/extract/streaming/xls/HSSFStreamingReader.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.extract.streaming.xls; import com.google.common.base.Preconditions; import org.apache.commons.lang3.ArrayUtils; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.util.concurrent.Executor; /** * The version for 2003 or early XSL excel file * streaming reader * * excel reader * * @author Ponfee */ public class HSSFStreamingReader { private int rowCacheSize = 0; // if less 0 then 0(SynchronousQueue) private int[] sheetIndexs; private String[] sheetNames; private HSSFStreamingReader() {} /** * Reads all sheet * * @return HSSFStreamingReader instance */ public static HSSFStreamingReader create() { return new HSSFStreamingReader(); } /** * Reads spec sheet that is in sheet index at int array * * @param rowCacheSize the size of read row count in memory * @param sheetIndexs sheet index at int array * @return HSSFStreamingReader instance */ public static HSSFStreamingReader create(int rowCacheSize, int... sheetIndexs) { Preconditions.checkArgument(rowCacheSize > 0); Preconditions.checkArgument(ArrayUtils.isNotEmpty(sheetIndexs)); HSSFStreamingReader reader = new HSSFStreamingReader(); reader.rowCacheSize = rowCacheSize; reader.sheetIndexs = sheetIndexs; return reader; } /** * Reads spec sheet that is in sheet name at string array * * @param rowCacheSize the size of read row count in memory * @return HSSFStreamingReader instance */ public static HSSFStreamingReader create(int rowCacheSize, String... sheetNames) { Preconditions.checkArgument(rowCacheSize > 0); Preconditions.checkArgument(ArrayUtils.isNotEmpty(sheetNames)); HSSFStreamingReader reader = new HSSFStreamingReader(); reader.rowCacheSize = rowCacheSize; reader.sheetNames = sheetNames; return reader; } public HSSFStreamingWorkbook open(InputStream input, Executor executor) { return new HSSFStreamingWorkbook( input, rowCacheSize, sheetIndexs, sheetNames, executor ); } public HSSFStreamingWorkbook open(File file, Executor executor) { try { return open(new FileInputStream(file), executor); } catch (FileNotFoundException e) { throw new RuntimeException(e); } } public HSSFStreamingWorkbook open(String filePath, Executor executor) { return open(new File(filePath), executor); } } ================================================ FILE: src/main/java/cn/ponfee/commons/extract/streaming/xls/HSSFStreamingRow.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.extract.streaming.xls; import cn.ponfee.commons.collect.Collects; import org.apache.poi.ss.usermodel.*; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * The version for 2003 or early XSL excel file * streaming reader * * excel row * * @author Ponfee */ public class HSSFStreamingRow implements Row { private final List cells = new ArrayList<>(); private final int rowNum; // excel row number private final int rowOrder; // excel row order public HSSFStreamingRow(int rowNum, int rowOrder) { this.rowNum = rowNum; this.rowOrder = rowOrder; } @Override public Iterator iterator() { return this.cells.iterator(); } @Override public void removeCell(Cell cell) { this.cells.remove(cell); } @Override public int getRowNum() { return this.rowNum; } public int getRowOrder() { return this.rowOrder; } @Override public Cell getCell(int cellnum) { return this.cells.get(cellnum); } @Override public Cell getCell(int cellnum, MissingCellPolicy policy) { return getCell(cellnum); } @Override public short getLastCellNum() { return (short) (cells.size() - 1); } @Override public int getPhysicalNumberOfCells() { return this.cells.size(); } @Override public Iterator cellIterator() { return iterator(); } public void putCell(int index, Cell cell) { Collects.set(this.cells, index, cell); } public boolean isEmpty() { return this.cells.isEmpty(); } // ----------------------------------------------unsupported operation @Override public Sheet getSheet() { throw new UnsupportedOperationException(); } @Override public Cell createCell(int column) { throw new UnsupportedOperationException(); } @Override public Cell createCell(int column, CellType type) { throw new UnsupportedOperationException(); } @Override public void setRowNum(int rowNum) { throw new UnsupportedOperationException(); } @Override public short getFirstCellNum() { throw new UnsupportedOperationException(); } @Override public void setHeight(short height) { throw new UnsupportedOperationException(); } @Override public void setZeroHeight(boolean zHeight) { throw new UnsupportedOperationException(); } @Override public boolean getZeroHeight() { throw new UnsupportedOperationException(); } @Override public void setHeightInPoints(float height) { throw new UnsupportedOperationException(); } @Override public short getHeight() { throw new UnsupportedOperationException(); } @Override public float getHeightInPoints() { throw new UnsupportedOperationException(); } @Override public boolean isFormatted() { throw new UnsupportedOperationException(); } @Override public CellStyle getRowStyle() { throw new UnsupportedOperationException(); } @Override public void setRowStyle(CellStyle style) { throw new UnsupportedOperationException(); } @Override public int getOutlineLevel() { throw new UnsupportedOperationException(); } @Override public void shiftCellsRight(int firstShiftColumnIndex, int lastShiftColumnIndex, int step) { throw new UnsupportedOperationException(); } @Override public void shiftCellsLeft(int firstShiftColumnIndex, int lastShiftColumnIndex, int step) { throw new UnsupportedOperationException(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/extract/streaming/xls/HSSFStreamingSheet.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.extract.streaming.xls; import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.util.CellAddress; import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.ss.util.PaneInformation; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.TimeUnit; import static cn.ponfee.commons.extract.streaming.xls.HSSFStreamingWorkbook.AWAIT_MILLIS; /** * The version for 2003 or early XSL excel file * streaming reader * * excel sheet * * @author Ponfee */ public class HSSFStreamingSheet implements Sheet { private final int index; private final String name; private final boolean discard; private final BlockingQueue rows; private final RowsIterator iterator; private volatile boolean end = false; public HSSFStreamingSheet(int index, String name, boolean discard, int rowCacheSize) { this.index = index; this.name = name; this.discard = discard; if (discard) { this.iterator = null; this.rows = null; toEnd(); } else { this.iterator = new RowsIterator(this); this.rows = rowCacheSize > 0 ? new LinkedBlockingQueue<>(rowCacheSize) : new SynchronousQueue<>(); // new LinkedBlockingQueue<>(); } } @Override public Iterator iterator() { return this.iterator; } @Override public Iterator rowIterator() { return this.iterator; } @Override public String getSheetName() { return this.name; } public int getSheetIndex() { return this.index; } public boolean isDiscard() { return discard; } public int getCacheRowCount() { return rows == null ? 0 : rows.size(); } void toEnd() { end = true; } private boolean isEnd() { return end && rows.isEmpty(); } void putRow(Row row) throws InterruptedException { this.rows.put(row); } private static class RowsIterator implements Iterator { final HSSFStreamingSheet sheet; Row currentRow; RowsIterator(HSSFStreamingSheet sheet) { this.sheet = sheet; } @Override public boolean hasNext() { try { while (!sheet.isEnd()) { if ( currentRow == null && (currentRow = sheet.rows.poll(AWAIT_MILLIS, TimeUnit.MILLISECONDS)) != null ) { return true; } } return false; } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } } @Override public Row next() { Row res = currentRow; currentRow = null; return res; } } // ----------------------------------------------unsupported operation @Override public Workbook getWorkbook() { throw new UnsupportedOperationException(); } @Override public Row createRow(int rownum) { throw new UnsupportedOperationException(); } @Override public void removeRow(Row row) { throw new UnsupportedOperationException(); } @Override public Row getRow(int rownum) { throw new UnsupportedOperationException(); } @Override public int getPhysicalNumberOfRows() { throw new UnsupportedOperationException(); } @Override public int getFirstRowNum() { throw new UnsupportedOperationException(); } @Override public int getLastRowNum() { throw new UnsupportedOperationException(); } @Override public void setColumnHidden(int columnIndex, boolean hidden) { throw new UnsupportedOperationException(); } @Override public boolean isColumnHidden(int columnIndex) { throw new UnsupportedOperationException(); } @Override public void setRightToLeft(boolean value) { throw new UnsupportedOperationException(); } @Override public boolean isRightToLeft() { throw new UnsupportedOperationException(); } @Override public void setColumnWidth(int columnIndex, int width) { throw new UnsupportedOperationException(); } @Override public int getColumnWidth(int columnIndex) { throw new UnsupportedOperationException(); } @Override public float getColumnWidthInPixels(int columnIndex) { throw new UnsupportedOperationException(); } @Override public void setDefaultColumnWidth(int width) { throw new UnsupportedOperationException(); } @Override public int getDefaultColumnWidth() { throw new UnsupportedOperationException(); } @Override public short getDefaultRowHeight() { throw new UnsupportedOperationException(); } @Override public float getDefaultRowHeightInPoints() { throw new UnsupportedOperationException(); } @Override public void setDefaultRowHeight(short height) { throw new UnsupportedOperationException(); } @Override public void setDefaultRowHeightInPoints(float height) { throw new UnsupportedOperationException(); } @Override public CellStyle getColumnStyle(int column) { throw new UnsupportedOperationException(); } @Override public int addMergedRegion(CellRangeAddress region) { throw new UnsupportedOperationException(); } @Override public int addMergedRegionUnsafe(CellRangeAddress region) { throw new UnsupportedOperationException(); } @Override public void validateMergedRegions() { throw new UnsupportedOperationException(); } @Override public void setVerticallyCenter(boolean value) { throw new UnsupportedOperationException(); } @Override public void setHorizontallyCenter(boolean value) { throw new UnsupportedOperationException(); } @Override public boolean getHorizontallyCenter() { throw new UnsupportedOperationException(); } @Override public boolean getVerticallyCenter() { throw new UnsupportedOperationException(); } @Override public void removeMergedRegion(int index) { throw new UnsupportedOperationException(); } @Override public void removeMergedRegions(Collection indices) { throw new UnsupportedOperationException(); } @Override public int getNumMergedRegions() { throw new UnsupportedOperationException(); } @Override public CellRangeAddress getMergedRegion(int index) { throw new UnsupportedOperationException(); } @Override public List getMergedRegions() { throw new UnsupportedOperationException(); } @Override public void setForceFormulaRecalculation(boolean value) { throw new UnsupportedOperationException(); } @Override public boolean getForceFormulaRecalculation() { throw new UnsupportedOperationException(); } @Override public void setAutobreaks(boolean value) { throw new UnsupportedOperationException(); } @Override public void setDisplayGuts(boolean value) { throw new UnsupportedOperationException(); } @Override public void setDisplayZeros(boolean value) { throw new UnsupportedOperationException(); } @Override public boolean isDisplayZeros() { throw new UnsupportedOperationException(); } @Override public void setFitToPage(boolean value) { throw new UnsupportedOperationException(); } @Override public void setRowSumsBelow(boolean value) { throw new UnsupportedOperationException(); } @Override public void setRowSumsRight(boolean value) { throw new UnsupportedOperationException(); } @Override public boolean getAutobreaks() { throw new UnsupportedOperationException(); } @Override public boolean getDisplayGuts() { throw new UnsupportedOperationException(); } @Override public boolean getFitToPage() { throw new UnsupportedOperationException(); } @Override public boolean getRowSumsBelow() { throw new UnsupportedOperationException(); } @Override public boolean getRowSumsRight() { throw new UnsupportedOperationException(); } @Override public boolean isPrintGridlines() { throw new UnsupportedOperationException(); } @Override public void setPrintGridlines(boolean show) { throw new UnsupportedOperationException(); } @Override public boolean isPrintRowAndColumnHeadings() { throw new UnsupportedOperationException(); } @Override public void setPrintRowAndColumnHeadings(boolean show) { throw new UnsupportedOperationException(); } @Override public PrintSetup getPrintSetup() { throw new UnsupportedOperationException(); } @Override public Header getHeader() { throw new UnsupportedOperationException(); } @Override public Footer getFooter() { throw new UnsupportedOperationException(); } @Override public void setSelected(boolean value) { throw new UnsupportedOperationException(); } @Override public double getMargin(short margin) { throw new UnsupportedOperationException(); } @Override public double getMargin(PageMargin margin) { throw new UnsupportedOperationException(); } @Override public void setMargin(short margin, double size) { throw new UnsupportedOperationException(); } @Override public void setMargin(PageMargin margin, double size) { throw new UnsupportedOperationException(); } @Override public boolean getProtect() { throw new UnsupportedOperationException(); } @Override public void protectSheet(String password) { throw new UnsupportedOperationException(); } @Override public boolean getScenarioProtect() { throw new UnsupportedOperationException(); } @Override public void setZoom(int scale) { throw new UnsupportedOperationException(); } @Override public short getTopRow() { throw new UnsupportedOperationException(); } @Override public short getLeftCol() { throw new UnsupportedOperationException(); } @Override public void showInPane(int toprow, int leftcol) { throw new UnsupportedOperationException(); } @Override public void shiftRows(int startRow, int endRow, int n) { throw new UnsupportedOperationException(); } @Override public void shiftRows(int startRow, int endRow, int n, boolean copyRowHeight, boolean resetOriginalRowHeight) { throw new UnsupportedOperationException(); } @Override public void shiftColumns(int startColumn, int endColumn, int n) { throw new UnsupportedOperationException(); } @Override public void createFreezePane(int colSplit, int rowSplit, int leftmostColumn, int topRow) { throw new UnsupportedOperationException(); } @Override public void createFreezePane(int colSplit, int rowSplit) { throw new UnsupportedOperationException(); } @Override public void createSplitPane(int xSplitPos, int ySplitPos, int leftmostColumn, int topRow, int activePane) { throw new UnsupportedOperationException(); } @Override public void createSplitPane(int xSplitPos, int ySplitPos, int leftmostColumn, int topRow, PaneType activePane) { throw new UnsupportedOperationException(); } @Override public PaneInformation getPaneInformation() { throw new UnsupportedOperationException(); } @Override public void setDisplayGridlines(boolean show) { throw new UnsupportedOperationException(); } @Override public boolean isDisplayGridlines() { throw new UnsupportedOperationException(); } @Override public void setDisplayFormulas(boolean show) { throw new UnsupportedOperationException(); } @Override public boolean isDisplayFormulas() { throw new UnsupportedOperationException(); } @Override public void setDisplayRowColHeadings(boolean show) { throw new UnsupportedOperationException(); } @Override public boolean isDisplayRowColHeadings() { throw new UnsupportedOperationException(); } @Override public void setRowBreak(int row) { throw new UnsupportedOperationException(); } @Override public boolean isRowBroken(int row) { throw new UnsupportedOperationException(); } @Override public void removeRowBreak(int row) { throw new UnsupportedOperationException(); } @Override public int[] getRowBreaks() { throw new UnsupportedOperationException(); } @Override public int[] getColumnBreaks() { throw new UnsupportedOperationException(); } @Override public void setColumnBreak(int column) { throw new UnsupportedOperationException(); } @Override public boolean isColumnBroken(int column) { throw new UnsupportedOperationException(); } @Override public void removeColumnBreak(int column) { throw new UnsupportedOperationException(); } @Override public void setColumnGroupCollapsed(int columnNumber, boolean collapsed) { throw new UnsupportedOperationException(); } @Override public void groupColumn(int fromColumn, int toColumn) { throw new UnsupportedOperationException(); } @Override public void ungroupColumn(int fromColumn, int toColumn) { throw new UnsupportedOperationException(); } @Override public void groupRow(int fromRow, int toRow) { throw new UnsupportedOperationException(); } @Override public void ungroupRow(int fromRow, int toRow) { throw new UnsupportedOperationException(); } @Override public void setRowGroupCollapsed(int row, boolean collapse) { throw new UnsupportedOperationException(); } @Override public void setDefaultColumnStyle(int column, CellStyle style) { throw new UnsupportedOperationException(); } @Override public void autoSizeColumn(int column) { throw new UnsupportedOperationException(); } @Override public void autoSizeColumn(int column, boolean useMergedCells) { throw new UnsupportedOperationException(); } @Override public Comment getCellComment(CellAddress ref) { throw new UnsupportedOperationException(); } @Override public Map getCellComments() { throw new UnsupportedOperationException(); } @Override public Drawing getDrawingPatriarch() { throw new UnsupportedOperationException(); } @Override public Drawing createDrawingPatriarch() { throw new UnsupportedOperationException(); } @Override public boolean isSelected() { throw new UnsupportedOperationException(); } @Override public CellRange setArrayFormula(String formula, CellRangeAddress range) { throw new UnsupportedOperationException(); } @Override public CellRange removeArrayFormula(Cell cell) { throw new UnsupportedOperationException(); } @Override public DataValidationHelper getDataValidationHelper() { throw new UnsupportedOperationException(); } @Override public List getDataValidations() { throw new UnsupportedOperationException(); } @Override public void addValidationData(DataValidation dataValidation) { throw new UnsupportedOperationException(); } @Override public AutoFilter setAutoFilter(CellRangeAddress range) { throw new UnsupportedOperationException(); } @Override public SheetConditionalFormatting getSheetConditionalFormatting() { throw new UnsupportedOperationException(); } @Override public CellRangeAddress getRepeatingRows() { throw new UnsupportedOperationException(); } @Override public CellRangeAddress getRepeatingColumns() { throw new UnsupportedOperationException(); } @Override public void setRepeatingRows(CellRangeAddress rowRangeRef) { throw new UnsupportedOperationException(); } @Override public void setRepeatingColumns(CellRangeAddress columnRangeRef) { throw new UnsupportedOperationException(); } @Override public int getColumnOutlineLevel(int columnIndex) { throw new UnsupportedOperationException(); } @Override public Hyperlink getHyperlink(int row, int column) { throw new UnsupportedOperationException(); } @Override public Hyperlink getHyperlink(CellAddress addr) { throw new UnsupportedOperationException(); } @Override public List getHyperlinkList() { throw new UnsupportedOperationException(); } @Override public CellAddress getActiveCell() { throw new UnsupportedOperationException(); } @Override public void setActiveCell(CellAddress address) { throw new UnsupportedOperationException(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/extract/streaming/xls/HSSFStreamingWorkbook.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.extract.streaming.xls; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.poi.hssf.eventusermodel.*; import org.apache.poi.hssf.record.*; import org.apache.poi.poifs.filesystem.DocumentInputStream; import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.ss.SpreadsheetVersion; import org.apache.poi.ss.formula.EvaluationWorkbook; import org.apache.poi.ss.formula.udf.UDFFinder; import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.usermodel.Row.MissingCellPolicy; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.Executor; /** * The version for 2003 or early XSL excel file streaming reader excel workbook * * @author Ponfee */ public class HSSFStreamingWorkbook implements Workbook, Closeable { public static final int AWAIT_MILLIS = 47; private volatile boolean allSheetReadied = false; private final List sheets = new ArrayList<>(); public HSSFStreamingWorkbook(InputStream input, int rowCacheSize, int[] sheetIndexs, String[] sheetNames, Executor executor) { executor.execute(new AsyncHSSFReader( rowCacheSize, sheetIndexs, sheetNames, input )); } @Override public Iterator iterator() { awaitReadAllSheet(); return sheets.iterator(); } @Override public Iterator sheetIterator() { awaitReadAllSheet(); return iterator(); } @Override public String getSheetName(int sheet) { awaitReadAllSheet(); return sheets.get(sheet).getSheetName(); } @Override public int getSheetIndex(String name) { awaitReadAllSheet(); for (int i = 0; i < sheets.size(); i++) { if (sheets.get(i).getSheetName().equals(name)) { return i; } } return -1; } @Override public int getSheetIndex(Sheet sheet) { awaitReadAllSheet(); for (int i = 0; i < sheets.size(); i++) { if (sheets.get(i) == sheet) { return i; } } return -1; } @Override public int getNumberOfSheets() { awaitReadAllSheet(); return sheets.size(); } @Override public Sheet getSheetAt(int index) { awaitReadAllSheet(); return sheets.size() > index ? sheets.get(index) : null; } @Override public Sheet getSheet(String name) { awaitReadAllSheet(); for (Sheet sheet : sheets) { if (sheet.getSheetName().equals(name)) { return sheet; } } return null; } @Override public void close() { // do nothing } private void awaitReadAllSheet() { try { while (!allSheetReadied) { Thread.sleep(AWAIT_MILLIS); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } } /** * Runs in alone thread */ private class AsyncHSSFReader implements HSSFListener, Runnable { private final InputStream input; private final int rowCacheSize; private final int[] sheetIndexs; private final String[] sheetNames; private int currentSheetIndex = -1; // start with 0 private HSSFStreamingSheet currentSheet; private int currentRowNumber = -1; // start with 0 private int currentRowOrder = -1; // start with 0 private HSSFStreamingRow currentRow; private SSTRecord sstrec; private FormatTrackingHSSFListener formatListener; private AsyncHSSFReader(int rowCacheSize, int[] sheetIndexs, String[] sheetNames, InputStream input) { this.rowCacheSize = rowCacheSize; this.sheetIndexs = sheetIndexs; this.sheetNames = sheetNames; this.input = input; } @Override public void run() { try (InputStream steam = input; POIFSFileSystem poi = new POIFSFileSystem(steam); DocumentInputStream doc = poi.createDocumentInputStream("Workbook") ) { HSSFRequest request = new HSSFRequest(); formatListener = new FormatTrackingHSSFListener( new MissingRecordAwareHSSFListener(this) ); request.addListenerForAllRecords(formatListener); new HSSFEventFactory().processEvents(request, doc); } catch (IOException e) { throw new RuntimeException(e); } finally { this.endRead(); // read end of xls file } } /** * This method listens for incoming records and handles them as required. * * @param record the record that was found while reading. */ @Override public void processRecord(Record record) { if (record instanceof BOFRecord) { // beginning of a sheet or the workbook if (((BOFRecord) record).getType() == BOFRecord.TYPE_WORKSHEET) { // beginning a sheet allSheetReadied = true; if (currentSheet != null) { putRow(currentRow); currentSheet.toEnd(); } currentRow = null; currentRowNumber = -1; currentRowOrder = -1; // reset current row currentSheet = (HSSFStreamingSheet) sheets.get(++currentSheetIndex); } else { // BOFRecord.TYPE_WORKBOOK: beginning the workbook // others uncapture ... } } else if (record instanceof BoundSheetRecord) { // the workbook all of sheet BoundSheetRecord bsr = (BoundSheetRecord) record; int sstIdx = sheets.size(); sheets.add(new HSSFStreamingSheet( sstIdx, bsr.getSheetname(), isDiscard(sstIdx, bsr.getSheetname()), rowCacheSize )); } else if (record instanceof SSTRecord) { // store a array of unique strings used in Excel. sstrec = (SSTRecord) record; } else if (record instanceof CellRecord) { // excel cell CellRecord cell = (CellRecord) record; if (currentRowNumber != cell.getRow()) { // new row putRow(currentRow); currentRowNumber = cell.getRow(); currentRow = new HSSFStreamingRow(currentRowNumber, ++currentRowOrder); } currentRow.putCell(cell.getColumn(), new HSSFStreamingCell(getString(cell))); } else { // RowRecord: batch row loading // MissingCellDummyRecord: missing cell // LastCellOfRowDummyRecord: last cell // others ... } } private void endRead() { allSheetReadied = true; if (currentSheet != null) { putRow(currentRow); // last row } sheets.forEach(s -> ((HSSFStreamingSheet) s).toEnd()); } private void putRow(HSSFStreamingRow row) { if ( this.currentSheet.isDiscard() || row == null || row.isEmpty() ) { return; } try { this.currentSheet.putRow(row); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } } private boolean isDiscard(int sstIdx, String sstName) { if ( ArrayUtils.isEmpty(sheetIndexs) && ArrayUtils.isEmpty(sheetNames)) { return false; } return !ArrayUtils.contains(sheetIndexs, sstIdx) && !ArrayUtils.contains(sheetNames, sstName); } private String getString(CellRecord record) { switch (record.getSid()) { case BoolErrRecord.sid: return Boolean.toString(((BoolErrRecord) record).getBooleanValue()); case FormulaRecord.sid: FormulaRecord frec = (FormulaRecord) record; if (Double.isNaN(frec.getValue())) { return null; // Formula result is a string, This is stored in the next record } else { return formatListener.formatNumberDateCell(frec); } // return '"' + HSSFFormulaParser.toFormulaString(stubWorkbook, frec.getParsedExpression()) + '"'; case LabelSSTRecord.sid: return sstrec == null ? null : sstrec.getString(((LabelSSTRecord) record).getSSTIndex()).getString(); case NumberRecord.sid: NumberRecord number = (NumberRecord) record; if (StringUtils.containsAny(formatListener.getFormatString(number), '/', ':')) { return formatListener.formatNumberDateCell(number); } else { return String.valueOf(number.getValue()); } default: return null; } } } // ------------------------------------------------------unsupported operation @Override @Deprecated public boolean isSheetHidden(int sheetIx) { throw new UnsupportedOperationException(); } @Override @Deprecated public boolean isSheetVeryHidden(int sheetIx) { throw new UnsupportedOperationException(); } @Override @Deprecated public int getActiveSheetIndex() { throw new UnsupportedOperationException(); } @Override @Deprecated public void setActiveSheet(int sheetIndex) { throw new UnsupportedOperationException(); } @Override @Deprecated public int getFirstVisibleTab() { throw new UnsupportedOperationException(); } @Override @Deprecated public void setFirstVisibleTab(int sheetIndex) { throw new UnsupportedOperationException(); } @Override @Deprecated public void setSheetOrder(String sheetname, int pos) { throw new UnsupportedOperationException(); } @Override @Deprecated public void setSelectedTab(int index) { throw new UnsupportedOperationException(); } @Override @Deprecated public void setSheetName(int sheet, String name) { throw new UnsupportedOperationException(); } @Override @Deprecated public Sheet createSheet() { throw new UnsupportedOperationException(); } @Override @Deprecated public Sheet createSheet(String sheetname) { throw new UnsupportedOperationException(); } @Override @Deprecated public Sheet cloneSheet(int sheetNum) { throw new UnsupportedOperationException(); } @Override @Deprecated public void removeSheetAt(int index) { throw new UnsupportedOperationException(); } @Override @Deprecated public Font createFont() { throw new UnsupportedOperationException(); } @Override @Deprecated public Font findFont(boolean bold, short color, short fontHeight, String name, boolean italic, boolean strikeout, short typeOffset, byte underline) { throw new UnsupportedOperationException(); } @Override @Deprecated public int getNumberOfFonts() { throw new UnsupportedOperationException(); } @Override @Deprecated public int getNumberOfFontsAsInt() { throw new UnsupportedOperationException(); } @Override @Deprecated public Font getFontAt(int idx) { throw new UnsupportedOperationException(); } @Override @Deprecated public CellStyle createCellStyle() { throw new UnsupportedOperationException(); } @Override @Deprecated public int getNumCellStyles() { throw new UnsupportedOperationException(); } @Override @Deprecated public CellStyle getCellStyleAt(int idx) { throw new UnsupportedOperationException(); } @Override @Deprecated public void write(OutputStream stream) { throw new UnsupportedOperationException(); } @Override @Deprecated public int getNumberOfNames() { throw new UnsupportedOperationException(); } @Override @Deprecated public Name getName(String name) { throw new UnsupportedOperationException(); } @Override @Deprecated public List getNames(String name) { throw new UnsupportedOperationException(); } @Override @Deprecated public List getAllNames() { throw new UnsupportedOperationException(); } @Override @Deprecated public Name createName() { throw new UnsupportedOperationException(); } @Override @Deprecated public void removeName(Name name) { throw new UnsupportedOperationException(); } @Override @Deprecated public int linkExternalWorkbook(String name, Workbook workbook) { throw new UnsupportedOperationException(); } @Override @Deprecated public void setPrintArea(int sheetIndex, String reference) { throw new UnsupportedOperationException(); } @Override @Deprecated public void setPrintArea(int sheetIndex, int startColumn, int endColumn, int startRow, int endRow) { throw new UnsupportedOperationException(); } @Override @Deprecated public String getPrintArea(int sheetIndex) { throw new UnsupportedOperationException(); } @Override @Deprecated public void removePrintArea(int sheetIndex) { throw new UnsupportedOperationException(); } @Override @Deprecated public MissingCellPolicy getMissingCellPolicy() { throw new UnsupportedOperationException(); } @Override @Deprecated public void setMissingCellPolicy(MissingCellPolicy missingCellPolicy) { throw new UnsupportedOperationException(); } @Override @Deprecated public DataFormat createDataFormat() { throw new UnsupportedOperationException(); } @Override @Deprecated public int addPicture(byte[] pictureData, int format) { throw new UnsupportedOperationException(); } @Override @Deprecated public List getAllPictures() { throw new UnsupportedOperationException(); } @Override @Deprecated public CreationHelper getCreationHelper() { throw new UnsupportedOperationException(); } @Override @Deprecated public boolean isHidden() { throw new UnsupportedOperationException(); } @Override @Deprecated public void setHidden(boolean hiddenFlag) { throw new UnsupportedOperationException(); } @Override @Deprecated public void setSheetHidden(int sheetIx, boolean hidden) { throw new UnsupportedOperationException(); } @Override @Deprecated public SheetVisibility getSheetVisibility(int sheetIx) { throw new UnsupportedOperationException(); } @Override @Deprecated public void setSheetVisibility(int sheetIx, SheetVisibility visibility) { throw new UnsupportedOperationException(); } @Override @Deprecated public void addToolPack(UDFFinder toopack) { throw new UnsupportedOperationException(); } @Override @Deprecated public void setForceFormulaRecalculation(boolean value) { throw new UnsupportedOperationException(); } @Override @Deprecated public boolean getForceFormulaRecalculation() { throw new UnsupportedOperationException(); } @Override @Deprecated public SpreadsheetVersion getSpreadsheetVersion() { throw new UnsupportedOperationException(); } @Override @Deprecated public int addOlePackage(byte[] oleData, String label, String fileName, String command) { throw new UnsupportedOperationException(); } @Override @Deprecated public EvaluationWorkbook createEvaluationWorkbook() { throw new UnsupportedOperationException(); } @Override @Deprecated public CellReferenceType getCellReferenceType() { throw new UnsupportedOperationException(); } @Override @Deprecated public void setCellReferenceType(CellReferenceType cellReferenceType) { throw new UnsupportedOperationException(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/http/ContentType.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.http; /** * Representing http 'Content-Type' header * * @author Ponfee */ public enum ContentType { APPLICATION_FORM_URLENCODED("application/x-www-form-urlencoded"), // APPLICATION_JSON("application/json"), // APPLICATION_OCTET_STREAM("application/octet-stream"), // APPLICATION_XML("application/xml"), // APPLICATION_ATOM_XML("application/atom+xml"), // APPLICATION_SVG_XML("application/svg+xml"), // APPLICATION_XHTML_XML("application/xhtml+xml"), // MULTIPART_FORM_DATA("multipart/form-data"), // TEXT_XML("text/xml"), // TEXT_HTML("text/html"), // TEXT_PLAIN("text/plain"), // IMAGE_JPEG("image/jpeg"), // IMAGE_PNG("image/png"), // IMAGE_BMP("image/bmp"), // IMAGE_ICO("image/ico"), // IMAGE_GIF("image/gif"), // WILDCARD("*/*"), // ; final String value; ContentType(String value) { this.value = value; } public String value() { return value; } public static ContentType ofValue(String value) { for (ContentType type : ContentType.values()) { if (type.value.equalsIgnoreCase(value)) { return type; } } return null; } } ================================================ FILE: src/main/java/cn/ponfee/commons/http/Http.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.http; import cn.ponfee.commons.io.Closeables; import cn.ponfee.commons.io.Files; import cn.ponfee.commons.json.Jsons; import com.fasterxml.jackson.databind.JavaType; import com.google.common.base.Preconditions; import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.EnumUtils; import org.apache.commons.lang3.StringUtils; import javax.annotation.Nonnull; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import java.io.*; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** *
 *  Accept属于请求头, Content-Type属于实体头
 *     请求方的http报头结构:通用报头|请求报头|实体报头 
 *     响应方的http报头结构:通用报头|响应报头|实体报头
 *
 *  请求报头有:Accept、Accept-Charset、Accept-Encoding、Accept-Language、Referer、Authorization、From、Host、If-Match、User-Agent、If-Modified-Since等
 *  Accept:告诉WEB服务器自己接受什么介质类型,*∕*表示任何类型,type∕*表示该类型下的所有子类型,type∕sub-type,如Accept(text/html)
 *
 *  响应报头有:Age、Server、Accept-Ranges、Vary等
 *
 *  实体报头有:Allow、Location、Content-Base、Content-Encoding、Content-Length、Content-Range、Content-MD5、Content-Type、Expires、Last-Modified等
 *  Content-Type:
 *     请求实体报头:浏览器告诉Web服务器自己发送的数据格式,如Content-Type(application/json, multipart/form-data, application/x-www-form-urlencoded)
 *     响应实体报头:Web服务器告诉浏览器自己响应的消息格式,例如Content-Type(application/xml, application/json)
 * 
* http://www.atool.org/httptest.php

* * Restful:https://www.cnblogs.com/pixy/p/4838268.html

* * http工具类

* Spring RestTemplate

* org.apache.httpcomponents:fluent-hc

* org.apache.httpcomponents.client5:httpclient5

* com.squareup.okhttp3:okhttp

* * @author Ponfee */ public final class Http { private final String url; // url private final HttpMethod method; // 请求方法 private final Map headers = new HashMap<>(0); // http请求头 private final Map params = new HashMap<>(0); // http请求参数(query string) private final List parts = new ArrayList<>(0); // http文件上传 private String data; // request body(json or form params, such as name1=value1&name2=value2&...&namen=valuen) private int connectTimeout = 2000; // 连接超时时间 private int readTimeout = 5000; // 读取返回数据超时时间(socket timeout) private Boolean encode = Boolean.TRUE; // 是否编码 private String contentType; // 请求内容类型:header("Content-Type", "application/json; charset=UTF-8") private String contentCharset; // 请求内容编码 private String accept; // 接收类型:header("Accept", "application/json") private SSLSocketFactory sslSocketFactory; // 走SSL/TSL通道 // ----------------------------------------------------------response private Map> respHeaders; private HttpStatus status; private Http(@Nonnull String url, @Nonnull HttpMethod method) { this.url = url; this.method = method; } // ------------------------------------------------------method public static Http get(String url) { return new Http(url, HttpMethod.GET); } public static Http post(String url) { return new Http(url, HttpMethod.POST); } public static Http put(String url) { return new Http(url, HttpMethod.PUT); } public static Http head(String url) { return new Http(url, HttpMethod.HEAD); } public static Http delete(String url) { return new Http(url, HttpMethod.DELETE); } public static Http trace(String url) { return new Http(url, HttpMethod.TRACE); } public static Http options(String url) { return new Http(url, HttpMethod.OPTIONS); } public static Http of(String url, String method) { return of(url, EnumUtils.getEnumIgnoreCase(HttpMethod.class, method, HttpMethod.GET)); } public static Http of(String url, HttpMethod method) { return new Http(url, method); } // ------------------------------------------------------header /** * 设置请求头 * @param name * @param value * @return */ public Http addHeader(String name, String value) { this.headers.put(name, value); return this; } /** * 设置请求头 * @param headers * @return */ public Http addHeader(Map headers) { if (MapUtils.isNotEmpty(headers)) { this.headers.putAll(headers); } return this; } // --------------------------------------------------------query params /** * * 最终是拼接成queryString的形式追加到url(即作为get的http请求参数) * get方式会有编码等问题,推荐使用data方式传参数:{@link #data(Map)} * @param params * @return */ public Http addParam(Map params) { this.params.putAll(params); return this; } public Http addParam(String name, T value) { this.params.put(name, value); return this; } // --------------------------------------------------------body params: form(text, file), text(eg. json), file /** * 发送到服务器的查询字符串或json串:name1=value1&name2=value2 * * @param data the http body payload data * @return a reference to this object */ public Http data(Map data) { return data(data, Files.UTF_8); } /** * 发送到服务器的查询字符串或json串:name1=value1&name2=value2 * * application/x-www-form-urlencoded * * @param data the http body payload data * @param charset the charset * @return a reference to this object */ public Http data(Map data, String charset) { return data(HttpParams.buildParams(data, charset)); } /** * HttpURLConnection.getOutputStream().write(data) * * @param data the http body data * @return a reference to this object */ public Http data(String data) { Preconditions.checkState(this.data == null, "data are already set."); Preconditions.checkArgument(data != null && data.length() != 0, "data cannot be empty."); this.data = data; return this; } // ------------------------------------------------------part public Http addPart(String formName, String fileName, Object mimePart) { return this.addPart(formName, fileName, null, mimePart); } /** * 文件上传 * @param formName 表单名称 * @param fileName 附件名称 * @param contentType 附件类型,value of the Content-Type part header * @param mimePart 上传数据 * @return */ public Http addPart(String formName, String fileName, String contentType, Object mimePart) { this.parts.add(new MimePart(formName, fileName, contentType, mimePart)); return this; } // ------------------------------------------------------encode /** * 编码url * @param encode * @return */ public Http encode(Boolean encode) { this.encode = encode; return this; } // ------------------------------------------------------request contentType /** *

     *  请求实体报头,发送信息至服务器时内容编码类型:
     *    multipart/form-data,application/x-www-form-urlencoded,
     *    application/json
     *  默认:application/x-www-form-urlencoded
     *  调用方式:contentType("application/json", "UTF-8")
     * 
* @param contentType * @param contentCharset * @return */ public Http contentType(ContentType contentType, String contentCharset) { this.contentType = contentType.value(); this.contentCharset = contentCharset; return this; } public Http contentType(ContentType contentType) { return this.contentType(contentType, Files.UTF_8); } // ------------------------------------------------------response accept /** * 内容类型发送请求头,告诉服务器什么样的响应会接受返回 * header("Accept", contentType) * @param contentType application/json * @return */ public Http accept(ContentType contentType) { this.accept = contentType.value(); return this; } // --------------------------------------------------------------timeout /** * set connect timeout * @param seconds (s) * @return this */ public Http connTimeoutSeconds(int seconds) { this.connectTimeout = seconds * 1000; return this; } /** * set read timeout * @param seconds (s) * @return this */ public Http readTimeoutSeconds(int seconds) { this.readTimeout = seconds * 1000; return this; } // ------------------------------------------------------trust spec cert /** * trust spec certificate * @param factory * @return */ public Http setSSLSocketFactory(SSLSocketFactory factory) { this.sslSocketFactory = factory; return this; } public Http setSSLSocketFactory(SSLContext sslContext) { return setSSLSocketFactory(sslContext.getSocketFactory()); } // --------------------------------------------------------------request public T request(JavaType type) { return Jsons.fromJson(request(), type); } public T request(Class type) { return Jsons.fromJson(request(), type); } /** * 发送请求获取响应数据 * @return */ public String request() { HttpRequest request = request0(); try { return request.body(); } finally { disconnect(request); } } public void download(String filepath) { try (OutputStream out = new FileOutputStream(filepath)) { download(out); } catch (IOException e) { throw new HttpException("download error: " + filepath, e); } } public byte[] download() { ByteArrayOutputStream output = new ByteArrayOutputStream(); download(output); return output.toByteArray(); } /** * http下载 * @param output output to stream of response data */ //private static final Pattern FILENAME_PATTERN = Pattern.compile("(?i)^.*;.*filename=(.*)$"); public void download(OutputStream output) { BufferedOutputStream bos = null; HttpRequest request = request0(); try { if (HttpStatus.Series.valueOf(status) == HttpStatus.Series.SUCCESSFUL) { /* // 获取文件名 String disposition = UrlCoder.decodeURIComponent(request.header("content-Disposition")); Matcher matcher = FILENAME_PATTERN.matcher(disposition); if (matcher.matches()) { String filename = matcher.group(1); } */ bos = new BufferedOutputStream(output); request.receive(bos); } else { throw new HttpException("request failed, status: " + request.code()); } } finally { disconnect(request); Closeables.console(bos); } } // ------------------------------------------------------response headers public Map> getRespHeaders() { return respHeaders; } public Map getReqHeaders() { return headers; } public String[] getRespHeaders(String name) { if (respHeaders == null) { return null; } List values = respHeaders.get(name); if (values == null) { return null; } return values.toArray(new String[0]); } public String getRespHeader(String name) { if (respHeaders == null) { return null; } List values = respHeaders.get(name); return (values == null || values.isEmpty()) ? null : values.get(0); } public HttpStatus getStatus() { return status; } // ------------------------------------------------------private methods private HttpRequest request0() { HttpRequest request; switch (method) { case GET: request = HttpRequest.get (url, params, encode); break; case POST: request = HttpRequest.post (url, params, encode); break; case PUT: request = HttpRequest.put (url, params, encode); break; case HEAD: request = HttpRequest.head (url, params, encode); break; case DELETE: request = HttpRequest.delete (url, params, encode); break; case TRACE: request = HttpRequest.trace (url ); break; case OPTIONS: request = HttpRequest.options(url ); break; default: throw new UnsupportedOperationException("unsupported http method " + method.name()); } request.connectTimeout(connectTimeout) .readTimeout(readTimeout) .decompress(true) .acceptGzipEncoding() .headers(headers); if (!StringUtils.isEmpty(contentType)) { request.contentType(contentType, contentCharset); } if (!StringUtils.isEmpty(accept)) { request.accept(accept); } request.trustAllHosts(); if (this.sslSocketFactory != null) { request.setSSLSocketFactory(this.sslSocketFactory); } else { request.trustAllCerts(); } if (!StringUtils.isEmpty(data)) { request.send(data); } for (MimePart part : parts) { request.part(part.formName, part.fileName, part.contentType, part.stream); } status = request.status(); return request; } private void disconnect(HttpRequest request) { if (request != null) { this.respHeaders = request.headers(); // get the response headers try { request.disconnect(); } catch (Exception ignored) { ignored.printStackTrace(); } } } /** * Http method */ public enum HttpMethod { GET, POST, PUT, DELETE, HEAD, TRACE, OPTIONS } /** * File upload */ private static final class MimePart { final String formName; // 表单域字段名 final String fileName; // 文件名 final String contentType; // 附件类型 final InputStream stream; // 文件流 MimePart(String formName, String fileName, String contentType, Object mime) { if (mime instanceof byte[]) { this.stream = new ByteArrayInputStream((byte[]) mime); } else if (mime instanceof Byte[]) { this.stream = new ByteArrayInputStream(ArrayUtils.toPrimitive((Byte[]) mime)); } else if (mime instanceof String || mime instanceof File) { File file = (mime instanceof File) ? (File) mime : new File((String) mime); try { this.stream = new FileInputStream(file); } catch (FileNotFoundException e) { throw new IllegalArgumentException(e); } } else if (mime instanceof InputStream) { this.stream = (InputStream) mime; } else { throw new IllegalArgumentException( "mime must be one of them: file, file path, byte array, input stream." ); } this.formName = formName; this.fileName = fileName; this.contentType = contentType; } } } ================================================ FILE: src/main/java/cn/ponfee/commons/http/HttpException.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.http; /** * Http exception * * @author Ponfee */ public class HttpException extends RuntimeException { private static final long serialVersionUID = 7195686343121118928L; public HttpException() { super(); } public HttpException(String message) { super(message); } public HttpException(String message, Throwable cause) { super(message, cause); } public HttpException(Throwable cause) { super(cause); } protected HttpException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } } ================================================ FILE: src/main/java/cn/ponfee/commons/http/HttpParams.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.http; import cn.ponfee.commons.collect.Maps; import cn.ponfee.commons.io.Files; import cn.ponfee.commons.util.ObjectUtils; import cn.ponfee.commons.util.URLCodes; import cn.ponfee.commons.util.UuidUtils; import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import java.lang.reflect.Array; import java.util.*; /** * http参数工具类 * * @author Ponfee */ public class HttpParams { // --------------------------------------------------获取url中的参数 public static Map parseUrlParams(String url) { return parseUrlParams(url, Files.UTF_8); } public static Map parseUrlParams(String url, String charset) { int idx = url.indexOf('?'); return idx == -1 ? Collections.emptyMap() : parseParams(url.substring(idx + 1), charset); } // --------------------------------------------------解析query string中的请求参数 public static Map parseParams(String queryString) { return parseParams(queryString, Files.UTF_8); } /** * 解析参数 * @param queryString * @param encoding * @return */ public static Map parseParams(String queryString, String encoding) { if (queryString == null || queryString.length() == 0) { return Collections.emptyMap(); } if (encoding == null) { encoding = Files.UTF_8; } Map params = new LinkedHashMap<>(); String[] kv; for (String param : queryString.split("&")) { kv = param.split("=", 2); putParam(params, kv[0], kv.length == 1 ? "" : URLCodes.decodeURIComponent(kv[1], encoding)); } return params; } // --------------------------------------------------构建参数 /** * 键值对构建参数(默认UTF-8) * @param params * @return */ public static String buildParams(Map params) { return HttpParams.buildParams(params, Files.UTF_8); } /** * 键值对构建参数 * @param params * @param encoding * @return */ public static String buildParams(Map params, String encoding) { StringBuilder builder = new StringBuilder(); String[] values; Object value; for (Map.Entry entry : params.entrySet()) { value = entry.getValue(); if (value != null && value.getClass().isArray()) { values = new String[Array.getLength(value)]; for (int length = values.length, i = 0; i < length; i++) { values[i] = Objects.toString(Array.get(value, i), ""); } } else { values = new String[] { Objects.toString(entry.getValue(), "") }; } for (String val : values) { builder.append(entry.getKey()) .append("=") .append(URLCodes.encodeURIComponent(val, encoding)) .append("&"); } } if (builder.length() > 0) { builder.setLength(builder.length() - 1); } return builder.toString(); } // --------------------------------------------------构建url地址 /** * 构建url地址 * @param url * @param encoding * @param params * @return */ public static String buildUrlPath(String url, String encoding, Map params) { if (params == null || params.isEmpty()) { return url; } return url + (url.indexOf('?') == -1 ? '?' : '&') + buildParams(params, encoding); } public static String buildUrlPath(String url, String encoding, Object... params) { return buildUrlPath(url, encoding, Maps.toMap(params)); } // --------------------------------------------------构建签名数据 /** * 构建待签名数据 * @param params 请求参数 * @return */ public static String buildSigning(Map params) { return buildSigning(params, "", null); } public static String buildSigning(Map params, String[] excludes) { return buildSigning(params, "", excludes); } public static String buildSigning(Map params, String wrapChar, String[] excludes) { List filter = (excludes == null || excludes.length == 0) ? Collections.emptyList() : Arrays.asList(excludes); // 过滤参数 Map signingMap = new TreeMap<>(); for (Map.Entry entry : params.entrySet()) { if (!filter.contains(entry.getKey()) && StringUtils.isNotEmpty(Objects.toString(entry.getValue(), null))) { signingMap.put(entry.getKey(), entry.getValue().toString()); } } // 拼接待签名串,blank string to prevent if signingMap is empty StringBuilder signing = new StringBuilder(); for (Map.Entry entry : signingMap.entrySet()) { signing.append(entry.getKey()) .append('=') .append(wrapChar).append(entry.getValue()).append(wrapChar) .append('&'); } if (signing.length() > 0) { // 删除未位的'&' signing.setLength(signing.length() - 1); } return signing.toString(); } // --------------------------------------------------构建Form表单 /** * 构建form表单 * @param url * @param params * @return */ public static String buildForm(String url, Map params) { StringBuilder form = new StringBuilder(256); String formName = UuidUtils.uuid32(); form.append("
"); Object value; for (Map.Entry param : params.entrySet()) { value = param.getValue(); if (value != null && value.getClass().isArray()) { for (int length = Array.getLength(value), i = 0; i < length; i++) { buildInputElement(form, param.getKey(), Array.get(value, i)); } } else { buildInputElement(form, param.getKey(), value); } } return form.append("") .toString(); } /** * Builds the webservice soap xml * * http://www.webxml.com.cn/WebServices/IpAddressSearchWebService.asmx * SOAP 1.1 * * @param method the method, like as "getCountryCityByIp" * @param namespace the namespace, like as "http://WebXml.com.cn/" * @param params the params * @return a soap xml string */ public static String buildSoap(String method, String namespace, Map params) { StringBuilder sb = new StringBuilder(); sb.append(""); sb.append(""); sb.append(""); if (MapUtils.isNotEmpty(params)) { params.forEach( (k, v) -> sb.append("<").append(k).append(">") .append(Objects.toString(v, "")) .append("") ); } sb.append(""); sb.append(""); sb.append(""); return sb.toString(); } // --------------------------------------------------private methods private static void putParam(Map params, String name, String value) { String[] oldValues = params.get(name); if (oldValues == null) { params.put(name, new String[] { value }); } else { params.put(name, ArrayUtils.add(oldValues, value)); } } private static void buildInputElement(StringBuilder form, String name, Object value) { form.append(""); } } ================================================ FILE: src/main/java/cn/ponfee/commons/http/HttpRequest.java ================================================ /* * Copyright (c) 2014 Kevin Sawicki * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. * * * com.github.kevinsawicki * http-request * 6.0 * */ package cn.ponfee.commons.http; import cn.ponfee.commons.collect.Collects; import cn.ponfee.commons.io.Files; import cn.ponfee.commons.jce.Providers; import cn.ponfee.commons.util.ObjectUtils; import cn.ponfee.commons.util.UuidUtils; import javax.net.ssl.*; import java.io.*; import java.net.*; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.nio.charset.StandardCharsets; import java.security.AccessController; import java.security.GeneralSecurityException; import java.security.PrivilegedAction; import java.security.SecureRandom; import java.security.cert.X509Certificate; import java.util.*; import java.util.Map.Entry; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.zip.GZIPInputStream; import static java.net.HttpURLConnection.*; import static java.net.Proxy.Type.HTTP; /** * A fluid interface for making HTTP requests using an underlying * {@link HttpURLConnection} (or sub-class). *

* Each instance supports making a single request and cannot be reused for * further requests. * * @author Kevin Sawicki, Ponfee * * Reference from internet and with optimization * https://github.com/kevinsawicki/http-request */ public class HttpRequest { /** * 'gzip' encoding header value */ public static final String ENCODING_GZIP = "gzip"; /** * 'Accept' header name */ public static final String HEADER_ACCEPT = "Accept"; /** * 'Accept-Charset' header name */ public static final String HEADER_ACCEPT_CHARSET = "Accept-Charset"; /** * 'Accept-Encoding' header name */ public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding"; /** * 'Authorization' header name */ public static final String HEADER_AUTHORIZATION = "Authorization"; /** * 'Cache-Control' header name */ public static final String HEADER_CACHE_CONTROL = "Cache-Control"; /** * 'Content-Encoding' header name */ public static final String HEADER_CONTENT_ENCODING = "Content-Encoding"; /** * 'Content-Length' header name */ public static final String HEADER_CONTENT_LENGTH = "Content-Length"; /** * 'Content-Type' header name */ public static final String HEADER_CONTENT_TYPE = "Content-Type"; /** * 'Date' header name */ public static final String HEADER_DATE = "Date"; /** * 'ETag' header name */ public static final String HEADER_ETAG = "ETag"; /** * 'Expires' header name */ public static final String HEADER_EXPIRES = "Expires"; /** * 'If-None-Match' header name */ public static final String HEADER_IF_NONE_MATCH = "If-None-Match"; /** * 'Last-Modified' header name */ public static final String HEADER_LAST_MODIFIED = "Last-Modified"; /** * 'Location' header name */ public static final String HEADER_LOCATION = "Location"; /** * 'Proxy-Authorization' header name */ public static final String HEADER_PROXY_AUTHORIZATION = "Proxy-Authorization"; /** * 'Referer' header name */ public static final String HEADER_REFERER = "Referer"; /** * 'Server' header name */ public static final String HEADER_SERVER = "Server"; /** * 'User-Agent' header name */ public static final String HEADER_USER_AGENT = "User-Agent"; /** * 'DELETE' request method */ public static final String METHOD_DELETE = "DELETE"; /** * 'GET' request method */ public static final String METHOD_GET = "GET"; /** * 'HEAD' request method */ public static final String METHOD_HEAD = "HEAD"; /** * 'OPTIONS' options method */ public static final String METHOD_OPTIONS = "OPTIONS"; /** * 'POST' request method */ public static final String METHOD_POST = "POST"; /** * 'PUT' request method */ public static final String METHOD_PUT = "PUT"; /** * 'TRACE' request method */ public static final String METHOD_TRACE = "TRACE"; /** * 'charset' header value parameter */ public static final String PARAM_CHARSET = "charset"; private static final String BOUNDARY = "00content0boundary00"; private static final String CONTENT_TYPE_MULTIPART = "multipart/form-data; boundary=" + BOUNDARY; private static final String CRLF = "\r\n"; private static final String[] EMPTY_STRINGS = {}; private static final HostnameVerifier TRUSTED_VERIFIER = (hostname, session) -> /*hostname.equalsIgnoreCase(session.getPeerHost())*/true; private static final SSLSocketFactory TRUSTED_FACTORY; static { try { SSLContext context = SSLContext.getInstance("TLSv1.3"); context.init(null, new TrustManager[] { new X509TrustManager() { @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } @Override public void checkClientTrusted(X509Certificate[] chain, String authType) { // Intentionally left blank } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) { // Intentionally left blank } } }, new SecureRandom(new SecureRandom(UuidUtils.uuid()).generateSeed(20))); TRUSTED_FACTORY = context.getSocketFactory(); } catch (GeneralSecurityException e) { throw new HttpException( new IOException("Security exception configuring SSL context", e) ); } } private static String getValidCharset(String charset) { if (charset != null && charset.length() > 0) { return charset; } else { return Files.UTF_8; } } private static StringBuilder addPathSeparator(String baseUrl, StringBuilder result) { // Add trailing slash if the base URL doesn't have any path segments. // // The following test is checking for the last slash not being part of // the protocol to host separator: '://'. if (baseUrl.indexOf(':') + 2 == baseUrl.lastIndexOf('/')) { result.append('/'); } return result; } private static StringBuilder addParamPrefix(String baseUrl, StringBuilder result) { // Add '?' if missing and add '&' if params already exist in base url int queryStart = baseUrl.indexOf('?'); int lastChar = result.length() - 1; if (queryStart == -1) { result.append('?'); } else if (queryStart < lastChar && baseUrl.charAt(lastChar) != '&') { result.append('&'); } return result; } private static StringBuilder addParam(Object key, Object value, StringBuilder result) { if (value != null && value.getClass().isArray()) { value = Collects.toList(value); } if (value instanceof Iterable) { Iterator iterator = ((Iterable) value).iterator(); while (iterator.hasNext()) { result.append(key); result.append("[]="); Object element = iterator.next(); if (element != null) { result.append(element); } if (iterator.hasNext()) { result.append("&"); } } } else { result.append(key); result.append("="); if (value != null) { result.append(value); } } return result; } /** * Creates {@link HttpURLConnection HTTP connections} for * {@link URL urls}. */ public interface ConnectionFactory { /** * Open an {@link HttpURLConnection} for the specified {@link URL}. * @throws IOException */ HttpURLConnection create(URL url) throws IOException; /** * Open an {@link HttpURLConnection} for the specified {@link URL} * and {@link Proxy}. * @throws IOException */ HttpURLConnection create(URL url, Proxy proxy) throws IOException; /** * A {@link ConnectionFactory} which uses the built-in * {@link URL#openConnection()} */ ConnectionFactory DEFAULT = new ConnectionFactory() { @Override public HttpURLConnection create(URL url) throws IOException { return (HttpURLConnection) url.openConnection(); } @Override public HttpURLConnection create(URL url, Proxy proxy) throws IOException { return (HttpURLConnection) url.openConnection(proxy); } }; } private static ConnectionFactory connectionFactory = ConnectionFactory.DEFAULT; /** * Specify the {@link ConnectionFactory} used to create new requests. */ public static void setConnectionFactory(ConnectionFactory cf) { connectionFactory = cf != null ? cf : ConnectionFactory.DEFAULT; } /** * Callback interface for reporting upload progress for a request. */ public interface UploadProgress { /** * Callback invoked as data is uploaded by the request. * * @param uploaded The number of bytes already uploaded * @param total The total number of bytes that will be uploaded or -1 if * the length is unknown. */ void onUpload(long uploaded, long total); UploadProgress DEFAULT = (uploaded, total) -> { // do-non }; } /** * Operation that handles executing a callback once complete and handling * nested exceptions * * @param */ private abstract static class Operation implements Callable { /** * Run operation * * @return result * @throws HttpException * @throws IOException */ protected abstract V run() throws HttpException, IOException; /** * Operation complete callback * * @throws IOException */ protected abstract void done() throws IOException; @Override public final V call() throws HttpException { try { return run(); } catch (HttpException e) { throw e; } catch (Exception e) { throw new HttpException(e); } finally { try { done(); } catch (Exception ignored) { ignored.printStackTrace(); } } } } /** * Class that ensures a {@link Closeable} gets closed with proper exception * handling. * * @param */ private abstract static class CloseOperation extends Operation { private final Closeable closeable; private final boolean ignoreCloseExceptions; /** * Create closer for operation * * @param closeable * @param ignoreCloseExceptions */ protected CloseOperation(Closeable closeable, boolean ignoreCloseExceptions) { this.closeable = closeable; this.ignoreCloseExceptions = ignoreCloseExceptions; } @Override protected void done() throws IOException { if (closeable instanceof Flushable) { ((Flushable) closeable).flush(); } if (ignoreCloseExceptions) { try { closeable.close(); } catch (IOException ignored) { ignored.printStackTrace(); // ignored } } else { closeable.close(); } } } /** * Class that and ensures a {@link Flushable} gets flushed with proper * exception handling. * * @param */ private abstract static class FlushOperation extends Operation { private final Flushable flushable; /** * Create flush operation * * @param flushable */ protected FlushOperation(Flushable flushable) { this.flushable = flushable; } @Override protected void done() throws IOException { flushable.flush(); } } /** * Request output stream */ public static class RequestOutputStream extends BufferedOutputStream { private final CharsetEncoder encoder; /** * Create request output stream * * @param stream * @param charset * @param bufferSize */ public RequestOutputStream(OutputStream stream, String charset, int bufferSize) { super(stream, bufferSize); encoder = Charset.forName(getValidCharset(charset)).newEncoder(); } /** * Write string to stream * * @param value * @return this stream * @throws IOException */ public RequestOutputStream write(String value) throws IOException { ByteBuffer bytes = encoder.encode(CharBuffer.wrap(value)); super.write(bytes.array(), 0, bytes.limit()); return this; } } /** * Encode the given URL as an ASCII {@link String} *

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* This will fall back to using the UTF-8 character set if the given charset * is null * * @param charset * @return reader * @throws HttpException */ public InputStreamReader reader(String charset) throws HttpException { try { return new InputStreamReader(stream(), getValidCharset(charset)); } catch (UnsupportedEncodingException e) { throw new HttpException(e); } } /** * Get reader to response body using the character set returned from * {@link #charset()} * * @return reader * @throws HttpException */ public InputStreamReader reader() throws HttpException { return reader(charset()); } /** * Get buffered reader to response body using the given character set r and * the configured buffer size * * * @see #bufferSize(int) * @param charset * @return reader * @throws HttpException */ public BufferedReader bufferedReader(String charset) throws HttpException { return new BufferedReader(reader(charset), bufferSize); } /** * Get buffered reader to response body using the character set returned from * {@link #charset()} and the configured buffer size * * @see #bufferSize(int) * @return reader * @throws HttpException */ public BufferedReader bufferedReader() throws HttpException { return bufferedReader(charset()); } /** * Stream response body to file * * @param file * @return this request * @throws HttpException */ public HttpRequest receive(File file) throws HttpException { OutputStream output; try { output = new BufferedOutputStream(new FileOutputStream(file), bufferSize); } catch (FileNotFoundException e) { throw new HttpException(e); } return new CloseOperation(output, ignoreCloseExceptions) { @Override protected HttpRequest run() throws HttpException { return receive(output); } }.call(); } /** * Stream response to given output stream * * @param output * @return this request */ public HttpRequest receive(OutputStream output) { return copy(buffer(), output); } /** * Stream response to given print stream * * @param output * @return this request * @throws HttpException */ public HttpRequest receive(PrintStream output) throws HttpException { return receive((OutputStream) output); } /** * Receive response into the given appendable * * @param appendable * @return this request * @throws HttpException */ public HttpRequest receive(Appendable appendable) throws HttpException { BufferedReader reader = bufferedReader(); return new CloseOperation(reader, ignoreCloseExceptions) { @Override public HttpRequest run() throws IOException { CharBuffer buffer = CharBuffer.allocate(bufferSize); int read; while ((read = reader.read(buffer)) != -1) { buffer.rewind(); appendable.append(buffer, 0, read); buffer.rewind(); } return HttpRequest.this; } }.call(); } /** * Receive response into the given writer * * @param writer * @return this request * @throws HttpException */ public HttpRequest receive(Writer writer) throws HttpException { BufferedReader reader = bufferedReader(); return new CloseOperation(reader, ignoreCloseExceptions) { @Override public HttpRequest run() { return copy(reader, writer); } }.call(); } /** * Set read timeout on connection to given value * * @param timeout * @return this request */ public HttpRequest readTimeout(int timeout) { getConnection().setReadTimeout(timeout); return this; } /** * Set connect timeout on connection to given value * * @param timeout * @return this request */ public HttpRequest connectTimeout(int timeout) { getConnection().setConnectTimeout(timeout); return this; } /** * Set header name to given value * * @param name * @param value * @return this request */ public HttpRequest header(String name, String value) { getConnection().setRequestProperty(name, value); return this; } /** * Set header name to given value * * @param name * @param value * @return this request */ public HttpRequest header(String name, Number value) { return header(name, value != null ? value.toString() : null); } /** * Set all headers found in given map where the keys are the header names and * the values are the header values * * @param headers * @return this request */ public HttpRequest headers(Map headers) { if (!headers.isEmpty()) { for (Entry header : headers.entrySet()) { header(header); } } return this; } /** * Set header to have given entry's key as the name and value as the value * * @param header * @return this request */ public HttpRequest header(Entry header) { return header(header.getKey(), header.getValue()); } /** * Get a response header * * @param name * @return response header * @throws HttpException */ public String header(String name) throws HttpException { closeOutputQuietly(); return getConnection().getHeaderField(name); } /** * Get all the response headers * * @return map of response header names to their value(s) * @throws HttpException */ public Map> headers() throws HttpException { closeOutputQuietly(); return getConnection().getHeaderFields(); } /** * Get a date header from the response falling back to returning -1 if the * header is missing or parsing fails * * @param name * @return date, -1 on failures * @throws HttpException */ public long dateHeader(String name) throws HttpException { return dateHeader(name, -1L); } /** * Get a date header from the response falling back to returning the given * default value if the header is missing or parsing fails * * @param name * @param defaultValue * @return date, default value on failures * @throws HttpException */ public long dateHeader(String name, long defaultValue) throws HttpException { closeOutputQuietly(); return getConnection().getHeaderFieldDate(name, defaultValue); } /** * Get an integer header from the response falling back to returning -1 if the * header is missing or parsing fails * * @param name * @return header value as an integer, -1 when missing or parsing fails * @throws HttpException */ public int intHeader(String name) throws HttpException { return intHeader(name, -1); } /** * Get an integer header value from the response falling back to the given * default value if the header is missing or if parsing fails * * @param name * @param defaultValue * @return header value as an integer, default value when missing or parsing * fails * @throws HttpException */ public int intHeader(String name, int defaultValue) throws HttpException { closeOutputQuietly(); return getConnection().getHeaderFieldInt(name, defaultValue); } /** * Get all values of the given header from the response * * @param name * @return non-null but possibly empty array of {@link String} header values */ public String[] headers(String name) { Map> headers = headers(); if (headers == null || headers.isEmpty()) { return EMPTY_STRINGS; } List values = headers.get(name); if (values != null && !values.isEmpty()) { return values.toArray(new String[0]); } else { return EMPTY_STRINGS; } } /** * Get parameter with given name from header value in response * * @param headerName * @param paramName * @return parameter value or null if missing */ public String parameter(String headerName, String paramName) { return getParam(header(headerName), paramName); } /** * Get all parameters from header value in response *

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

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

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

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

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

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

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

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

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

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

* This method does nothing if the current request is not a HTTPS request * * @return this request */ public HttpRequest trustAllHosts() { HttpURLConnection connection = getConnection(); if (connection instanceof HttpsURLConnection) { ((HttpsURLConnection) connection).setHostnameVerifier(TRUSTED_VERIFIER); } return this; } /** * Get the {@link URL} of this request's connection * * @return request URL */ public URL url() { return getConnection().getURL(); } /** * Get the HTTP method of this request * * @return method */ public String method() { return getConnection().getRequestMethod(); } /** * Configure an HTTP proxy on this connection. Use {{@link #proxyBasic(String, String)} if * this proxy requires basic authentication. * * @param proxyHost * @param proxyPort * @return this request */ public HttpRequest useProxy(String proxyHost, int proxyPort) { if (connection != null) { throw new IllegalStateException("The connection has already been created. This " + "method must be called before reading or writing to the request."); } this.httpProxyHost = proxyHost; this.httpProxyPort = proxyPort; return this; } /** * Set whether or not the underlying connection should follow redirects in * the response. * * @param followRedirects - true fo follow redirects, false to not. * @return this request */ public HttpRequest followRedirects(boolean followRedirects) { getConnection().setInstanceFollowRedirects(followRedirects); return this; } } ================================================ FILE: src/main/java/cn/ponfee/commons/http/HttpStatus.java ================================================ package cn.ponfee.commons.http; import com.google.common.collect.ImmutableMap; import java.util.Map; /** * Enumeration of HTTP status codes. * *

The HTTP status code series can be retrieved via {@link #series()}. * * @author Arjen Poutsma * @author Sebastien Deleuze * @author Brian Clozel * @since 3.0 * @see HttpStatus.Series * @see HTTP Status Code Registry * @see List of HTTP status codes - Wikipedia */ public enum HttpStatus { // ---------------------------------------1xx Informational /** * {@code 100 Continue}. * @see HTTP/1.1: Semantics and Content, section 6.2.1 */ CONTINUE(100, "Continue"), /** * {@code 101 Switching Protocols}. * @see HTTP/1.1: Semantics and Content, section 6.2.2 */ SWITCHING_PROTOCOLS(101, "Switching Protocols"), /** * {@code 102 Processing}. * @see WebDAV */ PROCESSING(102, "Processing"), /** * {@code 103 Checkpoint}. * @see A proposal for supporting * resumable POST/PUT HTTP requests in HTTP/1.0 */ CHECKPOINT(103, "Checkpoint"), // --------------------------------------- 2xx Success /** * {@code 200 OK}. * @see HTTP/1.1: Semantics and Content, section 6.3.1 */ OK(200, "OK"), /** * {@code 201 Created}. * @see HTTP/1.1: Semantics and Content, section 6.3.2 */ CREATED(201, "Created"), /** * {@code 202 Accepted}. * @see HTTP/1.1: Semantics and Content, section 6.3.3 */ ACCEPTED(202, "Accepted"), /** * {@code 203 Non-Authoritative Information}. * @see HTTP/1.1: Semantics and Content, section 6.3.4 */ NON_AUTHORITATIVE_INFORMATION(203, "Non-Authoritative Information"), /** * {@code 204 No Content}. * @see HTTP/1.1: Semantics and Content, section 6.3.5 */ NO_CONTENT(204, "No Content"), /** * {@code 205 Reset Content}. * @see HTTP/1.1: Semantics and Content, section 6.3.6 */ RESET_CONTENT(205, "Reset Content"), /** * {@code 206 Partial Content}. * @see HTTP/1.1: Range Requests, section 4.1 */ PARTIAL_CONTENT(206, "Partial Content"), /** * {@code 207 Multi-Status}. * @see WebDAV */ MULTI_STATUS(207, "Multi-Status"), /** * {@code 208 Already Reported}. * @see WebDAV Binding Extensions */ ALREADY_REPORTED(208, "Already Reported"), /** * {@code 226 IM Used}. * @see Delta encoding in HTTP */ IM_USED(226, "IM Used"), // --------------------------------------- 3xx Redirection /** * {@code 300 Multiple Choices}. * @see HTTP/1.1: Semantics and Content, section 6.4.1 */ MULTIPLE_CHOICES(300, "Multiple Choices"), /** * {@code 301 Moved Permanently}. * @see HTTP/1.1: Semantics and Content, section 6.4.2 */ MOVED_PERMANENTLY(301, "Moved Permanently"), /** * {@code 302 Found}. * @see HTTP/1.1: Semantics and Content, section 6.4.3 */ FOUND(302, "Found"), /** * {@code 303 See Other}. * @see HTTP/1.1: Semantics and Content, section 6.4.4 */ SEE_OTHER(303, "See Other"), /** * {@code 304 Not Modified}. * @see HTTP/1.1: Conditional Requests, section 4.1 */ NOT_MODIFIED(304, "Not Modified"), /** * {@code 307 Temporary Redirect}. * @see HTTP/1.1: Semantics and Content, section 6.4.7 */ TEMPORARY_REDIRECT(307, "Temporary Redirect"), /** * {@code 308 Permanent Redirect}. * @see RFC 7238 */ PERMANENT_REDIRECT(308, "Permanent Redirect"), // --------------------------------------- 4xx Client Error /** * {@code 400 Bad Request}. * @see HTTP/1.1: Semantics and Content, section 6.5.1 */ BAD_REQUEST(400, "Bad Request"), /** * {@code 401 Unauthorized}. * @see HTTP/1.1: Authentication, section 3.1 */ UNAUTHORIZED(401, "Unauthorized"), /** * {@code 402 Payment Required}. * @see HTTP/1.1: Semantics and Content, section 6.5.2 */ PAYMENT_REQUIRED(402, "Payment Required"), /** * {@code 403 Forbidden}. * @see HTTP/1.1: Semantics and Content, section 6.5.3 */ FORBIDDEN(403, "Forbidden"), /** * {@code 404 Not Found}. * @see HTTP/1.1: Semantics and Content, section 6.5.4 */ NOT_FOUND(404, "Not Found"), /** * {@code 405 Method Not Allowed}. * @see HTTP/1.1: Semantics and Content, section 6.5.5 */ METHOD_NOT_ALLOWED(405, "Method Not Allowed"), /** * {@code 406 Not Acceptable}. * @see HTTP/1.1: Semantics and Content, section 6.5.6 */ NOT_ACCEPTABLE(406, "Not Acceptable"), /** * {@code 407 Proxy Authentication Required}. * @see HTTP/1.1: Authentication, section 3.2 */ PROXY_AUTHENTICATION_REQUIRED(407, "Proxy Authentication Required"), /** * {@code 408 Request Timeout}. * @see HTTP/1.1: Semantics and Content, section 6.5.7 */ REQUEST_TIMEOUT(408, "Request Timeout"), /** * {@code 409 Conflict}. * @see HTTP/1.1: Semantics and Content, section 6.5.8 */ CONFLICT(409, "Conflict"), /** * {@code 410 Gone}. * @see HTTP/1.1: Semantics and Content, section 6.5.9 */ GONE(410, "Gone"), /** * {@code 411 Length Required}. * @see HTTP/1.1: Semantics and Content, section 6.5.10 */ LENGTH_REQUIRED(411, "Length Required"), /** * {@code 412 Precondition failed}. * @see HTTP/1.1: Conditional Requests, section 4.2 */ PRECONDITION_FAILED(412, "Precondition Failed"), /** * {@code 413 Payload Too Large}. * @since 4.1 * @see HTTP/1.1: Semantics and Content, section 6.5.11 */ PAYLOAD_TOO_LARGE(413, "Payload Too Large"), /** * {@code 414 URI Too Long}. * @since 4.1 * @see HTTP/1.1: Semantics and Content, section 6.5.12 */ URI_TOO_LONG(414, "URI Too Long"), /** * {@code 415 Unsupported Media Type}. * @see HTTP/1.1: Semantics and Content, section 6.5.13 */ UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type"), /** * {@code 416 Requested Range Not Satisfiable}. * @see HTTP/1.1: Range Requests, section 4.4 */ REQUESTED_RANGE_NOT_SATISFIABLE(416, "Requested range not satisfiable"), /** * {@code 417 Expectation Failed}. * @see HTTP/1.1: Semantics and Content, section 6.5.14 */ EXPECTATION_FAILED(417, "Expectation Failed"), /** * {@code 418 I'm a teapot}. * @see HTCPCP/1.0 */ I_AM_A_TEAPOT(418, "I'm a teapot"), /** * {@code 422 Unprocessable Entity}. * @see WebDAV */ UNPROCESSABLE_ENTITY(422, "Unprocessable Entity"), /** * {@code 423 Locked}. * @see WebDAV */ LOCKED(423, "Locked"), /** * {@code 424 Failed Dependency}. * @see WebDAV */ FAILED_DEPENDENCY(424, "Failed Dependency"), /** * {@code 426 Upgrade Required}. * @see Upgrading to TLS Within HTTP/1.1 */ UPGRADE_REQUIRED(426, "Upgrade Required"), /** * {@code 428 Precondition Required}. * @see Additional HTTP Status Codes */ PRECONDITION_REQUIRED(428, "Precondition Required"), /** * {@code 429 Too Many Requests}. * @see Additional HTTP Status Codes */ TOO_MANY_REQUESTS(429, "Too Many Requests"), /** * {@code 431 Request Header Fields Too Large}. * @see Additional HTTP Status Codes */ REQUEST_HEADER_FIELDS_TOO_LARGE(431, "Request Header Fields Too Large"), /** * {@code 451 Unavailable For Legal Reasons}. * @see * An HTTP Status Code to Report Legal Obstacles * @since 4.3 */ UNAVAILABLE_FOR_LEGAL_REASONS(451, "Unavailable For Legal Reasons"), // ---------------------------------------5xx Server Error /** * {@code 500 Internal Server Error}. * @see HTTP/1.1: Semantics and Content, section 6.6.1 */ INTERNAL_SERVER_ERROR(500, "Internal Server Error"), /** * {@code 501 Not Implemented}. * @see HTTP/1.1: Semantics and Content, section 6.6.2 */ NOT_IMPLEMENTED(501, "Not Implemented"), /** * {@code 502 Bad Gateway}. * @see HTTP/1.1: Semantics and Content, section 6.6.3 */ BAD_GATEWAY(502, "Bad Gateway"), /** * {@code 503 Service Unavailable}. * @see HTTP/1.1: Semantics and Content, section 6.6.4 */ SERVICE_UNAVAILABLE(503, "Service Unavailable"), /** * {@code 504 Gateway Timeout}. * @see HTTP/1.1: Semantics and Content, section 6.6.5 */ GATEWAY_TIMEOUT(504, "Gateway Timeout"), /** * {@code 505 HTTP Version Not Supported}. * @see HTTP/1.1: Semantics and Content, section 6.6.6 */ HTTP_VERSION_NOT_SUPPORTED(505, "HTTP Version not supported"), /** * {@code 506 Variant Also Negotiates} * @see Transparent Content Negotiation */ VARIANT_ALSO_NEGOTIATES(506, "Variant Also Negotiates"), /** * {@code 507 Insufficient Storage} * @see WebDAV */ INSUFFICIENT_STORAGE(507, "Insufficient Storage"), /** * {@code 508 Loop Detected} * @see WebDAV Binding Extensions */ LOOP_DETECTED(508, "Loop Detected"), /** * {@code 509 Bandwidth Limit Exceeded} */ BANDWIDTH_LIMIT_EXCEEDED(509, "Bandwidth Limit Exceeded"), /** * {@code 510 Not Extended} * @see HTTP Extension Framework */ NOT_EXTENDED(510, "Not Extended"), /** * {@code 511 Network Authentication Required}. * @see Additional HTTP Status Codes */ NETWORK_AUTHENTICATION_REQUIRED(511, "Network Authentication Required"); private final int code; private final String desc; private static final Map MAPPING; static { ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); for (HttpStatus status : HttpStatus.values()) { builder.put(status.code, status); } MAPPING = builder.build(); } HttpStatus(int code, String desc) { this.code = code; this.desc = desc; } /** * Return the integer value of this status code. */ public int code() { return this.code; } /** * Return the reason phrase of this status code. */ public String desc() { return this.desc; } /** * Return the HTTP status series of this status code. * @see HttpStatus.Series */ public Series series() { return Series.valueOf(this); } /** * Whether this status code is in the HTTP series * {@link org.springframework.http.HttpStatus.Series#INFORMATIONAL}. * This is a shortcut for checking the value of {@link #series()}. * @see #series() */ public boolean is1xxInformational() { return (series() == Series.INFORMATIONAL); } /** * Whether this status code is in the HTTP series * {@link org.springframework.http.HttpStatus.Series#SUCCESSFUL}. * This is a shortcut for checking the value of {@link #series()}. * @see #series() */ public boolean is2xxSuccessful() { return (series() == Series.SUCCESSFUL); } /** * Whether this status code is in the HTTP series * {@link org.springframework.http.HttpStatus.Series#REDIRECTION}. * This is a shortcut for checking the value of {@link #series()}. * @see #series() */ public boolean is3xxRedirection() { return (series() == Series.REDIRECTION); } /** * Whether this status code is in the HTTP series * {@link org.springframework.http.HttpStatus.Series#CLIENT_ERROR}. * This is a shortcut for checking the value of {@link #series()}. * @see #series() */ public boolean is4xxClientError() { return (series() == Series.CLIENT_ERROR); } /** * Whether this status code is in the HTTP series * {@link org.springframework.http.HttpStatus.Series#SERVER_ERROR}. * This is a shortcut for checking the value of {@link #series()}. * @see #series() */ public boolean is5xxServerError() { return (series() == Series.SERVER_ERROR); } /** * Return a string representation of this status code. */ @Override public String toString() { return Integer.toString(this.code); } /** * Return the enum constant of this type with the specified numeric value. * @param statusCode the numeric value of the enum to be returned * @return the enum constant with the specified numeric value * @throws IllegalArgumentException if this enum has no constant for the specified numeric value */ public static HttpStatus valueOf(int statusCode) { /*for (HttpStatus status : values()) { if (status.code == statusCode) { return status; } }*/ HttpStatus status = MAPPING.get(statusCode); if (status != null) { return status; } else { throw new IllegalArgumentException("No matching constant for [" + statusCode + "]"); } } /** * Enumeration of HTTP status series. *

Retrievable via {@link HttpStatus#series()}. */ public enum Series { INFORMATIONAL(1), // SUCCESSFUL (2), // REDIRECTION (3), // CLIENT_ERROR (4), // SERVER_ERROR (5); // private final int value; Series(int value) { this.value = value; } /** * Return the integer value of this status series. Ranges from 1 to 5. */ public int value() { return this.value; } /** * Return the enum constant of this type with the corresponding series. * @param status a standard HTTP status enum value * @return the enum constant of this type with the corresponding series * @throws IllegalArgumentException if this enum has no corresponding constant */ public static Series valueOf(HttpStatus status) { return valueOf(status.code); } /** * Return the enum constant of this type with the corresponding series. * @param statusCode the HTTP status code (potentially non-standard) * @return the enum constant of this type with the corresponding series * @throws IllegalArgumentException if this enum has no corresponding constant */ public static Series valueOf(int statusCode) { int seriesCode = statusCode / 100; for (Series series : values()) { if (series.value == seriesCode) { return series; } } throw new IllegalArgumentException("No matching constant for [" + statusCode + "]"); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/io/ByteOrderMarks.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.io; import cn.ponfee.commons.util.Enums; import org.apache.commons.io.output.ByteArrayOutputStream; import org.bouncycastle.util.Arrays; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Map; import static cn.ponfee.commons.io.CharsetDetector.DEFAULT_DETECT_LENGTH; import static cn.ponfee.commons.io.CharsetDetector.detect; /** *

 * BOM(byte order mark)是为UTF-16和UTF-32准备的,用于标记字节序(byte order)。
 * 微软在UTF-8中使用BOM是为了可以把UTF-8和ASCII等编码区分开,不含BOM的UTF-8才是标准形式
 * http://www.unicode.org/faq/utf_bom.html
 * 
 * BOM(byte-order mark) Encoding: 
 *   EF BB BF       UTF-8
 *   FF FE          UTF-16 (little-endian)
 *   FE FF          UTF-16 (big-endian)
 *   FF FE 00 00    UTF-32 (little-endian)
 *   00 00 FE FF    UTF-32 (big-endian)
 * 
 * link sun.nio.cs.StandardCharsets
 * 
* * @author Ponfee */ public enum ByteOrderMarks { UTF_8 (StandardCharsets.UTF_8 , (byte) 0xEF, (byte) 0xBB, (byte) 0xBF ), // UTF_16LE(StandardCharsets.UTF_16LE , (byte) 0xFF, (byte) 0xFE ), // UTF_16BE(StandardCharsets.UTF_16BE , (byte) 0xFE, (byte) 0xFF ), // UTF_32LE(Charset.forName("UTF-32LE"), (byte) 0xFF, (byte) 0xFE, (byte) 0x00, (byte) 0x00), // UTF_32BE(Charset.forName("UTF-32BE"), (byte) 0x00, (byte) 0x00, (byte) 0xFE, (byte) 0xFF), // ; private static final Map MAPPING = Enums.toMap(ByteOrderMarks.class, ByteOrderMarks::charset); private final Charset charset; private final byte[] bytes; ByteOrderMarks(Charset charset, byte... bytes) { this.charset = charset; this.bytes = bytes; } // ------------------------------------------------------------------------of bom without charset public static ByteOrderMarks of(String path) throws IOException { return of(new File(path)); } public static ByteOrderMarks of(File file) throws IOException { return of(Files.readByteArray(file, DEFAULT_DETECT_LENGTH)); } public static ByteOrderMarks of(InputStream input) throws IOException { return of(Files.readByteArray(input, DEFAULT_DETECT_LENGTH)); } public static ByteOrderMarks of(byte[] bytes) { return of(detect(bytes, DEFAULT_DETECT_LENGTH), bytes); } // ------------------------------------------------------------------------of bom specified charset public static ByteOrderMarks of(Charset charset, String path) throws IOException { return of(charset, new File(path)); } public static ByteOrderMarks of(Charset charset, File file) throws IOException { ByteOrderMarks bom = MAPPING.get(charset); if (bom == null) { return null; // no bom charset } return bom.match(Files.readByteArray(file, bom.length())) ? bom : null; } public static ByteOrderMarks of(Charset charset, InputStream input) throws IOException { ByteOrderMarks bom = MAPPING.get(charset); if (bom == null) { return null; // no bom charset } return bom.match(Files.readByteArray(input, bom.length())) ? bom : null; } public static ByteOrderMarks of(Charset charset, byte[] bytes) { ByteOrderMarks bom = MAPPING.get(charset); if (bom == null) { return null; // no bom charset } return bom.match(bytes) ? bom : null; } // ------------------------------------------------------------------------has bom without charset public static boolean has(String path) throws IOException { return has(new File(path)); } public static boolean has(File file) throws IOException { return has(Files.readByteArray(file, DEFAULT_DETECT_LENGTH)); } public static boolean has(InputStream input) throws IOException { return has(Files.readByteArray(input, DEFAULT_DETECT_LENGTH)); } public static boolean has(byte[] bytes) { return has(detect(bytes, DEFAULT_DETECT_LENGTH), bytes); } // ------------------------------------------------------------------------has bom specified charset public static boolean has(Charset charset, String path) throws IOException { return has(charset, new File(path)); } public static boolean has(Charset charset, File file) throws IOException { return of(charset, file) != null; } public static boolean has(Charset charset, InputStream input) throws IOException { return of(charset, input) != null; } public static boolean has(Charset charset, byte[] bytes) { return of(charset, bytes) != null; } // ------------------------------------------------------------------------add bom public static ByteOrderMarks add(String path) throws IOException { return add(null, new File(path)); } public static ByteOrderMarks add(File file) throws IOException { return add(null, file); } public static ByteOrderMarks add(Charset charset, String path) throws IOException { return add(charset, new File(path)); } public static ByteOrderMarks add(Charset charset, File file) throws IOException { try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) { byte[] headBytes; ByteOrderMarks bom; int count; if (charset == null) { headBytes = new byte[(int) Math.min(file.length(), DEFAULT_DETECT_LENGTH)]; count = raf.read(headBytes); charset = detect(headBytes, count); if ((bom = MAPPING.get(charset)) == null) { return null; // not bom charset } } else { if ((bom = MAPPING.get(charset)) == null) { return null; // not bom charset } headBytes = new byte[bom.length()]; count = raf.read(headBytes); } if (bom.match(headBytes, count)) { return bom; // already has bom } ByteArrayOutputStream tailBytes = new ByteArrayOutputStream(); byte[] buffer = new byte[Files.BUFF_SIZE]; for (int n; (n = raf.read(buffer)) != Files.EOF;) { tailBytes.write(buffer, 0, n); } raf.seek(0); // 将指针移动到文件首部 raf.write(bom.bytes); raf.write(headBytes, 0, count); raf.write(tailBytes.toByteArray()); tailBytes.close(); return bom; } } // ------------------------------------------------------------------------remove bom public static ByteOrderMarks remove(String path) throws IOException { return remove(null, new File(path)); } public static ByteOrderMarks remove(File file) throws IOException { return remove(null, file); } public static ByteOrderMarks remove(Charset charset, String path) throws IOException { return remove(charset, new File(path)); } public static ByteOrderMarks remove(Charset charset, File file) throws IOException { try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) { long length = raf.length(); byte[] headBytes; ByteOrderMarks bom; int count; if (charset == null) { headBytes = new byte[(int) Math.min(file.length(), DEFAULT_DETECT_LENGTH)]; count = raf.read(headBytes); charset = detect(headBytes, count); if ((bom = MAPPING.get(charset)) == null) { return null; // not bom charset } } else { if ((bom = MAPPING.get(charset)) == null) { return null; // not bom charset } headBytes = new byte[bom.length()]; count = raf.read(headBytes); } if (!bom.match(headBytes, count)) { return bom; // not has bom } ByteArrayOutputStream tailBytes = new ByteArrayOutputStream(); byte[] buffer = new byte[Files.BUFF_SIZE]; for (int n; (n = raf.read(buffer)) != Files.EOF;) { tailBytes.write(buffer, 0, n); } raf.seek(0); // 将指针移动到文件首部 raf.write(headBytes, bom.length(), count - bom.length()); raf.write(tailBytes.toByteArray()); raf.setLength(length - bom.length()); tailBytes.close(); return bom; } } public static byte[] get(Charset charset) { ByteOrderMarks bom = MAPPING.get(charset); return bom == null ? null : bom.bytes(); } // ------------------------------------------------------------------------public methods public Charset charset() { return this.charset; } public byte[] bytes() { return Arrays.copyOf(this.bytes, this.bytes.length); } public int length() { return this.bytes.length; } // ------------------------------------------------------------------------private methods private boolean match(byte[] array) { return match(array, array.length); } private boolean match(byte[] array, int count) { int n = this.length(); if (count < n) { return false; } for (int i = 0; i < n; i++) { if (array[i] != this.bytes[i]) { return false; } } return true; } } ================================================ FILE: src/main/java/cn/ponfee/commons/io/CharsetDetector.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.io; import cn.ponfee.commons.math.Numbers; import java.io.*; import java.net.URL; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; /** * 字符编码检测 * * @author Ponfee */ public class CharsetDetector { public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; public static final int DEFAULT_DETECT_LENGTH = 3600; public static Charset detect(String path) { return detect(path, DEFAULT_DETECT_LENGTH); } public static Charset detect(String path, int length) { return detect(new File(path), length); } public static Charset detect(File file) { return detect(file, DEFAULT_DETECT_LENGTH); } public static Charset detect(File file, int length) { try (InputStream input = new FileInputStream(file)) { return detect(input, (int) Math.min(file.length(), length)); } catch (IOException e) { throw new RuntimeException("Detect file '" + file.getPath() + "' occur error.", e); } } public static Charset detect(URL url) { return detect(url, DEFAULT_DETECT_LENGTH); } public static Charset detect(URL url, int length) { // 对于网络输入流的InputStream.available()表示对方发过来可用的数据,并不是整个流的大小 try (InputStream input = url.openStream()) { return detect(input, length); } catch (IOException e) { throw new RuntimeException("Detect url '" + url.getPath() + "' occur error.", e); } } public static Charset detect(byte[] bytes) { return detect(bytes, 0, bytes.length); } public static Charset detect(byte[] bytes, int length) { return detect(bytes, 0, length); } public static Charset detect(byte[] bytes, int offset, int length) { offset = Numbers.bounds(offset, 0, bytes.length); length = Numbers.bounds(length, 0, bytes.length - offset); try { return detect(new ByteArrayInputStream(bytes, offset, length), length); } catch (IOException e) { throw new RuntimeException("Detect byte array charset occur error.", e); } } public static Charset detect(InputStream input) throws IOException { return detect(input, DEFAULT_DETECT_LENGTH); } public static Charset detect(InputStream input, int length) throws IOException { //return cn.ponfee.commons.io.charset.CodepageDetector.detect(input, length); //return cn.ponfee.commons.io.charset.JchardetDetector.detect(input, length); //return cn.ponfee.commons.io.charset.BytesDetector.detect(input, length); return cn.ponfee.commons.io.charset.TikaDetector.detect(input, length); } } ================================================ FILE: src/main/java/cn/ponfee/commons/io/Closeables.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.io; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nullable; /** * * Close the AutoCloseable utility * * @author Ponfee */ public final class Closeables { private static final Logger LOG = LoggerFactory.getLogger(Closeables.class); /** * Close and ignore * * @param closeable the Closeable */ public static void ignore(@Nullable AutoCloseable closeable) { if (closeable != null) { try { closeable.close(); } catch (Exception ignored) { // ignored } } } /** * Close with console if occur exception * * @param closeable the Closeable */ public static void console(@Nullable AutoCloseable closeable) { if (closeable != null) { try { closeable.close(); } catch (Exception e) { e.printStackTrace(); } } } public static void log(@Nullable AutoCloseable closeable) { log(closeable, ""); } /** * Close the AutoCloseable, if occur exception then log error message * * @param closeable the Closeable * @param errMsg the error message */ public static void log(@Nullable AutoCloseable closeable, String errMsg) { if (closeable != null) { try { closeable.close(); } catch (Exception e) { LOG.error(errMsg, e); } } } } ================================================ FILE: src/main/java/cn/ponfee/commons/io/ExtendedGZIPOutputStream.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.io; import java.io.IOException; import java.io.OutputStream; import java.util.zip.Deflater; import java.util.zip.GZIPOutputStream; /** * 扩展自GZIPOutputStream的giz压缩输出流 * @author Ponfee */ public class ExtendedGZIPOutputStream extends GZIPOutputStream { public ExtendedGZIPOutputStream(OutputStream out) throws IOException { this(out, Deflater.DEFAULT_COMPRESSION); } public ExtendedGZIPOutputStream(OutputStream out, int level) throws IOException { super(out); super.def.setLevel(level); } } ================================================ FILE: src/main/java/cn/ponfee/commons/io/FileTransformer.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.io; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import java.io.File; import java.io.IOException; /** * 文件编码转换与文本内容替换 * * @author Ponfee */ public class FileTransformer { private static final int FIX_LENGTH = 85; private static final String[] CHARSETS = { "GBK", "GB2312", "UTF-8", "UTF-16", "UTF-16LE", "UTF-16BE", "UTF-32", "UTF-32LE", "UTF-32BE" }; private String includeFileExtensions = regexExtensions( "java", "txt", "properties", "xml", "sql", "html", "htm", "jsp", "css", "js", "log", "bak", "ini", "csv", "yml", "yaml" ); private final String sourcePath; private final String targetPath; private final String encoding; private final StringBuilder log = new StringBuilder(4096); private String[] searchList; private String[] replacementList; public FileTransformer(String source, String target) { this(source, target, null); } public FileTransformer(String source, String target, String encoding) { this.sourcePath = new File(source).getAbsolutePath(); this.targetPath = Files.mkdir(target).getAbsolutePath(); this.encoding = encoding; } /** * 文件后缀名,不加“.” * * @param includeFileExtensions */ public void setIncludeFileExtensions(String... includeFileExtensions) { this.includeFileExtensions = regexExtensions(includeFileExtensions); } public void setReplaceEach(String[] searchList, String[] replacementList) { this.searchList = searchList; this.replacementList = replacementList; } /** * 转换(移) */ public void transform() { transform(new File(sourcePath)); } public String getTransformLog() { return log.toString(); } private void transform(File file) { if (file == null) { // nothing to do } else if (file.isDirectory()) { File[] subfiles = file.listFiles(); if (subfiles != null) { for (File sub : subfiles) { transform(sub); } } } else { String path = file.getAbsolutePath(), charset; File dest = new File(targetPath + path.substring(sourcePath.length())); boolean isMatch = file.getName().matches(includeFileExtensions); if ( isMatch && StringUtils.isNotEmpty(encoding) && ArrayUtils.contains(CHARSETS, charset = CharsetDetector.detect(path).name().toUpperCase()) && !encoding.equalsIgnoreCase(charset) ) { log.append("转换:[").append(charset).append("]").append(StringUtils.rightPad(path, FIX_LENGTH)).append(" --> "); transform(file, dest, charset, encoding, searchList, replacementList); log.append("[").append(encoding).append("]").append(dest.getAbsolutePath()).append("\n"); } else if (isMatch && ArrayUtils.isNotEmpty(searchList)) { log.append("替换:").append(StringUtils.rightPad(path, FIX_LENGTH)).append(" --> "); transform(file, dest, searchList, replacementList); log.append(dest.getAbsolutePath()).append("\n"); } else { log.append("复制:").append(StringUtils.rightPad(path, FIX_LENGTH)).append(" --> "); transform(file, dest); log.append(dest.getAbsolutePath()).append("\n"); } } } /** * 采用nio方式转移 * * @param source * @param target */ public static void transform(File source, File target) { try { target.getParentFile().mkdirs(); //com.google.common.io.Files.copy(source, source); //sourceChannel.transferTo(0, sourceChannel.size(), targetChannel); java.nio.file.Files.copy(source.toPath(), target.toPath()); } catch (IOException e) { throw new RuntimeException(e); } } /** * @param source 源文件路径 * @param target 输出文件路径 * @param searchList the Strings to search for, no-op if null * @param replacementList the Strings to replace them with, no-op if null */ public static void transform(File source, File target, String[] searchList, String[] replacementList) { Files.touch(target); try (WrappedBufferedReader reader = new WrappedBufferedReader(source); WrappedBufferedWriter writer = new WrappedBufferedWriter(target) ) { writeln(reader, writer, searchList, replacementList); } catch (IOException e) { throw new RuntimeException(e); } } /** * Transforms the source file to target file charset * * @param source * @param target * @param fromCharset * @param toCharset */ public static void transform(File source, File target, String fromCharset, String toCharset) { transform(source, target, fromCharset, toCharset, null, null); } /** * @param source 源文件路径 * @param target 输出文件路径 * @param fromCharset 源文件编码 * @param toCharset 目标文件编码 * @param searchList the Strings to search for, no-op if null * @param replacementList the Strings to replace them with, no-op if null */ public static void transform(File source, File target, String fromCharset, String toCharset, String[] searchList, String[] replacementList) { Files.touch(target); try (WrappedBufferedReader reader = new WrappedBufferedReader(source, fromCharset); WrappedBufferedWriter writer = new WrappedBufferedWriter(target, toCharset) ) { writeln(reader, writer, searchList, replacementList); } catch (IOException e) { throw new RuntimeException(e); } } private static void writeln(WrappedBufferedReader reader, WrappedBufferedWriter writer, String[] searchList, String[] replacementList) throws IOException { String line; if (searchList != null && searchList.length > 0) { while ((line = reader.readLine()) != null) { writer.write(StringUtils.replaceEach(line, searchList, replacementList)); writer.write(Files.UNIX_LINE_SEPARATOR); } } else { while ((line = reader.readLine()) != null) { writer.write(line); writer.write(Files.UNIX_LINE_SEPARATOR); } } } private static String regexExtensions(String... fileExtensions) { return "(?i)^(.+\\.)(" + String.join("|", fileExtensions) + ")$"; } } ================================================ FILE: src/main/java/cn/ponfee/commons/io/Files.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.io; import cn.ponfee.commons.tree.PlainNode; import cn.ponfee.commons.tree.TreeNode; import cn.ponfee.commons.tree.print.MultiwayTreePrinter; import org.apache.commons.io.output.StringBuilderWriter; import java.io.*; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileChannel.MapMode; import java.nio.charset.Charset; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; /** * 文件工具类 * @author Ponfee */ public final class Files { public static final int EOF = -1; // end of file read public static final int BUFF_SIZE = 8192; // file buffer size public static final String TOP_PATH = ".."; public static final String CURRENT_PATH = "."; // ------------------------------------------------------------charset encoding public static final Charset DEFAULT_CHARSET = Charset.defaultCharset(); // default charset public static final String DEFAULT_CHARSET_NAME = DEFAULT_CHARSET.name(); // default charset name public static final String UTF_8 = "UTF-8"; // UTF-8 encoding // ------------------------------------------------------------file separator public static final String WINDOWS_FOLDER_SEPARATOR = "\\"; public static final String UNIX_FOLDER_SEPARATOR = "/"; public static final String SYSTEM_FOLDER_SEPARATOR = File.separator; // ------------------------------------------------------------line separator public static final String UNIX_LINE_SEPARATOR = "\n"; // unix file line separator spec \n LF public static final String WINDOWS_LINE_SEPARATOR = "\r\n"; // windows file line separator spec \r\n CRLF public static final String MAC_LINE_SEPARATOR = "\r"; // mac file line separator spec \r CR public static final String SYSTEM_LINE_SEPARATOR; // system file line separator static { /* String separator = java.security.AccessController.doPrivileged( new sun.security.action.GetPropertyAction("line.separator") ); if (separator == null || separator.length() == 0) { separator = System.getProperty("line.separator", "\n"); } SYSTEM_LINE_SEPARATOR = separator; */ StringBuilderWriter buffer = new StringBuilderWriter(4); PrintWriter out = new PrintWriter(buffer); out.println(); out.flush(); SYSTEM_LINE_SEPARATOR = buffer.toString(); out.close(); } /** * 创建目录 * @param filePath * @return */ public static File mkdir(String filePath) { File file = new File(filePath); mkdir(file); return file; } /** * 创建目录 * * @param file * @return */ public static void mkdir(File file) { if (file.isFile()) { throw new IllegalStateException(file.getAbsolutePath() + " is a directory."); } if (file.exists()) { return; } if (file.mkdirs()) { file.setLastModified(System.currentTimeMillis()); } } /** * 创建文件 * @param file * @return */ public static void touch(File file) { if (file.isDirectory()) { throw new IllegalStateException(file.getAbsolutePath() + " is a directory."); } if (file.exists()) { return; } if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } try { if (file.createNewFile()) { file.setLastModified(System.currentTimeMillis()); } } catch (IOException e) { throw new IllegalStateException(e); } } // --------------------------------------------------------------------------file to string public static String toString(File file) throws IOException { return toString(file, CharsetDetector.detect(file)); } public static String toString(File file, Charset charset) throws IOException { ByteOrderMarks bom = ByteOrderMarks.of(charset, file); try (FileInputStream input = new FileInputStream(file); FileChannel channel = input.getChannel() ) { //FileLock lock = channel.lock(); //lock.release(); long offset = 0, length = channel.size(); if (bom != null) { offset = bom.length(); length -= offset; } ByteBuffer buffer = channel.map(MapMode.READ_ONLY, offset, length); return charset.decode(buffer).toString(); } catch (IOException e) { throw new RuntimeException(e); } } /** * Reads file to byte array * * @param file * @return */ public static byte[] toByteArray(File file) { try (FileInputStream in = new FileInputStream(file); FileChannel channel = in.getChannel() ) { ByteBuffer buffer = channel.map(MapMode.READ_ONLY, 0, channel.size()); byte[] bytes = new byte[buffer.capacity()]; buffer.get(bytes, 0, bytes.length); return bytes; } catch (IOException e) { throw new RuntimeException(e); } } // -----------------------------------------------------------------readByteArray public static byte[] readByteArray(String filePath, int count) throws IOException { return readByteArray(new File(filePath), count); } public static byte[] readByteArray(File file, int count) throws IOException { try (InputStream input = new FileInputStream(file)) { return readByteArray(input, count); } } public static byte[] readByteArray(InputStream input, int count) throws IOException { byte[] bytes = new byte[count]; int n, index = 0; while (index < count && (n = input.read(bytes, index, count - index)) != EOF) { index += n; } return (index == count) ? bytes : Arrays.copyOf(bytes, index); } public static TreeNode listFiles(String filePath) { return listFiles(new File(filePath)); } /** * 递归列出所有文件 * * @param file * @return */ public static TreeNode listFiles(File file) { List> files = new LinkedList<>(); AtomicInteger counter = new AtomicInteger(1); Integer dummyRootPid = 0; Deque> stack = new LinkedList<>(); stack.push(new PlainNode<>(counter.getAndIncrement(), dummyRootPid, file)); while (!stack.isEmpty()) { PlainNode node = stack.pop(); files.add(node); if (node.getAttach().isDirectory()) { Arrays.stream(node.getAttach().listFiles()) .sorted(Comparator.comparing(File::getName)) .forEach(f -> stack.push(new PlainNode<>(counter.getAndIncrement(), node.getNid(), f))); } } return TreeNode.builder(dummyRootPid).build().mount(files).getChildren().get(0); } public static String tree(String filePath) throws IOException { return tree(new File(filePath)); } /** * 打印文件树 * * @param file * @return * @throws IOException */ public static String tree(File file) throws IOException { StringBuilder builder = new StringBuilder(); new MultiwayTreePrinter<>(builder, File::getName, f -> f.isDirectory() ? Arrays.asList(f.listFiles()) : null).print(file); return builder.toString(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/io/GzipProcessor.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.io; import org.apache.commons.io.IOUtils; import java.io.*; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; /** * gzip压缩/解压缩处理 * @author Ponfee */ public final class GzipProcessor { static final int BYTE_SIZE = 512; /** * gzip压缩 * @param data * @return */ public static byte[] compress(byte[] data) { ByteArrayOutputStream baos = new ByteArrayOutputStream(BYTE_SIZE); compress(data, baos); return baos.toByteArray(); } /** * gzip压缩 * @param data * @param output */ public static void compress(byte[] data, OutputStream output) { try (GZIPOutputStream gzout = new ExtendedGZIPOutputStream(output)) { gzout.write(data); gzout.flush(); gzout.finish(); } catch (IOException e) { throw new RuntimeException(e); } } /** * gzip压缩 * @param input * @param output */ public static long compress(InputStream input, OutputStream output) { try (GZIPOutputStream gzout = new ExtendedGZIPOutputStream(output)) { long size = IOUtils.copyLarge(input, gzout); gzout.flush(); gzout.finish(); return size; } catch (IOException e) { throw new RuntimeException(e); } } /** * gzip解压缩 * @param data * @return */ public static byte[] decompress(byte[] data) { ByteArrayOutputStream baos = new ByteArrayOutputStream(BYTE_SIZE); decompress(new ByteArrayInputStream(data), baos); return baos.toByteArray(); } public static void decompress(byte[] data, OutputStream output) { decompress(new ByteArrayInputStream(data), output); } /** * gzip解压缩 * @param input * @param output */ public static void decompress(InputStream input, OutputStream output) { try (GZIPInputStream gzin = new GZIPInputStream(input)) { IOUtils.copyLarge(gzin, output); } catch (IOException e) { throw new RuntimeException(e); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/io/HumanReadables.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.io; import java.text.DecimalFormat; import java.text.ParseException; import java.util.Arrays; import java.util.regex.Pattern; /** * The file size human readable utility class, * provide mutual conversions from human readable size to byte size

* * The similar function in stackoverflow, linked: * https://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java?r=SearchResults

* * Apache also provide similar function * {@link org.apache.commons.io.FileUtils#byteCountToDisplaySize(long)}

* * spring-core 5.x: org.springframework.util.unit.DataSize * * @author Ponfee */ public enum HumanReadables { SI (1000, "B", "KB", "MB", "GB", "TB", "PB", "EB" /*, "ZB", "YB" */), // BINARY(1024, "B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"/*, "ZiB", "YiB"*/), // ; private static final String FORMAT = "#,##0.##"; private static final Pattern PATTERN = Pattern.compile(".*[0-9]+.*"); private final int base; private final String[] units; private final long[] sizes; HumanReadables(int base, String... units) { this.base = base; this.units = units; this.sizes = new long[this.units.length]; this.sizes[0] = 1L; for (int i = 1; i < this.sizes.length; i++) { this.sizes[i] = this.sizes[i - 1] * base; // Maths.pow(base, i); } } /** * Returns a string of bytes count human-readable size * * @param size the size * @return string of human-readable data size */ public strictfp String human(long size) { if (size == 0) { return "0 " + this.units[0]; } String sign = ""; if (size < 0) { sign = "-"; size = size == Long.MIN_VALUE ? Long.MAX_VALUE : -size; } /*int unit = (int) Maths.log(size, this.base); return sign + new DecimalFormat(FORMAT).format(size / Math.pow(this.base, unit)) + " " + this.units[unit];*/ int unit = find(size); return new StringBuilder(13) // 13 max length like as "-1,023.45 GiB" .append(sign) .append(new DecimalFormat(FORMAT).format(size / (double) this.sizes[unit])) .append(" ") .append(this.units[unit]) .toString(); } public long parse(String size) { return parse(size, false); } /** * Parse the readable byte count, allowed suffix units: "1", "1 B", "1 MB", "1 MiB", "1 M" * * @param size the size * @param strict the strict, if BINARY then verify whether contains "i" * @return a long value bytes count */ public strictfp long parse(String size, boolean strict) { if (size == null || size.isEmpty()) { return 0L; } if (!PATTERN.matcher(size).matches()) { throw new IllegalArgumentException("Invalid format [" + size + "]"); } String value = size = size.trim(); long factor = this.sizes[0]; int sign = 1; switch (value.charAt(0)) { case '+': value = value.substring(1); break; case '-': value = value.substring(1); sign = -1; break; default : /* Nothing to do */ break; } int end = 0, lastPos = value.length() - 1; // last character isn't a digit char c = value.charAt(lastPos - end); if (c == 'i') { // last pos cannot end with "i" throw new IllegalArgumentException("Invalid format [" + size + "], cannot end with \"i\"."); } if (c == 'B') { end++; c = value.charAt(lastPos - end); boolean flag = isBlank(c); while (isBlank(c) && end < lastPos) { end++; c = value.charAt(lastPos - end); } // if "B" head has space char, then the first head non space char must be a digit if (flag && !Character.isDigit(c)) { throw new IllegalArgumentException("Invalid format [" + size + "]: \"" + c + "\"."); } } if (!Character.isDigit(c)) { // if not a digit character, then assume is a unit character if (c == 'i') { if (this == SI) { // SI cannot contains "i" throw new IllegalArgumentException("Invalid SI format [" + size + "], cannot contains \"i\"."); } end++; c = value.charAt(lastPos - end); } else { if (this == BINARY && strict) { // if strict, then BINARY must contains "i" throw new IllegalArgumentException("Invalid BINARY format [" + size + "], miss character \"i\"."); } } switch (c) { case 'K': factor = this.sizes[1]; break; case 'M': factor = this.sizes[2]; break; case 'G': factor = this.sizes[3]; break; case 'T': factor = this.sizes[4]; break; case 'P': factor = this.sizes[5]; break; case 'E': factor = this.sizes[6]; break; /* case 'Z': factor = this.bytes[7]; break; case 'Y': factor = this.bytes[8]; break; */ default: throw new IllegalArgumentException("Invalid format [" + size + "]: \"" + c + "\"."); } do { end++; c = value.charAt(lastPos - end); } while (isBlank(c) && end < lastPos); } value = value.substring(0, value.length() - end); try { return sign * (long) (factor * new DecimalFormat(FORMAT).parse(value).doubleValue()); } catch (NumberFormatException | ParseException e) { throw new IllegalArgumentException("Failed to parse [" + size + "]: \"" + value + "\"."); } } public int base() { return this.base; } public String[] units() { return Arrays.copyOf(this.units, this.units.length); } public long[] sizes() { return Arrays.copyOf(this.sizes, this.sizes.length); } // --------------------------------------------------------------private methods private int find(long bytes) { int n = this.sizes.length; for (int i = 1; i < n; i++) { if (bytes < this.sizes[i]) { return i - 1; } } return n - 1; } private boolean isBlank(char c) { return c == ' ' || c == '\t'; } } ================================================ FILE: src/main/java/cn/ponfee/commons/io/PrereadInputStream.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.io; import java.io.IOException; import java.io.InputStream; /** * 预先第一次读取InputStream的数据用来判断文件类型,文件编码等用途 * * @author Ponfee */ public class PrereadInputStream extends InputStream { private final InputStream input; private final byte[] heads; private final int limit; private int offset; public PrereadInputStream(InputStream input, int maxCount) throws IOException { this.input = input; this.heads = Files.readByteArray(input, maxCount); this.offset = 0; this.limit = this.heads.length; } /** * 读取下一个可用的字节数据,如果到达流的末尾而没有字节可用,则返回值-1 */ @Override public int read() throws IOException { if (this.offset < this.limit) { return this.heads[this.offset]; } else { return this.input.read(); } } @Override public int read(byte[] buf, int off, int len) throws IOException { int remaining = this.limit - this.offset; if (remaining > 0) { int count = len - off; if (remaining >= count) { System.arraycopy(this.heads, this.offset, buf, off, len); this.offset += count; return count; } else { System.arraycopy(this.heads, this.offset, buf, off, remaining); int cnt = this.input.read(buf, off + remaining, len - remaining); this.offset = this.limit; return cnt == -1 ? remaining : remaining + cnt; } } else { return this.input.read(buf, off, len); } } @Override public int read(byte[] buf) throws IOException { return read(buf, 0, buf.length); } public byte[] heads() { return this.heads; } @Override public long skip(long n) throws IOException { int remaining = this.limit - this.offset; if (remaining <= 0) { return this.input.skip(n); } else if (remaining > n) { this.offset += n; return n; } else { this.offset = this.limit; return this.input.skip(n - remaining) + remaining; } } @Override public int available() throws IOException { return this.input.available() + this.limit - this.offset; } @Override public void close() throws IOException { this.input.close(); this.offset = this.limit; } @Override @Deprecated public synchronized void mark(int readLimit) { throw new UnsupportedOperationException("mark/reset not supported"); } @Override public synchronized void reset() throws IOException { throw new IOException("mark/reset not supported"); } @Override public boolean markSupported() { return false; } } ================================================ FILE: src/main/java/cn/ponfee/commons/io/StringPrintWriter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.io; import java.io.PrintWriter; import java.io.StringWriter; /** * 字符串输出类 * @see java.io.StringWriter * @author Ponfee */ public class StringPrintWriter extends PrintWriter { public StringPrintWriter() { super(new StringWriter()); } public StringPrintWriter(int initialSize) { super(new StringWriter(initialSize)); } public String getString() { flush(); return super.out.toString(); } @Override public String toString() { return getString(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/io/WrappedBufferedReader.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.io; import java.io.*; import java.nio.CharBuffer; import java.nio.charset.Charset; /** * 包装文件/输入流缓冲读取(decorator) * * @author Ponfee */ public class WrappedBufferedReader extends Reader { private BufferedReader buffer; public WrappedBufferedReader(File file) throws FileNotFoundException { this(file, Charset.defaultCharset()); } public WrappedBufferedReader(File file, String charset) throws FileNotFoundException { this(new FileInputStream(file), Charset.forName(charset)); } public WrappedBufferedReader(File file, Charset charset) throws FileNotFoundException { this(new FileInputStream(file), charset); } public WrappedBufferedReader(InputStream input, Charset charset) { this.buffer = new BufferedReader( new InputStreamReader(input, charset), Files.BUFF_SIZE ); } @Override public void close() { Closeables.console(buffer); buffer = null; } @Override public int read(char[] cbuf, int off, int len) throws IOException { return buffer.read(cbuf, off, len); } @Override public int read(CharBuffer target) throws IOException { return buffer.read(target); } @Override public int read() throws IOException { return buffer.read(); } @Override public int read(char[] cbuf) throws IOException { return buffer.read(cbuf); } @Override public long skip(long n) throws IOException { return buffer.skip(n); } @Override public boolean ready() throws IOException { return buffer.ready(); } @Override public boolean markSupported() { return buffer.markSupported(); } @Override public void mark(int readAheadLimit) throws IOException { buffer.mark(readAheadLimit); } @Override public void reset() throws IOException { buffer.reset(); } public String readLine() throws IOException { return buffer.readLine(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/io/WrappedBufferedWriter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.io; import java.io.*; import java.nio.charset.Charset; /** * 包装文件/输入流缓冲写入(decorator) * * @author Ponfee */ public class WrappedBufferedWriter extends Writer { private OutputStream output; private BufferedWriter buffer; public WrappedBufferedWriter(File file) throws FileNotFoundException { this(file, Charset.defaultCharset()); } public WrappedBufferedWriter(File file, String charset) throws FileNotFoundException { this(new FileOutputStream(file), Charset.forName(charset)); } public WrappedBufferedWriter(File file, Charset charset) throws FileNotFoundException { this(new FileOutputStream(file), charset); } public WrappedBufferedWriter(OutputStream output, Charset charset) { this.output = output; this.buffer = new BufferedWriter( new OutputStreamWriter(output, charset), Files.BUFF_SIZE ); } @Override public void write(String str) throws IOException { buffer.write(str); } @Override public void write(int c) throws IOException { buffer.write(c); } @Override public void write(char[] cbuf) throws IOException { buffer.write(cbuf); } @Override public void write(String str, int off, int len) throws IOException { buffer.write(str, off, len); } @Override public Writer append(CharSequence csq) throws IOException { return buffer.append(csq); } @Override public Writer append(CharSequence csq, int start, int end) throws IOException { return buffer.append(csq, start, end); } @Override public Writer append(char c) throws IOException { return buffer.append(c); } @Override public void flush() throws IOException { buffer.flush(); } @Override public void close() { Closeables.console(buffer); buffer = null; output = null; } @Override public void write(char[] cbuf, int off, int len) throws IOException { buffer.write(cbuf, off, len); } public void newLine() throws IOException { buffer.newLine(); } public void write(byte[] bytes) throws IOException { output.write(bytes); } public void writeln() throws IOException { newLine(); } public void writeln(String str) throws IOException { synchronized (super.lock) { buffer.write(str); writeln(); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/io/charset/BytesDetector.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.io.charset; import cn.ponfee.commons.io.CharsetDetector; import cn.ponfee.commons.io.Files; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; /** * Byte array charset detector * * @author Ponfee */ public class BytesDetector { private static final Logger LOG = LoggerFactory.getLogger(BytesDetector.class); public static Charset detect(InputStream input, int length) throws IOException { String charset = detect(Files.readByteArray(input, length)); return charset == null ? CharsetDetector.DEFAULT_CHARSET : Charset.forName(charset); } public static String detect(byte[] rawtext) { int[] scores = new int[Encoding.TOTAL_TYPES]; // Assign Scores scores[Encoding.GB2312] = gb2312_probability(rawtext) ; scores[Encoding.GBK] = gbk_probability(rawtext) ; scores[Encoding.GB18030] = gb18030_probability(rawtext) ; scores[Encoding.HZ] = hz_probability(rawtext) ; scores[Encoding.BIG5] = big5_probability(rawtext) ; scores[Encoding.CNS11643] = euc_tw_probability(rawtext) ; scores[Encoding.UTF8] = utf8_probability(rawtext) ; scores[Encoding.UTF8T] = 0 ; scores[Encoding.UTF8S] = 0 ; scores[Encoding.UNICODE] = utf16_probability(rawtext) ; scores[Encoding.UNICODET] = 0 ; scores[Encoding.UNICODES] = 0 ; scores[Encoding.ISO2022CN] = iso_2022_cn_probability(rawtext); scores[Encoding.ISO2022CN_CNS] = 0 ; scores[Encoding.ISO2022CN_GB] = 0 ; scores[Encoding.EUC_KR] = euc_kr_probability(rawtext) ; scores[Encoding.CP949] = cp949_probability(rawtext) ; scores[Encoding.ISO2022KR] = iso_2022_kr_probability(rawtext); scores[Encoding.JOHAB] = 0 ; scores[Encoding.SJIS] = sjis_probability(rawtext) ; scores[Encoding.EUC_JP] = euc_jp_probability(rawtext) ; scores[Encoding.ISO2022JP] = iso_2022_jp_probability(rawtext); scores[Encoding.ASCII] = ascii_probability(rawtext) ; // Tabulate Scores int maxScore = 0, encodingGuess = -1; for (int i = 0; i < scores.length; i++) { LOG.debug("Encoding {} score {}", Encoding.JAVA_CHARSET[i], scores[i]); if (scores[i] > maxScore) { encodingGuess = i; maxScore = scores[i]; } } // Not guessed if nothing scored above 50 return maxScore > 50 ? Encoding.JAVA_CHARSET[encodingGuess] : null; } /* * Function: gb2312_probability Argument: pointer to byte array Returns : number from 0 to 100 representing * probability text in array uses GB-2312 encoding */ private static int gb2312_probability(byte[] rawtext) { int dbchars = 1, gbchars = 1; long gbfreq = 0, totalfreq = 1; // Stage 1: Check to see if characters fit into acceptable ranges for (int i = 0, row, column, n = rawtext.length - 1; i < n; i++) { // System.err.println(rawtext[i]); if (rawtext[i] >= 0) { // asciichars++; } else { dbchars++; if ((byte) 0xA1 <= rawtext[i] && rawtext[i] <= (byte) 0xF7 && (byte) 0xA1 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0xFE) { gbchars++; totalfreq += 500; row = rawtext[i] + 256 - 0xA1; column = rawtext[i + 1] + 256 - 0xA1; if (GB_FREQ[row][column] != 0) { gbfreq += GB_FREQ[row][column]; } else if (15 <= row && row < 55) { // In GB high-freq character range gbfreq += 200; } } i++; } } float rangeval = 50 * ((float) gbchars / (float) dbchars); float freqval = 50 * ((float) gbfreq / (float) totalfreq); return (int) (rangeval + freqval); } /* * Function: gbk_probability Argument: pointer to byte array Returns : number from 0 to 100 representing * probability text in array uses GBK encoding */ private static int gbk_probability(byte[] rawtext) { int dbchars = 1, gbchars = 1; long gbfreq = 0, totalfreq = 1; // Stage 1: Check to see if characters fit into acceptable ranges for (int i = 0, row, column, n = rawtext.length - 1; i < n; i++) { // System.err.println(rawtext[i]); if (rawtext[i] >= 0) { // asciichars++; } else { dbchars++; if ((byte) 0xA1 <= rawtext[i] && rawtext[i] <= (byte) 0xF7 && // Original GB range (byte) 0xA1 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0xFE) { gbchars++; totalfreq += 500; row = rawtext[i] + 256 - 0xA1; column = rawtext[i + 1] + 256 - 0xA1; // System.out.println("original row " + row + " column " + // column); if (GB_FREQ[row][column] != 0) { gbfreq += GB_FREQ[row][column]; } else if (15 <= row && row < 55) { gbfreq += 200; } } else if ((byte) 0x81 <= rawtext[i] && rawtext[i] <= (byte) 0xFE && // Extended GB range (((byte) 0x80 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0xFE) || ((byte) 0x40 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0x7E))) { gbchars++; totalfreq += 500; row = rawtext[i] + 256 - 0x81; if (0x40 <= rawtext[i + 1] && rawtext[i + 1] <= 0x7E) { column = rawtext[i + 1] - 0x40; } else { column = rawtext[i + 1] + 256 - 0x40; } // System.out.println("extended row " + row + " column " + // column + " rawtext[i] " + rawtext[i]); if (GBK_FREQ[row][column] != 0) { gbfreq += GBK_FREQ[row][column]; } } i++; } } float rangeval = 50 * ((float) gbchars / (float) dbchars); float freqval = 50 * ((float) gbfreq / (float) totalfreq); // For regular GB files, this would give the same score, so I handicap // it slightly return (int) (rangeval + freqval) - 1; } /* * Function: gb18030_probability Argument: pointer to byte array Returns : number from 0 to 100 representing * probability text in array uses GBK encoding */ private static int gb18030_probability(byte[] rawtext) { int dbchars = 1, gbchars = 1; long gbfreq = 0, totalfreq = 1; // Stage 1: Check to see if characters fit into acceptable ranges for (int i = 0, row, column, n = rawtext.length - 1; i < n; i++) { // System.err.println(rawtext[i]); if (rawtext[i] >= 0) { // asciichars++; } else { dbchars++; if ((byte) 0xA1 <= rawtext[i] && rawtext[i] <= (byte) 0xF7 && // Original GB range i + 1 < rawtext.length && (byte) 0xA1 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0xFE) { gbchars++; totalfreq += 500; row = rawtext[i] + 256 - 0xA1; column = rawtext[i + 1] + 256 - 0xA1; // System.out.println("original row " + row + " column " + // column); if (GB_FREQ[row][column] != 0) { gbfreq += GB_FREQ[row][column]; } else if (15 <= row && row < 55) { gbfreq += 200; } } else if ((byte) 0x81 <= rawtext[i] && rawtext[i] <= (byte) 0xFE && // Extended GB range i + 1 < rawtext.length && (((byte) 0x80 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0xFE) || ((byte) 0x40 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0x7E))) { gbchars++; totalfreq += 500; row = rawtext[i] + 256 - 0x81; if (0x40 <= rawtext[i + 1] && rawtext[i + 1] <= 0x7E) { column = rawtext[i + 1] - 0x40; } else { column = rawtext[i + 1] + 256 - 0x40; } // System.out.println("extended row " + row + " column " + // column + " rawtext[i] " + rawtext[i]); if (GBK_FREQ[row][column] != 0) { gbfreq += GBK_FREQ[row][column]; } } else if ((byte) 0x81 <= rawtext[i] && rawtext[i] <= (byte) 0xFE && // Extended GB range i + 3 < rawtext.length && (byte) 0x30 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0x39 && (byte) 0x81 <= rawtext[i + 2] && rawtext[i + 2] <= (byte) 0xFE && (byte) 0x30 <= rawtext[i + 3] && rawtext[i + 3] <= (byte) 0x39) { gbchars++; /* * totalfreq += 500; row = rawtext[i] + 256 - 0x81; if (0x40 <= rawtext[i+1] && rawtext[i+1] <= * 0x7E) { column = rawtext[i+1] - 0x40; } else { column = rawtext[i+1] + 256 - 0x40; } * //System.out.println("extended row " + row + " column " + column + " rawtext[i] " + * rawtext[i]); if (GBKFreq[row][column] != 0) { gbfreq += GBKFreq[row][column]; } */ } i++; } } float rangeval = 50 * ((float) gbchars / (float) dbchars); float freqval = 50 * ((float) gbfreq / (float) totalfreq); // For regular GB files, this would give the same score, so I handicap // it slightly return (int) (rangeval + freqval) - 1; } /* * Function: hz_probability Argument: byte array Returns : number from 0 to 100 representing probability text in * array uses HZ encoding */ private static int hz_probability(byte[] rawtext) { long hzfreq = 0, totalfreq = 1; int hzstart = 0; for (int i = 0, row, column, n = rawtext.length - 1; i < rawtext.length; i++) { if (rawtext[i] == '~') { if (rawtext[i + 1] == '{') { hzstart++; i += 2; while (i < n) { if (rawtext[i] == 0x0A || rawtext[i] == 0x0D) { break; } else if (rawtext[i] == '~' && rawtext[i + 1] == '}') { i++; break; } else if ((0x21 <= rawtext[i] && rawtext[i] <= 0x77) && (0x21 <= rawtext[i + 1] && rawtext[i + 1] <= 0x77)) { row = rawtext[i] - 0x21; column = rawtext[i + 1] - 0x21; totalfreq += 500; if (GB_FREQ[row][column] != 0) { hzfreq += GB_FREQ[row][column]; } else if (15 <= row && row < 55) { hzfreq += 200; } } else if (between(rawtext[i]) && between(rawtext[i + 1])) { row = rawtext[i] + 256 - 0xA1; column = rawtext[i + 1] + 256 - 0xA1; totalfreq += 500; if (GB_FREQ[row][column] != 0) { hzfreq += GB_FREQ[row][column]; } else if (15 <= row && row < 55) { hzfreq += 200; } } i += 2; } } else if (rawtext[i + 1] == '}') { i++; } else if (rawtext[i + 1] == '~') { i++; } } } float rangeval; if (hzstart > 4) { rangeval = 50; } else if (hzstart > 1) { rangeval = 41; } else if (hzstart > 0) { // Only 39 in case the sequence happened to // occur rangeval = 39; // in otherwise non-Hz text } else { rangeval = 0; } float freqval = 50 * ((float) hzfreq / (float) totalfreq); return (int) (rangeval + freqval); } private static boolean between(byte b) { return (byte) 0xA1 <= b && (byte) 0xF7 >= b; } /** * Function: big5_probability Argument: byte array Returns : number from 0 to 100 representing probability text * in array uses Big5 encoding */ private static int big5_probability(byte[] rawtext) { int dbchars = 1, bfchars = 1; long bffreq = 0, totalfreq = 1; // Check to see if characters fit into acceptable ranges for (int i = 0, row, column, n = rawtext.length - 1; i < n; i++) { if (rawtext[i] >= 0) { // asciichars++; } else { dbchars++; if ((byte) 0xA1 <= rawtext[i] && rawtext[i] <= (byte) 0xF9 && (((byte) 0x40 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0x7E) || ((byte) 0xA1 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0xFE))) { bfchars++; totalfreq += 500; row = rawtext[i] + 256 - 0xA1; if (0x40 <= rawtext[i + 1] && rawtext[i + 1] <= 0x7E) { column = rawtext[i + 1] - 0x40; } else { column = rawtext[i + 1] + 256 - 0x61; } if (BIG5_FREQ[row][column] != 0) { bffreq += BIG5_FREQ[row][column]; } else if (3 <= row && row <= 37) { bffreq += 200; } } i++; } } float rangeval = 50 * ((float) bfchars / (float) dbchars); float freqval = 50 * ((float) bffreq / (float) totalfreq); return (int) (rangeval + freqval); } /* * Function: euc_tw_probability Argument: byte array Returns : number from 0 to 100 representing probability * text in array uses EUC-TW (CNS 11643) encoding */ private static int euc_tw_probability(byte[] rawtext) { int dbchars = 1, cnschars = 1; long cnsfreq = 0, totalfreq = 1; // Check to see if characters fit into acceptable ranges // and have expected frequency of use for (int i = 0, row, column, n = rawtext.length - 1; i < n; i++) { if (rawtext[i] >= 0) { // in ASCII range // asciichars++; } else { // high bit set dbchars++; if (i + 3 < rawtext.length && (byte) 0x8E == rawtext[i] && (byte) 0xA1 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0xB0 && (byte) 0xA1 <= rawtext[i + 2] && rawtext[i + 2] <= (byte) 0xFE && (byte) 0xA1 <= rawtext[i + 3] && rawtext[i + 3] <= (byte) 0xFE) { // Planes 1 - 16 cnschars++; // System.out.println("plane 2 or above CNS char"); // These are all less frequent chars so just ignore freq i += 3; } else if ((byte) 0xA1 <= rawtext[i] && rawtext[i] <= (byte) 0xFE && // Plane 1 (byte) 0xA1 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0xFE) { cnschars++; totalfreq += 500; row = rawtext[i] + 256 - 0xA1; column = rawtext[i + 1] + 256 - 0xA1; if (EUC_TW_FREQ[row][column] != 0) { cnsfreq += EUC_TW_FREQ[row][column]; } else if (35 <= row && row <= 92) { cnsfreq += 150; } i++; } } } float rangeval = 50 * ((float) cnschars / (float) dbchars); float freqval = 50 * ((float) cnsfreq / (float) totalfreq); return (int) (rangeval + freqval); } /* * Function: iso_2022_cn_probability Argument: byte array Returns : number from 0 to 100 representing * probability text in array uses ISO 2022-CN encoding WORKS FOR BASIC CASES, BUT STILL NEEDS MORE WORK */ private static int iso_2022_cn_probability(byte[] rawtext) { int dbchars = 1, isochars = 1; long isofreq = 0, totalfreq = 1; // Check to see if characters fit into acceptable ranges // and have expected frequency of use for (int i = 0, row, column, n = rawtext.length - 1; i < n; i++) { if (rawtext[i] == (byte) 0x1B && i + 3 < rawtext.length) { // Escape // char ESC if (rawtext[i + 1] == (byte) 0x24 && rawtext[i + 2] == 0x29 && rawtext[i + 3] == (byte) 0x41) { // GB // Escape // $ // ) // A i += 4; while (rawtext[i] != (byte) 0x1B) { dbchars++; if ((0x21 <= rawtext[i] && rawtext[i] <= 0x77) && (0x21 <= rawtext[i + 1] && rawtext[i + 1] <= 0x77)) { isochars++; row = rawtext[i] - 0x21; column = rawtext[i + 1] - 0x21; totalfreq += 500; if (GB_FREQ[row][column] != 0) { isofreq += GB_FREQ[row][column]; } else if (15 <= row && row < 55) { isofreq += 200; } i++; } i++; } } else if (i + 3 < rawtext.length && rawtext[i + 1] == (byte) 0x24 && rawtext[i + 2] == (byte) 0x29 && rawtext[i + 3] == (byte) 0x47) { // CNS Escape $ ) G i += 4; while (rawtext[i] != (byte) 0x1B) { dbchars++; if ((byte) 0x21 <= rawtext[i] && rawtext[i] <= (byte) 0x7E && (byte) 0x21 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0x7E) { isochars++; totalfreq += 500; row = rawtext[i] - 0x21; column = rawtext[i + 1] - 0x21; if (EUC_TW_FREQ[row][column] != 0) { isofreq += EUC_TW_FREQ[row][column]; } else if (35 <= row && row <= 92) { isofreq += 150; } i++; } i++; } } if (rawtext[i] == (byte) 0x1B && i + 2 < rawtext.length && rawtext[i + 1] == (byte) 0x28 && rawtext[i + 2] == (byte) 0x42) { // ASCII: // ESC // ( B i += 2; } } } float rangeval = 50 * ((float) isochars / (float) dbchars); float freqval = 50 * ((float) isofreq / (float) totalfreq); // System.out.println("isochars dbchars isofreq totalfreq " + isochars + // " " + dbchars + " " + isofreq + " " + totalfreq + " // " + rangeval + " " + freqval); return (int) (rangeval + freqval); // return 0; } /* * Function: utf8_probability Argument: byte array Returns : number from 0 to 100 representing probability text * in array uses UTF-8 encoding of Unicode */ private static int utf8_probability(byte[] rawtext) { int goodbytes = 0, asciibytes = 0; // Maybe also use UTF8 Byte Order Mark: EF BB BF // Check to see if characters fit into acceptable ranges for (int i = 0; i < rawtext.length; i++) { if ((rawtext[i] & (byte) 0x7F) == rawtext[i]) { // One byte asciibytes++; // Ignore ASCII, can throw off count } else if (-64 <= rawtext[i] && rawtext[i] <= -33 && // Two bytes i + 1 < rawtext.length && -128 <= rawtext[i + 1] && rawtext[i + 1] <= -65) { goodbytes += 2; i++; } else if (-32 <= rawtext[i] && rawtext[i] <= -17 && // Three bytes i + 2 < rawtext.length && -128 <= rawtext[i + 1] && rawtext[i + 1] <= -65 && -128 <= rawtext[i + 2] && rawtext[i + 2] <= -65) { goodbytes += 3; i += 2; } } if (asciibytes == rawtext.length) { return 0; } int score = (int) (100 * ((float) goodbytes / (float) (rawtext.length - asciibytes))); // System.out.println("rawtextlen " + rawtextlen + " goodbytes " + // goodbytes + " asciibytes " + asciibytes + " score " + // score); // If not above 98, reduce to zero to prevent coincidental matches // Allows for some (few) bad formed sequences if (score > 98) { return score; } else if (score > 95 && goodbytes > 30) { return score; } else { return 0; } } /* * Function: utf16_probability Argument: byte array Returns : number from 0 to 100 representing probability text * in array uses UTF-16 encoding of Unicode, guess based on BOM // NOT VERY GENERAL, NEEDS MUCH MORE WORK */ private static int utf16_probability(byte[] rawtext) { // int score = 0; // int i, rawtextlen = 0; // int goodbytes = 0, asciibytes = 0; if (rawtext.length > 1 && ((byte) 0xFE == rawtext[0] && (byte) 0xFF == rawtext[1]) || // Big-endian ((byte) 0xFF == rawtext[0] && (byte) 0xFE == rawtext[1])) { // Little-endian return 100; } return 0; /* * // Check to see if characters fit into acceptable ranges rawtextlen = rawtext.length; for (i = 0; i < * rawtextlen; i++) { if ((rawtext[i] & (byte)0x7F) == rawtext[i]) { // One byte goodbytes += 1; * asciibytes++; } else if ((rawtext[i] & (byte)0xDF) == rawtext[i]) { // Two bytes if (i+1 < rawtextlen && * (rawtext[i+1] & (byte)0xBF) == rawtext[i+1]) { goodbytes += 2; i++; } } else if ((rawtext[i] & * (byte)0xEF) == rawtext[i]) { // Three bytes if (i+2 < rawtextlen && (rawtext[i+1] & (byte)0xBF) == * rawtext[i+1] && (rawtext[i+2] & (byte)0xBF) == rawtext[i+2]) { goodbytes += 3; i+=2; } } } * * score = (int)(100 * ((float)goodbytes/(float)rawtext.length)); // An all ASCII file is also a good UTF8 * file, but I'd rather it // get identified as ASCII. Can delete following 3 lines otherwise if (goodbytes * == asciibytes) { score = 0; } // If not above 90, reduce to zero to prevent coincidental matches if * (score > 90) { return score; } else { return 0; } */ } /* * Function: ascii_probability Argument: byte array Returns : number from 0 to 100 representing probability text * in array uses all ASCII Description: Sees if array has any characters not in ASCII range, if so, score is * reduced */ private static int ascii_probability(byte[] rawtext) { int score = 75; for (byte b : rawtext) { if (b < 0) { score = score - 5; } else if (b == (byte) 0x1B) { // ESC (used by ISO 2022) score = score - 5; } if (score <= 0) { return 0; } } return score; } /* * Function: euc_kr__probability Argument: pointer to byte array Returns : number from 0 to 100 representing * probability text in array uses EUC-KR encoding */ private static int euc_kr_probability(byte[] rawtext) { int dbchars = 1, krchars = 1; long krfreq = 0, totalfreq = 1; // Stage 1: Check to see if characters fit into acceptable ranges for (int i = 0, row, column, n = rawtext.length - 1; i < n; i++) { // System.err.println(rawtext[i]); if (rawtext[i] >= 0) { // asciichars++; } else { dbchars++; if ((byte) 0xA1 <= rawtext[i] && rawtext[i] <= (byte) 0xFE && (byte) 0xA1 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0xFE) { krchars++; totalfreq += 500; row = rawtext[i] + 256 - 0xA1; column = rawtext[i + 1] + 256 - 0xA1; if (KR_FREQ[row][column] != 0) { krfreq += KR_FREQ[row][column]; } else if (15 <= row && row < 55) { krfreq += 0; } } i++; } } float rangeval = 50 * ((float) krchars / (float) dbchars); float freqval = 50 * ((float) krfreq / (float) totalfreq); return (int) (rangeval + freqval); } /* * Function: cp949__probability Argument: pointer to byte array Returns : number from 0 to 100 representing * probability text in array uses Cp949 encoding */ private static int cp949_probability(byte[] rawtext) { int dbchars = 1, krchars = 1; long krfreq = 0, totalfreq = 1; // Stage 1: Check to see if characters fit into acceptable ranges for (int i = 0, row, column, n = rawtext.length - 1; i < n; i++) { // System.err.println(rawtext[i]); if (rawtext[i] >= 0) { // asciichars++; } else { dbchars++; if ((byte) 0x81 <= rawtext[i] && rawtext[i] <= (byte) 0xFE && ((byte) 0x41 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0x5A || (byte) 0x61 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0x7A || (byte) 0x81 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0xFE)) { krchars++; totalfreq += 500; if ((byte) 0xA1 <= rawtext[i] && rawtext[i] <= (byte) 0xFE && (byte) 0xA1 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0xFE) { row = rawtext[i] + 256 - 0xA1; column = rawtext[i + 1] + 256 - 0xA1; if (KR_FREQ[row][column] != 0) { krfreq += KR_FREQ[row][column]; } } } i++; } } float rangeval = 50 * ((float) krchars / (float) dbchars); float freqval = 50 * ((float) krfreq / (float) totalfreq); return (int) (rangeval + freqval); } private static int iso_2022_kr_probability(byte[] rawtext) { for (int i = 0; i < rawtext.length; i++) { if (i + 3 < rawtext.length && rawtext[i] == 0x1b && (char) rawtext[i + 1] == '$' && (char) rawtext[i + 2] == ')' && (char) rawtext[i + 3] == 'C') { return 100; } } return 0; } /* * Function: euc_jp_probability Argument: pointer to byte array Returns : number from 0 to 100 representing * probability text in array uses EUC-JP encoding */ private static int euc_jp_probability(byte[] rawtext) { int dbchars = 1, jpchars = 1; long jpfreq = 0, totalfreq = 1; // Stage 1: Check to see if characters fit into acceptable ranges for (int i = 0, row, column, n = rawtext.length - 1; i < n; i++) { // System.err.println(rawtext[i]); if (rawtext[i] >= 0) { // asciichars++; } else { dbchars++; if ((byte) 0xA1 <= rawtext[i] && rawtext[i] <= (byte) 0xFE && (byte) 0xA1 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0xFE) { jpchars++; totalfreq += 500; row = rawtext[i] + 256 - 0xA1; column = rawtext[i + 1] + 256 - 0xA1; if (JP_FREQ[row][column] != 0) { jpfreq += JP_FREQ[row][column]; } else if (15 <= row && row < 55) { jpfreq += 0; } } i++; } } float rangeval = 50 * ((float) jpchars / (float) dbchars); float freqval = 50 * ((float) jpfreq / (float) totalfreq); return (int) (rangeval + freqval); } private static int iso_2022_jp_probability(byte[] rawtext) { for (int i = 0; i < rawtext.length; i++) { if (i + 2 < rawtext.length && rawtext[i] == 0x1b && (char) rawtext[i + 1] == '$' && (char) rawtext[i + 2] == 'B') { return 100; } } return 0; } /* * Function: sjis_probability Argument: pointer to byte array Returns : number from 0 to 100 representing * probability text in array uses Shift-JIS encoding */ private static int sjis_probability(byte[] rawtext) { int dbchars = 1, jpchars = 1; long jpfreq = 0, totalfreq = 1; // Stage 1: Check to see if characters fit into acceptable ranges for (int i = 0, row, column, adjust, n = rawtext.length - 1; i < n; i++) { // System.err.println(rawtext[i]); if (rawtext[i] >= 0) { // asciichars++; } else { dbchars++; if (i + 1 < rawtext.length && (((byte) 0x81 <= rawtext[i] && rawtext[i] <= (byte) 0x9F) || ((byte) 0xE0 <= rawtext[i] && rawtext[i] <= (byte) 0xEF)) && (((byte) 0x40 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0x7E) || ((byte) 0x80 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0xFC))) { jpchars++; totalfreq += 500; row = rawtext[i] + 256; column = rawtext[i + 1] + 256; if (column < 0x9f) { adjust = 1; if (column > 0x7f) { column -= 0x20; } else { column -= 0x19; } } else { adjust = 0; column -= 0x7e; } if (row < 0xa0) { row = ((row - 0x70) << 1) - adjust; } else { row = ((row - 0xb0) << 1) - adjust; } row -= 0x20; column = 0x20; // System.out.println("original row " + row + " column " + // column); if (row < JP_FREQ.length && column < JP_FREQ[row].length && JP_FREQ[row][column] != 0) { jpfreq += JP_FREQ[row][column]; } i++; } else if ((byte) 0xA1 <= rawtext[i] && rawtext[i] <= (byte) 0xDF) { // half-width katakana, convert to full-width } } } float rangeval = 50 * ((float) jpchars / (float) dbchars); float freqval = 50 * ((float) jpfreq / (float) totalfreq); // For regular GB files, this would give the same score, so I handicap // it slightly return (int) (rangeval + freqval) - 1; } // ------------------------------------------------------------------------------------private static fields private static final int[][] GB_FREQ = new int[ 94][ 94]; private static final int[][] GBK_FREQ = new int[126][191]; private static final int[][] BIG5_FREQ = new int[ 94][158]; private static final int[][] BIG5P_FREQ = new int[126][191]; private static final int[][] EUC_TW_FREQ = new int[ 94][ 94]; private static final int[][] KR_FREQ = new int[ 94][ 94]; private static final int[][] JP_FREQ = new int[ 94][ 94]; static { // ------------------------------------------------------------------------------------GB_FREQ GB_FREQ[20][35] = 599; GB_FREQ[49][26] = 598; GB_FREQ[41][38] = 597; GB_FREQ[17][26] = 596; GB_FREQ[32][42] = 595; GB_FREQ[39][42] = 594; GB_FREQ[45][49] = 593; GB_FREQ[51][57] = 592; GB_FREQ[50][47] = 591; GB_FREQ[42][90] = 590; GB_FREQ[52][65] = 589; GB_FREQ[53][47] = 588; GB_FREQ[19][82] = 587; GB_FREQ[31][19] = 586; GB_FREQ[40][46] = 585; GB_FREQ[24][89] = 584; GB_FREQ[23][85] = 583; GB_FREQ[20][28] = 582; GB_FREQ[42][20] = 581; GB_FREQ[34][38] = 580; GB_FREQ[45][9] = 579; GB_FREQ[54][50] = 578; GB_FREQ[25][44] = 577; GB_FREQ[35][66] = 576; GB_FREQ[20][55] = 575; GB_FREQ[18][85] = 574; GB_FREQ[20][31] = 573; GB_FREQ[49][17] = 572; GB_FREQ[41][16] = 571; GB_FREQ[35][73] = 570; GB_FREQ[20][34] = 569; GB_FREQ[29][44] = 568; GB_FREQ[35][38] = 567; GB_FREQ[49][9] = 566; GB_FREQ[46][33] = 565; GB_FREQ[49][51] = 564; GB_FREQ[40][89] = 563; GB_FREQ[26][64] = 562; GB_FREQ[54][51] = 561; GB_FREQ[54][36] = 560; GB_FREQ[39][4] = 559; GB_FREQ[53][13] = 558; GB_FREQ[24][92] = 557; GB_FREQ[27][49] = 556; GB_FREQ[48][6] = 555; GB_FREQ[21][51] = 554; GB_FREQ[30][40] = 553; GB_FREQ[42][92] = 552; GB_FREQ[31][78] = 551; GB_FREQ[25][82] = 550; GB_FREQ[47][0] = 549; GB_FREQ[34][19] = 548; GB_FREQ[47][35] = 547; GB_FREQ[21][63] = 546; GB_FREQ[43][75] = 545; GB_FREQ[21][87] = 544; GB_FREQ[35][59] = 543; GB_FREQ[25][34] = 542; GB_FREQ[21][27] = 541; GB_FREQ[39][26] = 540; GB_FREQ[34][26] = 539; GB_FREQ[39][52] = 538; GB_FREQ[50][57] = 537; GB_FREQ[37][79] = 536; GB_FREQ[26][24] = 535; GB_FREQ[22][1] = 534; GB_FREQ[18][40] = 533; GB_FREQ[41][33] = 532; GB_FREQ[53][26] = 531; GB_FREQ[54][86] = 530; GB_FREQ[20][16] = 529; GB_FREQ[46][74] = 528; GB_FREQ[30][19] = 527; GB_FREQ[45][35] = 526; GB_FREQ[45][61] = 525; GB_FREQ[30][9] = 524; GB_FREQ[41][53] = 523; GB_FREQ[41][13] = 522; GB_FREQ[50][34] = 521; GB_FREQ[53][86] = 520; GB_FREQ[47][47] = 519; GB_FREQ[22][28] = 518; GB_FREQ[50][53] = 517; GB_FREQ[39][70] = 516; GB_FREQ[38][15] = 515; GB_FREQ[42][88] = 514; GB_FREQ[16][29] = 513; GB_FREQ[27][90] = 512; GB_FREQ[29][12] = 511; GB_FREQ[44][22] = 510; GB_FREQ[34][69] = 509; GB_FREQ[24][10] = 508; GB_FREQ[44][11] = 507; GB_FREQ[39][92] = 506; GB_FREQ[49][48] = 505; GB_FREQ[31][46] = 504; GB_FREQ[19][50] = 503; GB_FREQ[21][14] = 502; GB_FREQ[32][28] = 501; GB_FREQ[18][3] = 500; GB_FREQ[53][9] = 499; GB_FREQ[34][80] = 498; GB_FREQ[48][88] = 497; GB_FREQ[46][53] = 496; GB_FREQ[22][53] = 495; GB_FREQ[28][10] = 494; GB_FREQ[44][65] = 493; GB_FREQ[20][10] = 492; GB_FREQ[40][76] = 491; GB_FREQ[47][8] = 490; GB_FREQ[50][74] = 489; GB_FREQ[23][62] = 488; GB_FREQ[49][65] = 487; GB_FREQ[28][87] = 486; GB_FREQ[15][48] = 485; GB_FREQ[22][7] = 484; GB_FREQ[19][42] = 483; GB_FREQ[41][20] = 482; GB_FREQ[26][55] = 481; GB_FREQ[21][93] = 480; GB_FREQ[31][76] = 479; GB_FREQ[34][31] = 478; GB_FREQ[20][66] = 477; GB_FREQ[51][33] = 476; GB_FREQ[34][86] = 475; GB_FREQ[37][67] = 474; GB_FREQ[53][53] = 473; GB_FREQ[40][88] = 472; GB_FREQ[39][10] = 471; GB_FREQ[24][3] = 470; GB_FREQ[27][25] = 469; GB_FREQ[26][15] = 468; GB_FREQ[21][88] = 467; GB_FREQ[52][62] = 466; GB_FREQ[46][81] = 465; GB_FREQ[38][72] = 464; GB_FREQ[17][30] = 463; GB_FREQ[52][92] = 462; GB_FREQ[34][90] = 461; GB_FREQ[21][7] = 460; GB_FREQ[36][13] = 459; GB_FREQ[45][41] = 458; GB_FREQ[32][5] = 457; GB_FREQ[26][89] = 456; GB_FREQ[23][87] = 455; GB_FREQ[20][39] = 454; GB_FREQ[27][23] = 453; GB_FREQ[25][59] = 452; GB_FREQ[49][20] = 451; GB_FREQ[54][77] = 450; GB_FREQ[27][67] = 449; GB_FREQ[47][33] = 448; GB_FREQ[41][17] = 447; GB_FREQ[19][81] = 446; GB_FREQ[16][66] = 445; GB_FREQ[45][26] = 444; GB_FREQ[49][81] = 443; GB_FREQ[53][55] = 442; GB_FREQ[16][26] = 441; GB_FREQ[54][62] = 440; GB_FREQ[20][70] = 439; GB_FREQ[42][35] = 438; GB_FREQ[20][57] = 437; GB_FREQ[34][36] = 436; GB_FREQ[46][63] = 435; GB_FREQ[19][45] = 434; GB_FREQ[21][10] = 433; GB_FREQ[52][93] = 432; GB_FREQ[25][2] = 431; GB_FREQ[30][57] = 430; GB_FREQ[41][24] = 429; GB_FREQ[28][43] = 428; GB_FREQ[45][86] = 427; GB_FREQ[51][56] = 426; GB_FREQ[37][28] = 425; GB_FREQ[52][69] = 424; GB_FREQ[43][92] = 423; GB_FREQ[41][31] = 422; GB_FREQ[37][87] = 421; GB_FREQ[47][36] = 420; GB_FREQ[16][16] = 419; GB_FREQ[40][56] = 418; GB_FREQ[24][55] = 417; GB_FREQ[17][1] = 416; GB_FREQ[35][57] = 415; GB_FREQ[27][50] = 414; GB_FREQ[26][14] = 413; GB_FREQ[50][40] = 412; GB_FREQ[39][19] = 411; GB_FREQ[19][89] = 410; GB_FREQ[29][91] = 409; GB_FREQ[17][89] = 408; GB_FREQ[39][74] = 407; GB_FREQ[46][39] = 406; GB_FREQ[40][28] = 405; GB_FREQ[45][68] = 404; GB_FREQ[43][10] = 403; GB_FREQ[42][13] = 402; GB_FREQ[44][81] = 401; GB_FREQ[41][47] = 400; GB_FREQ[48][58] = 399; GB_FREQ[43][68] = 398; GB_FREQ[16][79] = 397; GB_FREQ[19][5] = 396; GB_FREQ[54][59] = 395; GB_FREQ[17][36] = 394; GB_FREQ[18][0] = 393; GB_FREQ[41][5] = 392; GB_FREQ[41][72] = 391; GB_FREQ[16][39] = 390; GB_FREQ[54][0] = 389; GB_FREQ[51][16] = 388; GB_FREQ[29][36] = 387; GB_FREQ[47][5] = 386; GB_FREQ[47][51] = 385; GB_FREQ[44][7] = 384; GB_FREQ[35][30] = 383; GB_FREQ[26][9] = 382; GB_FREQ[16][7] = 381; GB_FREQ[32][1] = 380; GB_FREQ[33][76] = 379; GB_FREQ[34][91] = 378; GB_FREQ[52][36] = 377; GB_FREQ[26][77] = 376; GB_FREQ[35][48] = 375; GB_FREQ[40][80] = 374; GB_FREQ[41][92] = 373; GB_FREQ[27][93] = 372; GB_FREQ[15][17] = 371; GB_FREQ[16][76] = 370; GB_FREQ[51][12] = 369; GB_FREQ[18][20] = 368; GB_FREQ[15][54] = 367; GB_FREQ[50][5] = 366; GB_FREQ[33][22] = 365; GB_FREQ[37][57] = 364; GB_FREQ[28][47] = 363; GB_FREQ[42][31] = 362; GB_FREQ[18][2] = 361; GB_FREQ[43][64] = 360; GB_FREQ[23][47] = 359; GB_FREQ[28][79] = 358; GB_FREQ[25][45] = 357; GB_FREQ[23][91] = 356; GB_FREQ[22][19] = 355; GB_FREQ[25][46] = 354; GB_FREQ[22][36] = 353; GB_FREQ[54][85] = 352; GB_FREQ[46][20] = 351; GB_FREQ[27][37] = 350; GB_FREQ[26][81] = 349; GB_FREQ[42][29] = 348; GB_FREQ[31][90] = 347; GB_FREQ[41][59] = 346; GB_FREQ[24][65] = 345; GB_FREQ[44][84] = 344; GB_FREQ[24][90] = 343; GB_FREQ[38][54] = 342; GB_FREQ[28][70] = 341; GB_FREQ[27][15] = 340; GB_FREQ[28][80] = 339; GB_FREQ[29][8] = 338; GB_FREQ[45][80] = 337; GB_FREQ[53][37] = 336; GB_FREQ[28][65] = 335; GB_FREQ[23][86] = 334; GB_FREQ[39][45] = 333; GB_FREQ[53][32] = 332; GB_FREQ[38][68] = 331; GB_FREQ[45][78] = 330; GB_FREQ[43][7] = 329; GB_FREQ[46][82] = 328; GB_FREQ[27][38] = 327; GB_FREQ[16][62] = 326; GB_FREQ[24][17] = 325; GB_FREQ[22][70] = 324; GB_FREQ[52][28] = 323; GB_FREQ[23][40] = 322; GB_FREQ[28][50] = 321; GB_FREQ[42][91] = 320; GB_FREQ[47][76] = 319; GB_FREQ[15][42] = 318; GB_FREQ[43][55] = 317; GB_FREQ[29][84] = 316; GB_FREQ[44][90] = 315; GB_FREQ[53][16] = 314; GB_FREQ[22][93] = 313; GB_FREQ[34][10] = 312; GB_FREQ[32][53] = 311; GB_FREQ[43][65] = 310; GB_FREQ[28][7] = 309; GB_FREQ[35][46] = 308; GB_FREQ[21][39] = 307; GB_FREQ[44][18] = 306; GB_FREQ[40][10] = 305; GB_FREQ[54][53] = 304; GB_FREQ[38][74] = 303; GB_FREQ[28][26] = 302; GB_FREQ[15][13] = 301; GB_FREQ[39][34] = 300; GB_FREQ[39][46] = 299; GB_FREQ[42][66] = 298; GB_FREQ[33][58] = 297; GB_FREQ[15][56] = 296; GB_FREQ[18][51] = 295; GB_FREQ[49][68] = 294; GB_FREQ[30][37] = 293; GB_FREQ[51][84] = 292; GB_FREQ[51][9] = 291; GB_FREQ[40][70] = 290; GB_FREQ[41][84] = 289; GB_FREQ[28][64] = 288; GB_FREQ[32][88] = 287; GB_FREQ[24][5] = 286; GB_FREQ[53][23] = 285; GB_FREQ[42][27] = 284; GB_FREQ[22][38] = 283; GB_FREQ[32][86] = 282; GB_FREQ[34][30] = 281; GB_FREQ[38][63] = 280; GB_FREQ[24][59] = 279; GB_FREQ[22][81] = 278; GB_FREQ[32][11] = 277; GB_FREQ[51][21] = 276; GB_FREQ[54][41] = 275; GB_FREQ[21][50] = 274; GB_FREQ[23][89] = 273; GB_FREQ[19][87] = 272; GB_FREQ[26][7] = 271; GB_FREQ[30][75] = 270; GB_FREQ[43][84] = 269; GB_FREQ[51][25] = 268; GB_FREQ[16][67] = 267; GB_FREQ[32][9] = 266; GB_FREQ[48][51] = 265; GB_FREQ[39][7] = 264; GB_FREQ[44][88] = 263; GB_FREQ[52][24] = 262; GB_FREQ[23][34] = 261; GB_FREQ[32][75] = 260; GB_FREQ[19][10] = 259; GB_FREQ[28][91] = 258; GB_FREQ[32][83] = 257; GB_FREQ[25][75] = 256; GB_FREQ[53][45] = 255; GB_FREQ[29][85] = 254; GB_FREQ[53][59] = 253; GB_FREQ[16][2] = 252; GB_FREQ[19][78] = 251; GB_FREQ[15][75] = 250; GB_FREQ[51][42] = 249; GB_FREQ[45][67] = 248; GB_FREQ[15][74] = 247; GB_FREQ[25][81] = 246; GB_FREQ[37][62] = 245; GB_FREQ[16][55] = 244; GB_FREQ[18][38] = 243; GB_FREQ[23][23] = 242; GB_FREQ[38][30] = 241; GB_FREQ[17][28] = 240; GB_FREQ[44][73] = 239; GB_FREQ[23][78] = 238; GB_FREQ[40][77] = 237; GB_FREQ[38][87] = 236; GB_FREQ[27][19] = 235; GB_FREQ[38][82] = 234; GB_FREQ[37][22] = 233; GB_FREQ[41][30] = 232; GB_FREQ[54][9] = 231; GB_FREQ[32][30] = 230; GB_FREQ[30][52] = 229; GB_FREQ[40][84] = 228; GB_FREQ[53][57] = 227; GB_FREQ[27][27] = 226; GB_FREQ[38][64] = 225; GB_FREQ[18][43] = 224; GB_FREQ[23][69] = 223; GB_FREQ[28][12] = 222; GB_FREQ[50][78] = 221; GB_FREQ[50][1] = 220; GB_FREQ[26][88] = 219; GB_FREQ[36][40] = 218; GB_FREQ[33][89] = 217; GB_FREQ[41][28] = 216; GB_FREQ[31][77] = 215; GB_FREQ[46][1] = 214; GB_FREQ[47][19] = 213; GB_FREQ[35][55] = 212; GB_FREQ[41][21] = 211; GB_FREQ[27][10] = 210; GB_FREQ[32][77] = 209; GB_FREQ[26][37] = 208; GB_FREQ[20][33] = 207; GB_FREQ[41][52] = 206; GB_FREQ[32][18] = 205; GB_FREQ[38][13] = 204; GB_FREQ[20][18] = 203; GB_FREQ[20][24] = 202; GB_FREQ[45][19] = 201; GB_FREQ[18][53] = 200; /* GBFreq[39][0] = 199; GBFreq[40][71] = 198; GBFreq[41][27] = 197; GBFreq[15][69] = 196; GBFreq[42][10] = 195; GBFreq[31][89] = 194; GBFreq[51][28] = 193; GBFreq[41][22] = 192; GBFreq[40][43] = 191; GBFreq[38][6] = 190; GBFreq[37][11] = 189; GBFreq[39][60] = 188; GBFreq[48][47] = 187; GBFreq[46][80] = 186; GBFreq[52][49] = 185; GBFreq[50][48] = 184; GBFreq[25][1] = 183; GBFreq[52][29] = 182; GBFreq[24][66] = 181; GBFreq[23][35] = 180; GBFreq[49][72] = 179; GBFreq[47][45] = 178; GBFreq[45][14] = 177; GBFreq[51][70] = 176; GBFreq[22][30] = 175; GBFreq[49][83] = 174; GBFreq[26][79] = 173; GBFreq[27][41] = 172; GBFreq[51][81] = 171; GBFreq[41][54] = 170; GBFreq[20][4] = 169; GBFreq[29][60] = 168; GBFreq[20][27] = 167; GBFreq[50][15] = 166; GBFreq[41][6] = 165; GBFreq[35][34] = 164; GBFreq[44][87] = 163; GBFreq[46][66] = 162; GBFreq[42][37] = 161; GBFreq[42][24] = 160; GBFreq[54][7] = 159; GBFreq[41][14] = 158; GBFreq[39][83] = 157; GBFreq[16][87] = 156; GBFreq[20][59] = 155; GBFreq[42][12] = 154; GBFreq[47][2] = 153; GBFreq[21][32] = 152; GBFreq[53][29] = 151; GBFreq[22][40] = 150; GBFreq[24][58] = 149; GBFreq[52][88] = 148; GBFreq[29][30] = 147; GBFreq[15][91] = 146; GBFreq[54][72] = 145; GBFreq[51][75] = 144; GBFreq[33][67] = 143; GBFreq[41][50] = 142; GBFreq[27][34] = 141; GBFreq[46][17] = 140; GBFreq[31][74] = 139; GBFreq[42][67] = 138; GBFreq[54][87] = 137; GBFreq[27][14] = 136; GBFreq[16][63] = 135; GBFreq[16][5] = 134; GBFreq[43][23] = 133; GBFreq[23][13] = 132; GBFreq[31][12] = 131; GBFreq[25][57] = 130; GBFreq[38][49] = 129; GBFreq[42][69] = 128; GBFreq[23][80] = 127; GBFreq[29][0] = 126; GBFreq[28][2] = 125; GBFreq[28][17] = 124; GBFreq[17][27] = 123; GBFreq[40][16] = 122; GBFreq[45][1] = 121; GBFreq[36][33] = 120; GBFreq[35][23] = 119; GBFreq[20][86] = 118; GBFreq[29][53] = 117; GBFreq[23][88] = 116; GBFreq[51][87] = 115; GBFreq[54][27] = 114; GBFreq[44][36] = 113; GBFreq[21][45] = 112; GBFreq[53][52] = 111; GBFreq[31][53] = 110; GBFreq[38][47] = 109; GBFreq[27][21] = 108; GBFreq[30][42] = 107; GBFreq[29][10] = 106; GBFreq[35][35] = 105; GBFreq[24][56] = 104; GBFreq[41][29] = 103; GBFreq[18][68] = 102; GBFreq[29][24] = 101; GBFreq[25][84] = 100; GBFreq[35][47] = 99; GBFreq[29][56] = 98; GBFreq[30][44] = 97; GBFreq[53][3] = 96; GBFreq[30][63] = 95; GBFreq[52][52] = 94; GBFreq[54][1] = 93; GBFreq[22][48] = 92; GBFreq[54][66] = 91; GBFreq[21][90] = 90; GBFreq[52][47] = 89; GBFreq[39][25] = 88; GBFreq[39][39] = 87; GBFreq[44][37] = 86; GBFreq[44][76] = 85; GBFreq[46][75] = 84; GBFreq[18][37] = 83; GBFreq[47][42] = 82; GBFreq[19][92] = 81; GBFreq[51][27] = 80; GBFreq[48][83] = 79; GBFreq[23][70] = 78; GBFreq[29][9] = 77; GBFreq[33][79] = 76; GBFreq[52][90] = 75; GBFreq[53][6] = 74; GBFreq[24][36] = 73; GBFreq[25][25] = 72; GBFreq[44][26] = 71; GBFreq[25][36] = 70; GBFreq[29][87] = 69; GBFreq[48][0] = 68; GBFreq[15][40] = 67; GBFreq[17][45] = 66; GBFreq[30][14] = 65; GBFreq[48][38] = 64; GBFreq[23][19] = 63; GBFreq[40][42] = 62; GBFreq[31][63] = 61; GBFreq[16][23] = 60; GBFreq[26][21] = 59; GBFreq[32][76] = 58; GBFreq[23][58] = 57; GBFreq[41][37] = 56; GBFreq[30][43] = 55; GBFreq[47][38] = 54; GBFreq[21][46] = 53; GBFreq[18][33] = 52; GBFreq[52][37] = 51; GBFreq[36][8] = 50; GBFreq[49][24] = 49; GBFreq[15][66] = 48; GBFreq[35][77] = 47; GBFreq[27][58] = 46; GBFreq[35][51] = 45; GBFreq[24][69] = 44; GBFreq[20][54] = 43; GBFreq[24][41] = 42; GBFreq[41][0] = 41; GBFreq[33][71] = 40; GBFreq[23][52] = 39; GBFreq[29][67] = 38; GBFreq[46][51] = 37; GBFreq[46][90] = 36; GBFreq[49][33] = 35; GBFreq[33][28] = 34; GBFreq[37][86] = 33; GBFreq[39][22] = 32; GBFreq[37][37] = 31; GBFreq[29][62] = 30; GBFreq[29][50] = 29; GBFreq[36][89] = 28; GBFreq[42][44] = 27; GBFreq[51][82] = 26; GBFreq[28][83] = 25; GBFreq[15][78] = 24; GBFreq[46][62] = 23; GBFreq[19][69] = 22; GBFreq[51][23] = 21; GBFreq[37][69] = 20; GBFreq[25][5] = 19; GBFreq[51][85] = 18; GBFreq[48][77] = 17; GBFreq[32][46] = 16; GBFreq[53][60] = 15; GBFreq[28][57] = 14; GBFreq[54][82] = 13; GBFreq[54][15] = 12; GBFreq[49][54] = 11; GBFreq[53][87] = 10; GBFreq[27][16] = 9; GBFreq[29][34] = 8; GBFreq[20][44] = 7; GBFreq[42][73] = 6; GBFreq[47][71] = 5; GBFreq[29][37] = 4; GBFreq[25][50] = 3; GBFreq[18][84] = 2; GBFreq[50][45] = 1; GBFreq[48][46] = 0; GBFreq[43][89] = -1; GBFreq[54][68] = -2; */ // ------------------------------------------------------------------------------------BIG5_FREQ BIG5_FREQ[9][89] = 600; BIG5_FREQ[11][15] = 599; BIG5_FREQ[3][66] = 598; BIG5_FREQ[6][121] = 597; BIG5_FREQ[3][0] = 596; BIG5_FREQ[5][82] = 595; BIG5_FREQ[3][42] = 594; BIG5_FREQ[5][34] = 593; BIG5_FREQ[3][8] = 592; BIG5_FREQ[3][6] = 591; BIG5_FREQ[3][67] = 590; BIG5_FREQ[7][139] = 589; BIG5_FREQ[23][137] = 588; BIG5_FREQ[12][46] = 587; BIG5_FREQ[4][8] = 586; BIG5_FREQ[4][41] = 585; BIG5_FREQ[18][47] = 584; BIG5_FREQ[12][114] = 583; BIG5_FREQ[6][1] = 582; BIG5_FREQ[22][60] = 581; BIG5_FREQ[5][46] = 580; BIG5_FREQ[11][79] = 579; BIG5_FREQ[3][23] = 578; BIG5_FREQ[7][114] = 577; BIG5_FREQ[29][102] = 576; BIG5_FREQ[19][14] = 575; BIG5_FREQ[4][133] = 574; BIG5_FREQ[3][29] = 573; BIG5_FREQ[4][109] = 572; BIG5_FREQ[14][127] = 571; BIG5_FREQ[5][48] = 570; BIG5_FREQ[13][104] = 569; BIG5_FREQ[3][132] = 568; BIG5_FREQ[26][64] = 567; BIG5_FREQ[7][19] = 566; BIG5_FREQ[4][12] = 565; BIG5_FREQ[11][124] = 564; BIG5_FREQ[7][89] = 563; BIG5_FREQ[15][124] = 562; BIG5_FREQ[4][108] = 561; BIG5_FREQ[19][66] = 560; BIG5_FREQ[3][21] = 559; BIG5_FREQ[24][12] = 558; BIG5_FREQ[28][111] = 557; BIG5_FREQ[12][107] = 556; BIG5_FREQ[3][112] = 555; BIG5_FREQ[8][113] = 554; BIG5_FREQ[5][40] = 553; BIG5_FREQ[26][145] = 552; BIG5_FREQ[3][48] = 551; BIG5_FREQ[3][70] = 550; BIG5_FREQ[22][17] = 549; BIG5_FREQ[16][47] = 548; BIG5_FREQ[3][53] = 547; BIG5_FREQ[4][24] = 546; BIG5_FREQ[32][120] = 545; BIG5_FREQ[24][49] = 544; BIG5_FREQ[24][142] = 543; BIG5_FREQ[18][66] = 542; BIG5_FREQ[29][150] = 541; BIG5_FREQ[5][122] = 540; BIG5_FREQ[5][114] = 539; BIG5_FREQ[3][44] = 538; BIG5_FREQ[10][128] = 537; BIG5_FREQ[15][20] = 536; BIG5_FREQ[13][33] = 535; BIG5_FREQ[14][87] = 534; BIG5_FREQ[3][126] = 533; BIG5_FREQ[4][53] = 532; BIG5_FREQ[4][40] = 531; BIG5_FREQ[9][93] = 530; BIG5_FREQ[15][137] = 529; BIG5_FREQ[10][123] = 528; BIG5_FREQ[4][56] = 527; BIG5_FREQ[5][71] = 526; BIG5_FREQ[10][8] = 525; BIG5_FREQ[5][16] = 524; BIG5_FREQ[5][146] = 523; BIG5_FREQ[18][88] = 522; BIG5_FREQ[24][4] = 521; BIG5_FREQ[20][47] = 520; BIG5_FREQ[5][33] = 519; BIG5_FREQ[9][43] = 518; BIG5_FREQ[20][12] = 517; BIG5_FREQ[20][13] = 516; BIG5_FREQ[5][156] = 515; BIG5_FREQ[22][140] = 514; BIG5_FREQ[8][146] = 513; BIG5_FREQ[21][123] = 512; BIG5_FREQ[4][90] = 511; BIG5_FREQ[5][62] = 510; BIG5_FREQ[17][59] = 509; BIG5_FREQ[10][37] = 508; BIG5_FREQ[18][107] = 507; BIG5_FREQ[14][53] = 506; BIG5_FREQ[22][51] = 505; BIG5_FREQ[8][13] = 504; BIG5_FREQ[5][29] = 503; BIG5_FREQ[9][7] = 502; BIG5_FREQ[22][14] = 501; BIG5_FREQ[8][55] = 500; BIG5_FREQ[33][9] = 499; BIG5_FREQ[16][64] = 498; BIG5_FREQ[7][131] = 497; BIG5_FREQ[34][4] = 496; BIG5_FREQ[7][101] = 495; BIG5_FREQ[11][139] = 494; BIG5_FREQ[3][135] = 493; BIG5_FREQ[7][102] = 492; BIG5_FREQ[17][13] = 491; BIG5_FREQ[3][20] = 490; BIG5_FREQ[27][106] = 489; BIG5_FREQ[5][88] = 488; BIG5_FREQ[6][33] = 487; BIG5_FREQ[5][139] = 486; BIG5_FREQ[6][0] = 485; BIG5_FREQ[17][58] = 484; BIG5_FREQ[5][133] = 483; BIG5_FREQ[9][107] = 482; BIG5_FREQ[23][39] = 481; BIG5_FREQ[5][23] = 480; BIG5_FREQ[3][79] = 479; BIG5_FREQ[32][97] = 478; BIG5_FREQ[3][136] = 477; BIG5_FREQ[4][94] = 476; BIG5_FREQ[21][61] = 475; BIG5_FREQ[23][123] = 474; BIG5_FREQ[26][16] = 473; BIG5_FREQ[24][137] = 472; BIG5_FREQ[22][18] = 471; BIG5_FREQ[5][1] = 470; BIG5_FREQ[20][119] = 469; BIG5_FREQ[3][7] = 468; BIG5_FREQ[10][79] = 467; BIG5_FREQ[15][105] = 466; BIG5_FREQ[3][144] = 465; BIG5_FREQ[12][80] = 464; BIG5_FREQ[15][73] = 463; BIG5_FREQ[3][19] = 462; BIG5_FREQ[8][109] = 461; BIG5_FREQ[3][15] = 460; BIG5_FREQ[31][82] = 459; BIG5_FREQ[3][43] = 458; BIG5_FREQ[25][119] = 457; BIG5_FREQ[16][111] = 456; BIG5_FREQ[7][77] = 455; BIG5_FREQ[3][95] = 454; BIG5_FREQ[24][82] = 453; BIG5_FREQ[7][52] = 452; BIG5_FREQ[9][151] = 451; BIG5_FREQ[3][129] = 450; BIG5_FREQ[5][87] = 449; BIG5_FREQ[3][55] = 448; BIG5_FREQ[8][153] = 447; BIG5_FREQ[4][83] = 446; BIG5_FREQ[3][114] = 445; BIG5_FREQ[23][147] = 444; BIG5_FREQ[15][31] = 443; BIG5_FREQ[3][54] = 442; BIG5_FREQ[11][122] = 441; BIG5_FREQ[4][4] = 440; BIG5_FREQ[34][149] = 439; BIG5_FREQ[3][17] = 438; BIG5_FREQ[21][64] = 437; BIG5_FREQ[26][144] = 436; BIG5_FREQ[4][62] = 435; BIG5_FREQ[8][15] = 434; BIG5_FREQ[35][80] = 433; BIG5_FREQ[7][110] = 432; BIG5_FREQ[23][114] = 431; BIG5_FREQ[3][108] = 430; BIG5_FREQ[3][62] = 429; BIG5_FREQ[21][41] = 428; BIG5_FREQ[15][99] = 427; BIG5_FREQ[5][47] = 426; BIG5_FREQ[4][96] = 425; BIG5_FREQ[20][122] = 424; BIG5_FREQ[5][21] = 423; BIG5_FREQ[4][157] = 422; BIG5_FREQ[16][14] = 421; BIG5_FREQ[3][117] = 420; BIG5_FREQ[7][129] = 419; BIG5_FREQ[4][27] = 418; BIG5_FREQ[5][30] = 417; BIG5_FREQ[22][16] = 416; BIG5_FREQ[5][64] = 415; BIG5_FREQ[17][99] = 414; BIG5_FREQ[17][57] = 413; BIG5_FREQ[8][105] = 412; BIG5_FREQ[5][112] = 411; BIG5_FREQ[20][59] = 410; BIG5_FREQ[6][129] = 409; BIG5_FREQ[18][17] = 408; BIG5_FREQ[3][92] = 407; BIG5_FREQ[28][118] = 406; BIG5_FREQ[3][109] = 405; BIG5_FREQ[31][51] = 404; BIG5_FREQ[13][116] = 403; BIG5_FREQ[6][15] = 402; BIG5_FREQ[36][136] = 401; BIG5_FREQ[12][74] = 400; BIG5_FREQ[20][88] = 399; BIG5_FREQ[36][68] = 398; BIG5_FREQ[3][147] = 397; BIG5_FREQ[15][84] = 396; BIG5_FREQ[16][32] = 395; BIG5_FREQ[16][58] = 394; BIG5_FREQ[7][66] = 393; BIG5_FREQ[23][107] = 392; BIG5_FREQ[9][6] = 391; BIG5_FREQ[12][86] = 390; BIG5_FREQ[23][112] = 389; BIG5_FREQ[37][23] = 388; BIG5_FREQ[3][138] = 387; BIG5_FREQ[20][68] = 386; BIG5_FREQ[15][116] = 385; BIG5_FREQ[18][64] = 384; BIG5_FREQ[12][139] = 383; BIG5_FREQ[11][155] = 382; BIG5_FREQ[4][156] = 381; BIG5_FREQ[12][84] = 380; BIG5_FREQ[18][49] = 379; BIG5_FREQ[25][125] = 378; BIG5_FREQ[25][147] = 377; BIG5_FREQ[15][110] = 376; BIG5_FREQ[19][96] = 375; BIG5_FREQ[30][152] = 374; BIG5_FREQ[6][31] = 373; BIG5_FREQ[27][117] = 372; BIG5_FREQ[3][10] = 371; BIG5_FREQ[6][131] = 370; BIG5_FREQ[13][112] = 369; BIG5_FREQ[36][156] = 368; BIG5_FREQ[4][60] = 367; BIG5_FREQ[15][121] = 366; BIG5_FREQ[4][112] = 365; BIG5_FREQ[30][142] = 364; BIG5_FREQ[23][154] = 363; BIG5_FREQ[27][101] = 362; BIG5_FREQ[9][140] = 361; BIG5_FREQ[3][89] = 360; BIG5_FREQ[18][148] = 359; BIG5_FREQ[4][69] = 358; BIG5_FREQ[16][49] = 357; BIG5_FREQ[6][117] = 356; BIG5_FREQ[36][55] = 355; BIG5_FREQ[5][123] = 354; BIG5_FREQ[4][126] = 353; BIG5_FREQ[4][119] = 352; BIG5_FREQ[9][95] = 351; BIG5_FREQ[5][24] = 350; BIG5_FREQ[16][133] = 349; BIG5_FREQ[10][134] = 348; BIG5_FREQ[26][59] = 347; BIG5_FREQ[6][41] = 346; BIG5_FREQ[6][146] = 345; BIG5_FREQ[19][24] = 344; BIG5_FREQ[5][113] = 343; BIG5_FREQ[10][118] = 342; BIG5_FREQ[34][151] = 341; BIG5_FREQ[9][72] = 340; BIG5_FREQ[31][25] = 339; BIG5_FREQ[18][126] = 338; BIG5_FREQ[18][28] = 337; BIG5_FREQ[4][153] = 336; BIG5_FREQ[3][84] = 335; BIG5_FREQ[21][18] = 334; BIG5_FREQ[25][129] = 333; BIG5_FREQ[6][107] = 332; BIG5_FREQ[12][25] = 331; BIG5_FREQ[17][109] = 330; BIG5_FREQ[7][76] = 329; BIG5_FREQ[15][15] = 328; BIG5_FREQ[4][14] = 327; BIG5_FREQ[23][88] = 326; BIG5_FREQ[18][2] = 325; BIG5_FREQ[6][88] = 324; BIG5_FREQ[16][84] = 323; BIG5_FREQ[12][48] = 322; BIG5_FREQ[7][68] = 321; BIG5_FREQ[5][50] = 320; BIG5_FREQ[13][54] = 319; BIG5_FREQ[7][98] = 318; BIG5_FREQ[11][6] = 317; BIG5_FREQ[9][80] = 316; BIG5_FREQ[16][41] = 315; BIG5_FREQ[7][43] = 314; BIG5_FREQ[28][117] = 313; BIG5_FREQ[3][51] = 312; BIG5_FREQ[7][3] = 311; BIG5_FREQ[20][81] = 310; BIG5_FREQ[4][2] = 309; BIG5_FREQ[11][16] = 308; BIG5_FREQ[10][4] = 307; BIG5_FREQ[10][119] = 306; BIG5_FREQ[6][142] = 305; BIG5_FREQ[18][51] = 304; BIG5_FREQ[8][144] = 303; BIG5_FREQ[10][65] = 302; BIG5_FREQ[11][64] = 301; BIG5_FREQ[11][130] = 300; BIG5_FREQ[9][92] = 299; BIG5_FREQ[18][29] = 298; BIG5_FREQ[18][78] = 297; BIG5_FREQ[18][151] = 296; BIG5_FREQ[33][127] = 295; BIG5_FREQ[35][113] = 294; BIG5_FREQ[10][155] = 293; BIG5_FREQ[3][76] = 292; BIG5_FREQ[36][123] = 291; BIG5_FREQ[13][143] = 290; BIG5_FREQ[5][135] = 289; BIG5_FREQ[23][116] = 288; BIG5_FREQ[6][101] = 287; BIG5_FREQ[14][74] = 286; BIG5_FREQ[7][153] = 285; BIG5_FREQ[3][101] = 284; BIG5_FREQ[9][74] = 283; BIG5_FREQ[3][156] = 282; BIG5_FREQ[4][147] = 281; BIG5_FREQ[9][12] = 280; BIG5_FREQ[18][133] = 279; BIG5_FREQ[4][0] = 278; BIG5_FREQ[7][155] = 277; BIG5_FREQ[9][144] = 276; BIG5_FREQ[23][49] = 275; BIG5_FREQ[5][89] = 274; BIG5_FREQ[10][11] = 273; BIG5_FREQ[3][110] = 272; BIG5_FREQ[3][40] = 271; BIG5_FREQ[29][115] = 270; BIG5_FREQ[9][100] = 269; BIG5_FREQ[21][67] = 268; BIG5_FREQ[23][145] = 267; BIG5_FREQ[10][47] = 266; BIG5_FREQ[4][31] = 265; BIG5_FREQ[4][81] = 264; BIG5_FREQ[22][62] = 263; BIG5_FREQ[4][28] = 262; BIG5_FREQ[27][39] = 261; BIG5_FREQ[27][54] = 260; BIG5_FREQ[32][46] = 259; BIG5_FREQ[4][76] = 258; BIG5_FREQ[26][15] = 257; BIG5_FREQ[12][154] = 256; BIG5_FREQ[9][150] = 255; BIG5_FREQ[15][17] = 254; BIG5_FREQ[5][129] = 253; BIG5_FREQ[10][40] = 252; BIG5_FREQ[13][37] = 251; BIG5_FREQ[31][104] = 250; BIG5_FREQ[3][152] = 249; BIG5_FREQ[5][22] = 248; BIG5_FREQ[8][48] = 247; BIG5_FREQ[4][74] = 246; BIG5_FREQ[6][17] = 245; BIG5_FREQ[30][82] = 244; BIG5_FREQ[4][116] = 243; BIG5_FREQ[16][42] = 242; BIG5_FREQ[5][55] = 241; BIG5_FREQ[4][64] = 240; BIG5_FREQ[14][19] = 239; BIG5_FREQ[35][82] = 238; BIG5_FREQ[30][139] = 237; BIG5_FREQ[26][152] = 236; BIG5_FREQ[32][32] = 235; BIG5_FREQ[21][102] = 234; BIG5_FREQ[10][131] = 233; BIG5_FREQ[9][128] = 232; BIG5_FREQ[3][87] = 231; BIG5_FREQ[4][51] = 230; BIG5_FREQ[10][15] = 229; BIG5_FREQ[4][150] = 228; BIG5_FREQ[7][4] = 227; BIG5_FREQ[7][51] = 226; BIG5_FREQ[7][157] = 225; BIG5_FREQ[4][146] = 224; BIG5_FREQ[4][91] = 223; BIG5_FREQ[7][13] = 222; BIG5_FREQ[17][116] = 221; BIG5_FREQ[23][21] = 220; BIG5_FREQ[5][106] = 219; BIG5_FREQ[14][100] = 218; BIG5_FREQ[10][152] = 217; BIG5_FREQ[14][89] = 216; BIG5_FREQ[6][138] = 215; BIG5_FREQ[12][157] = 214; BIG5_FREQ[10][102] = 213; BIG5_FREQ[19][94] = 212; BIG5_FREQ[7][74] = 211; BIG5_FREQ[18][128] = 210; BIG5_FREQ[27][111] = 209; BIG5_FREQ[11][57] = 208; BIG5_FREQ[3][131] = 207; BIG5_FREQ[30][23] = 206; BIG5_FREQ[30][126] = 205; BIG5_FREQ[4][36] = 204; BIG5_FREQ[26][124] = 203; BIG5_FREQ[4][19] = 202; BIG5_FREQ[9][152] = 201; BIG5P_FREQ[41][122] = 600; BIG5P_FREQ[35][0] = 599; BIG5P_FREQ[43][15] = 598; BIG5P_FREQ[35][99] = 597; BIG5P_FREQ[35][6] = 596; BIG5P_FREQ[35][8] = 595; BIG5P_FREQ[38][154] = 594; BIG5P_FREQ[37][34] = 593; BIG5P_FREQ[37][115] = 592; BIG5P_FREQ[36][12] = 591; BIG5P_FREQ[18][77] = 590; BIG5P_FREQ[35][100] = 589; BIG5P_FREQ[35][42] = 588; BIG5P_FREQ[120][75] = 587; BIG5P_FREQ[35][23] = 586; BIG5P_FREQ[13][72] = 585; BIG5P_FREQ[0][67] = 584; BIG5P_FREQ[39][172] = 583; BIG5P_FREQ[22][182] = 582; BIG5P_FREQ[15][186] = 581; BIG5P_FREQ[15][165] = 580; BIG5P_FREQ[35][44] = 579; BIG5P_FREQ[40][13] = 578; BIG5P_FREQ[38][1] = 577; BIG5P_FREQ[37][33] = 576; BIG5P_FREQ[36][24] = 575; BIG5P_FREQ[56][4] = 574; BIG5P_FREQ[35][29] = 573; BIG5P_FREQ[9][96] = 572; BIG5P_FREQ[37][62] = 571; BIG5P_FREQ[48][47] = 570; BIG5P_FREQ[51][14] = 569; BIG5P_FREQ[39][122] = 568; BIG5P_FREQ[44][46] = 567; BIG5P_FREQ[35][21] = 566; BIG5P_FREQ[36][8] = 565; BIG5P_FREQ[36][141] = 564; BIG5P_FREQ[3][81] = 563; BIG5P_FREQ[37][155] = 562; BIG5P_FREQ[42][84] = 561; BIG5P_FREQ[36][40] = 560; BIG5P_FREQ[35][103] = 559; BIG5P_FREQ[11][84] = 558; BIG5P_FREQ[45][33] = 557; BIG5P_FREQ[121][79] = 556; BIG5P_FREQ[2][77] = 555; BIG5P_FREQ[36][41] = 554; BIG5P_FREQ[37][47] = 553; BIG5P_FREQ[39][125] = 552; BIG5P_FREQ[37][26] = 551; BIG5P_FREQ[35][48] = 550; BIG5P_FREQ[35][28] = 549; BIG5P_FREQ[35][159] = 548; BIG5P_FREQ[37][40] = 547; BIG5P_FREQ[35][145] = 546; BIG5P_FREQ[37][147] = 545; BIG5P_FREQ[46][160] = 544; BIG5P_FREQ[37][46] = 543; BIG5P_FREQ[50][99] = 542; BIG5P_FREQ[52][13] = 541; BIG5P_FREQ[10][82] = 540; BIG5P_FREQ[35][169] = 539; BIG5P_FREQ[35][31] = 538; BIG5P_FREQ[47][31] = 537; BIG5P_FREQ[18][79] = 536; BIG5P_FREQ[16][113] = 535; BIG5P_FREQ[37][104] = 534; BIG5P_FREQ[39][134] = 533; BIG5P_FREQ[36][53] = 532; BIG5P_FREQ[38][0] = 531; BIG5P_FREQ[4][86] = 530; BIG5P_FREQ[54][17] = 529; BIG5P_FREQ[43][157] = 528; BIG5P_FREQ[35][165] = 527; BIG5P_FREQ[69][147] = 526; BIG5P_FREQ[117][95] = 525; BIG5P_FREQ[35][162] = 524; BIG5P_FREQ[35][17] = 523; BIG5P_FREQ[36][142] = 522; BIG5P_FREQ[36][4] = 521; BIG5P_FREQ[37][166] = 520; BIG5P_FREQ[35][168] = 519; BIG5P_FREQ[35][19] = 518; BIG5P_FREQ[37][48] = 517; BIG5P_FREQ[42][37] = 516; BIG5P_FREQ[40][146] = 515; BIG5P_FREQ[36][123] = 514; BIG5P_FREQ[22][41] = 513; BIG5P_FREQ[20][119] = 512; BIG5P_FREQ[2][74] = 511; BIG5P_FREQ[44][113] = 510; BIG5P_FREQ[35][125] = 509; BIG5P_FREQ[37][16] = 508; BIG5P_FREQ[35][20] = 507; BIG5P_FREQ[35][55] = 506; BIG5P_FREQ[37][145] = 505; BIG5P_FREQ[0][88] = 504; BIG5P_FREQ[3][94] = 503; BIG5P_FREQ[6][65] = 502; BIG5P_FREQ[26][15] = 501; BIG5P_FREQ[41][126] = 500; BIG5P_FREQ[36][129] = 499; BIG5P_FREQ[31][75] = 498; BIG5P_FREQ[19][61] = 497; BIG5P_FREQ[35][128] = 496; BIG5P_FREQ[29][79] = 495; BIG5P_FREQ[36][62] = 494; BIG5P_FREQ[37][189] = 493; BIG5P_FREQ[39][109] = 492; BIG5P_FREQ[39][135] = 491; BIG5P_FREQ[72][15] = 490; BIG5P_FREQ[47][106] = 489; BIG5P_FREQ[54][14] = 488; BIG5P_FREQ[24][52] = 487; BIG5P_FREQ[38][162] = 486; BIG5P_FREQ[41][43] = 485; BIG5P_FREQ[37][121] = 484; BIG5P_FREQ[14][66] = 483; BIG5P_FREQ[37][30] = 482; BIG5P_FREQ[35][7] = 481; BIG5P_FREQ[49][58] = 480; BIG5P_FREQ[43][188] = 479; BIG5P_FREQ[24][66] = 478; BIG5P_FREQ[35][171] = 477; BIG5P_FREQ[40][186] = 476; BIG5P_FREQ[39][164] = 475; BIG5P_FREQ[78][186] = 474; BIG5P_FREQ[8][72] = 473; BIG5P_FREQ[36][190] = 472; BIG5P_FREQ[35][53] = 471; BIG5P_FREQ[35][54] = 470; BIG5P_FREQ[22][159] = 469; BIG5P_FREQ[35][9] = 468; BIG5P_FREQ[41][140] = 467; BIG5P_FREQ[37][22] = 466; BIG5P_FREQ[48][97] = 465; BIG5P_FREQ[50][97] = 464; BIG5P_FREQ[36][127] = 463; BIG5P_FREQ[37][23] = 462; BIG5P_FREQ[40][55] = 461; BIG5P_FREQ[35][43] = 460; BIG5P_FREQ[26][22] = 459; BIG5P_FREQ[35][15] = 458; BIG5P_FREQ[72][179] = 457; BIG5P_FREQ[20][129] = 456; BIG5P_FREQ[52][101] = 455; BIG5P_FREQ[35][12] = 454; BIG5P_FREQ[42][156] = 453; BIG5P_FREQ[15][157] = 452; BIG5P_FREQ[50][140] = 451; BIG5P_FREQ[26][28] = 450; BIG5P_FREQ[54][51] = 449; BIG5P_FREQ[35][112] = 448; BIG5P_FREQ[36][116] = 447; BIG5P_FREQ[42][11] = 446; BIG5P_FREQ[37][172] = 445; BIG5P_FREQ[37][29] = 444; BIG5P_FREQ[44][107] = 443; BIG5P_FREQ[50][17] = 442; BIG5P_FREQ[39][107] = 441; BIG5P_FREQ[19][109] = 440; BIG5P_FREQ[36][60] = 439; BIG5P_FREQ[49][132] = 438; BIG5P_FREQ[26][16] = 437; BIG5P_FREQ[43][155] = 436; BIG5P_FREQ[37][120] = 435; BIG5P_FREQ[15][159] = 434; BIG5P_FREQ[43][6] = 433; BIG5P_FREQ[45][188] = 432; BIG5P_FREQ[35][38] = 431; BIG5P_FREQ[39][143] = 430; BIG5P_FREQ[48][144] = 429; BIG5P_FREQ[37][168] = 428; BIG5P_FREQ[37][1] = 427; BIG5P_FREQ[36][109] = 426; BIG5P_FREQ[46][53] = 425; BIG5P_FREQ[38][54] = 424; BIG5P_FREQ[36][0] = 423; BIG5P_FREQ[72][33] = 422; BIG5P_FREQ[42][8] = 421; BIG5P_FREQ[36][31] = 420; BIG5P_FREQ[35][150] = 419; BIG5P_FREQ[118][93] = 418; BIG5P_FREQ[37][61] = 417; BIG5P_FREQ[0][85] = 416; BIG5P_FREQ[36][27] = 415; BIG5P_FREQ[35][134] = 414; BIG5P_FREQ[36][145] = 413; BIG5P_FREQ[6][96] = 412; BIG5P_FREQ[36][14] = 411; BIG5P_FREQ[16][36] = 410; BIG5P_FREQ[15][175] = 409; BIG5P_FREQ[35][10] = 408; BIG5P_FREQ[36][189] = 407; BIG5P_FREQ[35][51] = 406; BIG5P_FREQ[35][109] = 405; BIG5P_FREQ[35][147] = 404; BIG5P_FREQ[35][180] = 403; BIG5P_FREQ[72][5] = 402; BIG5P_FREQ[36][107] = 401; BIG5P_FREQ[49][116] = 400; BIG5P_FREQ[73][30] = 399; BIG5P_FREQ[6][90] = 398; BIG5P_FREQ[2][70] = 397; BIG5P_FREQ[17][141] = 396; BIG5P_FREQ[35][62] = 395; BIG5P_FREQ[16][180] = 394; BIG5P_FREQ[4][91] = 393; BIG5P_FREQ[15][171] = 392; BIG5P_FREQ[35][177] = 391; BIG5P_FREQ[37][173] = 390; BIG5P_FREQ[16][121] = 389; BIG5P_FREQ[35][5] = 388; BIG5P_FREQ[46][122] = 387; BIG5P_FREQ[40][138] = 386; BIG5P_FREQ[50][49] = 385; BIG5P_FREQ[36][152] = 384; BIG5P_FREQ[13][43] = 383; BIG5P_FREQ[9][88] = 382; BIG5P_FREQ[36][159] = 381; BIG5P_FREQ[27][62] = 380; BIG5P_FREQ[40][18] = 379; BIG5P_FREQ[17][129] = 378; BIG5P_FREQ[43][97] = 377; BIG5P_FREQ[13][131] = 376; BIG5P_FREQ[46][107] = 375; BIG5P_FREQ[60][64] = 374; BIG5P_FREQ[36][179] = 373; BIG5P_FREQ[37][55] = 372; BIG5P_FREQ[41][173] = 371; BIG5P_FREQ[44][172] = 370; BIG5P_FREQ[23][187] = 369; BIG5P_FREQ[36][149] = 368; BIG5P_FREQ[17][125] = 367; BIG5P_FREQ[55][180] = 366; BIG5P_FREQ[51][129] = 365; BIG5P_FREQ[36][51] = 364; BIG5P_FREQ[37][122] = 363; BIG5P_FREQ[48][32] = 362; BIG5P_FREQ[51][99] = 361; BIG5P_FREQ[54][16] = 360; BIG5P_FREQ[41][183] = 359; BIG5P_FREQ[37][179] = 358; BIG5P_FREQ[38][179] = 357; BIG5P_FREQ[35][143] = 356; BIG5P_FREQ[37][24] = 355; BIG5P_FREQ[40][177] = 354; BIG5P_FREQ[47][117] = 353; BIG5P_FREQ[39][52] = 352; BIG5P_FREQ[22][99] = 351; BIG5P_FREQ[40][142] = 350; BIG5P_FREQ[36][49] = 349; BIG5P_FREQ[38][17] = 348; BIG5P_FREQ[39][188] = 347; BIG5P_FREQ[36][186] = 346; BIG5P_FREQ[35][189] = 345; BIG5P_FREQ[41][7] = 344; BIG5P_FREQ[18][91] = 343; BIG5P_FREQ[43][137] = 342; BIG5P_FREQ[35][142] = 341; BIG5P_FREQ[35][117] = 340; BIG5P_FREQ[39][138] = 339; BIG5P_FREQ[16][59] = 338; BIG5P_FREQ[39][174] = 337; BIG5P_FREQ[55][145] = 336; BIG5P_FREQ[37][21] = 335; BIG5P_FREQ[36][180] = 334; BIG5P_FREQ[37][156] = 333; BIG5P_FREQ[49][13] = 332; BIG5P_FREQ[41][107] = 331; BIG5P_FREQ[36][56] = 330; BIG5P_FREQ[53][8] = 329; BIG5P_FREQ[22][114] = 328; BIG5P_FREQ[5][95] = 327; BIG5P_FREQ[37][0] = 326; BIG5P_FREQ[26][183] = 325; BIG5P_FREQ[22][66] = 324; BIG5P_FREQ[35][58] = 323; BIG5P_FREQ[48][117] = 322; BIG5P_FREQ[36][102] = 321; BIG5P_FREQ[22][122] = 320; BIG5P_FREQ[35][11] = 319; BIG5P_FREQ[46][19] = 318; BIG5P_FREQ[22][49] = 317; BIG5P_FREQ[48][166] = 316; BIG5P_FREQ[41][125] = 315; BIG5P_FREQ[41][1] = 314; BIG5P_FREQ[35][178] = 313; BIG5P_FREQ[41][12] = 312; BIG5P_FREQ[26][167] = 311; BIG5P_FREQ[42][152] = 310; BIG5P_FREQ[42][46] = 309; BIG5P_FREQ[42][151] = 308; BIG5P_FREQ[20][135] = 307; BIG5P_FREQ[37][162] = 306; BIG5P_FREQ[37][50] = 305; BIG5P_FREQ[22][185] = 304; BIG5P_FREQ[36][166] = 303; BIG5P_FREQ[19][40] = 302; BIG5P_FREQ[22][107] = 301; BIG5P_FREQ[22][102] = 300; BIG5P_FREQ[57][162] = 299; BIG5P_FREQ[22][124] = 298; BIG5P_FREQ[37][138] = 297; BIG5P_FREQ[37][25] = 296; BIG5P_FREQ[0][69] = 295; BIG5P_FREQ[43][172] = 294; BIG5P_FREQ[42][167] = 293; BIG5P_FREQ[35][120] = 292; BIG5P_FREQ[41][128] = 291; BIG5P_FREQ[2][88] = 290; BIG5P_FREQ[20][123] = 289; BIG5P_FREQ[35][123] = 288; BIG5P_FREQ[36][28] = 287; BIG5P_FREQ[42][188] = 286; BIG5P_FREQ[42][164] = 285; BIG5P_FREQ[42][4] = 284; BIG5P_FREQ[43][57] = 283; BIG5P_FREQ[39][3] = 282; BIG5P_FREQ[42][3] = 281; BIG5P_FREQ[57][158] = 280; BIG5P_FREQ[35][146] = 279; BIG5P_FREQ[24][54] = 278; BIG5P_FREQ[13][110] = 277; BIG5P_FREQ[23][132] = 276; BIG5P_FREQ[26][102] = 275; BIG5P_FREQ[55][178] = 274; BIG5P_FREQ[17][117] = 273; BIG5P_FREQ[41][161] = 272; BIG5P_FREQ[38][150] = 271; BIG5P_FREQ[10][71] = 270; BIG5P_FREQ[47][60] = 269; BIG5P_FREQ[16][114] = 268; BIG5P_FREQ[21][47] = 267; BIG5P_FREQ[39][101] = 266; BIG5P_FREQ[18][45] = 265; BIG5P_FREQ[40][121] = 264; BIG5P_FREQ[45][41] = 263; BIG5P_FREQ[22][167] = 262; BIG5P_FREQ[26][149] = 261; BIG5P_FREQ[15][189] = 260; BIG5P_FREQ[41][177] = 259; BIG5P_FREQ[46][36] = 258; BIG5P_FREQ[20][40] = 257; BIG5P_FREQ[41][54] = 256; BIG5P_FREQ[3][87] = 255; BIG5P_FREQ[40][16] = 254; BIG5P_FREQ[42][15] = 253; BIG5P_FREQ[11][83] = 252; BIG5P_FREQ[0][94] = 251; BIG5P_FREQ[122][81] = 250; BIG5P_FREQ[41][26] = 249; BIG5P_FREQ[36][34] = 248; BIG5P_FREQ[44][148] = 247; BIG5P_FREQ[35][3] = 246; BIG5P_FREQ[36][114] = 245; BIG5P_FREQ[42][112] = 244; BIG5P_FREQ[35][183] = 243; BIG5P_FREQ[49][73] = 242; BIG5P_FREQ[39][2] = 241; BIG5P_FREQ[38][121] = 240; BIG5P_FREQ[44][114] = 239; BIG5P_FREQ[49][32] = 238; BIG5P_FREQ[1][65] = 237; BIG5P_FREQ[38][25] = 236; BIG5P_FREQ[39][4] = 235; BIG5P_FREQ[42][62] = 234; BIG5P_FREQ[35][40] = 233; BIG5P_FREQ[24][2] = 232; BIG5P_FREQ[53][49] = 231; BIG5P_FREQ[41][133] = 230; BIG5P_FREQ[43][134] = 229; BIG5P_FREQ[3][83] = 228; BIG5P_FREQ[38][158] = 227; BIG5P_FREQ[24][17] = 226; BIG5P_FREQ[52][59] = 225; BIG5P_FREQ[38][41] = 224; BIG5P_FREQ[37][127] = 223; BIG5P_FREQ[22][175] = 222; BIG5P_FREQ[44][30] = 221; BIG5P_FREQ[47][178] = 220; BIG5P_FREQ[43][99] = 219; BIG5P_FREQ[19][4] = 218; BIG5P_FREQ[37][97] = 217; BIG5P_FREQ[38][181] = 216; BIG5P_FREQ[45][103] = 215; BIG5P_FREQ[1][86] = 214; BIG5P_FREQ[40][15] = 213; BIG5P_FREQ[22][136] = 212; BIG5P_FREQ[75][165] = 211; BIG5P_FREQ[36][15] = 210; BIG5P_FREQ[46][80] = 209; BIG5P_FREQ[59][55] = 208; BIG5P_FREQ[37][108] = 207; BIG5P_FREQ[21][109] = 206; BIG5P_FREQ[24][165] = 205; BIG5P_FREQ[79][158] = 204; BIG5P_FREQ[44][139] = 203; BIG5P_FREQ[36][124] = 202; BIG5P_FREQ[42][185] = 201; BIG5P_FREQ[39][186] = 200; BIG5P_FREQ[22][128] = 199; BIG5P_FREQ[40][44] = 198; BIG5P_FREQ[41][105] = 197; BIG5P_FREQ[1][70] = 196; BIG5P_FREQ[1][68] = 195; BIG5P_FREQ[53][22] = 194; BIG5P_FREQ[36][54] = 193; BIG5P_FREQ[47][147] = 192; BIG5P_FREQ[35][36] = 191; BIG5P_FREQ[35][185] = 190; BIG5P_FREQ[45][37] = 189; BIG5P_FREQ[43][163] = 188; BIG5P_FREQ[56][115] = 187; BIG5P_FREQ[38][164] = 186; BIG5P_FREQ[35][141] = 185; BIG5P_FREQ[42][132] = 184; BIG5P_FREQ[46][120] = 183; BIG5P_FREQ[69][142] = 182; BIG5P_FREQ[38][175] = 181; BIG5P_FREQ[22][112] = 180; BIG5P_FREQ[38][142] = 179; BIG5P_FREQ[40][37] = 178; BIG5P_FREQ[37][109] = 177; BIG5P_FREQ[40][144] = 176; BIG5P_FREQ[44][117] = 175; BIG5P_FREQ[35][181] = 174; BIG5P_FREQ[26][105] = 173; BIG5P_FREQ[16][48] = 172; BIG5P_FREQ[44][122] = 171; BIG5P_FREQ[12][86] = 170; BIG5P_FREQ[84][53] = 169; BIG5P_FREQ[17][44] = 168; BIG5P_FREQ[59][54] = 167; BIG5P_FREQ[36][98] = 166; BIG5P_FREQ[45][115] = 165; BIG5P_FREQ[73][9] = 164; BIG5P_FREQ[44][123] = 163; BIG5P_FREQ[37][188] = 162; BIG5P_FREQ[51][117] = 161; BIG5P_FREQ[15][156] = 160; BIG5P_FREQ[36][155] = 159; BIG5P_FREQ[44][25] = 158; BIG5P_FREQ[38][12] = 157; BIG5P_FREQ[38][140] = 156; BIG5P_FREQ[23][4] = 155; BIG5P_FREQ[45][149] = 154; BIG5P_FREQ[22][189] = 153; BIG5P_FREQ[38][147] = 152; BIG5P_FREQ[27][5] = 151; BIG5P_FREQ[22][42] = 150; BIG5P_FREQ[3][68] = 149; BIG5P_FREQ[39][51] = 148; BIG5P_FREQ[36][29] = 147; BIG5P_FREQ[20][108] = 146; BIG5P_FREQ[50][57] = 145; BIG5P_FREQ[55][104] = 144; BIG5P_FREQ[22][46] = 143; BIG5P_FREQ[18][164] = 142; BIG5P_FREQ[50][159] = 141; BIG5P_FREQ[85][131] = 140; BIG5P_FREQ[26][79] = 139; BIG5P_FREQ[38][100] = 138; BIG5P_FREQ[53][112] = 137; BIG5P_FREQ[20][190] = 136; BIG5P_FREQ[14][69] = 135; BIG5P_FREQ[23][11] = 134; BIG5P_FREQ[40][114] = 133; BIG5P_FREQ[40][148] = 132; BIG5P_FREQ[53][130] = 131; BIG5P_FREQ[36][2] = 130; BIG5P_FREQ[66][82] = 129; BIG5P_FREQ[45][166] = 128; BIG5P_FREQ[4][88] = 127; BIG5P_FREQ[16][57] = 126; BIG5P_FREQ[22][116] = 125; BIG5P_FREQ[36][108] = 124; BIG5P_FREQ[13][48] = 123; BIG5P_FREQ[54][12] = 122; BIG5P_FREQ[40][136] = 121; BIG5P_FREQ[36][128] = 120; BIG5P_FREQ[23][6] = 119; BIG5P_FREQ[38][125] = 118; BIG5P_FREQ[45][154] = 117; BIG5P_FREQ[51][127] = 116; BIG5P_FREQ[44][163] = 115; BIG5P_FREQ[16][173] = 114; BIG5P_FREQ[43][49] = 113; BIG5P_FREQ[20][112] = 112; BIG5P_FREQ[15][168] = 111; BIG5P_FREQ[35][129] = 110; BIG5P_FREQ[20][45] = 109; BIG5P_FREQ[38][10] = 108; BIG5P_FREQ[57][171] = 107; BIG5P_FREQ[44][190] = 106; BIG5P_FREQ[40][56] = 105; BIG5P_FREQ[36][156] = 104; BIG5P_FREQ[3][88] = 103; BIG5P_FREQ[50][122] = 102; BIG5P_FREQ[36][7] = 101; BIG5P_FREQ[39][43] = 100; BIG5P_FREQ[15][166] = 99; BIG5P_FREQ[42][136] = 98; BIG5P_FREQ[22][131] = 97; BIG5P_FREQ[44][23] = 96; BIG5P_FREQ[54][147] = 95; BIG5P_FREQ[41][32] = 94; BIG5P_FREQ[23][121] = 93; BIG5P_FREQ[39][108] = 92; BIG5P_FREQ[2][78] = 91; BIG5P_FREQ[40][155] = 90; BIG5P_FREQ[55][51] = 89; BIG5P_FREQ[19][34] = 88; BIG5P_FREQ[48][128] = 87; BIG5P_FREQ[48][159] = 86; BIG5P_FREQ[20][70] = 85; BIG5P_FREQ[34][71] = 84; BIG5P_FREQ[16][31] = 83; BIG5P_FREQ[42][157] = 82; BIG5P_FREQ[20][44] = 81; BIG5P_FREQ[11][92] = 80; BIG5P_FREQ[44][180] = 79; BIG5P_FREQ[84][33] = 78; BIG5P_FREQ[16][116] = 77; BIG5P_FREQ[61][163] = 76; BIG5P_FREQ[35][164] = 75; BIG5P_FREQ[36][42] = 74; BIG5P_FREQ[13][40] = 73; BIG5P_FREQ[43][176] = 72; BIG5P_FREQ[2][66] = 71; BIG5P_FREQ[20][133] = 70; BIG5P_FREQ[36][65] = 69; BIG5P_FREQ[38][33] = 68; BIG5P_FREQ[12][91] = 67; BIG5P_FREQ[36][26] = 66; BIG5P_FREQ[15][174] = 65; BIG5P_FREQ[77][32] = 64; BIG5P_FREQ[16][1] = 63; BIG5P_FREQ[25][86] = 62; BIG5P_FREQ[17][13] = 61; BIG5P_FREQ[5][75] = 60; BIG5P_FREQ[36][52] = 59; BIG5P_FREQ[51][164] = 58; BIG5P_FREQ[12][85] = 57; BIG5P_FREQ[39][168] = 56; BIG5P_FREQ[43][16] = 55; BIG5P_FREQ[40][69] = 54; BIG5P_FREQ[26][108] = 53; BIG5P_FREQ[51][56] = 52; BIG5P_FREQ[16][37] = 51; BIG5P_FREQ[40][29] = 50; BIG5P_FREQ[46][171] = 49; BIG5P_FREQ[40][128] = 48; BIG5P_FREQ[72][114] = 47; BIG5P_FREQ[21][103] = 46; BIG5P_FREQ[22][44] = 45; BIG5P_FREQ[40][115] = 44; BIG5P_FREQ[43][7] = 43; BIG5P_FREQ[43][153] = 42; BIG5P_FREQ[17][20] = 41; BIG5P_FREQ[16][49] = 40; BIG5P_FREQ[36][57] = 39; BIG5P_FREQ[18][38] = 38; BIG5P_FREQ[45][184] = 37; BIG5P_FREQ[37][167] = 36; BIG5P_FREQ[26][106] = 35; BIG5P_FREQ[61][121] = 34; BIG5P_FREQ[89][140] = 33; BIG5P_FREQ[46][61] = 32; BIG5P_FREQ[39][163] = 31; BIG5P_FREQ[40][62] = 30; BIG5P_FREQ[38][165] = 29; BIG5P_FREQ[47][37] = 28; BIG5P_FREQ[18][155] = 27; BIG5P_FREQ[20][33] = 26; BIG5P_FREQ[29][90] = 25; BIG5P_FREQ[20][103] = 24; BIG5P_FREQ[37][51] = 23; BIG5P_FREQ[57][0] = 22; BIG5P_FREQ[40][31] = 21; BIG5P_FREQ[45][32] = 20; BIG5P_FREQ[59][23] = 19; BIG5P_FREQ[18][47] = 18; BIG5P_FREQ[45][134] = 17; BIG5P_FREQ[37][59] = 16; BIG5P_FREQ[21][128] = 15; BIG5P_FREQ[36][106] = 14; BIG5P_FREQ[31][39] = 13; BIG5P_FREQ[40][182] = 12; BIG5P_FREQ[52][155] = 11; BIG5P_FREQ[42][166] = 10; BIG5P_FREQ[35][27] = 9; BIG5P_FREQ[38][3] = 8; BIG5P_FREQ[13][44] = 7; BIG5P_FREQ[58][157] = 6; BIG5P_FREQ[47][51] = 5; BIG5P_FREQ[41][37] = 4; BIG5P_FREQ[41][172] = 3; BIG5P_FREQ[51][165] = 2; BIG5P_FREQ[15][161] = 1; BIG5P_FREQ[24][181] = 0; /* Big5Freq[5][0] = 200; Big5Freq[26][57] = 199; Big5Freq[13][155] = 198; Big5Freq[3][38] = 197; Big5Freq[9][155] = 196; Big5Freq[28][53] = 195; Big5Freq[15][71] = 194; Big5Freq[21][95] = 193; Big5Freq[15][112] = 192; Big5Freq[14][138] = 191; Big5Freq[8][18] = 190; Big5Freq[20][151] = 189; Big5Freq[37][27] = 188; Big5Freq[32][48] = 187; Big5Freq[23][66] = 186; Big5Freq[9][2] = 185; Big5Freq[13][133] = 184; Big5Freq[7][127] = 183; Big5Freq[3][11] = 182; Big5Freq[12][118] = 181; Big5Freq[13][101] = 180; Big5Freq[30][153] = 179; Big5Freq[4][65] = 178; Big5Freq[5][25] = 177; Big5Freq[5][140] = 176; Big5Freq[6][25] = 175; Big5Freq[4][52] = 174; Big5Freq[30][156] = 173; Big5Freq[16][13] = 172; Big5Freq[21][8] = 171; Big5Freq[19][74] = 170; Big5Freq[15][145] = 169; Big5Freq[9][15] = 168; Big5Freq[13][82] = 167; Big5Freq[26][86] = 166; Big5Freq[18][52] = 165; Big5Freq[6][109] = 164; Big5Freq[10][99] = 163; Big5Freq[18][101] = 162; Big5Freq[25][49] = 161; Big5Freq[31][79] = 160; Big5Freq[28][20] = 159; Big5Freq[12][115] = 158; Big5Freq[15][66] = 157; Big5Freq[11][104] = 156; Big5Freq[23][106] = 155; Big5Freq[34][157] = 154; Big5Freq[32][94] = 153; Big5Freq[29][88] = 152; Big5Freq[10][46] = 151; Big5Freq[13][118] = 150; Big5Freq[20][37] = 149; Big5Freq[12][30] = 148; Big5Freq[21][4] = 147; Big5Freq[16][33] = 146; Big5Freq[13][52] = 145; Big5Freq[4][7] = 144; Big5Freq[21][49] = 143; Big5Freq[3][27] = 142; Big5Freq[16][91] = 141; Big5Freq[5][155] = 140; Big5Freq[29][130] = 139; Big5Freq[3][125] = 138; Big5Freq[14][26] = 137; Big5Freq[15][39] = 136; Big5Freq[24][110] = 135; Big5Freq[7][141] = 134; Big5Freq[21][15] = 133; Big5Freq[32][104] = 132; Big5Freq[8][31] = 131; Big5Freq[34][112] = 130; Big5Freq[10][75] = 129; Big5Freq[21][23] = 128; Big5Freq[34][131] = 127; Big5Freq[12][3] = 126; Big5Freq[10][62] = 125; Big5Freq[9][120] = 124; Big5Freq[32][149] = 123; Big5Freq[8][44] = 122; Big5Freq[24][2] = 121; Big5Freq[6][148] = 120; Big5Freq[15][103] = 119; Big5Freq[36][54] = 118; Big5Freq[36][134] = 117; Big5Freq[11][7] = 116; Big5Freq[3][90] = 115; Big5Freq[36][73] = 114; Big5Freq[8][102] = 113; Big5Freq[12][87] = 112; Big5Freq[25][64] = 111; Big5Freq[9][1] = 110; Big5Freq[24][121] = 109; Big5Freq[5][75] = 108; Big5Freq[17][83] = 107; Big5Freq[18][57] = 106; Big5Freq[8][95] = 105; Big5Freq[14][36] = 104; Big5Freq[28][113] = 103; Big5Freq[12][56] = 102; Big5Freq[14][61] = 101; Big5Freq[25][138] = 100; Big5Freq[4][34] = 99; Big5Freq[11][152] = 98; Big5Freq[35][0] = 97; Big5Freq[4][15] = 96; Big5Freq[8][82] = 95; Big5Freq[20][73] = 94; Big5Freq[25][52] = 93; Big5Freq[24][6] = 92; Big5Freq[21][78] = 91; Big5Freq[17][32] = 90; Big5Freq[17][91] = 89; Big5Freq[5][76] = 88; Big5Freq[15][60] = 87; Big5Freq[15][150] = 86; Big5Freq[5][80] = 85; Big5Freq[15][81] = 84; Big5Freq[28][108] = 83; Big5Freq[18][14] = 82; Big5Freq[19][109] = 81; Big5Freq[28][133] = 80; Big5Freq[21][97] = 79; Big5Freq[5][105] = 78; Big5Freq[18][114] = 77; Big5Freq[16][95] = 76; Big5Freq[5][51] = 75; Big5Freq[3][148] = 74; Big5Freq[22][102] = 73; Big5Freq[4][123] = 72; Big5Freq[8][88] = 71; Big5Freq[25][111] = 70; Big5Freq[8][149] = 69; Big5Freq[9][48] = 68; Big5Freq[16][126] = 67; Big5Freq[33][150] = 66; Big5Freq[9][54] = 65; Big5Freq[29][104] = 64; Big5Freq[3][3] = 63; Big5Freq[11][49] = 62; Big5Freq[24][109] = 61; Big5Freq[28][116] = 60; Big5Freq[34][113] = 59; Big5Freq[5][3] = 58; Big5Freq[21][106] = 57; Big5Freq[4][98] = 56; Big5Freq[12][135] = 55; Big5Freq[16][101] = 54; Big5Freq[12][147] = 53; Big5Freq[27][55] = 52; Big5Freq[3][5] = 51; Big5Freq[11][101] = 50; Big5Freq[16][157] = 49; Big5Freq[22][114] = 48; Big5Freq[18][46] = 47; Big5Freq[4][29] = 46; Big5Freq[8][103] = 45; Big5Freq[16][151] = 44; Big5Freq[8][29] = 43; Big5Freq[15][114] = 42; Big5Freq[22][70] = 41; Big5Freq[13][121] = 40; Big5Freq[7][112] = 39; Big5Freq[20][83] = 38; Big5Freq[3][36] = 37; Big5Freq[10][103] = 36; Big5Freq[3][96] = 35; Big5Freq[21][79] = 34; Big5Freq[25][120] = 33; Big5Freq[29][121] = 32; Big5Freq[23][71] = 31; Big5Freq[21][22] = 30; Big5Freq[18][89] = 29; Big5Freq[25][104] = 28; Big5Freq[10][124] = 27; Big5Freq[26][4] = 26; Big5Freq[21][136] = 25; Big5Freq[6][112] = 24; Big5Freq[12][103] = 23; Big5Freq[17][66] = 22; Big5Freq[13][151] = 21; Big5Freq[33][152] = 20; Big5Freq[11][148] = 19; Big5Freq[13][57] = 18; Big5Freq[13][41] = 17; Big5Freq[7][60] = 16; Big5Freq[21][29] = 15; Big5Freq[9][157] = 14; Big5Freq[24][95] = 13; Big5Freq[15][148] = 12; Big5Freq[15][122] = 11; Big5Freq[6][125] = 10; Big5Freq[11][25] = 9; Big5Freq[20][55] = 8; Big5Freq[19][84] = 7; Big5Freq[21][82] = 6; Big5Freq[24][3] = 5; Big5Freq[13][70] = 4; Big5Freq[6][21] = 3; Big5Freq[21][86] = 2; Big5Freq[12][23] = 1; Big5Freq[3][85] = 0; */ // ------------------------------------------------------------------------------------EUC_TW_FREQ EUC_TW_FREQ[48][49] = 599; EUC_TW_FREQ[35][65] = 598; EUC_TW_FREQ[41][27] = 597; EUC_TW_FREQ[35][0] = 596; EUC_TW_FREQ[39][19] = 595; EUC_TW_FREQ[35][42] = 594; EUC_TW_FREQ[38][66] = 593; EUC_TW_FREQ[35][8] = 592; EUC_TW_FREQ[35][6] = 591; EUC_TW_FREQ[35][66] = 590; EUC_TW_FREQ[43][14] = 589; EUC_TW_FREQ[69][80] = 588; EUC_TW_FREQ[50][48] = 587; EUC_TW_FREQ[36][71] = 586; EUC_TW_FREQ[37][10] = 585; EUC_TW_FREQ[60][52] = 584; EUC_TW_FREQ[51][21] = 583; EUC_TW_FREQ[40][2] = 582; EUC_TW_FREQ[67][35] = 581; EUC_TW_FREQ[38][78] = 580; EUC_TW_FREQ[49][18] = 579; EUC_TW_FREQ[35][23] = 578; EUC_TW_FREQ[42][83] = 577; EUC_TW_FREQ[79][47] = 576; EUC_TW_FREQ[61][82] = 575; EUC_TW_FREQ[38][7] = 574; EUC_TW_FREQ[35][29] = 573; EUC_TW_FREQ[37][77] = 572; EUC_TW_FREQ[54][67] = 571; EUC_TW_FREQ[38][80] = 570; EUC_TW_FREQ[52][74] = 569; EUC_TW_FREQ[36][37] = 568; EUC_TW_FREQ[74][8] = 567; EUC_TW_FREQ[41][83] = 566; EUC_TW_FREQ[36][75] = 565; EUC_TW_FREQ[49][63] = 564; EUC_TW_FREQ[42][58] = 563; EUC_TW_FREQ[56][33] = 562; EUC_TW_FREQ[37][76] = 561; EUC_TW_FREQ[62][39] = 560; EUC_TW_FREQ[35][21] = 559; EUC_TW_FREQ[70][19] = 558; EUC_TW_FREQ[77][88] = 557; EUC_TW_FREQ[51][14] = 556; EUC_TW_FREQ[36][17] = 555; EUC_TW_FREQ[44][51] = 554; EUC_TW_FREQ[38][72] = 553; EUC_TW_FREQ[74][90] = 552; EUC_TW_FREQ[35][48] = 551; EUC_TW_FREQ[35][69] = 550; EUC_TW_FREQ[66][86] = 549; EUC_TW_FREQ[57][20] = 548; EUC_TW_FREQ[35][53] = 547; EUC_TW_FREQ[36][87] = 546; EUC_TW_FREQ[84][67] = 545; EUC_TW_FREQ[70][56] = 544; EUC_TW_FREQ[71][54] = 543; EUC_TW_FREQ[60][70] = 542; EUC_TW_FREQ[80][1] = 541; EUC_TW_FREQ[39][59] = 540; EUC_TW_FREQ[39][51] = 539; EUC_TW_FREQ[35][44] = 538; EUC_TW_FREQ[48][4] = 537; EUC_TW_FREQ[55][24] = 536; EUC_TW_FREQ[52][4] = 535; EUC_TW_FREQ[54][26] = 534; EUC_TW_FREQ[36][31] = 533; EUC_TW_FREQ[37][22] = 532; EUC_TW_FREQ[37][9] = 531; EUC_TW_FREQ[46][0] = 530; EUC_TW_FREQ[56][46] = 529; EUC_TW_FREQ[47][93] = 528; EUC_TW_FREQ[37][25] = 527; EUC_TW_FREQ[39][8] = 526; EUC_TW_FREQ[46][73] = 525; EUC_TW_FREQ[38][48] = 524; EUC_TW_FREQ[39][83] = 523; EUC_TW_FREQ[60][92] = 522; EUC_TW_FREQ[70][11] = 521; EUC_TW_FREQ[63][84] = 520; EUC_TW_FREQ[38][65] = 519; EUC_TW_FREQ[45][45] = 518; EUC_TW_FREQ[63][49] = 517; EUC_TW_FREQ[63][50] = 516; EUC_TW_FREQ[39][93] = 515; EUC_TW_FREQ[68][20] = 514; EUC_TW_FREQ[44][84] = 513; EUC_TW_FREQ[66][34] = 512; EUC_TW_FREQ[37][58] = 511; EUC_TW_FREQ[39][0] = 510; EUC_TW_FREQ[59][1] = 509; EUC_TW_FREQ[47][8] = 508; EUC_TW_FREQ[61][17] = 507; EUC_TW_FREQ[53][87] = 506; EUC_TW_FREQ[67][26] = 505; EUC_TW_FREQ[43][46] = 504; EUC_TW_FREQ[38][61] = 503; EUC_TW_FREQ[45][9] = 502; EUC_TW_FREQ[66][83] = 501; EUC_TW_FREQ[43][88] = 500; EUC_TW_FREQ[85][20] = 499; EUC_TW_FREQ[57][36] = 498; EUC_TW_FREQ[43][6] = 497; EUC_TW_FREQ[86][77] = 496; EUC_TW_FREQ[42][70] = 495; EUC_TW_FREQ[49][78] = 494; EUC_TW_FREQ[36][40] = 493; EUC_TW_FREQ[42][71] = 492; EUC_TW_FREQ[58][49] = 491; EUC_TW_FREQ[35][20] = 490; EUC_TW_FREQ[76][20] = 489; EUC_TW_FREQ[39][25] = 488; EUC_TW_FREQ[40][34] = 487; EUC_TW_FREQ[39][76] = 486; EUC_TW_FREQ[40][1] = 485; EUC_TW_FREQ[59][0] = 484; EUC_TW_FREQ[39][70] = 483; EUC_TW_FREQ[46][14] = 482; EUC_TW_FREQ[68][77] = 481; EUC_TW_FREQ[38][55] = 480; EUC_TW_FREQ[35][78] = 479; EUC_TW_FREQ[84][44] = 478; EUC_TW_FREQ[36][41] = 477; EUC_TW_FREQ[37][62] = 476; EUC_TW_FREQ[65][67] = 475; EUC_TW_FREQ[69][66] = 474; EUC_TW_FREQ[73][55] = 473; EUC_TW_FREQ[71][49] = 472; EUC_TW_FREQ[66][87] = 471; EUC_TW_FREQ[38][33] = 470; EUC_TW_FREQ[64][61] = 469; EUC_TW_FREQ[35][7] = 468; EUC_TW_FREQ[47][49] = 467; EUC_TW_FREQ[56][14] = 466; EUC_TW_FREQ[36][49] = 465; EUC_TW_FREQ[50][81] = 464; EUC_TW_FREQ[55][76] = 463; EUC_TW_FREQ[35][19] = 462; EUC_TW_FREQ[44][47] = 461; EUC_TW_FREQ[35][15] = 460; EUC_TW_FREQ[82][59] = 459; EUC_TW_FREQ[35][43] = 458; EUC_TW_FREQ[73][0] = 457; EUC_TW_FREQ[57][83] = 456; EUC_TW_FREQ[42][46] = 455; EUC_TW_FREQ[36][0] = 454; EUC_TW_FREQ[70][88] = 453; EUC_TW_FREQ[42][22] = 452; EUC_TW_FREQ[46][58] = 451; EUC_TW_FREQ[36][34] = 450; EUC_TW_FREQ[39][24] = 449; EUC_TW_FREQ[35][55] = 448; EUC_TW_FREQ[44][91] = 447; EUC_TW_FREQ[37][51] = 446; EUC_TW_FREQ[36][19] = 445; EUC_TW_FREQ[69][90] = 444; EUC_TW_FREQ[55][35] = 443; EUC_TW_FREQ[35][54] = 442; EUC_TW_FREQ[49][61] = 441; EUC_TW_FREQ[36][67] = 440; EUC_TW_FREQ[88][34] = 439; EUC_TW_FREQ[35][17] = 438; EUC_TW_FREQ[65][69] = 437; EUC_TW_FREQ[74][89] = 436; EUC_TW_FREQ[37][31] = 435; EUC_TW_FREQ[43][48] = 434; EUC_TW_FREQ[89][27] = 433; EUC_TW_FREQ[42][79] = 432; EUC_TW_FREQ[69][57] = 431; EUC_TW_FREQ[36][13] = 430; EUC_TW_FREQ[35][62] = 429; EUC_TW_FREQ[65][47] = 428; EUC_TW_FREQ[56][8] = 427; EUC_TW_FREQ[38][79] = 426; EUC_TW_FREQ[37][64] = 425; EUC_TW_FREQ[64][64] = 424; EUC_TW_FREQ[38][53] = 423; EUC_TW_FREQ[38][31] = 422; EUC_TW_FREQ[56][81] = 421; EUC_TW_FREQ[36][22] = 420; EUC_TW_FREQ[43][4] = 419; EUC_TW_FREQ[36][90] = 418; EUC_TW_FREQ[38][62] = 417; EUC_TW_FREQ[66][85] = 416; EUC_TW_FREQ[39][1] = 415; EUC_TW_FREQ[59][40] = 414; EUC_TW_FREQ[58][93] = 413; EUC_TW_FREQ[44][43] = 412; EUC_TW_FREQ[39][49] = 411; EUC_TW_FREQ[64][2] = 410; EUC_TW_FREQ[41][35] = 409; EUC_TW_FREQ[60][22] = 408; EUC_TW_FREQ[35][91] = 407; EUC_TW_FREQ[78][1] = 406; EUC_TW_FREQ[36][14] = 405; EUC_TW_FREQ[82][29] = 404; EUC_TW_FREQ[52][86] = 403; EUC_TW_FREQ[40][16] = 402; EUC_TW_FREQ[91][52] = 401; EUC_TW_FREQ[50][75] = 400; EUC_TW_FREQ[64][30] = 399; EUC_TW_FREQ[90][78] = 398; EUC_TW_FREQ[36][52] = 397; EUC_TW_FREQ[55][87] = 396; EUC_TW_FREQ[57][5] = 395; EUC_TW_FREQ[57][31] = 394; EUC_TW_FREQ[42][35] = 393; EUC_TW_FREQ[69][50] = 392; EUC_TW_FREQ[45][8] = 391; EUC_TW_FREQ[50][87] = 390; EUC_TW_FREQ[69][55] = 389; EUC_TW_FREQ[92][3] = 388; EUC_TW_FREQ[36][43] = 387; EUC_TW_FREQ[64][10] = 386; EUC_TW_FREQ[56][25] = 385; EUC_TW_FREQ[60][68] = 384; EUC_TW_FREQ[51][46] = 383; EUC_TW_FREQ[50][0] = 382; EUC_TW_FREQ[38][30] = 381; EUC_TW_FREQ[50][85] = 380; EUC_TW_FREQ[60][54] = 379; EUC_TW_FREQ[73][6] = 378; EUC_TW_FREQ[73][28] = 377; EUC_TW_FREQ[56][19] = 376; EUC_TW_FREQ[62][69] = 375; EUC_TW_FREQ[81][66] = 374; EUC_TW_FREQ[40][32] = 373; EUC_TW_FREQ[76][31] = 372; EUC_TW_FREQ[35][10] = 371; EUC_TW_FREQ[41][37] = 370; EUC_TW_FREQ[52][82] = 369; EUC_TW_FREQ[91][72] = 368; EUC_TW_FREQ[37][29] = 367; EUC_TW_FREQ[56][30] = 366; EUC_TW_FREQ[37][80] = 365; EUC_TW_FREQ[81][56] = 364; EUC_TW_FREQ[70][3] = 363; EUC_TW_FREQ[76][15] = 362; EUC_TW_FREQ[46][47] = 361; EUC_TW_FREQ[35][88] = 360; EUC_TW_FREQ[61][58] = 359; EUC_TW_FREQ[37][37] = 358; EUC_TW_FREQ[57][22] = 357; EUC_TW_FREQ[41][23] = 356; EUC_TW_FREQ[90][66] = 355; EUC_TW_FREQ[39][60] = 354; EUC_TW_FREQ[38][0] = 353; EUC_TW_FREQ[37][87] = 352; EUC_TW_FREQ[46][2] = 351; EUC_TW_FREQ[38][56] = 350; EUC_TW_FREQ[58][11] = 349; EUC_TW_FREQ[48][10] = 348; EUC_TW_FREQ[74][4] = 347; EUC_TW_FREQ[40][42] = 346; EUC_TW_FREQ[41][52] = 345; EUC_TW_FREQ[61][92] = 344; EUC_TW_FREQ[39][50] = 343; EUC_TW_FREQ[47][88] = 342; EUC_TW_FREQ[88][36] = 341; EUC_TW_FREQ[45][73] = 340; EUC_TW_FREQ[82][3] = 339; EUC_TW_FREQ[61][36] = 338; EUC_TW_FREQ[60][33] = 337; EUC_TW_FREQ[38][27] = 336; EUC_TW_FREQ[35][83] = 335; EUC_TW_FREQ[65][24] = 334; EUC_TW_FREQ[73][10] = 333; EUC_TW_FREQ[41][13] = 332; EUC_TW_FREQ[50][27] = 331; EUC_TW_FREQ[59][50] = 330; EUC_TW_FREQ[42][45] = 329; EUC_TW_FREQ[55][19] = 328; EUC_TW_FREQ[36][77] = 327; EUC_TW_FREQ[69][31] = 326; EUC_TW_FREQ[60][7] = 325; EUC_TW_FREQ[40][88] = 324; EUC_TW_FREQ[57][56] = 323; EUC_TW_FREQ[50][50] = 322; EUC_TW_FREQ[42][37] = 321; EUC_TW_FREQ[38][82] = 320; EUC_TW_FREQ[52][25] = 319; EUC_TW_FREQ[42][67] = 318; EUC_TW_FREQ[48][40] = 317; EUC_TW_FREQ[45][81] = 316; EUC_TW_FREQ[57][14] = 315; EUC_TW_FREQ[42][13] = 314; EUC_TW_FREQ[78][0] = 313; EUC_TW_FREQ[35][51] = 312; EUC_TW_FREQ[41][67] = 311; EUC_TW_FREQ[64][23] = 310; EUC_TW_FREQ[36][65] = 309; EUC_TW_FREQ[48][50] = 308; EUC_TW_FREQ[46][69] = 307; EUC_TW_FREQ[47][89] = 306; EUC_TW_FREQ[41][48] = 305; EUC_TW_FREQ[60][56] = 304; EUC_TW_FREQ[44][82] = 303; EUC_TW_FREQ[47][35] = 302; EUC_TW_FREQ[49][3] = 301; EUC_TW_FREQ[49][69] = 300; EUC_TW_FREQ[45][93] = 299; EUC_TW_FREQ[60][34] = 298; EUC_TW_FREQ[60][82] = 297; EUC_TW_FREQ[61][61] = 296; EUC_TW_FREQ[86][42] = 295; EUC_TW_FREQ[89][60] = 294; EUC_TW_FREQ[48][31] = 293; EUC_TW_FREQ[35][75] = 292; EUC_TW_FREQ[91][39] = 291; EUC_TW_FREQ[53][19] = 290; EUC_TW_FREQ[39][72] = 289; EUC_TW_FREQ[69][59] = 288; EUC_TW_FREQ[41][7] = 287; EUC_TW_FREQ[54][13] = 286; EUC_TW_FREQ[43][28] = 285; EUC_TW_FREQ[36][6] = 284; EUC_TW_FREQ[45][75] = 283; EUC_TW_FREQ[36][61] = 282; EUC_TW_FREQ[38][21] = 281; EUC_TW_FREQ[45][14] = 280; EUC_TW_FREQ[61][43] = 279; EUC_TW_FREQ[36][63] = 278; EUC_TW_FREQ[43][30] = 277; EUC_TW_FREQ[46][51] = 276; EUC_TW_FREQ[68][87] = 275; EUC_TW_FREQ[39][26] = 274; EUC_TW_FREQ[46][76] = 273; EUC_TW_FREQ[36][15] = 272; EUC_TW_FREQ[35][40] = 271; EUC_TW_FREQ[79][60] = 270; EUC_TW_FREQ[46][7] = 269; EUC_TW_FREQ[65][72] = 268; EUC_TW_FREQ[69][88] = 267; EUC_TW_FREQ[47][18] = 266; EUC_TW_FREQ[37][0] = 265; EUC_TW_FREQ[37][49] = 264; EUC_TW_FREQ[67][37] = 263; EUC_TW_FREQ[36][91] = 262; EUC_TW_FREQ[75][48] = 261; EUC_TW_FREQ[75][63] = 260; EUC_TW_FREQ[83][87] = 259; EUC_TW_FREQ[37][44] = 258; EUC_TW_FREQ[73][54] = 257; EUC_TW_FREQ[51][61] = 256; EUC_TW_FREQ[46][57] = 255; EUC_TW_FREQ[55][21] = 254; EUC_TW_FREQ[39][66] = 253; EUC_TW_FREQ[47][11] = 252; EUC_TW_FREQ[52][8] = 251; EUC_TW_FREQ[82][81] = 250; EUC_TW_FREQ[36][57] = 249; EUC_TW_FREQ[38][54] = 248; EUC_TW_FREQ[43][81] = 247; EUC_TW_FREQ[37][42] = 246; EUC_TW_FREQ[40][18] = 245; EUC_TW_FREQ[80][90] = 244; EUC_TW_FREQ[37][84] = 243; EUC_TW_FREQ[57][15] = 242; EUC_TW_FREQ[38][87] = 241; EUC_TW_FREQ[37][32] = 240; EUC_TW_FREQ[53][53] = 239; EUC_TW_FREQ[89][29] = 238; EUC_TW_FREQ[81][53] = 237; EUC_TW_FREQ[75][3] = 236; EUC_TW_FREQ[83][73] = 235; EUC_TW_FREQ[66][13] = 234; EUC_TW_FREQ[48][7] = 233; EUC_TW_FREQ[46][35] = 232; EUC_TW_FREQ[35][86] = 231; EUC_TW_FREQ[37][20] = 230; EUC_TW_FREQ[46][80] = 229; EUC_TW_FREQ[38][24] = 228; EUC_TW_FREQ[41][68] = 227; EUC_TW_FREQ[42][21] = 226; EUC_TW_FREQ[43][32] = 225; EUC_TW_FREQ[38][20] = 224; EUC_TW_FREQ[37][59] = 223; EUC_TW_FREQ[41][77] = 222; EUC_TW_FREQ[59][57] = 221; EUC_TW_FREQ[68][59] = 220; EUC_TW_FREQ[39][43] = 219; EUC_TW_FREQ[54][39] = 218; EUC_TW_FREQ[48][28] = 217; EUC_TW_FREQ[54][28] = 216; EUC_TW_FREQ[41][44] = 215; EUC_TW_FREQ[51][64] = 214; EUC_TW_FREQ[47][72] = 213; EUC_TW_FREQ[62][67] = 212; EUC_TW_FREQ[42][43] = 211; EUC_TW_FREQ[61][38] = 210; EUC_TW_FREQ[76][25] = 209; EUC_TW_FREQ[48][91] = 208; EUC_TW_FREQ[36][36] = 207; EUC_TW_FREQ[80][32] = 206; EUC_TW_FREQ[81][40] = 205; EUC_TW_FREQ[37][5] = 204; EUC_TW_FREQ[74][69] = 203; EUC_TW_FREQ[36][82] = 202; EUC_TW_FREQ[46][59] = 201; /* EUC_TWFreq[38][32] = 200; EUC_TWFreq[74][2] = 199; EUC_TWFreq[53][31] = 198; EUC_TWFreq[35][38] = 197; EUC_TWFreq[46][62] = 196; EUC_TWFreq[77][31] = 195; EUC_TWFreq[55][74] = 194; EUC_TWFreq[66][6] = 193; EUC_TWFreq[56][21] = 192; EUC_TWFreq[54][78] = 191; EUC_TWFreq[43][51] = 190; EUC_TWFreq[64][93] = 189; EUC_TWFreq[92][7] = 188; EUC_TWFreq[83][89] = 187; EUC_TWFreq[69][9] = 186; EUC_TWFreq[45][4] = 185; EUC_TWFreq[53][9] = 184; EUC_TWFreq[43][2] = 183; EUC_TWFreq[35][11] = 182; EUC_TWFreq[51][25] = 181; EUC_TWFreq[52][71] = 180; EUC_TWFreq[81][67] = 179; EUC_TWFreq[37][33] = 178; EUC_TWFreq[38][57] = 177; EUC_TWFreq[39][77] = 176; EUC_TWFreq[40][26] = 175; EUC_TWFreq[37][21] = 174; EUC_TWFreq[81][70] = 173; EUC_TWFreq[56][80] = 172; EUC_TWFreq[65][14] = 171; EUC_TWFreq[62][47] = 170; EUC_TWFreq[56][54] = 169; EUC_TWFreq[45][17] = 168; EUC_TWFreq[52][52] = 167; EUC_TWFreq[74][30] = 166; EUC_TWFreq[60][57] = 165; EUC_TWFreq[41][15] = 164; EUC_TWFreq[47][69] = 163; EUC_TWFreq[61][11] = 162; EUC_TWFreq[72][25] = 161; EUC_TWFreq[82][56] = 160; EUC_TWFreq[76][92] = 159; EUC_TWFreq[51][22] = 158; EUC_TWFreq[55][69] = 157; EUC_TWFreq[49][43] = 156; EUC_TWFreq[69][49] = 155; EUC_TWFreq[88][42] = 154; EUC_TWFreq[84][41] = 153; EUC_TWFreq[79][33] = 152; EUC_TWFreq[47][17] = 151; EUC_TWFreq[52][88] = 150; EUC_TWFreq[63][74] = 149; EUC_TWFreq[50][32] = 148; EUC_TWFreq[65][10] = 147; EUC_TWFreq[57][6] = 146; EUC_TWFreq[52][23] = 145; EUC_TWFreq[36][70] = 144; EUC_TWFreq[65][55] = 143; EUC_TWFreq[35][27] = 142; EUC_TWFreq[57][63] = 141; EUC_TWFreq[39][92] = 140; EUC_TWFreq[79][75] = 139; EUC_TWFreq[36][30] = 138; EUC_TWFreq[53][60] = 137; EUC_TWFreq[55][43] = 136; EUC_TWFreq[71][22] = 135; EUC_TWFreq[43][16] = 134; EUC_TWFreq[65][21] = 133; EUC_TWFreq[84][51] = 132; EUC_TWFreq[43][64] = 131; EUC_TWFreq[87][91] = 130; EUC_TWFreq[47][45] = 129; EUC_TWFreq[65][29] = 128; EUC_TWFreq[88][16] = 127; EUC_TWFreq[50][5] = 126; EUC_TWFreq[47][33] = 125; EUC_TWFreq[46][27] = 124; EUC_TWFreq[85][2] = 123; EUC_TWFreq[43][77] = 122; EUC_TWFreq[70][9] = 121; EUC_TWFreq[41][54] = 120; EUC_TWFreq[56][12] = 119; EUC_TWFreq[90][65] = 118; EUC_TWFreq[91][50] = 117; EUC_TWFreq[48][41] = 116; EUC_TWFreq[35][89] = 115; EUC_TWFreq[90][83] = 114; EUC_TWFreq[44][40] = 113; EUC_TWFreq[50][88] = 112; EUC_TWFreq[72][39] = 111; EUC_TWFreq[45][3] = 110; EUC_TWFreq[71][33] = 109; EUC_TWFreq[39][12] = 108; EUC_TWFreq[59][24] = 107; EUC_TWFreq[60][62] = 106; EUC_TWFreq[44][33] = 105; EUC_TWFreq[53][70] = 104; EUC_TWFreq[77][90] = 103; EUC_TWFreq[50][58] = 102; EUC_TWFreq[54][1] = 101; EUC_TWFreq[73][19] = 100; EUC_TWFreq[37][3] = 99; EUC_TWFreq[49][91] = 98; EUC_TWFreq[88][43] = 97; EUC_TWFreq[36][78] = 96; EUC_TWFreq[44][20] = 95; EUC_TWFreq[64][15] = 94; EUC_TWFreq[72][28] = 93; EUC_TWFreq[70][13] = 92; EUC_TWFreq[65][83] = 91; EUC_TWFreq[58][68] = 90; EUC_TWFreq[59][32] = 89; EUC_TWFreq[39][13] = 88; EUC_TWFreq[55][64] = 87; EUC_TWFreq[56][59] = 86; EUC_TWFreq[39][17] = 85; EUC_TWFreq[55][84] = 84; EUC_TWFreq[77][85] = 83; EUC_TWFreq[60][19] = 82; EUC_TWFreq[62][82] = 81; EUC_TWFreq[78][16] = 80; EUC_TWFreq[66][8] = 79; EUC_TWFreq[39][42] = 78; EUC_TWFreq[61][24] = 77; EUC_TWFreq[57][67] = 76; EUC_TWFreq[38][83] = 75; EUC_TWFreq[36][53] = 74; EUC_TWFreq[67][76] = 73; EUC_TWFreq[37][91] = 72; EUC_TWFreq[44][26] = 71; EUC_TWFreq[72][86] = 70; EUC_TWFreq[44][87] = 69; EUC_TWFreq[45][50] = 68; EUC_TWFreq[58][4] = 67; EUC_TWFreq[86][65] = 66; EUC_TWFreq[45][56] = 65; EUC_TWFreq[79][49] = 64; EUC_TWFreq[35][3] = 63; EUC_TWFreq[48][83] = 62; EUC_TWFreq[71][21] = 61; EUC_TWFreq[77][93] = 60; EUC_TWFreq[87][92] = 59; EUC_TWFreq[38][35] = 58; EUC_TWFreq[66][17] = 57; EUC_TWFreq[37][66] = 56; EUC_TWFreq[51][42] = 55; EUC_TWFreq[57][73] = 54; EUC_TWFreq[51][54] = 53; EUC_TWFreq[75][64] = 52; EUC_TWFreq[35][5] = 51; EUC_TWFreq[49][40] = 50; EUC_TWFreq[58][35] = 49; EUC_TWFreq[67][88] = 48; EUC_TWFreq[60][51] = 47; EUC_TWFreq[36][92] = 46; EUC_TWFreq[44][41] = 45; EUC_TWFreq[58][29] = 44; EUC_TWFreq[43][62] = 43; EUC_TWFreq[56][23] = 42; EUC_TWFreq[67][44] = 41; EUC_TWFreq[52][91] = 40; EUC_TWFreq[42][81] = 39; EUC_TWFreq[64][25] = 38; EUC_TWFreq[35][36] = 37; EUC_TWFreq[47][73] = 36; EUC_TWFreq[36][1] = 35; EUC_TWFreq[65][84] = 34; EUC_TWFreq[73][1] = 33; EUC_TWFreq[79][66] = 32; EUC_TWFreq[69][14] = 31; EUC_TWFreq[65][28] = 30; EUC_TWFreq[60][93] = 29; EUC_TWFreq[72][79] = 28; EUC_TWFreq[48][0] = 27; EUC_TWFreq[73][43] = 26; EUC_TWFreq[66][47] = 25; EUC_TWFreq[41][18] = 24; EUC_TWFreq[51][10] = 23; EUC_TWFreq[59][7] = 22; EUC_TWFreq[53][27] = 21; EUC_TWFreq[86][67] = 20; EUC_TWFreq[49][87] = 19; EUC_TWFreq[52][28] = 18; EUC_TWFreq[52][12] = 17; EUC_TWFreq[42][30] = 16; EUC_TWFreq[65][35] = 15; EUC_TWFreq[46][64] = 14; EUC_TWFreq[71][7] = 13; EUC_TWFreq[56][57] = 12; EUC_TWFreq[56][31] = 11; EUC_TWFreq[41][31] = 10; EUC_TWFreq[48][59] = 9; EUC_TWFreq[63][92] = 8; EUC_TWFreq[62][57] = 7; EUC_TWFreq[65][87] = 6; EUC_TWFreq[70][10] = 5; EUC_TWFreq[52][40] = 4; EUC_TWFreq[40][22] = 3; EUC_TWFreq[65][91] = 2; EUC_TWFreq[50][25] = 1; EUC_TWFreq[35][84] = 0; EUC_TWFreq[45][90] = 600; */ // ------------------------------------------------------------------------------------GBK_FREQ GBK_FREQ[52][132] = 600; GBK_FREQ[73][135] = 599; GBK_FREQ[49][123] = 598; GBK_FREQ[77][146] = 597; GBK_FREQ[81][123] = 596; GBK_FREQ[82][144] = 595; GBK_FREQ[51][179] = 594; GBK_FREQ[83][154] = 593; GBK_FREQ[71][139] = 592; GBK_FREQ[64][139] = 591; GBK_FREQ[85][144] = 590; GBK_FREQ[52][125] = 589; GBK_FREQ[88][25] = 588; GBK_FREQ[81][106] = 587; GBK_FREQ[81][148] = 586; GBK_FREQ[62][137] = 585; GBK_FREQ[94][0] = 584; GBK_FREQ[1][64] = 583; GBK_FREQ[67][163] = 582; GBK_FREQ[20][190] = 581; GBK_FREQ[57][131] = 580; GBK_FREQ[29][169] = 579; GBK_FREQ[72][143] = 578; GBK_FREQ[0][173] = 577; GBK_FREQ[11][23] = 576; GBK_FREQ[61][141] = 575; GBK_FREQ[60][123] = 574; GBK_FREQ[81][114] = 573; GBK_FREQ[82][131] = 572; GBK_FREQ[67][156] = 571; GBK_FREQ[71][167] = 570; GBK_FREQ[20][50] = 569; GBK_FREQ[77][132] = 568; GBK_FREQ[84][38] = 567; GBK_FREQ[26][29] = 566; GBK_FREQ[74][187] = 565; GBK_FREQ[62][116] = 564; GBK_FREQ[67][135] = 563; GBK_FREQ[5][86] = 562; GBK_FREQ[72][186] = 561; GBK_FREQ[75][161] = 560; GBK_FREQ[78][130] = 559; GBK_FREQ[94][30] = 558; GBK_FREQ[84][72] = 557; GBK_FREQ[1][67] = 556; GBK_FREQ[75][172] = 555; GBK_FREQ[74][185] = 554; GBK_FREQ[53][160] = 553; GBK_FREQ[123][14] = 552; GBK_FREQ[79][97] = 551; GBK_FREQ[85][110] = 550; GBK_FREQ[78][171] = 549; GBK_FREQ[52][131] = 548; GBK_FREQ[56][100] = 547; GBK_FREQ[50][182] = 546; GBK_FREQ[94][64] = 545; GBK_FREQ[106][74] = 544; GBK_FREQ[11][102] = 543; GBK_FREQ[53][124] = 542; GBK_FREQ[24][3] = 541; GBK_FREQ[86][148] = 540; GBK_FREQ[53][184] = 539; GBK_FREQ[86][147] = 538; GBK_FREQ[96][161] = 537; GBK_FREQ[82][77] = 536; GBK_FREQ[59][146] = 535; GBK_FREQ[84][126] = 534; GBK_FREQ[79][132] = 533; GBK_FREQ[85][123] = 532; GBK_FREQ[71][101] = 531; GBK_FREQ[85][106] = 530; GBK_FREQ[6][184] = 529; GBK_FREQ[57][156] = 528; GBK_FREQ[75][104] = 527; GBK_FREQ[50][137] = 526; GBK_FREQ[79][133] = 525; GBK_FREQ[76][108] = 524; GBK_FREQ[57][142] = 523; GBK_FREQ[84][130] = 522; GBK_FREQ[52][128] = 521; GBK_FREQ[47][44] = 520; GBK_FREQ[52][152] = 519; GBK_FREQ[54][104] = 518; GBK_FREQ[30][47] = 517; GBK_FREQ[71][123] = 516; GBK_FREQ[52][107] = 515; GBK_FREQ[45][84] = 514; GBK_FREQ[107][118] = 513; GBK_FREQ[5][161] = 512; GBK_FREQ[48][126] = 511; GBK_FREQ[67][170] = 510; GBK_FREQ[43][6] = 509; GBK_FREQ[70][112] = 508; GBK_FREQ[86][174] = 507; GBK_FREQ[84][166] = 506; GBK_FREQ[79][130] = 505; GBK_FREQ[57][141] = 504; GBK_FREQ[81][178] = 503; GBK_FREQ[56][187] = 502; GBK_FREQ[81][162] = 501; GBK_FREQ[53][104] = 500; GBK_FREQ[123][35] = 499; GBK_FREQ[70][169] = 498; GBK_FREQ[69][164] = 497; GBK_FREQ[109][61] = 496; GBK_FREQ[73][130] = 495; GBK_FREQ[62][134] = 494; GBK_FREQ[54][125] = 493; GBK_FREQ[79][105] = 492; GBK_FREQ[70][165] = 491; GBK_FREQ[71][189] = 490; GBK_FREQ[23][147] = 489; GBK_FREQ[51][139] = 488; GBK_FREQ[47][137] = 487; GBK_FREQ[77][123] = 486; GBK_FREQ[86][183] = 485; GBK_FREQ[63][173] = 484; GBK_FREQ[79][144] = 483; GBK_FREQ[84][159] = 482; GBK_FREQ[60][91] = 481; GBK_FREQ[66][187] = 480; GBK_FREQ[73][114] = 479; GBK_FREQ[85][56] = 478; GBK_FREQ[71][149] = 477; GBK_FREQ[84][189] = 476; GBK_FREQ[104][31] = 475; GBK_FREQ[83][82] = 474; GBK_FREQ[68][35] = 473; GBK_FREQ[11][77] = 472; GBK_FREQ[15][155] = 471; GBK_FREQ[83][153] = 470; GBK_FREQ[71][1] = 469; GBK_FREQ[53][190] = 468; GBK_FREQ[50][135] = 467; GBK_FREQ[3][147] = 466; GBK_FREQ[48][136] = 465; GBK_FREQ[66][166] = 464; GBK_FREQ[55][159] = 463; GBK_FREQ[82][150] = 462; GBK_FREQ[58][178] = 461; GBK_FREQ[64][102] = 460; GBK_FREQ[16][106] = 459; GBK_FREQ[68][110] = 458; GBK_FREQ[54][14] = 457; GBK_FREQ[60][140] = 456; GBK_FREQ[91][71] = 455; GBK_FREQ[54][150] = 454; GBK_FREQ[78][177] = 453; GBK_FREQ[78][117] = 452; GBK_FREQ[104][12] = 451; GBK_FREQ[73][150] = 450; GBK_FREQ[51][142] = 449; GBK_FREQ[81][145] = 448; GBK_FREQ[66][183] = 447; GBK_FREQ[51][178] = 446; GBK_FREQ[75][107] = 445; GBK_FREQ[65][119] = 444; GBK_FREQ[69][176] = 443; GBK_FREQ[59][122] = 442; GBK_FREQ[78][160] = 441; GBK_FREQ[85][183] = 440; GBK_FREQ[105][16] = 439; GBK_FREQ[73][110] = 438; GBK_FREQ[104][39] = 437; GBK_FREQ[119][16] = 436; GBK_FREQ[76][162] = 435; GBK_FREQ[67][152] = 434; GBK_FREQ[82][24] = 433; GBK_FREQ[73][121] = 432; GBK_FREQ[83][83] = 431; GBK_FREQ[82][145] = 430; GBK_FREQ[49][133] = 429; GBK_FREQ[94][13] = 428; GBK_FREQ[58][139] = 427; GBK_FREQ[74][189] = 426; GBK_FREQ[66][177] = 425; GBK_FREQ[85][184] = 424; GBK_FREQ[55][183] = 423; GBK_FREQ[71][107] = 422; GBK_FREQ[11][98] = 421; GBK_FREQ[72][153] = 420; GBK_FREQ[2][137] = 419; GBK_FREQ[59][147] = 418; GBK_FREQ[58][152] = 417; GBK_FREQ[55][144] = 416; GBK_FREQ[73][125] = 415; GBK_FREQ[52][154] = 414; GBK_FREQ[70][178] = 413; GBK_FREQ[79][148] = 412; GBK_FREQ[63][143] = 411; GBK_FREQ[50][140] = 410; GBK_FREQ[47][145] = 409; GBK_FREQ[48][123] = 408; GBK_FREQ[56][107] = 407; GBK_FREQ[84][83] = 406; GBK_FREQ[59][112] = 405; GBK_FREQ[124][72] = 404; GBK_FREQ[79][99] = 403; GBK_FREQ[3][37] = 402; GBK_FREQ[114][55] = 401; GBK_FREQ[85][152] = 400; GBK_FREQ[60][47] = 399; GBK_FREQ[65][96] = 398; GBK_FREQ[74][110] = 397; GBK_FREQ[86][182] = 396; GBK_FREQ[50][99] = 395; GBK_FREQ[67][186] = 394; GBK_FREQ[81][74] = 393; GBK_FREQ[80][37] = 392; GBK_FREQ[21][60] = 391; GBK_FREQ[110][12] = 390; GBK_FREQ[60][162] = 389; GBK_FREQ[29][115] = 388; GBK_FREQ[83][130] = 387; GBK_FREQ[52][136] = 386; GBK_FREQ[63][114] = 385; GBK_FREQ[49][127] = 384; GBK_FREQ[83][109] = 383; GBK_FREQ[66][128] = 382; GBK_FREQ[78][136] = 381; GBK_FREQ[81][180] = 380; GBK_FREQ[76][104] = 379; GBK_FREQ[56][156] = 378; GBK_FREQ[61][23] = 377; GBK_FREQ[4][30] = 376; GBK_FREQ[69][154] = 375; GBK_FREQ[100][37] = 374; GBK_FREQ[54][177] = 373; GBK_FREQ[23][119] = 372; GBK_FREQ[71][171] = 371; GBK_FREQ[84][146] = 370; GBK_FREQ[20][184] = 369; GBK_FREQ[86][76] = 368; GBK_FREQ[74][132] = 367; GBK_FREQ[47][97] = 366; GBK_FREQ[82][137] = 365; GBK_FREQ[94][56] = 364; GBK_FREQ[92][30] = 363; GBK_FREQ[19][117] = 362; GBK_FREQ[48][173] = 361; GBK_FREQ[2][136] = 360; GBK_FREQ[7][182] = 359; GBK_FREQ[74][188] = 358; GBK_FREQ[14][132] = 357; GBK_FREQ[62][172] = 356; GBK_FREQ[25][39] = 355; GBK_FREQ[85][129] = 354; GBK_FREQ[64][98] = 353; GBK_FREQ[67][127] = 352; GBK_FREQ[72][167] = 351; GBK_FREQ[57][143] = 350; GBK_FREQ[76][187] = 349; GBK_FREQ[83][181] = 348; GBK_FREQ[84][10] = 347; GBK_FREQ[55][166] = 346; GBK_FREQ[55][188] = 345; GBK_FREQ[13][151] = 344; GBK_FREQ[62][124] = 343; GBK_FREQ[53][136] = 342; GBK_FREQ[106][57] = 341; GBK_FREQ[47][166] = 340; GBK_FREQ[109][30] = 339; GBK_FREQ[78][114] = 338; GBK_FREQ[83][19] = 337; GBK_FREQ[56][162] = 336; GBK_FREQ[60][177] = 335; GBK_FREQ[88][9] = 334; GBK_FREQ[74][163] = 333; GBK_FREQ[52][156] = 332; GBK_FREQ[71][180] = 331; GBK_FREQ[60][57] = 330; GBK_FREQ[72][173] = 329; GBK_FREQ[82][91] = 328; GBK_FREQ[51][186] = 327; GBK_FREQ[75][86] = 326; GBK_FREQ[75][78] = 325; GBK_FREQ[76][170] = 324; GBK_FREQ[60][147] = 323; GBK_FREQ[82][75] = 322; GBK_FREQ[80][148] = 321; GBK_FREQ[86][150] = 320; GBK_FREQ[13][95] = 319; GBK_FREQ[0][11] = 318; GBK_FREQ[84][190] = 317; GBK_FREQ[76][166] = 316; GBK_FREQ[14][72] = 315; GBK_FREQ[67][144] = 314; GBK_FREQ[84][44] = 313; GBK_FREQ[72][125] = 312; GBK_FREQ[66][127] = 311; GBK_FREQ[60][25] = 310; GBK_FREQ[70][146] = 309; GBK_FREQ[79][135] = 308; GBK_FREQ[54][135] = 307; GBK_FREQ[60][104] = 306; GBK_FREQ[55][132] = 305; GBK_FREQ[94][2] = 304; GBK_FREQ[54][133] = 303; GBK_FREQ[56][190] = 302; GBK_FREQ[58][174] = 301; GBK_FREQ[80][144] = 300; GBK_FREQ[85][113] = 299; /* GBKFreq[83][15] = 298; GBKFreq[105][80] = 297; GBKFreq[7][179] = 296; GBKFreq[93][4] = 295; GBKFreq[123][40] = 294; GBKFreq[85][120] = 293; GBKFreq[77][165] = 292; GBKFreq[86][67] = 291; GBKFreq[25][162] = 290; GBKFreq[77][183] = 289; GBKFreq[83][71] = 288; GBKFreq[78][99] = 287; GBKFreq[72][177] = 286; GBKFreq[71][97] = 285; GBKFreq[58][111] = 284; GBKFreq[77][175] = 283; GBKFreq[76][181] = 282; GBKFreq[71][142] = 281; GBKFreq[64][150] = 280; GBKFreq[5][142] = 279; GBKFreq[73][128] = 278; GBKFreq[73][156] = 277; GBKFreq[60][188] = 276; GBKFreq[64][56] = 275; GBKFreq[74][128] = 274; GBKFreq[48][163] = 273; GBKFreq[54][116] = 272; GBKFreq[73][127] = 271; GBKFreq[16][176] = 270; GBKFreq[62][149] = 269; GBKFreq[105][96] = 268; GBKFreq[55][186] = 267; GBKFreq[4][51] = 266; GBKFreq[48][113] = 265; GBKFreq[48][152] = 264; GBKFreq[23][9] = 263; GBKFreq[56][102] = 262; GBKFreq[11][81] = 261; GBKFreq[82][112] = 260; GBKFreq[65][85] = 259; GBKFreq[69][125] = 258; GBKFreq[68][31] = 257; GBKFreq[5][20] = 256; GBKFreq[60][176] = 255; GBKFreq[82][81] = 254; GBKFreq[72][107] = 253; GBKFreq[3][52] = 252; GBKFreq[71][157] = 251; GBKFreq[24][46] = 250; GBKFreq[69][108] = 249; GBKFreq[78][178] = 248; GBKFreq[9][69] = 247; GBKFreq[73][144] = 246; GBKFreq[63][187] = 245; GBKFreq[68][36] = 244; GBKFreq[47][151] = 243; GBKFreq[14][74] = 242; GBKFreq[47][114] = 241; GBKFreq[80][171] = 240; GBKFreq[75][152] = 239; GBKFreq[86][40] = 238; GBKFreq[93][43] = 237; GBKFreq[2][50] = 236; GBKFreq[62][66] = 235; GBKFreq[1][183] = 234; GBKFreq[74][124] = 233; GBKFreq[58][104] = 232; GBKFreq[83][106] = 231; GBKFreq[60][144] = 230; GBKFreq[48][99] = 229; GBKFreq[54][157] = 228; GBKFreq[70][179] = 227; GBKFreq[61][127] = 226; GBKFreq[57][135] = 225; GBKFreq[59][190] = 224; GBKFreq[77][116] = 223; GBKFreq[26][17] = 222; GBKFreq[60][13] = 221; GBKFreq[71][38] = 220; GBKFreq[85][177] = 219; GBKFreq[59][73] = 218; GBKFreq[50][150] = 217; GBKFreq[79][102] = 216; GBKFreq[76][118] = 215; GBKFreq[67][132] = 214; GBKFreq[73][146] = 213; GBKFreq[83][184] = 212; GBKFreq[86][159] = 211; GBKFreq[95][120] = 210; GBKFreq[23][139] = 209; GBKFreq[64][183] = 208; GBKFreq[85][103] = 207; GBKFreq[41][90] = 206; GBKFreq[87][72] = 205; GBKFreq[62][104] = 204; GBKFreq[79][168] = 203; GBKFreq[79][150] = 202; GBKFreq[104][20] = 201; GBKFreq[56][114] = 200; GBKFreq[84][26] = 199; GBKFreq[57][99] = 198; GBKFreq[62][154] = 197; GBKFreq[47][98] = 196; GBKFreq[61][64] = 195; GBKFreq[112][18] = 194; GBKFreq[123][19] = 193; GBKFreq[4][98] = 192; GBKFreq[47][163] = 191; GBKFreq[66][188] = 190; GBKFreq[81][85] = 189; GBKFreq[82][30] = 188; GBKFreq[65][83] = 187; GBKFreq[67][24] = 186; GBKFreq[68][179] = 185; GBKFreq[55][177] = 184; GBKFreq[2][122] = 183; GBKFreq[47][139] = 182; GBKFreq[79][158] = 181; GBKFreq[64][143] = 180; GBKFreq[100][24] = 179; GBKFreq[73][103] = 178; GBKFreq[50][148] = 177; GBKFreq[86][97] = 176; GBKFreq[59][116] = 175; GBKFreq[64][173] = 174; GBKFreq[99][91] = 173; GBKFreq[11][99] = 172; GBKFreq[78][179] = 171; GBKFreq[18][17] = 170; GBKFreq[58][185] = 169; GBKFreq[47][165] = 168; GBKFreq[67][131] = 167; GBKFreq[94][40] = 166; GBKFreq[74][153] = 165; GBKFreq[79][142] = 164; GBKFreq[57][98] = 163; GBKFreq[1][164] = 162; GBKFreq[55][168] = 161; GBKFreq[13][141] = 160; GBKFreq[51][31] = 159; GBKFreq[57][178] = 158; GBKFreq[50][189] = 157; GBKFreq[60][167] = 156; GBKFreq[80][34] = 155; GBKFreq[109][80] = 154; GBKFreq[85][54] = 153; GBKFreq[69][183] = 152; GBKFreq[67][143] = 151; GBKFreq[47][120] = 150; GBKFreq[45][75] = 149; GBKFreq[82][98] = 148; GBKFreq[83][22] = 147; GBKFreq[13][103] = 146; GBKFreq[49][174] = 145; GBKFreq[57][181] = 144; GBKFreq[64][127] = 143; GBKFreq[61][131] = 142; GBKFreq[52][180] = 141; GBKFreq[74][134] = 140; GBKFreq[84][187] = 139; GBKFreq[81][189] = 138; GBKFreq[47][160] = 137; GBKFreq[66][148] = 136; GBKFreq[7][4] = 135; GBKFreq[85][134] = 134; GBKFreq[88][13] = 133; GBKFreq[88][80] = 132; GBKFreq[69][166] = 131; GBKFreq[86][18] = 130; GBKFreq[79][141] = 129; GBKFreq[50][108] = 128; GBKFreq[94][69] = 127; GBKFreq[81][110] = 126; GBKFreq[69][119] = 125; GBKFreq[72][161] = 124; GBKFreq[106][45] = 123; GBKFreq[73][124] = 122; GBKFreq[94][28] = 121; GBKFreq[63][174] = 120; GBKFreq[3][149] = 119; GBKFreq[24][160] = 118; GBKFreq[113][94] = 117; GBKFreq[56][138] = 116; GBKFreq[64][185] = 115; GBKFreq[86][56] = 114; GBKFreq[56][150] = 113; GBKFreq[110][55] = 112; GBKFreq[28][13] = 111; GBKFreq[54][190] = 110; GBKFreq[8][180] = 109; GBKFreq[73][149] = 108; GBKFreq[80][155] = 107; GBKFreq[83][172] = 106; GBKFreq[67][174] = 105; GBKFreq[64][180] = 104; GBKFreq[84][46] = 103; GBKFreq[91][74] = 102; GBKFreq[69][134] = 101; GBKFreq[61][107] = 100; GBKFreq[47][171] = 99; GBKFreq[59][51] = 98; GBKFreq[109][74] = 97; GBKFreq[64][174] = 96; GBKFreq[52][151] = 95; GBKFreq[51][176] = 94; GBKFreq[80][157] = 93; GBKFreq[94][31] = 92; GBKFreq[79][155] = 91; GBKFreq[72][174] = 90; GBKFreq[69][113] = 89; GBKFreq[83][167] = 88; GBKFreq[83][122] = 87; GBKFreq[8][178] = 86; GBKFreq[70][186] = 85; GBKFreq[59][153] = 84; GBKFreq[84][68] = 83; GBKFreq[79][39] = 82; GBKFreq[47][180] = 81; GBKFreq[88][53] = 80; GBKFreq[57][154] = 79; GBKFreq[47][153] = 78; GBKFreq[3][153] = 77; GBKFreq[76][134] = 76; GBKFreq[51][166] = 75; GBKFreq[58][176] = 74; GBKFreq[27][138] = 73; GBKFreq[73][126] = 72; GBKFreq[76][185] = 71; GBKFreq[52][186] = 70; GBKFreq[81][151] = 69; GBKFreq[26][50] = 68; GBKFreq[76][173] = 67; GBKFreq[106][56] = 66; GBKFreq[85][142] = 65; GBKFreq[11][103] = 64; GBKFreq[69][159] = 63; GBKFreq[53][142] = 62; GBKFreq[7][6] = 61; GBKFreq[84][59] = 60; GBKFreq[86][3] = 59; GBKFreq[64][144] = 58; GBKFreq[1][187] = 57; GBKFreq[82][128] = 56; GBKFreq[3][66] = 55; GBKFreq[68][133] = 54; GBKFreq[55][167] = 53; GBKFreq[52][130] = 52; GBKFreq[61][133] = 51; GBKFreq[72][181] = 50; GBKFreq[25][98] = 49; GBKFreq[84][149] = 48; GBKFreq[91][91] = 47; GBKFreq[47][188] = 46; GBKFreq[68][130] = 45; GBKFreq[22][44] = 44; GBKFreq[81][121] = 43; GBKFreq[72][140] = 42; GBKFreq[55][133] = 41; GBKFreq[55][185] = 40; GBKFreq[56][105] = 39; GBKFreq[60][30] = 38; GBKFreq[70][103] = 37; GBKFreq[62][141] = 36; GBKFreq[70][144] = 35; GBKFreq[59][111] = 34; GBKFreq[54][17] = 33; GBKFreq[18][190] = 32; GBKFreq[65][164] = 31; GBKFreq[83][125] = 30; GBKFreq[61][121] = 29; GBKFreq[48][13] = 28; GBKFreq[51][189] = 27; GBKFreq[65][68] = 26; GBKFreq[7][0] = 25; GBKFreq[76][188] = 24; GBKFreq[85][117] = 23; GBKFreq[45][33] = 22; GBKFreq[78][187] = 21; GBKFreq[106][48] = 20; GBKFreq[59][52] = 19; GBKFreq[86][185] = 18; GBKFreq[84][121] = 17; GBKFreq[82][189] = 16; GBKFreq[68][156] = 15; GBKFreq[55][125] = 14; GBKFreq[65][175] = 13; GBKFreq[7][140] = 12; GBKFreq[50][106] = 11; GBKFreq[59][124] = 10; GBKFreq[67][115] = 9; GBKFreq[82][114] = 8; GBKFreq[74][121] = 7; GBKFreq[106][69] = 6; GBKFreq[94][27] = 5; GBKFreq[78][98] = 4; GBKFreq[85][186] = 3; GBKFreq[108][90] = 2; GBKFreq[62][160] = 1; GBKFreq[60][169] = 0; */ // ------------------------------------------------------------------------------------KR_FREQ KR_FREQ[31][43] = 600; KR_FREQ[19][56] = 599; KR_FREQ[38][46] = 598; KR_FREQ[3][3] = 597; KR_FREQ[29][77] = 596; KR_FREQ[19][33] = 595; KR_FREQ[30][0] = 594; KR_FREQ[29][89] = 593; KR_FREQ[31][26] = 592; KR_FREQ[31][38] = 591; KR_FREQ[32][85] = 590; KR_FREQ[15][0] = 589; KR_FREQ[16][54] = 588; KR_FREQ[15][76] = 587; KR_FREQ[31][25] = 586; KR_FREQ[23][13] = 585; KR_FREQ[28][34] = 584; KR_FREQ[18][9] = 583; KR_FREQ[29][37] = 582; KR_FREQ[22][45] = 581; KR_FREQ[19][46] = 580; KR_FREQ[16][65] = 579; KR_FREQ[23][5] = 578; KR_FREQ[26][70] = 577; KR_FREQ[31][53] = 576; KR_FREQ[27][12] = 575; KR_FREQ[30][67] = 574; KR_FREQ[31][57] = 573; KR_FREQ[20][20] = 572; KR_FREQ[30][31] = 571; KR_FREQ[20][72] = 570; KR_FREQ[15][51] = 569; KR_FREQ[3][8] = 568; KR_FREQ[32][53] = 567; KR_FREQ[27][85] = 566; KR_FREQ[25][23] = 565; KR_FREQ[15][44] = 564; KR_FREQ[32][3] = 563; KR_FREQ[31][68] = 562; KR_FREQ[30][24] = 561; KR_FREQ[29][49] = 560; KR_FREQ[27][49] = 559; KR_FREQ[23][23] = 558; KR_FREQ[31][91] = 557; KR_FREQ[31][46] = 556; KR_FREQ[19][74] = 555; KR_FREQ[27][27] = 554; KR_FREQ[3][17] = 553; KR_FREQ[20][38] = 552; KR_FREQ[21][82] = 551; KR_FREQ[28][25] = 550; KR_FREQ[32][5] = 549; KR_FREQ[31][23] = 548; KR_FREQ[25][45] = 547; KR_FREQ[32][87] = 546; KR_FREQ[18][26] = 545; KR_FREQ[24][10] = 544; KR_FREQ[26][82] = 543; KR_FREQ[15][89] = 542; KR_FREQ[28][36] = 541; KR_FREQ[28][31] = 540; KR_FREQ[16][23] = 539; KR_FREQ[16][77] = 538; KR_FREQ[19][84] = 537; KR_FREQ[23][72] = 536; KR_FREQ[38][48] = 535; KR_FREQ[23][2] = 534; KR_FREQ[30][20] = 533; KR_FREQ[38][47] = 532; KR_FREQ[39][12] = 531; KR_FREQ[23][21] = 530; KR_FREQ[18][17] = 529; KR_FREQ[30][87] = 528; KR_FREQ[29][62] = 527; KR_FREQ[29][87] = 526; KR_FREQ[34][53] = 525; KR_FREQ[32][29] = 524; KR_FREQ[35][0] = 523; KR_FREQ[24][43] = 522; KR_FREQ[36][44] = 521; KR_FREQ[20][30] = 520; KR_FREQ[39][86] = 519; KR_FREQ[22][14] = 518; KR_FREQ[29][39] = 517; KR_FREQ[28][38] = 516; KR_FREQ[23][79] = 515; KR_FREQ[24][56] = 514; KR_FREQ[29][63] = 513; KR_FREQ[31][45] = 512; KR_FREQ[23][26] = 511; KR_FREQ[15][87] = 510; KR_FREQ[30][74] = 509; KR_FREQ[24][69] = 508; KR_FREQ[20][4] = 507; KR_FREQ[27][50] = 506; KR_FREQ[30][75] = 505; KR_FREQ[24][13] = 504; KR_FREQ[30][8] = 503; KR_FREQ[31][6] = 502; KR_FREQ[25][80] = 501; KR_FREQ[36][8] = 500; KR_FREQ[15][18] = 499; KR_FREQ[39][23] = 498; KR_FREQ[16][24] = 497; KR_FREQ[31][89] = 496; KR_FREQ[15][71] = 495; KR_FREQ[15][57] = 494; KR_FREQ[30][11] = 493; KR_FREQ[15][36] = 492; KR_FREQ[16][60] = 491; KR_FREQ[24][45] = 490; KR_FREQ[37][35] = 489; KR_FREQ[24][87] = 488; KR_FREQ[20][45] = 487; KR_FREQ[31][90] = 486; KR_FREQ[32][21] = 485; KR_FREQ[19][70] = 484; KR_FREQ[24][15] = 483; KR_FREQ[26][92] = 482; KR_FREQ[37][13] = 481; KR_FREQ[39][2] = 480; KR_FREQ[23][70] = 479; KR_FREQ[27][25] = 478; KR_FREQ[15][69] = 477; KR_FREQ[19][61] = 476; KR_FREQ[31][58] = 475; KR_FREQ[24][57] = 474; KR_FREQ[36][74] = 473; KR_FREQ[21][6] = 472; KR_FREQ[30][44] = 471; KR_FREQ[15][91] = 470; KR_FREQ[27][16] = 469; KR_FREQ[29][42] = 468; KR_FREQ[33][86] = 467; KR_FREQ[29][41] = 466; KR_FREQ[20][68] = 465; KR_FREQ[25][47] = 464; KR_FREQ[22][0] = 463; KR_FREQ[18][14] = 462; KR_FREQ[31][28] = 461; KR_FREQ[15][2] = 460; KR_FREQ[23][76] = 459; KR_FREQ[38][32] = 458; KR_FREQ[29][82] = 457; KR_FREQ[21][86] = 456; KR_FREQ[24][62] = 455; KR_FREQ[31][64] = 454; KR_FREQ[38][26] = 453; KR_FREQ[32][86] = 452; KR_FREQ[22][32] = 451; KR_FREQ[19][59] = 450; KR_FREQ[34][18] = 449; KR_FREQ[18][54] = 448; KR_FREQ[38][63] = 447; KR_FREQ[36][23] = 446; KR_FREQ[35][35] = 445; KR_FREQ[32][62] = 444; KR_FREQ[28][35] = 443; KR_FREQ[27][13] = 442; KR_FREQ[31][59] = 441; KR_FREQ[29][29] = 440; KR_FREQ[15][64] = 439; KR_FREQ[26][84] = 438; KR_FREQ[21][90] = 437; KR_FREQ[20][24] = 436; KR_FREQ[16][18] = 435; KR_FREQ[22][23] = 434; KR_FREQ[31][14] = 433; KR_FREQ[15][1] = 432; KR_FREQ[18][63] = 431; KR_FREQ[19][10] = 430; KR_FREQ[25][49] = 429; KR_FREQ[36][57] = 428; KR_FREQ[20][22] = 427; KR_FREQ[15][15] = 426; KR_FREQ[31][51] = 425; KR_FREQ[24][60] = 424; KR_FREQ[31][70] = 423; KR_FREQ[15][7] = 422; KR_FREQ[28][40] = 421; KR_FREQ[18][41] = 420; KR_FREQ[15][38] = 419; KR_FREQ[32][0] = 418; KR_FREQ[19][51] = 417; KR_FREQ[34][62] = 416; KR_FREQ[16][27] = 415; KR_FREQ[20][70] = 414; KR_FREQ[22][33] = 413; KR_FREQ[26][73] = 412; KR_FREQ[20][79] = 411; KR_FREQ[23][6] = 410; KR_FREQ[24][85] = 409; KR_FREQ[38][51] = 408; KR_FREQ[29][88] = 407; KR_FREQ[38][55] = 406; KR_FREQ[32][32] = 405; KR_FREQ[27][18] = 404; KR_FREQ[23][87] = 403; KR_FREQ[35][6] = 402; KR_FREQ[34][27] = 401; KR_FREQ[39][35] = 400; KR_FREQ[30][88] = 399; KR_FREQ[32][92] = 398; KR_FREQ[32][49] = 397; KR_FREQ[24][61] = 396; KR_FREQ[18][74] = 395; KR_FREQ[23][77] = 394; KR_FREQ[23][50] = 393; KR_FREQ[23][32] = 392; KR_FREQ[23][36] = 391; KR_FREQ[38][38] = 390; KR_FREQ[29][86] = 389; KR_FREQ[36][15] = 388; KR_FREQ[31][50] = 387; KR_FREQ[15][86] = 386; KR_FREQ[39][13] = 385; KR_FREQ[34][26] = 384; KR_FREQ[19][34] = 383; KR_FREQ[16][3] = 382; KR_FREQ[26][93] = 381; KR_FREQ[19][67] = 380; KR_FREQ[24][72] = 379; KR_FREQ[29][17] = 378; KR_FREQ[23][24] = 377; KR_FREQ[25][19] = 376; KR_FREQ[18][65] = 375; KR_FREQ[30][78] = 374; KR_FREQ[27][52] = 373; KR_FREQ[22][18] = 372; KR_FREQ[16][38] = 371; KR_FREQ[21][26] = 370; KR_FREQ[34][20] = 369; KR_FREQ[15][42] = 368; KR_FREQ[16][71] = 367; KR_FREQ[17][17] = 366; KR_FREQ[24][71] = 365; KR_FREQ[18][84] = 364; KR_FREQ[15][40] = 363; KR_FREQ[31][62] = 362; KR_FREQ[15][8] = 361; KR_FREQ[16][69] = 360; KR_FREQ[29][79] = 359; KR_FREQ[38][91] = 358; KR_FREQ[31][92] = 357; KR_FREQ[20][77] = 356; KR_FREQ[3][16] = 355; KR_FREQ[27][87] = 354; KR_FREQ[16][25] = 353; KR_FREQ[36][33] = 352; KR_FREQ[37][76] = 351; KR_FREQ[30][12] = 350; KR_FREQ[26][75] = 349; KR_FREQ[25][14] = 348; KR_FREQ[32][26] = 347; KR_FREQ[23][22] = 346; KR_FREQ[20][90] = 345; KR_FREQ[19][8] = 344; KR_FREQ[38][41] = 343; KR_FREQ[34][2] = 342; KR_FREQ[39][4] = 341; KR_FREQ[27][89] = 340; KR_FREQ[28][41] = 339; KR_FREQ[28][44] = 338; KR_FREQ[24][92] = 337; KR_FREQ[34][65] = 336; KR_FREQ[39][14] = 335; KR_FREQ[21][38] = 334; KR_FREQ[19][31] = 333; KR_FREQ[37][39] = 332; KR_FREQ[33][41] = 331; KR_FREQ[38][4] = 330; KR_FREQ[23][80] = 329; KR_FREQ[25][24] = 328; KR_FREQ[37][17] = 327; KR_FREQ[22][16] = 326; KR_FREQ[22][46] = 325; KR_FREQ[33][91] = 324; KR_FREQ[24][89] = 323; KR_FREQ[30][52] = 322; KR_FREQ[29][38] = 321; KR_FREQ[38][85] = 320; KR_FREQ[15][12] = 319; KR_FREQ[27][58] = 318; KR_FREQ[29][52] = 317; KR_FREQ[37][38] = 316; KR_FREQ[34][41] = 315; KR_FREQ[31][65] = 314; KR_FREQ[29][53] = 313; KR_FREQ[22][47] = 312; KR_FREQ[22][19] = 311; KR_FREQ[26][0] = 310; KR_FREQ[37][86] = 309; KR_FREQ[35][4] = 308; KR_FREQ[36][54] = 307; KR_FREQ[20][76] = 306; KR_FREQ[30][9] = 305; KR_FREQ[30][33] = 304; KR_FREQ[23][17] = 303; KR_FREQ[23][33] = 302; KR_FREQ[38][52] = 301; KR_FREQ[15][19] = 300; KR_FREQ[28][45] = 299; KR_FREQ[29][78] = 298; KR_FREQ[23][15] = 297; KR_FREQ[33][5] = 296; KR_FREQ[17][40] = 295; KR_FREQ[30][83] = 294; KR_FREQ[18][1] = 293; KR_FREQ[30][81] = 292; KR_FREQ[19][40] = 291; KR_FREQ[24][47] = 290; KR_FREQ[17][56] = 289; KR_FREQ[39][80] = 288; KR_FREQ[30][46] = 287; KR_FREQ[16][61] = 286; KR_FREQ[26][78] = 285; KR_FREQ[26][57] = 284; KR_FREQ[20][46] = 283; KR_FREQ[25][15] = 282; KR_FREQ[25][91] = 281; KR_FREQ[21][83] = 280; KR_FREQ[30][77] = 279; KR_FREQ[35][30] = 278; KR_FREQ[30][34] = 277; KR_FREQ[20][69] = 276; KR_FREQ[35][10] = 275; KR_FREQ[29][70] = 274; KR_FREQ[22][50] = 273; KR_FREQ[18][0] = 272; KR_FREQ[22][64] = 271; KR_FREQ[38][65] = 270; KR_FREQ[22][70] = 269; KR_FREQ[24][58] = 268; KR_FREQ[19][66] = 267; KR_FREQ[30][59] = 266; KR_FREQ[37][14] = 265; KR_FREQ[16][56] = 264; KR_FREQ[29][85] = 263; KR_FREQ[31][15] = 262; KR_FREQ[36][84] = 261; KR_FREQ[39][15] = 260; KR_FREQ[39][90] = 259; KR_FREQ[18][12] = 258; KR_FREQ[21][93] = 257; KR_FREQ[24][66] = 256; KR_FREQ[27][90] = 255; KR_FREQ[25][90] = 254; KR_FREQ[22][24] = 253; KR_FREQ[36][67] = 252; KR_FREQ[33][90] = 251; KR_FREQ[15][60] = 250; KR_FREQ[23][85] = 249; KR_FREQ[34][1] = 248; KR_FREQ[39][37] = 247; KR_FREQ[21][18] = 246; KR_FREQ[34][4] = 245; KR_FREQ[28][33] = 244; KR_FREQ[15][13] = 243; KR_FREQ[32][22] = 242; KR_FREQ[30][76] = 241; KR_FREQ[20][21] = 240; KR_FREQ[38][66] = 239; KR_FREQ[32][55] = 238; KR_FREQ[32][89] = 237; KR_FREQ[25][26] = 236; KR_FREQ[16][80] = 235; KR_FREQ[15][43] = 234; KR_FREQ[38][54] = 233; KR_FREQ[39][68] = 232; KR_FREQ[22][88] = 231; KR_FREQ[21][84] = 230; KR_FREQ[21][17] = 229; KR_FREQ[20][28] = 228; KR_FREQ[32][1] = 227; KR_FREQ[33][87] = 226; KR_FREQ[38][71] = 225; KR_FREQ[37][47] = 224; KR_FREQ[18][77] = 223; KR_FREQ[37][58] = 222; KR_FREQ[34][74] = 221; KR_FREQ[32][54] = 220; KR_FREQ[27][33] = 219; KR_FREQ[32][93] = 218; KR_FREQ[23][51] = 217; KR_FREQ[20][57] = 216; KR_FREQ[22][37] = 215; KR_FREQ[39][10] = 214; KR_FREQ[39][17] = 213; KR_FREQ[33][4] = 212; KR_FREQ[32][84] = 211; KR_FREQ[34][3] = 210; KR_FREQ[28][27] = 209; KR_FREQ[15][79] = 208; KR_FREQ[34][21] = 207; KR_FREQ[34][69] = 206; KR_FREQ[21][62] = 205; KR_FREQ[36][24] = 204; KR_FREQ[16][89] = 203; KR_FREQ[18][48] = 202; KR_FREQ[38][15] = 201; KR_FREQ[36][58] = 200; KR_FREQ[21][56] = 199; KR_FREQ[34][48] = 198; KR_FREQ[21][15] = 197; KR_FREQ[39][3] = 196; KR_FREQ[16][44] = 195; KR_FREQ[18][79] = 194; KR_FREQ[25][13] = 193; KR_FREQ[29][47] = 192; KR_FREQ[38][88] = 191; KR_FREQ[20][71] = 190; KR_FREQ[16][58] = 189; KR_FREQ[35][57] = 188; KR_FREQ[29][30] = 187; KR_FREQ[29][23] = 186; KR_FREQ[34][93] = 185; KR_FREQ[30][85] = 184; KR_FREQ[15][80] = 183; KR_FREQ[32][78] = 182; KR_FREQ[37][82] = 181; KR_FREQ[22][40] = 180; KR_FREQ[21][69] = 179; KR_FREQ[26][85] = 178; KR_FREQ[31][31] = 177; KR_FREQ[28][64] = 176; KR_FREQ[38][13] = 175; KR_FREQ[25][2] = 174; KR_FREQ[22][34] = 173; KR_FREQ[28][28] = 172; KR_FREQ[24][91] = 171; KR_FREQ[33][74] = 170; KR_FREQ[29][40] = 169; KR_FREQ[15][77] = 168; KR_FREQ[32][80] = 167; KR_FREQ[30][41] = 166; KR_FREQ[23][30] = 165; KR_FREQ[24][63] = 164; KR_FREQ[30][53] = 163; KR_FREQ[39][70] = 162; KR_FREQ[23][61] = 161; KR_FREQ[37][27] = 160; KR_FREQ[16][55] = 159; KR_FREQ[22][74] = 158; KR_FREQ[26][50] = 157; KR_FREQ[16][10] = 156; KR_FREQ[34][63] = 155; KR_FREQ[35][14] = 154; KR_FREQ[17][7] = 153; KR_FREQ[15][59] = 152; KR_FREQ[27][23] = 151; KR_FREQ[18][70] = 150; KR_FREQ[32][56] = 149; KR_FREQ[37][87] = 148; KR_FREQ[17][61] = 147; KR_FREQ[18][83] = 146; KR_FREQ[23][86] = 145; KR_FREQ[17][31] = 144; KR_FREQ[23][83] = 143; KR_FREQ[35][2] = 142; KR_FREQ[18][64] = 141; KR_FREQ[27][43] = 140; KR_FREQ[32][42] = 139; KR_FREQ[25][76] = 138; KR_FREQ[19][85] = 137; KR_FREQ[37][81] = 136; KR_FREQ[38][83] = 135; KR_FREQ[35][7] = 134; KR_FREQ[16][51] = 133; KR_FREQ[27][22] = 132; KR_FREQ[16][76] = 131; KR_FREQ[22][4] = 130; KR_FREQ[38][84] = 129; KR_FREQ[17][83] = 128; KR_FREQ[24][46] = 127; KR_FREQ[33][15] = 126; KR_FREQ[20][48] = 125; KR_FREQ[17][30] = 124; KR_FREQ[30][93] = 123; KR_FREQ[28][11] = 122; KR_FREQ[28][30] = 121; KR_FREQ[15][62] = 120; KR_FREQ[17][87] = 119; KR_FREQ[32][81] = 118; KR_FREQ[23][37] = 117; KR_FREQ[30][22] = 116; KR_FREQ[32][66] = 115; KR_FREQ[33][78] = 114; KR_FREQ[21][4] = 113; KR_FREQ[31][17] = 112; KR_FREQ[39][61] = 111; KR_FREQ[18][76] = 110; KR_FREQ[15][85] = 109; KR_FREQ[31][47] = 108; KR_FREQ[19][57] = 107; KR_FREQ[23][55] = 106; KR_FREQ[27][29] = 105; KR_FREQ[29][46] = 104; KR_FREQ[33][0] = 103; KR_FREQ[16][83] = 102; KR_FREQ[39][78] = 101; KR_FREQ[32][77] = 100; KR_FREQ[36][25] = 99; KR_FREQ[34][19] = 98; KR_FREQ[38][49] = 97; KR_FREQ[19][25] = 96; KR_FREQ[23][53] = 95; KR_FREQ[28][43] = 94; KR_FREQ[31][44] = 93; KR_FREQ[36][34] = 92; KR_FREQ[16][34] = 91; KR_FREQ[35][1] = 90; KR_FREQ[19][87] = 89; KR_FREQ[18][53] = 88; KR_FREQ[29][54] = 87; KR_FREQ[22][41] = 86; KR_FREQ[38][18] = 85; KR_FREQ[22][2] = 84; KR_FREQ[20][3] = 83; KR_FREQ[39][69] = 82; KR_FREQ[30][29] = 81; KR_FREQ[28][19] = 80; KR_FREQ[29][90] = 79; KR_FREQ[17][86] = 78; KR_FREQ[15][9] = 77; KR_FREQ[39][73] = 76; KR_FREQ[15][37] = 75; KR_FREQ[35][40] = 74; KR_FREQ[33][77] = 73; KR_FREQ[27][86] = 72; KR_FREQ[36][79] = 71; KR_FREQ[23][18] = 70; KR_FREQ[34][87] = 69; KR_FREQ[39][24] = 68; KR_FREQ[26][8] = 67; KR_FREQ[33][48] = 66; KR_FREQ[39][30] = 65; KR_FREQ[33][28] = 64; KR_FREQ[16][67] = 63; KR_FREQ[31][78] = 62; KR_FREQ[32][23] = 61; KR_FREQ[24][55] = 60; KR_FREQ[30][68] = 59; KR_FREQ[18][60] = 58; KR_FREQ[15][17] = 57; KR_FREQ[23][34] = 56; KR_FREQ[20][49] = 55; KR_FREQ[15][78] = 54; KR_FREQ[24][14] = 53; KR_FREQ[19][41] = 52; KR_FREQ[31][55] = 51; KR_FREQ[21][39] = 50; KR_FREQ[35][9] = 49; KR_FREQ[30][15] = 48; KR_FREQ[20][52] = 47; KR_FREQ[35][71] = 46; KR_FREQ[20][7] = 45; KR_FREQ[29][72] = 44; KR_FREQ[37][77] = 43; KR_FREQ[22][35] = 42; KR_FREQ[20][61] = 41; KR_FREQ[31][60] = 40; KR_FREQ[20][93] = 39; KR_FREQ[27][92] = 38; KR_FREQ[28][16] = 37; KR_FREQ[36][26] = 36; KR_FREQ[18][89] = 35; KR_FREQ[21][63] = 34; KR_FREQ[22][52] = 33; KR_FREQ[24][65] = 32; KR_FREQ[31][8] = 31; KR_FREQ[31][49] = 30; KR_FREQ[33][30] = 29; KR_FREQ[37][15] = 28; KR_FREQ[18][18] = 27; KR_FREQ[25][50] = 26; KR_FREQ[29][20] = 25; KR_FREQ[35][48] = 24; KR_FREQ[38][75] = 23; KR_FREQ[26][83] = 22; KR_FREQ[21][87] = 21; KR_FREQ[27][71] = 20; KR_FREQ[32][91] = 19; KR_FREQ[25][73] = 18; KR_FREQ[16][84] = 17; KR_FREQ[25][31] = 16; KR_FREQ[17][90] = 15; KR_FREQ[18][40] = 14; KR_FREQ[17][77] = 13; KR_FREQ[17][35] = 12; KR_FREQ[23][52] = 11; KR_FREQ[23][35] = 10; KR_FREQ[16][5] = 9; KR_FREQ[23][58] = 8; KR_FREQ[19][60] = 7; KR_FREQ[30][32] = 6; KR_FREQ[38][34] = 5; KR_FREQ[23][4] = 4; KR_FREQ[23][1] = 3; KR_FREQ[27][57] = 2; KR_FREQ[39][38] = 1; KR_FREQ[32][33] = 0; // ------------------------------------------------------------------------------------JP_FREQ JP_FREQ[3][74] = 600; JP_FREQ[3][45] = 599; JP_FREQ[3][3] = 598; JP_FREQ[3][24] = 597; JP_FREQ[3][30] = 596; JP_FREQ[3][42] = 595; JP_FREQ[3][46] = 594; JP_FREQ[3][39] = 593; JP_FREQ[3][11] = 592; JP_FREQ[3][37] = 591; JP_FREQ[3][38] = 590; JP_FREQ[3][31] = 589; JP_FREQ[3][41] = 588; JP_FREQ[3][5] = 587; JP_FREQ[3][10] = 586; JP_FREQ[3][75] = 585; JP_FREQ[3][65] = 584; JP_FREQ[3][72] = 583; JP_FREQ[37][91] = 582; JP_FREQ[0][27] = 581; JP_FREQ[3][18] = 580; JP_FREQ[3][22] = 579; JP_FREQ[3][61] = 578; JP_FREQ[3][14] = 577; JP_FREQ[24][80] = 576; JP_FREQ[4][82] = 575; JP_FREQ[17][80] = 574; JP_FREQ[30][44] = 573; JP_FREQ[3][73] = 572; JP_FREQ[3][64] = 571; JP_FREQ[38][14] = 570; JP_FREQ[33][70] = 569; JP_FREQ[3][1] = 568; JP_FREQ[3][16] = 567; JP_FREQ[3][35] = 566; JP_FREQ[3][40] = 565; JP_FREQ[4][74] = 564; JP_FREQ[4][24] = 563; JP_FREQ[42][59] = 562; JP_FREQ[3][7] = 561; JP_FREQ[3][71] = 560; JP_FREQ[3][12] = 559; JP_FREQ[15][75] = 558; JP_FREQ[3][20] = 557; JP_FREQ[4][39] = 556; JP_FREQ[34][69] = 555; JP_FREQ[3][28] = 554; JP_FREQ[35][24] = 553; JP_FREQ[3][82] = 552; JP_FREQ[28][47] = 551; JP_FREQ[3][67] = 550; JP_FREQ[37][16] = 549; JP_FREQ[26][93] = 548; JP_FREQ[4][1] = 547; JP_FREQ[26][85] = 546; JP_FREQ[31][14] = 545; JP_FREQ[4][3] = 544; JP_FREQ[4][72] = 543; JP_FREQ[24][51] = 542; JP_FREQ[27][51] = 541; JP_FREQ[27][49] = 540; JP_FREQ[22][77] = 539; JP_FREQ[27][10] = 538; JP_FREQ[29][68] = 537; JP_FREQ[20][35] = 536; JP_FREQ[41][11] = 535; JP_FREQ[24][70] = 534; JP_FREQ[36][61] = 533; JP_FREQ[31][23] = 532; JP_FREQ[43][16] = 531; JP_FREQ[23][68] = 530; JP_FREQ[32][15] = 529; JP_FREQ[3][32] = 528; JP_FREQ[19][53] = 527; JP_FREQ[40][83] = 526; JP_FREQ[4][14] = 525; JP_FREQ[36][9] = 524; JP_FREQ[4][73] = 523; JP_FREQ[23][10] = 522; JP_FREQ[3][63] = 521; JP_FREQ[39][14] = 520; JP_FREQ[3][78] = 519; JP_FREQ[33][47] = 518; JP_FREQ[21][39] = 517; JP_FREQ[34][46] = 516; JP_FREQ[36][75] = 515; JP_FREQ[41][92] = 514; JP_FREQ[37][93] = 513; JP_FREQ[4][34] = 512; JP_FREQ[15][86] = 511; JP_FREQ[46][1] = 510; JP_FREQ[37][65] = 509; JP_FREQ[3][62] = 508; JP_FREQ[32][73] = 507; JP_FREQ[21][65] = 506; JP_FREQ[29][75] = 505; JP_FREQ[26][51] = 504; JP_FREQ[3][34] = 503; JP_FREQ[4][10] = 502; JP_FREQ[30][22] = 501; JP_FREQ[35][73] = 500; JP_FREQ[17][82] = 499; JP_FREQ[45][8] = 498; JP_FREQ[27][73] = 497; JP_FREQ[18][55] = 496; JP_FREQ[25][2] = 495; JP_FREQ[3][26] = 494; JP_FREQ[45][46] = 493; JP_FREQ[4][22] = 492; JP_FREQ[4][40] = 491; JP_FREQ[18][10] = 490; JP_FREQ[32][9] = 489; JP_FREQ[26][49] = 488; JP_FREQ[3][47] = 487; JP_FREQ[24][65] = 486; JP_FREQ[4][76] = 485; JP_FREQ[43][67] = 484; JP_FREQ[3][9] = 483; JP_FREQ[41][37] = 482; JP_FREQ[33][68] = 481; JP_FREQ[43][31] = 480; JP_FREQ[19][55] = 479; JP_FREQ[4][30] = 478; JP_FREQ[27][33] = 477; JP_FREQ[16][62] = 476; JP_FREQ[36][35] = 475; JP_FREQ[37][15] = 474; JP_FREQ[27][70] = 473; JP_FREQ[22][71] = 472; JP_FREQ[33][45] = 471; JP_FREQ[31][78] = 470; JP_FREQ[43][59] = 469; JP_FREQ[32][19] = 468; JP_FREQ[17][28] = 467; JP_FREQ[40][28] = 466; JP_FREQ[20][93] = 465; JP_FREQ[18][15] = 464; JP_FREQ[4][23] = 463; JP_FREQ[3][23] = 462; JP_FREQ[26][64] = 461; JP_FREQ[44][92] = 460; JP_FREQ[17][27] = 459; JP_FREQ[3][56] = 458; JP_FREQ[25][38] = 457; JP_FREQ[23][31] = 456; JP_FREQ[35][43] = 455; JP_FREQ[4][54] = 454; JP_FREQ[35][19] = 453; JP_FREQ[22][47] = 452; JP_FREQ[42][0] = 451; JP_FREQ[23][28] = 450; JP_FREQ[46][33] = 449; JP_FREQ[36][85] = 448; JP_FREQ[31][12] = 447; JP_FREQ[3][76] = 446; JP_FREQ[4][75] = 445; JP_FREQ[36][56] = 444; JP_FREQ[4][64] = 443; JP_FREQ[25][77] = 442; JP_FREQ[15][52] = 441; JP_FREQ[33][73] = 440; JP_FREQ[3][55] = 439; JP_FREQ[43][82] = 438; JP_FREQ[27][82] = 437; JP_FREQ[20][3] = 436; JP_FREQ[40][51] = 435; JP_FREQ[3][17] = 434; JP_FREQ[27][71] = 433; JP_FREQ[4][52] = 432; JP_FREQ[44][48] = 431; JP_FREQ[27][2] = 430; JP_FREQ[17][39] = 429; JP_FREQ[31][8] = 428; JP_FREQ[44][54] = 427; JP_FREQ[43][18] = 426; JP_FREQ[43][77] = 425; JP_FREQ[4][61] = 424; JP_FREQ[19][91] = 423; JP_FREQ[31][13] = 422; JP_FREQ[44][71] = 421; JP_FREQ[20][0] = 420; JP_FREQ[23][87] = 419; JP_FREQ[21][14] = 418; JP_FREQ[29][13] = 417; JP_FREQ[3][58] = 416; JP_FREQ[26][18] = 415; JP_FREQ[4][47] = 414; JP_FREQ[4][18] = 413; JP_FREQ[3][53] = 412; JP_FREQ[26][92] = 411; JP_FREQ[21][7] = 410; JP_FREQ[4][37] = 409; JP_FREQ[4][63] = 408; JP_FREQ[36][51] = 407; JP_FREQ[4][32] = 406; JP_FREQ[28][73] = 405; JP_FREQ[4][50] = 404; JP_FREQ[41][60] = 403; JP_FREQ[23][1] = 402; JP_FREQ[36][92] = 401; JP_FREQ[15][41] = 400; JP_FREQ[21][71] = 399; JP_FREQ[41][30] = 398; JP_FREQ[32][76] = 397; JP_FREQ[17][34] = 396; JP_FREQ[26][15] = 395; JP_FREQ[26][25] = 394; JP_FREQ[31][77] = 393; JP_FREQ[31][3] = 392; JP_FREQ[46][34] = 391; JP_FREQ[27][84] = 390; JP_FREQ[23][8] = 389; JP_FREQ[16][0] = 388; JP_FREQ[28][80] = 387; JP_FREQ[26][54] = 386; JP_FREQ[33][18] = 385; JP_FREQ[31][20] = 384; JP_FREQ[31][62] = 383; JP_FREQ[30][41] = 382; JP_FREQ[33][30] = 381; JP_FREQ[45][45] = 380; JP_FREQ[37][82] = 379; JP_FREQ[15][33] = 378; JP_FREQ[20][12] = 377; JP_FREQ[18][5] = 376; JP_FREQ[28][86] = 375; JP_FREQ[30][19] = 374; JP_FREQ[42][43] = 373; JP_FREQ[36][31] = 372; JP_FREQ[17][93] = 371; JP_FREQ[4][15] = 370; JP_FREQ[21][20] = 369; JP_FREQ[23][21] = 368; JP_FREQ[28][72] = 367; JP_FREQ[4][20] = 366; JP_FREQ[26][55] = 365; JP_FREQ[21][5] = 364; JP_FREQ[19][16] = 363; JP_FREQ[23][64] = 362; JP_FREQ[40][59] = 361; JP_FREQ[37][26] = 360; JP_FREQ[26][56] = 359; JP_FREQ[4][12] = 358; JP_FREQ[33][71] = 357; JP_FREQ[32][39] = 356; JP_FREQ[38][40] = 355; JP_FREQ[22][74] = 354; JP_FREQ[3][25] = 353; JP_FREQ[15][48] = 352; JP_FREQ[41][82] = 351; JP_FREQ[41][9] = 350; JP_FREQ[25][48] = 349; JP_FREQ[31][71] = 348; JP_FREQ[43][29] = 347; JP_FREQ[26][80] = 346; JP_FREQ[4][5] = 345; JP_FREQ[18][71] = 344; JP_FREQ[29][0] = 343; JP_FREQ[43][43] = 342; JP_FREQ[23][81] = 341; JP_FREQ[4][42] = 340; JP_FREQ[44][28] = 339; JP_FREQ[23][93] = 338; JP_FREQ[17][81] = 337; JP_FREQ[25][25] = 336; JP_FREQ[41][23] = 335; JP_FREQ[34][35] = 334; JP_FREQ[4][53] = 333; JP_FREQ[28][36] = 332; JP_FREQ[4][41] = 331; JP_FREQ[25][60] = 330; JP_FREQ[23][20] = 329; JP_FREQ[3][43] = 328; JP_FREQ[24][79] = 327; JP_FREQ[29][41] = 326; JP_FREQ[30][83] = 325; JP_FREQ[3][50] = 324; JP_FREQ[22][18] = 323; JP_FREQ[18][3] = 322; JP_FREQ[39][30] = 321; JP_FREQ[4][28] = 320; JP_FREQ[21][64] = 319; JP_FREQ[4][68] = 318; JP_FREQ[17][71] = 317; JP_FREQ[27][0] = 316; JP_FREQ[39][28] = 315; JP_FREQ[30][13] = 314; JP_FREQ[36][70] = 313; JP_FREQ[20][82] = 312; JP_FREQ[33][38] = 311; JP_FREQ[44][87] = 310; JP_FREQ[34][45] = 309; JP_FREQ[4][26] = 308; JP_FREQ[24][44] = 307; JP_FREQ[38][67] = 306; JP_FREQ[38][6] = 305; JP_FREQ[30][68] = 304; JP_FREQ[15][89] = 303; JP_FREQ[24][93] = 302; JP_FREQ[40][41] = 301; JP_FREQ[38][3] = 300; JP_FREQ[28][23] = 299; JP_FREQ[26][17] = 298; JP_FREQ[4][38] = 297; JP_FREQ[22][78] = 296; JP_FREQ[15][37] = 295; JP_FREQ[25][85] = 294; JP_FREQ[4][9] = 293; JP_FREQ[4][7] = 292; JP_FREQ[27][53] = 291; JP_FREQ[39][29] = 290; JP_FREQ[41][43] = 289; JP_FREQ[25][62] = 288; JP_FREQ[4][48] = 287; JP_FREQ[28][28] = 286; JP_FREQ[21][40] = 285; JP_FREQ[36][73] = 284; JP_FREQ[26][39] = 283; JP_FREQ[22][54] = 282; JP_FREQ[33][5] = 281; JP_FREQ[19][21] = 280; JP_FREQ[46][31] = 279; JP_FREQ[20][64] = 278; JP_FREQ[26][63] = 277; JP_FREQ[22][23] = 276; JP_FREQ[25][81] = 275; JP_FREQ[4][62] = 274; JP_FREQ[37][31] = 273; JP_FREQ[40][52] = 272; JP_FREQ[29][79] = 271; JP_FREQ[41][48] = 270; JP_FREQ[31][57] = 269; JP_FREQ[32][92] = 268; JP_FREQ[36][36] = 267; JP_FREQ[27][7] = 266; JP_FREQ[35][29] = 265; JP_FREQ[37][34] = 264; JP_FREQ[34][42] = 263; JP_FREQ[27][15] = 262; JP_FREQ[33][27] = 261; JP_FREQ[31][38] = 260; JP_FREQ[19][79] = 259; JP_FREQ[4][31] = 258; JP_FREQ[4][66] = 257; JP_FREQ[17][32] = 256; JP_FREQ[26][67] = 255; JP_FREQ[16][30] = 254; JP_FREQ[26][46] = 253; JP_FREQ[24][26] = 252; JP_FREQ[35][10] = 251; JP_FREQ[18][37] = 250; JP_FREQ[3][19] = 249; JP_FREQ[33][69] = 248; JP_FREQ[31][9] = 247; JP_FREQ[45][29] = 246; JP_FREQ[3][15] = 245; JP_FREQ[18][54] = 244; JP_FREQ[3][44] = 243; JP_FREQ[31][29] = 242; JP_FREQ[18][45] = 241; JP_FREQ[38][28] = 240; JP_FREQ[24][12] = 239; JP_FREQ[35][82] = 238; JP_FREQ[17][43] = 237; JP_FREQ[28][9] = 236; JP_FREQ[23][25] = 235; JP_FREQ[44][37] = 234; JP_FREQ[23][75] = 233; JP_FREQ[23][92] = 232; JP_FREQ[0][24] = 231; JP_FREQ[19][74] = 230; JP_FREQ[45][32] = 229; JP_FREQ[16][72] = 228; JP_FREQ[16][93] = 227; JP_FREQ[45][13] = 226; JP_FREQ[24][8] = 225; JP_FREQ[25][47] = 224; JP_FREQ[28][26] = 223; JP_FREQ[43][81] = 222; JP_FREQ[32][71] = 221; JP_FREQ[18][41] = 220; JP_FREQ[26][62] = 219; JP_FREQ[41][24] = 218; JP_FREQ[40][11] = 217; JP_FREQ[43][57] = 216; JP_FREQ[34][53] = 215; JP_FREQ[20][32] = 214; JP_FREQ[34][43] = 213; JP_FREQ[41][91] = 212; JP_FREQ[29][57] = 211; JP_FREQ[15][43] = 210; JP_FREQ[22][89] = 209; JP_FREQ[33][83] = 208; JP_FREQ[43][20] = 207; JP_FREQ[25][58] = 206; JP_FREQ[30][30] = 205; JP_FREQ[4][56] = 204; JP_FREQ[17][64] = 203; JP_FREQ[23][0] = 202; JP_FREQ[44][12] = 201; JP_FREQ[25][37] = 200; JP_FREQ[35][13] = 199; JP_FREQ[20][30] = 198; JP_FREQ[21][84] = 197; JP_FREQ[29][14] = 196; JP_FREQ[30][5] = 195; JP_FREQ[37][2] = 194; JP_FREQ[4][78] = 193; JP_FREQ[29][78] = 192; JP_FREQ[29][84] = 191; JP_FREQ[32][86] = 190; JP_FREQ[20][68] = 189; JP_FREQ[30][39] = 188; JP_FREQ[15][69] = 187; JP_FREQ[4][60] = 186; JP_FREQ[20][61] = 185; JP_FREQ[41][67] = 184; JP_FREQ[16][35] = 183; JP_FREQ[36][57] = 182; JP_FREQ[39][80] = 181; JP_FREQ[4][59] = 180; JP_FREQ[4][44] = 179; JP_FREQ[40][54] = 178; JP_FREQ[30][8] = 177; JP_FREQ[44][30] = 176; JP_FREQ[31][93] = 175; JP_FREQ[31][47] = 174; JP_FREQ[16][70] = 173; JP_FREQ[21][0] = 172; JP_FREQ[17][35] = 171; JP_FREQ[21][67] = 170; JP_FREQ[44][18] = 169; JP_FREQ[36][29] = 168; JP_FREQ[18][67] = 167; JP_FREQ[24][28] = 166; JP_FREQ[36][24] = 165; JP_FREQ[23][5] = 164; JP_FREQ[31][65] = 163; JP_FREQ[26][59] = 162; JP_FREQ[28][2] = 161; JP_FREQ[39][69] = 160; JP_FREQ[42][40] = 159; JP_FREQ[37][80] = 158; JP_FREQ[15][66] = 157; JP_FREQ[34][38] = 156; JP_FREQ[28][48] = 155; JP_FREQ[37][77] = 154; JP_FREQ[29][34] = 153; JP_FREQ[33][12] = 152; JP_FREQ[4][65] = 151; JP_FREQ[30][31] = 150; JP_FREQ[27][92] = 149; JP_FREQ[4][2] = 148; JP_FREQ[4][51] = 147; JP_FREQ[23][77] = 146; JP_FREQ[4][35] = 145; JP_FREQ[3][13] = 144; JP_FREQ[26][26] = 143; JP_FREQ[44][4] = 142; JP_FREQ[39][53] = 141; JP_FREQ[20][11] = 140; JP_FREQ[40][33] = 139; JP_FREQ[45][7] = 138; JP_FREQ[4][70] = 137; JP_FREQ[3][49] = 136; JP_FREQ[20][59] = 135; JP_FREQ[21][12] = 134; JP_FREQ[33][53] = 133; JP_FREQ[20][14] = 132; JP_FREQ[37][18] = 131; JP_FREQ[18][17] = 130; JP_FREQ[36][23] = 129; JP_FREQ[18][57] = 128; JP_FREQ[26][74] = 127; JP_FREQ[35][2] = 126; JP_FREQ[38][58] = 125; JP_FREQ[34][68] = 124; JP_FREQ[29][81] = 123; JP_FREQ[20][69] = 122; JP_FREQ[39][86] = 121; JP_FREQ[4][16] = 120; JP_FREQ[16][49] = 119; JP_FREQ[15][72] = 118; JP_FREQ[26][35] = 117; JP_FREQ[32][14] = 116; JP_FREQ[40][90] = 115; JP_FREQ[33][79] = 114; JP_FREQ[35][4] = 113; JP_FREQ[23][33] = 112; JP_FREQ[19][19] = 111; JP_FREQ[31][41] = 110; JP_FREQ[44][1] = 109; JP_FREQ[22][56] = 108; JP_FREQ[31][27] = 107; JP_FREQ[32][18] = 106; JP_FREQ[27][32] = 105; JP_FREQ[37][39] = 104; JP_FREQ[42][11] = 103; JP_FREQ[29][71] = 102; JP_FREQ[32][58] = 101; JP_FREQ[46][10] = 100; JP_FREQ[17][30] = 99; JP_FREQ[38][15] = 98; JP_FREQ[29][60] = 97; JP_FREQ[4][11] = 96; JP_FREQ[38][31] = 95; JP_FREQ[40][79] = 94; JP_FREQ[28][49] = 93; JP_FREQ[28][84] = 92; JP_FREQ[26][77] = 91; JP_FREQ[22][32] = 90; JP_FREQ[33][17] = 89; JP_FREQ[23][18] = 88; JP_FREQ[32][64] = 87; JP_FREQ[4][6] = 86; JP_FREQ[33][51] = 85; JP_FREQ[44][77] = 84; JP_FREQ[29][5] = 83; JP_FREQ[46][25] = 82; JP_FREQ[19][58] = 81; JP_FREQ[4][46] = 80; JP_FREQ[15][71] = 79; JP_FREQ[18][58] = 78; JP_FREQ[26][45] = 77; JP_FREQ[45][66] = 76; JP_FREQ[34][10] = 75; JP_FREQ[19][37] = 74; JP_FREQ[33][65] = 73; JP_FREQ[44][52] = 72; JP_FREQ[16][38] = 71; JP_FREQ[36][46] = 70; JP_FREQ[20][26] = 69; JP_FREQ[30][37] = 68; JP_FREQ[4][58] = 67; JP_FREQ[43][2] = 66; JP_FREQ[30][18] = 65; JP_FREQ[19][35] = 64; JP_FREQ[15][68] = 63; JP_FREQ[3][36] = 62; JP_FREQ[35][40] = 61; JP_FREQ[36][32] = 60; JP_FREQ[37][14] = 59; JP_FREQ[17][11] = 58; JP_FREQ[19][78] = 57; JP_FREQ[37][11] = 56; JP_FREQ[28][63] = 55; JP_FREQ[29][61] = 54; JP_FREQ[33][3] = 53; JP_FREQ[41][52] = 52; JP_FREQ[33][63] = 51; JP_FREQ[22][41] = 50; JP_FREQ[4][19] = 49; JP_FREQ[32][41] = 48; JP_FREQ[24][4] = 47; JP_FREQ[31][28] = 46; JP_FREQ[43][30] = 45; JP_FREQ[17][3] = 44; JP_FREQ[43][70] = 43; JP_FREQ[34][19] = 42; JP_FREQ[20][77] = 41; JP_FREQ[18][83] = 40; JP_FREQ[17][15] = 39; JP_FREQ[23][61] = 38; JP_FREQ[40][27] = 37; JP_FREQ[16][48] = 36; JP_FREQ[39][78] = 35; JP_FREQ[41][53] = 34; JP_FREQ[40][91] = 33; JP_FREQ[40][72] = 32; JP_FREQ[18][52] = 31; JP_FREQ[35][66] = 30; JP_FREQ[39][93] = 29; JP_FREQ[19][48] = 28; JP_FREQ[26][36] = 27; JP_FREQ[27][25] = 26; JP_FREQ[42][71] = 25; JP_FREQ[42][85] = 24; JP_FREQ[26][48] = 23; JP_FREQ[28][15] = 22; JP_FREQ[3][66] = 21; JP_FREQ[25][24] = 20; JP_FREQ[27][43] = 19; JP_FREQ[27][78] = 18; JP_FREQ[45][43] = 17; JP_FREQ[27][72] = 16; JP_FREQ[40][29] = 15; JP_FREQ[41][0] = 14; JP_FREQ[19][57] = 13; JP_FREQ[15][59] = 12; JP_FREQ[29][29] = 11; JP_FREQ[4][25] = 10; JP_FREQ[21][42] = 9; JP_FREQ[23][35] = 8; JP_FREQ[33][1] = 7; JP_FREQ[4][57] = 6; JP_FREQ[17][60] = 5; JP_FREQ[25][19] = 4; JP_FREQ[22][65] = 3; JP_FREQ[42][29] = 2; JP_FREQ[27][66] = 1; JP_FREQ[26][89] = 0; } private static class Encoding { private static final int TOTAL_TYPES = 23; // Supported Encoding Types private static final int GB2312 = 0; // 1980年中国发布了第一个汉字编码标准 private static final int GBK = 1; // 在GB2312的基础上添加了部分字符(如GB2312发布后才简化的汉字、部分缺少的人名/繁体字/日语/朝鲜语中的汉字) private static final int GB18030 = 2; // 全称《信息技术中文编码字符集》,在GBK的基础上增加了中日韩语中的汉字和少数名族的文字及字符 private static final int HZ = 3; private static final int BIG5 = 4; private static final int CNS11643 = 5; private static final int UTF8 = 6; private static final int UTF8T = 7; private static final int UTF8S = 8; private static final int UNICODE = 9; private static final int UNICODET = 10; private static final int UNICODES = 11; private static final int ISO2022CN = 12; private static final int ISO2022CN_CNS = 13; private static final int ISO2022CN_GB = 14; private static final int EUC_KR = 15; private static final int CP949 = 16; private static final int ISO2022KR = 17; private static final int JOHAB = 18; private static final int SJIS = 19; private static final int EUC_JP = 20; private static final int ISO2022JP = 21; private static final int ASCII = 22; private static final String[] JAVA_CHARSET = new String[TOTAL_TYPES]; // Names of the encodings as understood by Java private static final String[] NICE_CHARSET = new String[TOTAL_TYPES]; // Names of the encodings for human viewing private static final String[] HTML_CHARSET = new String[TOTAL_TYPES]; // Names of charsets as used in charset parameter of HTML Meta tag static { // Assign encoding names JAVA_CHARSET[GB2312] = "GB2312"; JAVA_CHARSET[GBK] = "GBK"; JAVA_CHARSET[GB18030] = "GB18030"; JAVA_CHARSET[HZ] = "ASCII"; // What to put here? Sun doesn't support HZ JAVA_CHARSET[ISO2022CN_GB] = "ISO2022CN_GB"; JAVA_CHARSET[BIG5] = "Big5"; JAVA_CHARSET[CNS11643] = "EUC-TW"; JAVA_CHARSET[ISO2022CN_CNS] = "ISO2022CN_CNS"; JAVA_CHARSET[ISO2022CN] = "ISO2022CN"; JAVA_CHARSET[UTF8] = "UTF-8"; JAVA_CHARSET[UTF8T] = "UTF-8"; JAVA_CHARSET[UTF8S] = "UTF-8"; JAVA_CHARSET[UNICODE] = "Unicode"; JAVA_CHARSET[UNICODET] = "Unicode"; JAVA_CHARSET[UNICODES] = "Unicode"; JAVA_CHARSET[EUC_KR] = "EUC-KR"; JAVA_CHARSET[CP949] = "MS949"; JAVA_CHARSET[ISO2022KR] = "ISO2022KR"; JAVA_CHARSET[JOHAB] = "Johab"; JAVA_CHARSET[SJIS] = "SJIS"; JAVA_CHARSET[EUC_JP] = "EUC_JP"; JAVA_CHARSET[ISO2022JP] = "ISO2022JP"; JAVA_CHARSET[ASCII] = "ASCII"; // Assign encoding names HTML_CHARSET[GB2312] = "GB2312"; HTML_CHARSET[GBK] = "GBK"; HTML_CHARSET[GB18030] = "GB18030"; HTML_CHARSET[HZ] = "HZ-GB-2312"; HTML_CHARSET[ISO2022CN_GB] = "ISO-2022-CN-EXT"; HTML_CHARSET[BIG5] = "Big5"; HTML_CHARSET[CNS11643] = "EUC-TW"; HTML_CHARSET[ISO2022CN_CNS] = "ISO-2022-CN-EXT"; HTML_CHARSET[ISO2022CN] = "ISO-2022-CN"; HTML_CHARSET[UTF8] = "UTF-8"; HTML_CHARSET[UTF8T] = "UTF-8"; HTML_CHARSET[UTF8S] = "UTF-8"; HTML_CHARSET[UNICODE] = "UTF-16"; HTML_CHARSET[UNICODET] = "UTF-16"; HTML_CHARSET[UNICODES] = "UTF-16"; HTML_CHARSET[EUC_KR] = "EUC-KR"; HTML_CHARSET[CP949] = "x-windows-949"; HTML_CHARSET[ISO2022KR] = "ISO-2022-KR"; HTML_CHARSET[JOHAB] = "x-Johab"; HTML_CHARSET[SJIS] = "Shift_JIS"; HTML_CHARSET[EUC_JP] = "EUC-JP"; HTML_CHARSET[ISO2022JP] = "ISO-2022-JP"; HTML_CHARSET[ASCII] = "ASCII"; // Assign Human readable names NICE_CHARSET[GB2312] = "GB-2312"; NICE_CHARSET[GBK] = "GBK"; NICE_CHARSET[GB18030] = "GB18030"; NICE_CHARSET[HZ] = "HZ"; NICE_CHARSET[ISO2022CN_GB] = "ISO2022CN-GB"; NICE_CHARSET[BIG5] = "Big5"; NICE_CHARSET[CNS11643] = "CNS11643"; NICE_CHARSET[ISO2022CN_CNS] = "ISO2022CN-CNS"; NICE_CHARSET[ISO2022CN] = "ISO2022 CN"; NICE_CHARSET[UTF8] = "UTF-8"; NICE_CHARSET[UTF8T] = "UTF-8 (Trad)"; NICE_CHARSET[UTF8S] = "UTF-8 (Simp)"; NICE_CHARSET[UNICODE] = "Unicode"; NICE_CHARSET[UNICODET] = "Unicode (Trad)"; NICE_CHARSET[UNICODES] = "Unicode (Simp)"; NICE_CHARSET[EUC_KR] = "EUC-KR"; NICE_CHARSET[CP949] = "CP949"; NICE_CHARSET[ISO2022KR] = "ISO 2022 KR"; NICE_CHARSET[JOHAB] = "Johab"; NICE_CHARSET[SJIS] = "Shift-JIS"; NICE_CHARSET[EUC_JP] = "EUC-JP"; NICE_CHARSET[ISO2022JP] = "ISO 2022 JP"; NICE_CHARSET[ASCII] = "ASCII"; } } } ================================================ FILE: src/main/java/cn/ponfee/commons/io/charset/CodepageDetector.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ //package cn.ponfee.commons.io.charset; // //import cn.ponfee.commons.io.CharsetDetector; //import info.monitorenter.cpdetector.io.*; // //import java.io.BufferedInputStream; //import java.io.IOException; //import java.io.InputStream; //import java.nio.charset.Charset; // ///** // *

// *  
// *  
// *    net.sourceforge.cpdetector
// *    cpdetector
// *    1.0.7
// *  
// *
// *  
// *  
// *    antlr
// *    antlr
// *    2.7.7
// *  
// * 
// * // * @author Ponfee // */ //public class CodepageDetector { // // public static Charset detect(InputStream input, int length) throws IOException { // CodepageDetectorProxy detector = CodepageDetectorProxy.getInstance(); // detector.add(new ByteOrderMarkDetector()); // 通过BOM来测定编码 // detector.add(JChardetFacade.getInstance()); // 封装了由Mozilla提供的JChardet // detector.add(UnicodeDetector.getInstance()); // 用于Unicode家族编码的测定 // detector.add(ASCIIDetector.getInstance()); // 用于ASCII编码测定 // detector.add(new ParsingDetector(false)); // 用于检查HTML、XML等文件或字符流的编码 // input = input.markSupported() ? input : new BufferedInputStream(input, length); // String charset = detector.detectCodepage(input, length).name(); // return "void".equalsIgnoreCase(charset) ? CharsetDetector.DEFAULT_CHARSET : Charset.forName(charset); // } // //} ================================================ FILE: src/main/java/cn/ponfee/commons/io/charset/JchardetDetector.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ //package cn.ponfee.commons.io.charset; // //import cn.ponfee.commons.io.CharsetDetector; //import org.apache.commons.lang3.StringUtils; //import org.mozilla.intl.chardet.nsDetector; //import org.mozilla.intl.chardet.nsICharsetDetectionObserver; // //import java.io.BufferedInputStream; //import java.io.IOException; //import java.io.InputStream; //import java.nio.charset.Charset; //import java.util.Arrays; // ///** // *
// *  
// *  
// *    net.sourceforge.jchardet
// *    jchardet
// *    1.0
// *  
// * 
// * // * @author Ponfee // */ //public class JchardetDetector { // // public static Charset detect(InputStream input, int length) throws IOException { // nsDetector detector = new nsDetector(nsDetector.ALL); // DetectorObserver observer = new DetectorObserver(); // detector.Init(observer); // try (BufferedInputStream bufInput = new BufferedInputStream(input)) { // byte[] buf = new byte[length]; // boolean isAscii = true; // int len, count = 0; // while ((len = bufInput.read(buf, 0, buf.length)) != -1) { // if (isAscii) { // isAscii = detector.isAscii(buf, len); // } // if (!isAscii && detector.DoIt(buf, len, false)) { // break; // } // count += len; // if (count >= length) { // break; // } // } // detector.DataEnd(); // // if (isAscii) { // return CharsetDetector.DEFAULT_CHARSET; // } else if (observer.result != null) { // return Charset.forName(observer.result); // } else { // String[] probableCharsets = detector.getProbableCharsets(); // String probableCharset = Arrays.stream(probableCharsets) // .filter(s -> !StringUtils.startsWithAny(s, "UTF-16", "UTF-32", "GB18030")) // .findAny() // .orElse(probableCharsets[0]); // return Charset.forName(probableCharset); // } // } // } // // private static class DetectorObserver implements nsICharsetDetectionObserver { // private String result = null; // // @Override // public void Notify(String charset) { // this.result = charset; // } // } // //} ================================================ FILE: src/main/java/cn/ponfee/commons/io/charset/TikaDetector.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.io.charset; import cn.ponfee.commons.io.CharsetDetector; import cn.ponfee.commons.io.Files; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; /** *
 *  
 *  
 *    org.apache.tika
 *    tika-bom
 *    2.6.0
 *    pom
 *    import
 *  
 *
 *  
 *  
 *    org.apache.tika
 *    tika-parsers-standard-package
 *  
 * 
* * @author Ponfee */ public class TikaDetector { public static Charset detect(InputStream input, int length) throws IOException { org.apache.tika.parser.txt.CharsetDetector charsetDetector = new org.apache.tika.parser.txt.CharsetDetector(); charsetDetector.setText(Files.readByteArray(input, length)); org.apache.tika.parser.txt.CharsetMatch charsetMatch = charsetDetector.detect(); return charsetMatch == null ? CharsetDetector.DEFAULT_CHARSET : Charset.forName(charsetMatch.getName()); } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/CryptoProvider.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce; import cn.ponfee.commons.io.Files; import cn.ponfee.commons.jce.security.ECDSASigner; import cn.ponfee.commons.jce.security.RSACryptor; import cn.ponfee.commons.jce.security.RSAPrivateKeys; import cn.ponfee.commons.jce.security.RSAPublicKeys; import cn.ponfee.commons.jce.sm.SM2; import cn.ponfee.commons.jce.symmetric.SymmetricCryptor; import cn.ponfee.commons.util.Base64UrlSafe; import org.apache.commons.lang3.StringUtils; import javax.annotation.Nonnull; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.util.Arrays; import java.util.Objects; /** * 加解密服务提供 * * @author Ponfee */ public abstract class CryptoProvider { /** * Encrypts data * * @param original the origin data * @return encrypted data */ public abstract byte[] encrypt(@Nonnull byte[] original); /** * Decrypts data * * @param encrypted the encrypted data * @return origin data */ public abstract byte[] decrypt(@Nonnull byte[] encrypted); /** * Signs the data * * @param data the data * @return signature data */ public byte[] sign(byte[] data) { throw new UnsupportedOperationException("cannot support signature."); } /** * Verify the data signature * * @param data the origin data * @param signed the signed data * @return {@code true} verify success */ public boolean verify(byte[] data, byte[] signed) { throw new UnsupportedOperationException("cannot support verify signature."); } /** * Encrypts the string data * * @param plaintext the plain text * @return encrypted data */ public final String encrypt(String plaintext) { return encrypt(plaintext, StandardCharsets.UTF_8); } /** * 字符串数据加密 * * @param plaintext 明文 * @param charset 字符串编码 * @return encrypted data */ public final String encrypt(String plaintext, Charset charset) { if (plaintext == null) { return null; } return Base64UrlSafe.encode( this.encrypt(plaintext.getBytes(charset)) ); } /** * Decrypts data * * @param ciphertext the encryted data of base64 string * @return origin data of string */ public final String decrypt(String ciphertext) { return decrypt(ciphertext, StandardCharsets.UTF_8); } /** * Decrypts data * * @param ciphertext the encryted data of base64 string * @param charset the origin data charset * @return origin data of string */ public final String decrypt(String ciphertext, Charset charset) { if (ciphertext == null) { return null; } return new String( decrypt(Base64UrlSafe.decode(ciphertext)), charset ); } /** * Signs data * * @param data the data * @return signed data */ public final String sign(String data) { return sign(data, Files.UTF_8); } /** * Signs data * * @param data the string data * @param charset the charset of string data * @return signed data */ public final String sign(String data, String charset) { if (StringUtils.isEmpty(data)) { return null; } return Base64UrlSafe.encode( sign(data.getBytes(Charset.forName(charset))) ); } /** * Verifys the data * * @param data the origin data * @param signed the signed data * @return {@code true} verify success */ public final boolean verify(String data, String signed) { return verify(data, Files.UTF_8, signed); } /** * Verifys the data * * @param data the data * @param charset the charset * @param signed the signed data * @return {@code true} verify success */ public final boolean verify(String data, String charset, String signed) { return verify( data.getBytes(Charset.forName(charset)), Base64UrlSafe.decode(signed) ); } // -----------------------------------------------------------------------SymmetricCryptor /** * 对称密钥组件 * @param symmetricKey {@link SymmetricCryptor} * @return */ public static CryptoProvider symmetricKeyProvider(SymmetricCryptor symmetricKey) { // the symmetricKey is thread-safe return new CryptoProvider() { @Override public byte[] encrypt(byte[] original) { return symmetricKey.encrypt( Objects.requireNonNull(original) ); } @Override public byte[] decrypt(byte[] encrypted) { return symmetricKey.decrypt(encrypted); } }; } // -----------------------------------------------------------------------RSA /** * rsa public key密钥组件 * * @param pkcs8PublicKey the string of pkcs8 public key format * @return */ public static CryptoProvider rsaPublicKeyProvider(String pkcs8PublicKey) { return new CryptoProvider() { final RSAPublicKey pubKey = RSAPublicKeys.fromPkcs8(pkcs8PublicKey); // thread-safe @Override public byte[] encrypt(byte[] original) { return RSACryptor.encrypt(original, pubKey); // 公钥加密 } @Override public byte[] decrypt(byte[] encrypted) { // cannot support public key decrypt throw new UnsupportedOperationException("cannot support decrypt."); } @Override public boolean verify(byte[] data, byte[] signed) { return RSACryptor.verifySha1(data, pubKey, signed); } }; } /** * pkcs8PrivateKey include public exponent * * forbid use private key encrypt and use public key decrypt * * @param pkcs8PrivateKey the string of pkcs8 private key format * @return */ public static CryptoProvider rsaPrivateKeyProvider(String pkcs8PrivateKey) { RSAPrivateKey priKey = RSAPrivateKeys.fromPkcs8(pkcs8PrivateKey); // thread-safe return rsaProvider(priKey, RSAPrivateKeys.extractPublicKey(priKey)); } /** * Creates CryptoProvider of RSA * * @param priKey the RSAPrivateKey * @param pubKey the pubKey * @return a CryptoProvider of RSA */ public static CryptoProvider rsaProvider(RSAPrivateKey priKey, RSAPublicKey pubKey) { return new CryptoProvider() { @Override public byte[] encrypt(byte[] original) { // only support public key encrypt // forbid encrypt with private key return RSACryptor.encrypt(original, pubKey); // 公钥加密 } @Override public byte[] decrypt(byte[] encrypted) { // only support private key decrypt return RSACryptor.decrypt(encrypted, priKey); // 私钥解密 } @Override public byte[] sign(byte[] data) { return RSACryptor.signSha1(data, priKey); } @Override public boolean verify(byte[] data, byte[] signed) { return RSACryptor.verifySha1(data, pubKey, signed); } }; } // -----------------------------------------------------------------------SM2 public static CryptoProvider sm2PublicKeyProvider(byte[] publicKey) { return sm2PublicKeyProvider(ECParameters.SM2_BEST, publicKey); } public static CryptoProvider sm2PublicKeyProvider(ECParameters ecParameter, byte[] publicKey) { return new CryptoProvider() { final byte[] publicKey0 = Arrays.copyOf(publicKey, publicKey.length); @Override public byte[] encrypt(byte[] original) { return SM2.encrypt(ecParameter, publicKey0, original); // 公钥加密 } @Override public byte[] decrypt(byte[] encrypted) { throw new UnsupportedOperationException("cannot support decrypt."); } @Override public boolean verify(byte[] data, byte[] signed) { return SM2.verify(ecParameter, data, signed, publicKey0); } }; } public static CryptoProvider sm2PrivateKeyProvider(byte[] publicKey, byte[] privateKey) { return sm2PrivateKeyProvider(ECParameters.SM2_BEST, publicKey, privateKey); } public static CryptoProvider sm2PrivateKeyProvider(ECParameters ecParameter, byte[] publicKey, byte[] privateKey) { return new CryptoProvider() { final byte[] publicKey0 = Arrays.copyOf(publicKey, publicKey.length); final byte[] privateKey0 = Arrays.copyOf(privateKey, privateKey.length); @Override public byte[] encrypt(byte[] original) { return SM2.encrypt(ecParameter, publicKey0, original); // 公钥加密 } @Override public byte[] decrypt(byte[] encrypted) { // 私钥解密 return SM2.decrypt(ecParameter, privateKey0, encrypted); } @Override public byte[] sign(byte[] data) { // sign data by SM3WithSM2 return SM2.sign(ecParameter, data, publicKey0, privateKey0); } @Override public boolean verify(byte[] data, byte[] signed) { // verify the SM3WithSM2 signature return SM2.verify(ecParameter, data, signed, publicKey0); } }; } // -----------------------------------------------------------------------ECDSASinger public static CryptoProvider ecdsaPublicKeyProvider(byte[] publicKey) { return new CryptoProvider() { final ECPublicKey publicKey0 = ECDSASigner.decodePublicKey(publicKey); @Override public byte[] encrypt(byte[] original) { throw new UnsupportedOperationException("ECDSA cannot support encrypt."); } @Override public byte[] decrypt(byte[] encrypted) { throw new UnsupportedOperationException("ECDSA cannot support decrypt."); } @Override public boolean verify(byte[] data, byte[] signed) { return ECDSASigner.verifySha256(data, signed, publicKey0); } }; } public static CryptoProvider ecdsaPrivateKeyProvider(byte[] publicKey, byte[] privateKey) { return new CryptoProvider() { final ECPublicKey publicKey0 = ECDSASigner.decodePublicKey(publicKey); final ECPrivateKey privateKey0 = ECDSASigner.decodePrivateKey(privateKey); @Override public byte[] encrypt(byte[] original) { throw new UnsupportedOperationException("ECDSA cannot support encrypt."); } @Override public byte[] decrypt(byte[] encrypted) { throw new UnsupportedOperationException("ECDSA cannot support decrypt."); } @Override public byte[] sign(byte[] data) { return ECDSASigner.signSha256(data, privateKey0); } @Override public boolean verify(byte[] data, byte[] signed) { return ECDSASigner.verifySha256(data, signed, publicKey0); } }; } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/DigestAlgorithms.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce; /** * The Digest Algorithms

* * SHA-3 *

  • https://www.oschina.net/translate/keccak-the-new-sha-3-encryption-standard
  • *
  • http://www.cnblogs.com/dacainiao/p/5554756.html
  • *
  • SHA256算法原理:https://zhuanlan.zhihu.com/p/94619052
  • * * @author Ponfee */ public enum DigestAlgorithms { MD5(128), // RipeMD128(128), RipeMD160(160), RipeMD256(256), RipeMD320(320), // SHA1("SHA-1", 160), SHA224("SHA-224", 224), SHA256("SHA-256", 256), // SHA384("SHA-384", 384), SHA512("SHA-512", 512), // /** * @see org.bouncycastle.crypto.digests.SM3Digest * @see org.bouncycastle.jcajce.provider.digest.SM3 */ SM3(256), // // SHAKE128 algorithm only support use in org.bouncycastle.crypto.digests.SHAKEDigest //SHAKE128(128), SHAKE256(256), // -----------------------SHA-3 Finalists: BLAKE, Grstl, JH, Keccak and Skein /** * @see org.bouncycastle.crypto.digests.Blake2sDigest * @see org.bouncycastle.jcajce.provider.digest.Blake2s */ BLAKE2S128("BLAKE2S-128", 128), BLAKE2S160("BLAKE2S-160", 160), // BLAKE2S224("BLAKE2S-224", 224), BLAKE2S256("BLAKE2S-256", 256), // /** * @see org.bouncycastle.crypto.digests.Blake2bDigest * @see org.bouncycastle.jcajce.provider.digest.Blake2b */ BLAKE2B160("BLAKE2B-160", 160), BLAKE2B256("BLAKE2B-256", 256), // BLAKE2B384("BLAKE2B-384", 384), BLAKE2B512("BLAKE2B-512", 512), // /** * @see org.bouncycastle.crypto.digests.KeccakDigest * @see org.bouncycastle.jcajce.provider.digest.Keccak */ KECCAK224("KECCAK-224", 224), KECCAK256("KECCAK-256", 256), // KECCAK288("KECCAK-288", 288), KECCAK384("KECCAK-384", 384), // KECCAK512("KECCAK-512", 512), // /** * @see org.bouncycastle.crypto.digests.SkeinDigest * @see org.bouncycastle.jcajce.provider.digest.Skein */ SKEIN_256_128 ("Skein-256-128", 128), SKEIN_256_256 ("Skein-256-256", 256), // SKEIN_512_256 ("Skein-512-256", 256), SKEIN_512_512 ("Skein-512-512", 512), // SKEIN_1024_512("Skein-1024-512", 512), SKEIN_1024_1024("Skein-1024-1024", 1024), // /** * @see org.bouncycastle.crypto.digests.SHA3Digest * @see org.bouncycastle.jcajce.provider.digest.SHA3 */ SHA3_224("SHA3-224", 224), SHA3_256("SHA3-256", 256), // SHA3_384("SHA3-384", 384), SHA3_512("SHA3-512", 512), // ; private final String algorithm; private final int byteSize; DigestAlgorithms(int bitLen) { this.algorithm = this.name(); this.byteSize = bitLen >>> 3; } DigestAlgorithms(String algorithm, int bitLen) { this.algorithm = algorithm; this.byteSize = bitLen >>> 3; } public String algorithm() { return this.algorithm; } public int byteSize() { return byteSize; } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/ECParameters.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce; import cn.ponfee.commons.math.Numbers; import cn.ponfee.commons.util.SecureRandoms; import com.google.common.collect.ImmutableMap; import org.apache.commons.codec.binary.Hex; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.sec.SECNamedCurves; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.crypto.generators.ECKeyPairGenerator; import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.crypto.params.ECKeyGenerationParameters; import org.bouncycastle.math.ec.ECCurve; import org.bouncycastle.math.ec.ECPoint; import java.lang.reflect.Field; import java.math.BigInteger; import java.security.SecureRandom; import java.util.Hashtable; import java.util.Map; /** * Specifications completely defining an elliptic curve. * Used to define an elliptic curve by EllipticCurve, * define(ECParamters ecp). * NOTE: This is designed for an elliptic curve on the form: * y^2 = x^3 + ax + b (mod p) * with fixed generator and precomputed order. * * {@link SECNamedCurves#getByName(String)} * * @author Ponfee */ @SuppressWarnings("unchecked") public class ECParameters implements java.io.Serializable { private static final long serialVersionUID = 6479779256927237118L; private static final char SEPARATOR = ','; public static final ECParameters SM2_BEST = new ECParameters( "sm2-best", "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", "28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", "0" // placeholder ); public static final ECParameters SM2_CUST = new ECParameters( "sm2-cust", "8542D69E4C044F18E8B92435BF6FF7DE457283915C45517D722EDB8B08F1DFC3", "787968B4FA32C3FD2417842E73BBFEFF2F3C848B6831D7E0EC65228B3937E498", "63E4C6D3B23B0C849CF84241484BFE48F61D59A5B16BA06E6E12D1DA27C5249A", "421DEBD61B62EAB6746434EBC3CC315E32220B3BADD50BDC4C4E6C147FEDD43D", "0680512BCBB42C07D47349D2153B70C4E5D7FDFCBFA36EA1A85841B9E46E09A2", "8542D69E4C044F18E8B92435BF6FF7DD297720630485628D5AE74EE7C32E79B7", "0" // placeholder ); public static final ECParameters secp112r1 = new ECParameters( "secp112r1", "DB7C2ABF62E35E668076BEAD208B", "DB7C2ABF62E35E668076BEAD2088", "659EF8BA043916EEDE8911702B22", "09487239995A5EE76B55F9C2F098", "A89CE5AF8724C0A23E0E0FF77500", "DB7C2ABF62E35E7628DFAC6561C5", "00F50B028E4D696E676875615175290472783FB1" ); public static final ECParameters secp160r1 = new ECParameters( "secp160r1", "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFF", "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFC", "1C97BEFC54BD7A8B65ACF89F81D4D4ADC565FA45", "4A96B5688EF573284664698968C38BB913CBFC82", "23A628553168947D59DCC912042351377AC5FB32", "0100000000000000000001F4C8F927AED3CA752257", "1053CDE42C14D696E67687561517533BF3F83345" ); public static final ECParameters secp256r1 = new ECParameters( "secp256r1", "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", "5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B", "6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296", "4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5", "FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", "C49D360886E704936A6678E1139D26B7819F7E90" ); public static final ECParameters secp256k1 = new ECParameters( "secp256k1", "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", "0", "7", "79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", "483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", "0" ); public static final ECParameters secp521r1 = new ECParameters( "secp521r1", "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC", "0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00", "C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66", "11839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE72995EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650", "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409", "D09E8800291CB85396CC6717393284AAA0DA64BA" ); public static final ImmutableMap NAME_OID_MAPPING; public static final ImmutableMap EC_PARAMETERS; static { ImmutableMap.Builder nameOids = ImmutableMap.builder(); ImmutableMap.Builder nameParams = ImmutableMap.builder(); try { Field field = SECNamedCurves.class.getDeclaredField("objIds"); field.setAccessible(true); Hashtable table = (Hashtable) field.get(null); // static field field.setAccessible(false); for (Map.Entry entry : table.entrySet()) { String name = entry.getKey(); X9ECParameters params = SECNamedCurves.getByName(name); nameOids.put(name, entry.getValue()); nameParams.put(name, new ECParameters( name, Numbers.toHex(params.getCurve().getField().getCharacteristic()), Numbers.toHex(params.getCurve().getA().toBigInteger()), Numbers.toHex(params.getCurve().getB().toBigInteger()), Numbers.toHex(params.getG().getXCoord().toBigInteger()), Numbers.toHex(params.getG().getYCoord().toBigInteger()), Numbers.toHex(params.getN()), encodeHex(params.getSeed()) )); } } catch (Exception ignored) { ignored.printStackTrace(); } NAME_OID_MAPPING = nameOids.build(); EC_PARAMETERS = nameParams.build(); } /** init parameter */ public final String name; public final BigInteger p; // p为素数域内点的个数 public final BigInteger a; // a和b是其内的两个大数 public final BigInteger b; public final BigInteger gx; // x,y为基点G的坐标 public final BigInteger gy; public final BigInteger n; // n为点基点G的阶(nP=O∞) public final BigInteger S; // secure random seed //public final BigInteger h; // 有时还会用到h(椭圆曲线上所有点的个数p与n相除的整数部分) /** build parameter */ public transient final ECCurve curve; // the curve public transient final ECPoint pointG; // the base point public transient final ECDomainParameters bcSpec; public transient final ECKeyPairGenerator keyPairGenerator; public ECParameters(String name, String p, String a, String b, String gx, String gy, String n, String S) { this.name = name; this.p = hexToBigInteger(p); this.a = hexToBigInteger(a); this.b = hexToBigInteger(b); this.gx = hexToBigInteger(gx); this.gy = hexToBigInteger(gy); this.n = hexToBigInteger(n); this.S = hexToBigInteger(S); ECCurve curve = null; ECPoint pointG = null; ECDomainParameters bcSpec = null; ECKeyPairGenerator keyPairGenerator = null; try { curve = new ECCurve.Fp(this.p, this.a, this.b, null, null); pointG = curve.createPoint(this.gx, this.gy); bcSpec = new ECDomainParameters(curve, pointG, this.n); keyPairGenerator = new ECKeyPairGenerator(); keyPairGenerator.init(new ECKeyGenerationParameters( bcSpec, new SecureRandom(SecureRandoms.generateSeed(24)) )); } catch (Exception ignored) { // x value invalid in Fp field element //System.err.println(this.toString() + ", error:" + ignored.getMessage()); } this.curve = curve; this.pointG = pointG; this.bcSpec = bcSpec; this.keyPairGenerator = keyPairGenerator; } @Override public String toString() { return new StringBuilder() .append(name).append(SEPARATOR) .append(Numbers.toHex(p)).append(SEPARATOR) .append(Numbers.toHex(a)).append(SEPARATOR) .append(Numbers.toHex(b)).append(SEPARATOR) .append(Numbers.toHex(gx)).append(SEPARATOR) .append(Numbers.toHex(gy)).append(SEPARATOR) .append(Numbers.toHex(n)).append(SEPARATOR) .append(Numbers.toHex(S)).toString(); } public static ECParameters fromString(String parameter) { String[] array = parameter.split(String.valueOf(SEPARATOR), 8); return new ECParameters( array[0], array[1], array[2], array[3], array[4], array[5], array[6], array[7] ); } private static String encodeHex(byte[] bytes) { return (bytes == null || bytes.length == 0) ? "0" : Hex.encodeHexString(bytes); } @Override public int hashCode() { return new HashCodeBuilder().append(p) .append(a).append(b).append(gx).append(gy) .append(n).append(S).toHashCode(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof ECParameters)) { return false; } ECParameters other = (ECParameters) obj; return new EqualsBuilder() .append(this.p, other.p) .append(this.a, other.a) .append(this.b, other.b) .append(this.gx, other.gx) .append(this.gy, other.gy) .append(this.n, other.n) .append(this.S, other.S) .isEquals(); //return EqualsBuilder.reflectionEquals(this, obj, false, null, false, "name"); } private static BigInteger hexToBigInteger(String hex) { return new BigInteger(hex, 16); } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/HmacAlgorithms.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce; import com.google.common.collect.ImmutableBiMap; /** * The Hamc Algorithms * @author Ponfee */ public enum HmacAlgorithms { HmacMD5(128), // HmacRipeMD128(128), HmacRipeMD160(160), // HmacRipeMD256(256), HmacRipeMD320(320), // HmacSHA1(160), HmacSHA224(224), // HmacSHA256(256), HmacSHA384(384), // HmacSHA512(512), // // org.bouncycastle.crypto.digests.SM3Digest cannot support hmac algorithm //HmacSM3(256), // org.bouncycastle.crypto.digests.SHAKEDigest cannot support hmac algorithm //HmacSHAKE128(128), HmacSHAKE256(256), /** * @see org.bouncycastle.crypto.digests.KeccakDigest * @see org.bouncycastle.jcajce.provider.digest.Keccak */ HmacKECCAK224(224), HmacKECCAK288(288), // HmacKECCAK256(256), HmacKECCAK384(384), // HmacKECCAK512(512), // HmacSKEIN_256_128("Skein-MAC-256-128", 128), // HmacSKEIN_256_256("Skein-MAC-256-256", 256), // HmacSKEIN_512_256("Skein-MAC-512-256", 256), // HmacSKEIN_512_512("Skein-MAC-512-512", 512), // HmacSKEIN_1024_512("Skein-MAC-1024-512", 512), // HmacSKEIN_1024_1024("Skein-MAC-1024-1024", 1024), // /** * @see org.bouncycastle.crypto.digests.SHA3Digest * @see org.bouncycastle.jcajce.provider.digest.SHA3 */ HmacSHA3_224("HmacSHA3-224", 224), HmacSHA3_256("HmacSHA3-256", 256), // HmacSHA3_384("HmacSHA3-384", 384), HmacSHA3_512("HmacSHA3-512", 512), // ; private final String algorithm; private final int byteSize; HmacAlgorithms(int bitLen) { this.algorithm = this.name(); this.byteSize = bitLen >>> 3; } HmacAlgorithms(String algorithm, int bitLen) { this.algorithm = algorithm; this.byteSize = bitLen >>> 3; } public String algorithm() { return this.algorithm; } public int byteSize() { return this.byteSize; } public static final ImmutableBiMap ALGORITHM_MAPPING = ImmutableBiMap. builder() .put(1, HmacAlgorithms.HmacSHA256) .put(2, HmacAlgorithms.HmacSHA512) .put(3, HmacAlgorithms.HmacKECCAK256) .put(4, HmacAlgorithms.HmacKECCAK512) .put(5, HmacAlgorithms.HmacSHA3_256) .put(6, HmacAlgorithms.HmacSHA3_512) .build(); } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/Providers.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce; import javax.crypto.*; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; import java.security.*; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * security providers * there has not any method defined except a static method * * @author Ponfee */ @SuppressWarnings("restriction") public interface Providers { static Provider get(String name) { return ProvidersHolder.NAME_HOLDER.get(name); } static Provider get(Class type) { Provider provider = ProvidersHolder.CLASS_HOLDER.get(type); if (provider != null) { return provider; } try { provider = type.getDeclaredConstructor().newInstance(); Security.addProvider(provider); ProvidersHolder.NAME_HOLDER.put(provider.getName(), provider); } catch (Exception ignored) { provider = NullProvider.INSTANCE; ignored.printStackTrace(); } ProvidersHolder.CLASS_HOLDER.put(type, provider); return provider; } // ---------------------------------------------------------- static void set(Provider provider) { ProvidersHolder.CURRENT_PROVIDER.set(provider); } static void clear() { ProvidersHolder.CURRENT_PROVIDER.remove(); } static void setGlobal(Provider provider) { ProvidersHolder.globalProvider = provider; } static void clearGlobal() { ProvidersHolder.globalProvider = null; } // ---------------------------------------------------------- static KeyAgreement getKeyAgreement(String algorithm) { return getKeyAgreement(algorithm, null); } static KeyAgreement getKeyAgreement(String algorithm, Provider provider) { provider = ProvidersHolder.getProvider(provider); try { return provider == null ? KeyAgreement.getInstance(algorithm) : KeyAgreement.getInstance(algorithm, provider); } catch (NoSuchAlgorithmException e) { throw new SecurityException(e); } } static KeyGenerator getKeyGenerator(String algorithm) { return getKeyGenerator(algorithm, null); } static KeyGenerator getKeyGenerator(String algorithm, Provider provider) { provider = ProvidersHolder.getProvider(provider); try { return provider == null ? KeyGenerator.getInstance(algorithm) : KeyGenerator.getInstance(algorithm, provider); } catch (NoSuchAlgorithmException e) { throw new SecurityException(e); } } static Cipher getCipher(String algorithm) { return getCipher(algorithm, null); } static Cipher getCipher(String algorithm, Provider provider) { provider = ProvidersHolder.getProvider(provider); try { return provider == null ? Cipher.getInstance(algorithm) : Cipher.getInstance(algorithm, provider); } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { throw new SecurityException(e); } } static KeyPairGenerator getKeyPairGenerator(String algorithm) { return getKeyPairGenerator(algorithm, null); } static KeyPairGenerator getKeyPairGenerator(String algorithm, Provider provider) { provider = ProvidersHolder.getProvider(provider); try { return provider == null ? KeyPairGenerator.getInstance(algorithm) : KeyPairGenerator.getInstance(algorithm, provider); } catch (NoSuchAlgorithmException e) { throw new SecurityException(e); } } static KeyFactory getKeyFactory(String algorithm) { return getKeyFactory(algorithm, null); } static KeyFactory getKeyFactory(String algorithm, Provider provider) { provider = ProvidersHolder.getProvider(provider); try { return provider == null ? KeyFactory.getInstance(algorithm) : KeyFactory.getInstance(algorithm, provider); } catch (NoSuchAlgorithmException e) { throw new SecurityException(e); } } static Signature getSignature(String algorithm) { return getSignature(algorithm, null); } static Signature getSignature(String algorithm, Provider provider) { provider = ProvidersHolder.getProvider(provider); try { return provider == null ? Signature.getInstance(algorithm) : Signature.getInstance(algorithm, provider); } catch (NoSuchAlgorithmException e) { throw new SecurityException(e); } } static KeyStore getKeyStore(String algorithm) { return getKeyStore(algorithm, null); } static KeyStore getKeyStore(String algorithm, Provider provider) { provider = ProvidersHolder.getProvider(provider); try { return provider == null ? KeyStore.getInstance(algorithm) : KeyStore.getInstance(algorithm, provider); } catch (KeyStoreException e) { throw new SecurityException(e); } } static TrustManagerFactory getTrustManagerFactory(String algorithm) { return getTrustManagerFactory(algorithm, null); } static TrustManagerFactory getTrustManagerFactory(String algorithm, Provider provider) { provider = ProvidersHolder.getProvider(provider); try { return provider == null ? TrustManagerFactory.getInstance(algorithm) : TrustManagerFactory.getInstance(algorithm, provider); } catch (NoSuchAlgorithmException e) { throw new SecurityException(e); } } static KeyManagerFactory getKeyManagerFactory(String algorithm) { return getKeyManagerFactory(algorithm, null); } static KeyManagerFactory getKeyManagerFactory(String algorithm, Provider provider) { provider = ProvidersHolder.getProvider(provider); try { return provider == null ? KeyManagerFactory.getInstance(algorithm) : KeyManagerFactory.getInstance(algorithm, provider); } catch (NoSuchAlgorithmException e) { throw new SecurityException(e); } } static SSLContext getSSLContext(String algorithm) { return getSSLContext(algorithm, null); } static SSLContext getSSLContext(String algorithm, Provider provider) { provider = ProvidersHolder.getProvider(provider); try { return provider == null ? SSLContext.getInstance(algorithm) : SSLContext.getInstance(algorithm, provider); } catch (NoSuchAlgorithmException e) { throw new SecurityException(e); } } static CertificateFactory getCertificateFactory(String algorithm) { return getCertificateFactory(algorithm, null); } static CertificateFactory getCertificateFactory(String algorithm, Provider provider) { provider = ProvidersHolder.getProvider(provider); try { return provider == null ? CertificateFactory.getInstance(algorithm) : CertificateFactory.getInstance(algorithm, provider); } catch (CertificateException e) { throw new SecurityException(e); } } static SecretKeyFactory getSecretKeyFactory(String algorithm) { return getSecretKeyFactory(algorithm, null); } static SecretKeyFactory getSecretKeyFactory(String algorithm, Provider provider) { provider = ProvidersHolder.getProvider(provider); try { return provider == null ? SecretKeyFactory.getInstance(algorithm) : SecretKeyFactory.getInstance(algorithm, provider); } catch (NoSuchAlgorithmException e) { throw new SecurityException(e); } } // ------------------------------------------------------------------------------ // BouncyCastleProvider.PROVIDER_NAME Provider BC = get(org.bouncycastle.jce.provider.BouncyCastleProvider.class); Provider BC_PQC = get(org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider.class); Provider BC_JSSE = get(org.bouncycastle.jsse.provider.BouncyCastleJsseProvider.class); Provider SunJSSE = get(com.sun.net.ssl.internal.ssl.Provider.class); Provider SunJCE = get(com.sun.crypto.provider.SunJCE.class); Provider SunSASL = get(com.sun.security.sasl.Provider.class); /* Provider XMLDSig = get(org.jcp.xml.dsig.internal.dom.XMLDSigRI.class); Provider SUN = get(sun.security.provider.Sun.class); Provider SunRsaSign = get(sun.security.rsa.SunRsaSign.class); Provider SunEC = get(sun.security.ec.SunEC.class); Provider SunJGSS = get(sun.security.jgss.SunProvider.class); Provider SunPCSC = get(sun.security.smartcardio.SunPCSC.class); Provider SunMSCAPI = get(sun.security.mscapi.SunMSCAPI.class); */ /** * provider holder */ final class ProvidersHolder { // -------------------------------------------------------------------------- private static final Map, Provider> CLASS_HOLDER = new ConcurrentHashMap<>(16); private static final Map NAME_HOLDER = new ConcurrentHashMap<>(16); static { Provider[] providers = Security.getProviders(); if (providers != null && providers.length > 0) { for (Provider provider : providers) { CLASS_HOLDER.put(provider.getClass(), provider); NAME_HOLDER.put(provider.getName(), provider); } } } // -------------------------------------------------------------------------- private static final ThreadLocal CURRENT_PROVIDER = new ThreadLocal<>(); private static Provider getProvider(Provider provider) { return provider != null ? provider : (provider = CURRENT_PROVIDER.get()) != null ? provider : globalProvider; } // -------------------------------------------------------------------------- private static Provider globalProvider = null; } /** * The NullProvider representing the not exists provider */ final class NullProvider extends Provider { private static final long serialVersionUID = 7420890884380155994L; private static final NullProvider INSTANCE = new NullProvider(); private NullProvider() { super("Null", 1.0D, "Non provider"); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/RSACipherPaddings.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce; /** * RSA加密填充 * Jdk Sun only support:RSA/ECB/PKCS1Padding * * @see org.bouncycastle.jcajce.provider.asymmetric.rsa.CipherSpi * * @author Ponfee */ public enum RSACipherPaddings { ECB_NOPADDING("/ECB/NOPADDING"), // ECB_PKCS1PADDING("/ECB/PKCS1PADDING"), // 原文必须 比RSA钥模长(modulus)短至少11个字节 ECB_ISO9796_1PADDING("/ECB/ISO9796-1PADDING"), // ECB_OAEPWITHMD5ANDMGF1PADDING("/ECB/OAEPWITHMD5ANDMGF1PADDING"), // ECB_OAEPPADDING("/ECB/OAEPPADDING"), // RSA_size(rsa) – 41 ECB_OAEPWITHSHA1ANDMGF1PADDING("/ECB/OAEPWITHSHA1ANDMGF1PADDING"), // OAEPWITHSHA-1ANDMGF1PADDING ECB_OAEPWITHSHA224ANDMGF1PADDING("/ECB/OAEPWITHSHA224ANDMGF1PADDING"), // OAEPWITHSHA-224ANDMGF1PADDING ECB_OAEPWITHSHA256ANDMGF1PADDING("/ECB/OAEPWITHSHA256ANDMGF1PADDING"), // OAEPWITHSHA-256ANDMGF1PADDING ECB_OAEPWITHSHA384ANDMGF1PADDING("/ECB/OAEPWITHSHA384ANDMGF1PADDING"), // OAEPWITHSHA-384ANDMGF1PADDING ECB_OAEPWITHSHA512ANDMGF1PADDING("/ECB/OAEPWITHSHA512ANDMGF1PADDING"), // OAEPWITHSHA-512ANDMGF1PADDING ECB_OAEPWITHSHA3_224ANDMGF1PADDING("/ECB/OAEPWITHSHA3-224ANDMGF1PADDING"), // ECB_OAEPWITHSHA3_256ANDMGF1PADDING("/ECB/OAEPWITHSHA3-256ANDMGF1PADDING"), // ECB_OAEPWITHSHA3_384ANDMGF1PADDING("/ECB/OAEPWITHSHA3-384ANDMGF1PADDING"), // ECB_OAEPWITHSHA3_512ANDMGF1PADDING("/ECB/OAEPWITHSHA3-512ANDMGF1PADDING"), // NONE_NOPADDING("/NONE/NOPADDING"), // NONE_PKCS1PADDING("/NONE/PKCS1PADDING"), // 原文必须 比RSA钥模长(modulus)短至少11个字节 NONE_ISO9796_1PADDING("/NONE/ISO9796-1PADDING"), // NONE_OAEPWITHMD5ANDMGF1PADDING("/NONE/OAEPWITHMD5ANDMGF1PADDING"), // NONE_OAEPPADDING("/NONE/OAEPPADDING"), // RSA_size(rsa) – 41 NONE_OAEPWITHSHA1ANDMGF1PADDING("/NONE/OAEPWITHSHA1ANDMGF1PADDING"), // OAEPWITHSHA-1ANDMGF1PADDING NONE_OAEPWITHSHA224ANDMGF1PADDING("/NONE/OAEPWITHSHA224ANDMGF1PADDING"), // OAEPWITHSHA-224ANDMGF1PADDING NONE_OAEPWITHSHA256ANDMGF1PADDING("/NONE/OAEPWITHSHA256ANDMGF1PADDING"), // OAEPWITHSHA-256ANDMGF1PADDING NONE_OAEPWITHSHA384ANDMGF1PADDING("/NONE/OAEPWITHSHA384ANDMGF1PADDING"), // OAEPWITHSHA-384ANDMGF1PADDING NONE_OAEPWITHSHA512ANDMGF1PADDING("/NONE/OAEPWITHSHA512ANDMGF1PADDING"), // OAEPWITHSHA-512ANDMGF1PADDING NONE_OAEPWITHSHA3_224ANDMGF1PADDING("/NONE/OAEPWITHSHA3-224ANDMGF1PADDING"), // NONE_OAEPWITHSHA3_256ANDMGF1PADDING("/NONE/OAEPWITHSHA3-256ANDMGF1PADDING"), // NONE_OAEPWITHSHA3_384ANDMGF1PADDING("/NONE/OAEPWITHSHA3-384ANDMGF1PADDING"), // NONE_OAEPWITHSHA3_512ANDMGF1PADDING("/NONE/OAEPWITHSHA3-512ANDMGF1PADDING"), // ECB_SSLV23("/ECB/SSLV23"), // NONE_SSLV23("/NONE/SSLV23"); // private final String transform; RSACipherPaddings(String transform) { this.transform = transform; } public String transform() { return transform; } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/RSASignAlgorithms.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce; /** * RSA签名算法 * * @author Ponfee */ public enum RSASignAlgorithms { MD5withRSA, SHA1withRSA, SHA256withRSA, // SHA384withRSA, SHA512withRSA } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/cert/CertPKCS1Verifier.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.cert; import cn.ponfee.commons.jce.Providers; import java.security.InvalidKeyException; import java.security.Signature; import java.security.SignatureException; import java.security.cert.X509CRL; import java.security.cert.X509Certificate; /** * pkcs1 signature verifier * * @author Ponfee */ public class CertPKCS1Verifier extends CertSignedVerifier { /** * the signature of pkcs1 verifer * @param rootCert the ca root cert * @param crl the cert revoke list * @param subject the subject cert * @param info origin data info * @param signed signed info */ public CertPKCS1Verifier(X509Certificate rootCert, X509CRL crl, X509Certificate subject, byte[] info, byte[] signed) { super(rootCert, crl, info); this.subjects = new X509Certificate[] { subject }; this.signedInfos.add(signed); } @Override public void verifySigned() { String subjectCN = null; Signature sign = Providers.getSignature(this.subjects[0].getSigAlgName()); try { subjectCN = X509CertUtils.getCertInfo(this.subjects[0], X509CertInfo.SUBJECT_CN); sign.initVerify(this.subjects[0].getPublicKey()); sign.update(this.info); if (!sign.verify(this.signedInfos.get(0))) { throw new SecurityException("[" + subjectCN + "]验签不通过"); } } catch (SignatureException e) { throw new SecurityException("[" + subjectCN + "]证书签名信息错误", e); } catch (InvalidKeyException e) { throw new SecurityException("证书验签出错", e); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/cert/CertPKCS7Verifier.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.cert; import cn.ponfee.commons.jce.pkcs.PKCS7Signature; import sun.security.pkcs.PKCS7; import sun.security.pkcs.SignerInfo; import java.io.IOException; import java.math.BigInteger; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; import java.security.cert.X509CRL; import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.Map; /** * pkcs7方式验签 * * @author Ponfee */ @SuppressWarnings("restriction") public class CertPKCS7Verifier extends CertSignedVerifier { private final PKCS7 pkcs7; /** * 不附原文的多人pkcs7签名 * @param rootCert the root ca cert * @param crl the cert revoke list * @param pkcs7Data the pkcs7 byte array data * @param info the origin byte array data */ public CertPKCS7Verifier(X509Certificate rootCert, X509CRL crl, byte[] pkcs7Data, byte[] info) { this(rootCert, crl, PKCS7Signature.getPkcs7(pkcs7Data), info); } /** * 附原文的多人pkcs7签名 * @param rootCert the root ca cert * @param crl the cert revoke list * @param pkcs7Data the pkcs7 byte array data, attached origin byte array data */ public CertPKCS7Verifier(X509Certificate rootCert, X509CRL crl, byte[] pkcs7Data) { this(rootCert, crl, PKCS7Signature.getPkcs7(pkcs7Data)); } /** * 附原文的多人pkcs7签名 * @param rootCert the root ca cert * @param crl the cert revoke list * @param pkcs7 the pkcs7 */ public CertPKCS7Verifier(X509Certificate rootCert, X509CRL crl, PKCS7 pkcs7) { this(rootCert, crl, pkcs7, PKCS7Signature.getContent(pkcs7)); } /** * 附原文的多人pkcs7签名 * @param rootCert the root ca cert * @param crl the cert revoke list * @param pkcs7 the pkck7 * @param info the origin byte array data */ public CertPKCS7Verifier(X509Certificate rootCert, X509CRL crl, PKCS7 pkcs7, byte[] info) { super(rootCert, crl, info); this.pkcs7 = pkcs7; SignerInfo[] signs = pkcs7.getSignerInfos(); Map certs = new HashMap<>(signs.length << 1); for (X509Certificate cert : pkcs7.getCertificates()) { certs.put(cert.getSerialNumber(), cert); } this.subjects = new X509Certificate[signs.length]; for (int i = 0; i < signs.length; i++) { X509Certificate cert = certs.get(signs[i].getCertificateSerialNumber()); if (cert == null) { throw new IllegalArgumentException("cannot found the sign cert: " + signs[i].getCertificateSerialNumber()); } else { this.subjects[i++] = cert; this.signedInfos.add(signs[i].getEncryptedDigest()); } } } @Override public void verifySigned() { String subjectCN = null; try { for (SignerInfo signer : pkcs7.getSignerInfos()) { subjectCN = X509CertUtils.getCertInfo(signer.getCertificate(pkcs7), X509CertInfo.SUBJECT_CN); if (pkcs7.verify(signer, this.info) == null) { throw new SecurityException("[" + subjectCN + "]验签不通过"); } } } catch (SignatureException e) { throw new SecurityException("[" + subjectCN + "]签名信息错误", e); } catch (IOException e) { throw new SecurityException("获取证书主题异常", e); } catch (NoSuchAlgorithmException e) { throw new SecurityException("证书验签出错", e); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/cert/CertSignedVerifier.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.cert; import java.security.SignatureException; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509CRL; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * 证书验签(template method patterns) * * @author Ponfee */ public abstract class CertSignedVerifier { protected final X509Certificate rootCert; // 根证书 protected final X509CRL crl; // 吊销列表 protected final byte[] info; // 原文信息 protected final List signedInfos = new ArrayList<>(); // 签名数据 protected X509Certificate[] subjects; // 多人签名证书 private boolean verifySigned = true; protected CertSignedVerifier(X509Certificate rootCert, X509CRL crl, byte[] info) { this.rootCert = rootCert; this.crl = crl; this.info = info; } /** * 根据加载的根证进行证书验证 */ public final void verify() { for (X509Certificate subject : subjects) { String subjectCN = X509CertUtils.getCertInfo(subject, X509CertInfo.SUBJECT_CN); // 获取根证书 if (rootCert == null) { throw new SecurityException("[" + subjectCN + "]的根证未受信任"); } // 校验 verifyCertDate(subject); verifyIssuingSign(subject, rootCert); if (crl != null) { verifyCrlRevoke(subject, crl); } } // 签名验证 if (verifySigned) { verifySigned(); } } /** * 验证签名 */ public abstract void verifySigned(); /** * 校验证书是否过期 * @param subject */ public static void verifyCertDate(X509Certificate subject) { String subjectCN = null; try { subjectCN = X509CertUtils.getCertInfo(subject, X509CertInfo.SUBJECT_CN); subject.checkValidity(new Date()); } catch (CertificateExpiredException e) { throw new SecurityException("[" + subjectCN + "]已过期", e); } catch (CertificateNotYetValidException e) { throw new SecurityException("[" + subjectCN + "]尚未生效", e); } } /** * 校验是否由指定根证签发 * @param subject * @param root */ public static void verifyIssuingSign(X509Certificate subject, X509Certificate root) { String subjectCN = null; try { subjectCN = X509CertUtils.getCertInfo(subject, X509CertInfo.SUBJECT_CN); subject.verify(root.getPublicKey()); } catch (SignatureException e) { throw new SecurityException("[" + subjectCN + "]的根证未受信任", e); } catch (Exception e) { throw new SecurityException("根证验签出错", e); } } /** * 校验是否已被吊销 * @param subject * @param crl */ public static void verifyCrlRevoke(X509Certificate subject, X509CRL crl) { String subjectCN = X509CertUtils.getCertInfo(subject, X509CertInfo.SUBJECT_CN); if (crl.isRevoked(subject)) { throw new SecurityException("[" + subjectCN + "]已被吊销"); } } public X509Certificate[] getSubjects() { return this.subjects; } public byte[] getInfo() { return this.info; } public List getSignedInfo() { return this.signedInfos; } public void setVerifySigned(boolean verifySigned) { this.verifySigned = verifySigned; } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/cert/ObjectIdentifiers.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.cert; /** * 证书扩展信息 * * @author Ponfee * @see org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers */ public final class ObjectIdentifiers { public static final int[] SubjectDirectoryAttributes = new int[] { 2, 5, 29, 9 }; /** Subject Key Identifier */ public static final int[] SubjectKeyIdentifier = new int[] { 2, 5, 29, 14 }; /** Key Usage */ public static final int[] KeyUsage = new int[] { 2, 5, 29, 15 }; /** Private Key Usage Period 私钥使用周期 */ public static final int[] PrivateKeyUsagePeriod = new int[] { 2, 5, 29, 16 }; /** Subject Alternative Name */ public static final int[] SubjectAlternativeName = new int[] { 2, 5, 29, 17 }; /** Issuer Alternative Name */ public static final int[] IssuerAlternativeName = new int[] { 2, 5, 29, 18 }; /** Basic Constraints */ public static final int[] BasicConstraints = new int[] { 2, 5, 29, 19 }; /** CRL Number */ public static final int[] CRLNumber = new int[] { 2, 5, 29, 20 }; /** Reason code */ public static final int[] ReasonCode = new int[] { 2, 5, 29, 21 }; /** Hold Instruction Code */ public static final int[] InstructionCode = new int[] { 2, 5, 29, 23 }; /** Invalidity Date */ public static final int[] InvalidityDate = new int[] { 2, 5, 29, 24 }; /** Delta CRL indicator */ public static final int[] DeltaCRLIndicator = new int[] { 2, 5, 29, 27 }; /** Issuing Distribution Point */ public static final int[] IssuingDistributionPoint = new int[] { 2, 5, 29, 28 }; /** Certificate Issuer */ public static final int[] CertificateIssuer = new int[] { 2, 5, 29, 29 }; /** Name Constraints */ public static final int[] NameConstraints = new int[] { 2, 5, 29, 30 }; /** CRL Distribution Points */ public static final int[] CRLDistributionPoints = new int[] { 2, 5, 29, 31 }; /** Certificate Policies */ public static final int[] CertificatePolicies = new int[] { 2, 5, 29, 32 }; /** Policy Mappings */ public static final int[] PolicyMappings = new int[] { 2, 5, 29, 33 }; /** Authority Key Identifier */ public static final int[] AuthorityKeyIdentifier = new int[] { 2, 5, 29, 35 }; /** Policy Constraints */ public static final int[] PolicyConstraints = new int[] { 2, 5, 29, 36 }; /** Extended Key Usage */ public static final int[] ExtendedKeyUsage = new int[] { 2, 5, 29, 37 }; /** Freshest CRL */ public static final int[] FreshestCRL = new int[] { 2, 5, 29, 46 }; /** Inhibit Any Policy */ public static final int[] InhibitAnyPolicy = new int[] { 2, 5, 29, 54 }; /** Authority Info Access */ public static final int[] AuthorityInfoAccess = new int[] { 1, 3, 6, 1, 5, 5, 7, 1, 1 }; /** Subject Info Access */ public static final int[] SubjectInfoAccess = new int[] { 1, 3, 6, 1, 5, 5, 7, 1, 11 }; /** Logo Type */ public static final int[] LogoType = new int[] { 1, 3, 6, 1, 5, 5, 7, 1, 12 }; /** BiometricInfo */ public static final int[] BiometricInfo = new int[] { 1, 3, 6, 1, 5, 5, 7, 1, 2 }; /** QCStatements */ public static final int[] QCStatements = new int[] { 1, 3, 6, 1, 5, 5, 7, 1, 3 }; /** Audit identity extension in attribute certificates */ public static final int[] AuditIdentity = new int[] { 1, 3, 6, 1, 5, 5, 7, 1, 4 }; /** NoRevAvail extension in attribute certificates */ public static final int[] NoRevAvail = new int[] { 2, 5, 29, 56 }; /** TargetInformation extension in attribute certificates */ public static final int[] TargetInformation = new int[] { 2, 5, 29, 55 }; } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/cert/RepairX500Principal.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.cert; import javax.security.auth.x500.X500Principal; import java.io.ByteArrayInputStream; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.Principal; /** * 解决X500Principal乱码问题 * * @author Ponfee */ public class RepairX500Principal implements Principal { private static final byte[][] OID_ARRAY = { { 0x55, 0x04, 0x06 }, { 0x55, 0x04, 0x08 }, { 0x55, 0x04, 0x07 }, { 0x55, 0x04, 0x0a }, { 0x55, 0x04, 0x0b }, { 0x55, 0x04, 0x03 }, { 0x2a, (byte) 0x86, 0x48, (byte) 0x86, (byte) 0xf7, 0x0d, 0x01, 0x09, 0x01 } }; private static final String[] DN_STR = { "C", "ST", "L", "O", "OU", "CN", "E" }; private final ByteArrayInputStream input; public RepairX500Principal(X500Principal principal) { input = new ByteArrayInputStream(principal.getEncoded()); } @Override public String getName() { if (preLen(0x30) != input.available()) { return null; } byte[] oid = new byte[9]; int oidType; StringBuilder builder = new StringBuilder(); for (;;) { if (preLen(0x31) == 0) { break; } if (preLen(0x30) == 0) { break; } int len = preLen(0x06); if (len == 0) { break; } if (len > 9) { oidType = -1; input.skip(len); } else { input.read(oid, 0, len); for (oidType = DN_STR.length - 1; oidType > -1; oidType--) { for (len = OID_ARRAY[oidType].length - 1; len > -1; len--) { if (oid[len] != OID_ARRAY[oidType][len]) { break; } } if (len < 0) { break; } } } Charset charset = (input.read() == 0x1e) ? StandardCharsets.UTF_16BE : StandardCharsets.UTF_8; len = preLen(-1); if (oidType > -1) { byte[] value = new byte[len]; input.read(value, 0, value.length); if (builder.length() > 0) { builder.append(','); } builder.append(DN_STR[oidType]).append('=') .append(new String(value, charset)); } else { input.skip(len); } } return builder.length() == 0 ? null : builder.toString(); } private int preLen(int tag) { int itag; if (tag != -1) { itag = input.read(); if (itag != tag) { return 0; } } itag = input.read(); if (itag < 0x80) { return itag; } if (itag == 0x81) { return input.read(); } if (itag == 0x82) { itag = input.read(); return (itag << 8) + input.read(); } return 0; } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/cert/X509CertGenerator.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.cert; import cn.ponfee.commons.jce.Providers; import cn.ponfee.commons.jce.RSASignAlgorithms; import cn.ponfee.commons.util.ObjectUtils; import cn.ponfee.commons.util.UuidUtils; import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder; import org.bouncycastle.pkcs.PKCS10CertificationRequest; import sun.security.pkcs10.PKCS10; import sun.security.util.ObjectIdentifier; import sun.security.x509.X509CertInfo; import sun.security.x509.*; import javax.annotation.Nullable; import java.io.IOException; import java.math.BigInteger; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; import java.security.cert.X509Certificate; import java.util.Date; import java.util.Vector; /** * 证书生成工具类 * * @author Ponfee */ @SuppressWarnings({ "restriction" }) public class X509CertGenerator { // ------------------------create root ca cert of self sign ----------------------------- public static X509Certificate createRootCert(String issuer, RSASignAlgorithms sigAlg, PrivateKey privateKey, PublicKey publicKey, Date notBefore, Date notAfter) { return createRootCert(null, issuer, sigAlg, privateKey, publicKey, notBefore, notAfter); } /** * 创建CA根证书(自签名) * @param sn * @param issuer * @param sigAlg * @param privateKey * @param publicKey * @param notBefore * @param notAfter * @return */ public static X509Certificate createRootCert(BigInteger sn, String issuer, RSASignAlgorithms sigAlg, PrivateKey privateKey, PublicKey publicKey, Date notBefore, Date notAfter) { PKCS10 pkcs10 = createPkcs10(issuer, privateKey, publicKey, sigAlg); X509CertInfo certInfo = createCertInfo(sn, pkcs10, notBefore, notAfter, createExtensions(true)); return selfSign(privateKey, certInfo); } // ---------------------------------create subject cert of ca sign ------------------------------ public static X509Certificate createSubjectCert(X509Certificate caCert, PrivateKey caKey, String subject, RSASignAlgorithms sigAlg, PrivateKey privateKey, PublicKey publicKey, Date notBefore, Date notAfter) { return createSubjectCert(caCert, caKey, null, subject, sigAlg, privateKey, publicKey, notBefore, notAfter); } /** * 创建证书并用根证签发 * @param caCert * @param caKey * @param sn * @param subject * @param sigAlg * @param privateKey * @param publicKey * @param notBefore * @param notAfter * @return */ public static X509Certificate createSubjectCert(X509Certificate caCert, PrivateKey caKey, BigInteger sn, String subject, RSASignAlgorithms sigAlg, PrivateKey privateKey, PublicKey publicKey, Date notBefore, Date notAfter) { PKCS10 pkcs10 = createPkcs10(subject, privateKey, publicKey, sigAlg); X509CertInfo certInfo = createCertInfo(sn, pkcs10, notBefore, notAfter, createExtensions(false)); return caSign(caCert, caKey, certInfo); } public static X509Certificate createSubjectCert(X509Certificate caCert, PrivateKey caKey, PKCS10 pkcs10, Date notBefore, Date notAfter) { return createSubjectCert(caCert, caKey, null, pkcs10, notBefore, notAfter); } /** * pkcs10请求CA签发证书 * @param caCert * @param caKey * @param sn * @param pkcs10 * @param notBefore * @param notAfter * @return */ public static X509Certificate createSubjectCert(X509Certificate caCert, PrivateKey caKey, BigInteger sn, PKCS10 pkcs10, Date notBefore, Date notAfter) { X509CertInfo certInfo = createCertInfo(sn, pkcs10, notBefore, notAfter, createExtensions(false)); return caSign(caCert, caKey, certInfo); } // -------------------------------------------create pkcs10 ------------------------------------------ /** * 创建pkcs10 * @param subject * @param privateKey * @param publicKey * @param sigAlg * @return */ public static PKCS10 createPkcs10(String subject, PrivateKey privateKey, PublicKey publicKey, RSASignAlgorithms sigAlg) { Signature signature = Providers.getSignature(sigAlg.name()); try { PKCS10 pkcs10 = new PKCS10(publicKey); signature.initSign(privateKey); pkcs10.encodeAndSign(new X500Name(subject), signature); return pkcs10; } catch (Exception e) { throw new SecurityException(e); } } // -------------------------------------------create cert ext----------------------------------------- /** * 创建默认的扩展信息 * @param isCA {@code true} is create CA cert * {@code false} is create subject cert * @return */ public static CertificateExtensions createExtensions(boolean isCA) { try { CertificateExtensions extensions = new CertificateExtensions(); //byte[] userData; // 密钥用法 KeyUsageExtension keyUsage = new KeyUsageExtension(); keyUsage.set(KeyUsageExtension.DIGITAL_SIGNATURE, true); // 支持数据签名 if (isCA) { //userData = "Digital Signature, Certificate Signing, Off-line CRL Signing, CRL Signing (86)".getBytes(); keyUsage.set(KeyUsageExtension.KEY_ENCIPHERMENT, true); // 支持密钥加密 keyUsage.set(KeyUsageExtension.KEY_AGREEMENT, true); // 支持密钥协议 keyUsage.set(KeyUsageExtension.KEY_CERTSIGN, true); // 支持证书签名 keyUsage.set(KeyUsageExtension.CRL_SIGN, true); // 支持吊销列表签名 } else { //userData = "Digital Signature, Data Encipherment (90)".getBytes(); keyUsage.set(KeyUsageExtension.DATA_ENCIPHERMENT, true); // 支持数据加密 // 增强密钥用法 Vector extendedKeyUsage = new Vector<>(); extendedKeyUsage.add(new ObjectIdentifier(new int[] { 1, 3, 6, 1, 5, 5, 7, 3, 3 })); // 代码签名 extensions.set(ExtendedKeyUsageExtension.NAME, new ExtendedKeyUsageExtension(extendedKeyUsage)); } extensions.set(KeyUsageExtension.NAME, keyUsage); /*// 版本号:v1、v2、v3,此扩展信息必须是v3版本,生成一个extension对象参数分别为oid,是否关键扩展,byte[]型的内容值 ObjectIdentifier oid = new ObjectIdentifier(new int[] { 1, 22 }); // 扩展域:第1位最大为2,第2位最大为39,后续不明 userData = ObjectUtils.concat(new byte[] { 0x04, (byte) userData.length }, userData); // flag,data length, data // PKCS7Signature验证签名会报错:java.security.SignatureException: Certificate has unsupported critical extension(s) extensions.set("UserData", new sun.security.x509.Extension(oid, true, userData)); */ return extensions; } catch (IOException e) { throw new SecurityException(e); } } // -------------------------------------------private methods---------------------------------------- /** * 根据pkcs10创建证书 * @param sn * @param pkcs10 * @param notBefore * @param notAfter * @param extensions * @return */ private static X509CertInfo createCertInfo(@Nullable BigInteger sn, PKCS10 pkcs10, Date notBefore, Date notAfter, CertificateExtensions extensions) { if (sn == null) { //sn = BigInteger.valueOf(ThreadLocalRandom.current().nextLong() & Long.MAX_VALUE); sn = new BigInteger(1, UuidUtils.uuid()); } try { // 验证pkcs10 PKCS10CertificationRequest req = new PKCS10CertificationRequest(pkcs10.getEncoded()); JcaContentVerifierProviderBuilder builder = new JcaContentVerifierProviderBuilder(); builder.setProvider(Providers.BC); if (!req.isSignatureValid(builder.build(req.getSubjectPublicKeyInfo()))) { throw new SecurityException("Invalid pkcs10 signature data."); } /*org.bouncycastle.jce.PKCS10CertificationRequest req = new org.bouncycastle.jce.PKCS10CertificationRequest(pkcs10.getEncoded()); if (!req.verify()) { throw new SecurityException("Invalid pkcs10 signature data."); }*/ AlgorithmId signAlg = AlgorithmId.get(req.getSignatureAlgorithm().getAlgorithm().getId()); X509CertInfo x509certInfo = new X509CertInfo(); x509certInfo.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3)); x509certInfo.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(sn)); x509certInfo.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(signAlg)); x509certInfo.set(X509CertInfo.SUBJECT, pkcs10.getSubjectName()); x509certInfo.set(X509CertInfo.KEY, new CertificateX509Key(pkcs10.getSubjectPublicKeyInfo())); x509certInfo.set(X509CertInfo.VALIDITY, new CertificateValidity(notBefore, notAfter)); if (extensions != null) { x509certInfo.set(X509CertInfo.EXTENSIONS, extensions); } return x509certInfo; } catch (Exception e) { throw new SecurityException(e); } } /** * 自签名证书(根证书) * @param caKey * @param caCertInfo * @return */ private static X509Certificate selfSign(PrivateKey caKey, X509CertInfo caCertInfo) { try { CertificateAlgorithmId algId = (CertificateAlgorithmId) caCertInfo.get(X509CertInfo.ALGORITHM_ID); caCertInfo.set(X509CertInfo.ISSUER, caCertInfo.get(X509CertInfo.SUBJECT)); X509CertImpl signedCert = new X509CertImpl(caCertInfo); signedCert.sign(caKey, algId.get(CertificateAlgorithmId.ALGORITHM).getName()); // 签名 return signedCert; } catch (Exception e) { throw new SecurityException(e); } } /** * CA签名证书 * @param caCert * @param caKey * @param subjectCertInfo * @return */ private static X509Certificate caSign(X509Certificate caCert, PrivateKey caKey, X509CertInfo subjectCertInfo) { try { // 从CA的证书中提取签发者的信息 X509CertImpl caCertImpl = new X509CertImpl(caCert.getEncoded()); // 获取X509CertInfo对象 X509CertInfo caCertInfo = (X509CertInfo) caCertImpl.get(X509CertImpl.NAME + "." + X509CertImpl.INFO); // 获取X509Name类型的签发者信息 X500Name issuer = (X500Name) caCertInfo.get(X509CertInfo.SUBJECT + "." + CertificateIssuerName.DN_NAME); subjectCertInfo.set(X509CertInfo.ISSUER, issuer); X509CertImpl signedCert = new X509CertImpl(subjectCertInfo); signedCert.sign(caKey, caCert.getSigAlgName()); // 使用CA私钥对其签名 return signedCert; } catch (Exception e) { throw new SecurityException(e); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/cert/X509CertInfo.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.cert; /** *
      *  CN=测试证书,OU=20121219,O=XXXCA,L=深圳市,ST=广东省,C=CN
      *    CN:公用名称 (Common Name) 简称:CN 字段,对于 SSL 证书,一般为网站域名;而对于代码签名证书则为申请单位名称;而对于客户端证书则为证书申请者的姓名;
      *     O:单位名称 (Organization Name) :简称:ON(O) 字段,对于 SSL 证书,一般为网站域名;而对于代码签名证书则为申请单位名称;而对于客户端单位证书则为证书申请者所在单位名称;
      *    OU:部门或分部的名称 (Organization Util),简称:OU字段,一般为机构代码或个人身份证号码
      *     L:所在城市 (Locality) 简称:L 字段
      *    ST:所在省份 (State/Provice) 简称:S(ST) 字段
      *     C:所在国家 (Country) 简称:C 字段,只能是国家字母缩写,如中国:CN
      * 其它字段:
      *    电子邮件 (Email) 简称:E 字段
      *    多个姓名字段 简称:G 字段
      *    介绍:Description 字段
      *    电话号码:Phone 字段,格式要求 + 国家区号 城市区号 电话号码,如: +86 732 88888888
      *    地址:STREET  字段
      *    邮政编码:PostalCode 字段
      * 
    * * 证书信息枚举类 * * @author Ponfee */ public enum X509CertInfo { SUBJECT_DN("主题"), ISSUER_DN("颁发者主题"), // CERT_SN("序列号"), VERSION("版本"), ALG_NAME("算法名称"), // START_TM("生效时间(格式:yyyy-MM-dd'T'HH:mm:ss.SSSZ)"), // END_TM("失效时间(格式:yyyy-MM-dd'T'HH:mm:ss.SSSZ)"), // USAGE("密钥用法(signature签名,encipherment加密)"), // PUBLIC_KEY("公钥(base64编码)"), // SUBJECT_CN("CN", "证书主体(CN)"), SUBJECT_O("O", "证书主体(O)"), // SUBJECT_OU("OU", "证书主体(OU)"), SUBJECT_L("L", "证书主体(L)"), // SUBJECT_ST("ST", "证书主体(ST)"), SUBJECT_C("C", "证书主体(C)"), // ISSUER_CN("CN", "证书颁发者(CN)"), ISSUER_O("O", "证书颁发者(O)"), // ISSUER_OU("OU", "证书颁发者(OU)"), ISSUER_L("L", "证书颁发者(L)"), // ISSUER_ST("ST", "证书颁发者(ST)"), ISSUER_C("C", "证书颁发者(C)"); // private final String attr; private final String desc; X509CertInfo(String desc) { this(null, desc); } X509CertInfo(String attr, String desc) { this.attr = attr; this.desc = desc; } public String attr() { return this.attr; } public String desc() { return desc; } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/cert/X509CertUtils.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.cert; import cn.ponfee.commons.io.Closeables; import cn.ponfee.commons.io.Files; import cn.ponfee.commons.jce.Providers; import org.apache.commons.codec.binary.Hex; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.time.FastDateFormat; import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.x509.Certificate; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cms.CMSSignedData; import org.bouncycastle.cms.SignerInformation; import org.bouncycastle.cms.SignerInformationStore; import org.bouncycastle.jce.provider.X509CRLObject; import org.bouncycastle.jce.provider.X509CRLParser; import org.bouncycastle.jce.provider.X509CertificateObject; import org.bouncycastle.openssl.jcajce.JcaPEMWriter; import org.bouncycastle.util.Store; import java.io.*; import java.nio.charset.StandardCharsets; import java.security.cert.CertificateFactory; import java.security.cert.X509CRL; import java.security.cert.X509CRLEntry; import java.security.cert.X509Certificate; import java.util.Base64; import java.util.Collection; import java.util.HashMap; import java.util.Map; /** * 证书工具类 * * @author Ponfee */ @SuppressWarnings({ "deprecation" }) public class X509CertUtils { private static final String X509 = "X.509"; private static final char[] ENDBOUNDARY = "-----END".toCharArray(); private static final FastDateFormat DATE_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); /** * pem加载证书 * @param pem * @return */ public static X509Certificate loadPemCert(String pem) { return loadX509Cert(pem.getBytes()); } /** * load from cert bytes or pem bytes * @param bytes * @return */ public static X509Certificate loadX509Cert(byte[] bytes) { CertificateFactory cf = Providers.getCertificateFactory(X509); try { // RSA证书 return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(bytes)); } catch (Exception e) { // SM2证书 ASN1InputStream input = null; try { if (isBase64(new ByteArrayInputStream(bytes))) { // base64(pem)编码证书 bytes = base64ToBinary(new ByteArrayInputStream(bytes)); } input = new ASN1InputStream(new ByteArrayInputStream(bytes)); ASN1Sequence seq = (ASN1Sequence) input.readObject(); //X509CertificateStructure struct = new org.bouncycastle.asn1.x509.X509CertificateStructure(seq); // bcmail-jdk16 Certificate struct = Certificate.getInstance(seq); // bcmail-jdk15on // JDK1.5可以运行,并且可以获取SM2 publicKey // JDK1.6不行:Unknown named curve: 1.2.156.10197.1.301 /*DERObject publicKey = struct.getSubjectPublicKeyInfo().getPublicKey(); struct.getSubjectPublicKeyInfo().getPublicKeyData(); byte[] encodedPublicKey = publicKey.getEncoded(); byte[] eP = Arrays.copyOfRange(encodedPublicKey, 5, 69);*/ return new X509CertificateObject(struct); } catch (Exception ex) { SecurityException se = new SecurityException(e.getMessage() + "; " + ex.getMessage()); se.setStackTrace(ArrayUtils.addAll(e.getStackTrace(), ex.getStackTrace())); throw se; } finally { Closeables.console(input); } } } /** * 根据证书文件流加载证书 * @param input * @return * @throws IOException */ public static X509Certificate loadX509Cert(InputStream input) throws IOException { return loadX509Cert(IOUtils.toByteArray(input)); } /** * 通过证书文件路径加载证书 * @param certFile * @return * @throws IOException */ public static X509Certificate loadX509Cert(File certFile) throws IOException { return loadX509Cert(IOUtils.toByteArray(new FileInputStream(certFile))); } /** * certpem = "-----BEGIN CERTIFICATE-----\n" + * toBase64Encoded(chain[0].getEncoded())) + * "\n-----END CERTIFICATE-----\n"; * certificate export to pem format text * * java.security.cert.Certificate * X509Certificate,X509CRL,KeyPair,PrivateKey,PublicKey * * @param obj * @return */ public static String exportToPem(Object obj) { try (StringWriter writer = new StringWriter(); JcaPEMWriter pemWriter = new JcaPEMWriter(writer) ) { pemWriter.writeObject(obj); pemWriter.flush(); return writer.toString(); } catch (IOException e) { throw new SecurityException(e); } } // ---------------------------------------crl-------------------------------------- /** * 根据byte流获取吊销列表 * @param bytes * @return */ public static X509CRL loadX509Crl(byte[] bytes) { ByteArrayInputStream bais; CertificateFactory cf = Providers.getCertificateFactory(X509); try { bais = new ByteArrayInputStream(bytes); //构建X509工厂 //生成X509格式的CRL对象并返回 return (X509CRL) cf.generateCRL(bais); } catch (Exception e) { X509CRLParser parser = new X509CRLParser(); try { ByteArrayInputStream in = new ByteArrayInputStream(bytes); parser.engineInit(in); return (X509CRLObject) parser.engineRead(); } catch (Exception ex) { SecurityException se = new SecurityException(e.getMessage() + "; " + ex.getMessage()); se.setStackTrace(ArrayUtils.addAll(e.getStackTrace(), ex.getStackTrace())); throw se; } } } /** * 获取crl * @throws IOException */ public static X509CRL loadX509Crl(InputStream is) throws IOException { return loadX509Crl(IOUtils.toByteArray(is)); } /** * 加载CRL * @param crlFile * @return * @throws IOException */ public static X509CRL loadX509Crl(File crlFile) throws IOException { return loadX509Crl(IOUtils.toByteArray(new FileInputStream(crlFile))); } /** * 获取证书掉销实体 * @param crlFile * @param certFile * @return * @throws IOException */ public static X509CRLEntry getX509CrlEntry(File crlFile, File certFile) throws IOException { X509CRL crl = loadX509Crl(crlFile); X509Certificate cert = loadX509Cert(certFile); return crl.getRevokedCertificate(cert); } /** * 获取证书扩展项信息 * @param cert * @param oid * @return */ public static String getCertExtVal(X509Certificate cert, String oid) { byte[] bytes = cert.getExtensionValue(oid); String reuslt = null; if (null != bytes && bytes.length > 0) { //String result = new String(bytes); //if (result.charAt(0) == 12) result = result.substring(2); String value = new String(bytes); reuslt = value.substring(4); } return reuslt; } /** * 查询证书信息 * @param cert * @param info * @return * @throws IOException */ public static String getCertInfo(X509Certificate cert, X509CertInfo info) { try { switch (info) { case VERSION: return Integer.toString(cert.getVersion()); case CERT_SN: return Hex.encodeHexString(cert.getSerialNumber().toByteArray(), false); case ALG_NAME: return cert.getSigAlgName(); case START_TM: return DATE_FORMAT.format(cert.getNotBefore()); case END_TM: return DATE_FORMAT.format(cert.getNotAfter()); case SUBJECT_DN: return cert.getSubjectDN().getName(); case ISSUER_DN: return cert.getIssuerDN().getName(); case PUBLIC_KEY: return Base64.getEncoder().encodeToString(cert.getPublicKey().getEncoded()); case USAGE: if (cert.getKeyUsage()[0]) { return "signature"; } else if (cert.getKeyUsage()[3]) { return "encipherment"; } else { return null; } case SUBJECT_C: case SUBJECT_CN: case SUBJECT_L: case SUBJECT_O: case SUBJECT_OU: case SUBJECT_ST: return parseCertDN(cert.getSubjectDN().getName(), info); case ISSUER_C: case ISSUER_CN: case ISSUER_L: case ISSUER_O: case ISSUER_OU: case ISSUER_ST: return parseCertDN(cert.getIssuerDN().getName(), info); default: return null; } } catch (Exception e) { return null; } } /** * 筛选证书主题信息 * @param certDN * @param ci * @return */ private static String parseCertDN(String certDN, X509CertInfo ci) { String type = ci.attr() + "="; String[] split = certDN.split(","); for (String x : split) { if (x.contains(type)) { return x.trim().substring(type.length()); } } return null; } /** * 解析PKCS7(SM2证书) * @param p7bytes * @return */ @SuppressWarnings("unchecked") public static Map parseP7(byte[] p7bytes) { try { Map result = new HashMap<>(3); CMSSignedData cms = new CMSSignedData(p7bytes); result.put("content", cms.getSignedContent().getContent()); // 原文 Store certStore = cms.getCertificates(); SignerInformationStore signerStore = cms.getSignerInfos(); Collection signers = signerStore.getSigners(); //List certs = new ArrayList<>(); // 报错 X509CertificateObject[] certs = new X509CertificateObject[signers.size()]; int i = 0; for (SignerInformation signer : signers) { Collection certChain = certStore.getMatches(signer.getSID()); Certificate cert = certChain.iterator().next().toASN1Structure(); // bcmail-jdk15on certs[i++] = new X509CertificateObject(cert); } result.put("certs", certs); result.put("signers", cms.getSignerInfos().getSigners()); // 签名值(支持多人签名) return result; } catch (Exception e) { throw new SecurityException("解析P7S异常", e); } } // --------------以下是解析Base64(pem)格式证书所用到的方法 start----------------- // private static boolean isBase64(InputStream inputstream) throws IOException { try { if (!inputstream.markSupported()) { byte[] abyte0 = getTotalBytes(new BufferedInputStream(inputstream)); inputstream = new ByteArrayInputStream(abyte0); } if (inputstream.available() >= 10) { inputstream.mark(10); int i = inputstream.read(); int j = inputstream.read(); int k = inputstream.read(); int l = inputstream.read(); int i1 = inputstream.read(); int j1 = inputstream.read(); int k1 = inputstream.read(); int l1 = inputstream.read(); int i2 = inputstream.read(); int j2 = inputstream.read(); inputstream.reset(); return i == 45 && j == 45 && k == 45 && l == 45 && i1 == 45 && j1 == 66 && k1 == 69 && l1 == 71 && i2 == 73 && j2 == 78; } else { return false; } } finally { Closeables.console(inputstream); } } private static byte[] getTotalBytes(InputStream input) throws IOException { byte[] abyte0 = new byte[8192]; ByteArrayOutputStream baos = new ByteArrayOutputStream(2048); baos.reset(); for (int len; (len = input.read(abyte0, 0, abyte0.length)) != Files.EOF;) { baos.write(abyte0, 0, len); } return baos.toByteArray(); } private static byte[] base64ToBinary(InputStream input) throws IOException { try { long len = 0L; input.mark(input.available()); BufferedInputStream bufferedinputstream = new BufferedInputStream(input); BufferedReader reader = new BufferedReader( new InputStreamReader(bufferedinputstream, StandardCharsets.US_ASCII) ); String s; if ((s = readLine(reader)) == null || !s.startsWith("-----BEGIN")) { throw new IOException("Unsupported encoding"); } len += s.length(); StringBuilder sb = new StringBuilder(); for (; (s = readLine(reader)) != null && !s.startsWith("-----END"); sb.append(s)) { // do-non } if (s == null) { throw new IOException("Unsupported encoding"); } else { len += s.length(); len += sb.length(); input.reset(); input.skip(len); return Base64.getDecoder().decode(sb.toString()); } } finally { Closeables.console(input); } } private static String readLine(BufferedReader bufferedreader) throws IOException { int j = 0; boolean flag = true; boolean flag1 = false; StringBuilder builder = new StringBuilder(80); int i; do { i = bufferedreader.read(); if (flag && j < ENDBOUNDARY.length) { flag = (char) i == ENDBOUNDARY[j++]; } if (!flag1) { flag1 = flag && j == ENDBOUNDARY.length; } builder.append((char) i); } while (i != -1 && i != 10 && i != 13); if (!flag1 && i == -1) { return null; } if (i == 13) { bufferedreader.mark(1); int k = bufferedreader.read(); if (k == 10) { builder.append((char) i); } else { bufferedreader.reset(); } } return builder.toString(); } // --------------以上是解析Base64(pem)格式证书所用到的方法 end----------------- // } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/digest/DigestUtils.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.digest; import cn.ponfee.commons.io.Files; import cn.ponfee.commons.jce.DigestAlgorithms; import cn.ponfee.commons.jce.Providers; import org.apache.commons.codec.binary.Hex; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.Provider; /** * digest算法封装 * * @author Ponfee */ public final class DigestUtils { private static final int BUFF_SIZE = 4096; public static byte[] md5(InputStream input) { return digest(DigestAlgorithms.MD5, input); } public static byte[] md5(byte[] data) { return digest(DigestAlgorithms.MD5, data); } public static String md5Hex(InputStream input) { return Hex.encodeHexString(md5(input)); } public static String md5Hex(byte[] data) { return Hex.encodeHexString(md5(data)); } public static String md5Hex(String data) { return md5Hex(data.getBytes()); } public static String md5Hex(String data, String charset) { return md5Hex(data.getBytes(Charset.forName(charset))); } public static byte[] sha1(InputStream input) { return digest(DigestAlgorithms.SHA1, input); } public static byte[] sha1(String data) { return digest(DigestAlgorithms.SHA1, data.getBytes()); } public static byte[] sha1(byte[] data) { return digest(DigestAlgorithms.SHA1, data); } public static String sha1Hex(InputStream input) { return Hex.encodeHexString(sha1(input)); } public static String sha1Hex(byte[] data) { return Hex.encodeHexString(sha1(data)); } public static String sha1Hex(String data) { return sha1Hex(data.getBytes()); } public static String sha1Hex(String data, String charset) { return sha1Hex(data.getBytes(Charset.forName(charset))); } public static byte[] sha224(byte[] data) { return digest(DigestAlgorithms.SHA224, data); } public static String sha224Hex(byte[] data) { return Hex.encodeHexString(sha224(data)); } public static byte[] sha256(byte[] data) { return digest(DigestAlgorithms.SHA256, data); } public static String sha256Hex(byte[] data) { return Hex.encodeHexString(sha256(data)); } public static String sha256Hex(String data, String charset) { return sha256Hex(data.getBytes(Charset.forName(charset))); } public static byte[] sha384(byte[] data) { return digest(DigestAlgorithms.SHA384, data); } public static String sha384Hex(byte[] data) { return Hex.encodeHexString(sha384(data)); } public static byte[] sha512(byte[] input, byte[]... data) { return digest(DigestAlgorithms.SHA512, input, data); } public static String sha512Hex(byte[] data) { return Hex.encodeHexString(sha512(data)); } // ---------------------------------------RipeMD public static byte[] ripeMD128(byte[] data) { return digest(DigestAlgorithms.RipeMD128, Providers.BC, data); } public static String ripeMD128Hex(byte[] data) { return Hex.encodeHexString(ripeMD128(data)); } public static byte[] ripeMD160(byte[] data) { return digest(DigestAlgorithms.RipeMD160, Providers.BC, data); } public static String ripeMD160Hex(byte[] data) { return Hex.encodeHexString(ripeMD160(data)); } public static byte[] ripeMD256(byte[] data) { return digest(DigestAlgorithms.RipeMD256, Providers.BC, data); } public static String ripeMD256Hex(byte[] data) { return Hex.encodeHexString(ripeMD256(data)); } public static byte[] ripeMD320(byte[] data) { return digest(DigestAlgorithms.RipeMD320, Providers.BC, data); } public static String ripeMD320Hex(byte[] data) { return Hex.encodeHexString(ripeMD320(data)); } // ---------------------------------------------digest public static MessageDigest getMessageDigest(DigestAlgorithms alg, Provider provider) { try { return (provider == null) ? MessageDigest.getInstance(alg.algorithm()) : MessageDigest.getInstance(alg.algorithm(), provider); } catch (NoSuchAlgorithmException e) { throw new IllegalArgumentException(e); // cannot happened } } public static byte[] digest(DigestAlgorithms alg, byte[] input) { return digest(alg, null, input); } public static byte[] digest(DigestAlgorithms alg, Provider provider, byte[] input) { MessageDigest digest = getMessageDigest(alg, provider); digest.update(input); return digest.digest(); } public static byte[] digest(DigestAlgorithms alg, byte[] input, byte[]... data) { return digest(alg, null, input, data); } /** * 数据摘要 * @param alg digest算法 * @param provider * @param data digest data of byte array * @return */ public static byte[] digest(DigestAlgorithms alg, Provider provider, byte[] input, byte[]... data) { MessageDigest digest = getMessageDigest(alg, provider); digest.update(input); for (byte[] bytes : data) { digest.update(bytes); } return digest.digest(); } public static byte[] digest(DigestAlgorithms alg, InputStream input) { return digest(alg, null, input); } /** * 数据摘要 * @param alg digest 算法 * @param provider * @param input digest data of input stream * @return */ public static byte[] digest(DigestAlgorithms alg, Provider provider, InputStream input) { byte[] buff = new byte[BUFF_SIZE]; MessageDigest digest = getMessageDigest(alg, provider); try (InputStream in = input) { for (int n; (n = in.read(buff, 0, BUFF_SIZE)) != Files.EOF;) { digest.update(buff, 0, n); } return digest.digest(); } catch (IOException e) { throw new RuntimeException(e); } /*try (InputStream in = input; DigestInputStream dIn = new DigestInputStream(input, digest) ) { while (dIn.read(buff, 0, buff.length) != Files.EOF) { // do-non } return digest.digest(); } catch (IOException e) { throw new RuntimeException(e); }*/ } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/digest/HmacUtils.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.digest; import cn.ponfee.commons.io.Files; import cn.ponfee.commons.jce.HmacAlgorithms; import cn.ponfee.commons.jce.Providers; import org.apache.commons.codec.binary.Hex; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.io.IOException; import java.io.InputStream; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.Provider; /** * HMAC的一个典型应用是用在“质询/响应”(Challenge/Response)身份认证中 * Hmac算法封装,计算“text”的HMAC: * * if (length(K) > blocksize) { * K = H(K) // keys longer than blocksize are shortened * } else if (length(key) < blocksize) { * K += [0x00 * (blocksize - length(K))] // keys shorter than blocksize are zero-padded * } * opad = [0x5c * B] XOR K * ipad = [0x36 * B] XOR K * hash = H(opad + H(ipad + text)) * * 其中:H为散列函数,K为密钥,text为数据, * B表示数据块的字长(the blocksize is that of the underlying hash function) * * @author Ponfee */ public final class HmacUtils { private static final int BUFF_SIZE = 4096; public static byte[] sha1(byte[] key, byte[] data) { return crypt(key, data, HmacAlgorithms.HmacSHA1); } public static byte[] sha1(byte[] key, InputStream data) { return crypt(key, data, HmacAlgorithms.HmacSHA1); } public static String sha1Hex(byte[] key, byte[] data) { return Hex.encodeHexString(sha1(key, data)); } public static String sha1Hex(byte[] key, InputStream data) { return Hex.encodeHexString(sha1(key, data)); } public static byte[] md5(byte[] key, byte[] data) { return crypt(key, data, HmacAlgorithms.HmacMD5); } public static String md5Hex(byte[] key, byte[] data) { return Hex.encodeHexString(md5(key, data)); } public static byte[] sha224(byte[] key, byte[] data) { return crypt(key, data, HmacAlgorithms.HmacSHA224); } public static String sha224Hex(byte[] key, byte[] data) { return Hex.encodeHexString(sha224(key, data)); } public static byte[] sha256(byte[] key, byte[] data) { return crypt(key, data, HmacAlgorithms.HmacSHA256); } public static String sha256Hex(byte[] key, byte[] data) { return Hex.encodeHexString(sha256(key, data)); } public static byte[] sha384(byte[] key, byte[] data) { return crypt(key, data, HmacAlgorithms.HmacSHA384); } public static String sha384Hex(byte[] key, byte[] data) { return Hex.encodeHexString(sha384(key, data)); } public static byte[] sha512(byte[] key, byte[] data) { return crypt(key, data, HmacAlgorithms.HmacSHA512); } public static String sha512Hex(byte[] key, byte[] data) { return Hex.encodeHexString(sha512(key, data)); } public static byte[] ripeMD128(byte[] key, byte[] data) { return crypt(key, data, HmacAlgorithms.HmacRipeMD128, Providers.BC); } public static String ripeMD128Hex(byte[] key, byte[] data) { return Hex.encodeHexString(ripeMD128(key, data)); } public static byte[] ripeMD160(byte[] key, byte[] data) { return crypt(key, data, HmacAlgorithms.HmacRipeMD160, Providers.BC); } public static String ripeMD160Hex(byte[] key, byte[] data) { return Hex.encodeHexString(ripeMD160(key, data)); } public static byte[] ripeMD256(byte[] key, byte[] data) { return crypt(key, data, HmacAlgorithms.HmacRipeMD256, Providers.BC); } public static String ripeMD256Hex(byte[] key, byte[] data) { return Hex.encodeHexString(ripeMD256(key, data)); } public static byte[] ripeMD320(byte[] key, byte[] data) { return crypt(key, data, HmacAlgorithms.HmacRipeMD320, Providers.BC); } public static String ripeMD320Hex(byte[] key, byte[] data) { return Hex.encodeHexString(ripeMD320(key, data)); } public static Mac getInitializedMac(HmacAlgorithms algorithm, byte[] key) { return getInitializedMac(algorithm, null, key); } public static Mac getInitializedMac(HmacAlgorithms algorithm, Provider provider, byte[] key) { if (key == null) { throw new IllegalArgumentException("Null key"); } try { Mac mac = (provider == null) ? Mac.getInstance(algorithm.algorithm()) : Mac.getInstance(algorithm.algorithm(), provider); mac.init(new SecretKeySpec(key, mac.getAlgorithm())); return mac; } catch (final NoSuchAlgorithmException e) { throw new IllegalArgumentException("unknown algorithm: " + algorithm, e); } catch (final InvalidKeyException e) { throw new IllegalArgumentException("invalid key: " + Hex.encodeHexString(key), e); } } public static byte[] crypt(byte[] key, byte[] data, HmacAlgorithms alg) { return crypt(key, data, alg, null); } public static byte[] crypt(byte[] key, byte[] data, HmacAlgorithms alg, Provider provider) { return getInitializedMac(alg, provider, key).doFinal(data); } public static byte[] crypt(byte[] key, InputStream input, HmacAlgorithms alg) { return crypt(key, input, alg, null); } public static byte[] crypt(byte[] key, InputStream input, HmacAlgorithms alg, Provider provider) { try (InputStream in = input) { Mac mac = getInitializedMac(alg, provider, key); byte[] buffer = new byte[BUFF_SIZE]; for (int n; (n = in.read(buffer, 0, BUFF_SIZE)) != Files.EOF;) { mac.update(buffer, 0, n); } return mac.doFinal(); } catch (IOException e) { throw new IllegalArgumentException("read data error:" + e); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/implementation/Cryptor.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.implementation; /** * This class of the Cryptor base class * @author Ponfee */ public abstract class Cryptor { public final byte[] encrypt(byte[] original, Key ek) { return encrypt(original, original.length, ek); } /** * encrypt original data in length byte * @param original * @param length the byte length of original * @param ek encrypt key * @return */ public abstract byte[] encrypt(byte[] original, int length, Key ek); /** * decrypt the cipher use decrypt key * @param cipher * @param dk * @return */ public abstract byte[] decrypt(byte[] cipher, Key dk); /** * generate cryptor key * @return */ public abstract Key generateKey(); } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/implementation/Key.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.implementation; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * Key interface * @author Ponfee */ public interface Key { Key readKey(InputStream in) throws IOException; void writeKey(OutputStream out) throws IOException; Key getPublic(); boolean isPublic(); } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/implementation/NoopCryptor.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.implementation; import java.io.InputStream; import java.io.OutputStream; /** * Null Cryptor that do nothing * * @author Ponfee */ public final class NoopCryptor extends Cryptor { public static final NoopCryptor SINGLETON = new NoopCryptor(); private NoopCryptor() {} @Override public byte[] encrypt(byte[] input, int length, Key ek) { return input; } @Override public byte[] decrypt(byte[] cipher, Key dk) { return cipher; } @Override public Key generateKey() { return NoopKey.SINGLETON; } @Override public String toString() { return NoopCryptor.class.getSimpleName(); } private static final class NoopKey implements Key { private static final NoopKey SINGLETON = new NoopKey(); private NoopKey() {} @Override public Key readKey(InputStream in) { return null; } @Override public void writeKey(OutputStream out) {} @Override public Key getPublic() { return null; } @Override public boolean isPublic() { return false; } } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/implementation/digest/RipeMD160Digest.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.implementation.digest; /** * The RipeMD160 digest implementation * * @author Ponfee */ public class RipeMD160Digest { private static final int[][] ARG_ARRAY = { { 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6 }, { 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11 } }; private static final int[][] IDX_ARRAY = { { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13 }, { 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11 } }; /** 链变量 */ private static final int[] CHAIN_VAR = { 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 }; /** 分组中每块的大小 */ private static final int BLOCK_SIZE = 64; /** 摘要byte大小 */ private static final int DIGEST_SIZE = 20; private final int[] digest = new int[CHAIN_VAR.length]; private int[] working; private int wOffset; private int byteCount; private RipeMD160Digest() { reset(); } private RipeMD160Digest(RipeMD160Digest d) { System.arraycopy(d.digest, 0, this.digest, 0, d.digest.length); System.arraycopy(d.working, 0, this.working, 0, d.working.length); this.wOffset = d.wOffset; this.byteCount = d.byteCount; } public static RipeMD160Digest getInstance() { return new RipeMD160Digest(); } public static RipeMD160Digest getInstance(RipeMD160Digest d) { return new RipeMD160Digest(d); } public void reset() { System.arraycopy(CHAIN_VAR, 0, digest, 0, CHAIN_VAR.length); working = new int[16]; wOffset = 0; byteCount = 0; } public void update(byte input) { working[wOffset >> 2] ^= ((int) input) << ((wOffset & 3) << 3); wOffset++; if (wOffset == BLOCK_SIZE) { digestBlock(working); for (int j = 0; j < 16; j++) { working[j] = 0; } wOffset = 0; } byteCount++; } public void update(byte[] input) { this.update(input, 0, input.length); } public void update(byte[] input, int offset, int len) { len = Math.min(len, input.length - offset); for (int i = offset; i < offset + len; i++) { this.update(input[i]); } } public void update(String s) { byte[] array = new byte[s.length()]; for (int i = 0; i < array.length; i++) { array[i] = (byte) s.charAt(i); } update(array); } public byte[] doFinal() { finish(working, byteCount, 0); byte[] result = new byte[digest.length << 2]; for (int i = 0; i < DIGEST_SIZE; i++) { result[i] = (byte) (digest[i >> 2] >>> ((i & 3) << 3)); } reset(); return result; } public byte[] doFinal(byte[] input) { this.update(input, 0, input.length); return doFinal(); } public byte[] doFinal(byte[] input, int offset, int len) { this.update(input, offset, len); return doFinal(); } // --------------------------------------------------private methods private void digestBlock(int[] X) { int a, b, c, d, e; int A, B, C, D, E; int i = 0, temp, s; A = a = digest[0]; B = b = digest[1]; C = c = digest[2]; D = d = digest[3]; E = e = digest[4]; for (; i < 16; i++) { // The 16 FF functions - round 1 */ temp = a + (b ^ c ^ d) + X[IDX_ARRAY[0][i]]; a = e; e = d; d = (c << 10) | (c >>> 22); c = b; s = ARG_ARRAY[0][i]; b = ((temp << s) | (temp >>> (32 - s))) + a; // The 16 JJJ functions - parallel round 1 */ temp = A + (B ^ (C | ~D)) + X[IDX_ARRAY[1][i]] + 0x50a28be6; A = E; E = D; D = (C << 10) | (C >>> 22); C = B; s = ARG_ARRAY[1][i]; B = ((temp << s) | (temp >>> (32 - s))) + A; } for (; i < 32; i++) { // The 16 GG functions - round 2 */ temp = a + ((b & c) | (~b & d)) + X[IDX_ARRAY[0][i]] + 0x5a827999; a = e; e = d; d = (c << 10) | (c >>> 22); c = b; s = ARG_ARRAY[0][i]; b = ((temp << s) | (temp >>> (32 - s))) + a; // The 16 III functions - parallel round 2 */ temp = A + ((B & D) | (C & ~D)) + X[IDX_ARRAY[1][i]] + 0x5c4dd124; A = E; E = D; D = (C << 10) | (C >>> 22); C = B; s = ARG_ARRAY[1][i]; B = ((temp << s) | (temp >>> (32 - s))) + A; } for (; i < 48; i++) { // The 16 HH functions - round 3 */ temp = a + ((b | ~c) ^ d) + X[IDX_ARRAY[0][i]] + 0x6ed9eba1; a = e; e = d; d = (c << 10) | (c >>> 22); c = b; s = ARG_ARRAY[0][i]; b = ((temp << s) | (temp >>> (32 - s))) + a; // The 16 HHH functions - parallel round 3 */ temp = A + ((B | ~C) ^ D) + X[IDX_ARRAY[1][i]] + 0x6d703ef3; A = E; E = D; D = (C << 10) | (C >>> 22); C = B; s = ARG_ARRAY[1][i]; B = ((temp << s) | (temp >>> (32 - s))) + A; } for (; i < 64; i++) { // The 16 II functions - round 4 */ temp = a + ((b & d) | (c & ~d)) + X[IDX_ARRAY[0][i]] + 0x8f1bbcdc; a = e; e = d; d = (c << 10) | (c >>> 22); c = b; s = ARG_ARRAY[0][i]; b = ((temp << s) | (temp >>> (32 - s))) + a; // The 16 GGG functions - parallel round 4 */ temp = A + ((B & C) | (~B & D)) + X[IDX_ARRAY[1][i]] + 0x7a6d76e9; A = E; E = D; D = (C << 10) | (C >>> 22); C = B; s = ARG_ARRAY[1][i]; B = ((temp << s) | (temp >>> (32 - s))) + A; } for (; i < 80; i++) { // The 16 JJ functions - round 5 */ temp = a + (b ^ (c | ~d)) + X[IDX_ARRAY[0][i]] + 0xa953fd4e; a = e; e = d; d = (c << 10) | (c >>> 22); c = b; s = ARG_ARRAY[0][i]; b = ((temp << s) | (temp >>> (32 - s))) + a; // The 16 FFF functions - parallel round 5 */ temp = A + (B ^ C ^ D) + X[IDX_ARRAY[1][i]]; A = E; E = D; D = (C << 10) | (C >>> 22); C = B; s = ARG_ARRAY[1][i]; B = ((temp << s) | (temp >>> (32 - s))) + A; } /* combine results */ D += c + digest[1]; /* final result for MDbuf[0] */ digest[1] = digest[2] + d + E; digest[2] = digest[3] + e + A; digest[3] = digest[4] + a + B; digest[4] = digest[0] + b + C; digest[0] = D; } private void finish(int[] array, int lswlen, int mswlen) { /* append the bit m_n == 1 */ array[(lswlen >> 2) & 15] ^= 1 << (((lswlen & 3) << 3) + 7); if ((lswlen & 63) > 55) { /* length goes to next block */ digestBlock(array); for (int i = 0; i < 14; i++) { array[i] = 0; } } /* append length in bits*/ array[14] = lswlen << 3; array[15] = (lswlen >> 29) | (mswlen << 3); digestBlock(array); } public static int getDigestSize() { return DIGEST_SIZE; } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/implementation/digest/SHA1Digest.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.implementation.digest; import cn.ponfee.commons.math.Maths; import cn.ponfee.commons.util.Bytes; import java.util.Arrays; import static cn.ponfee.commons.math.Numbers.ZERO_BYTE; /** *
     * The SHA-1 digest implementation(maximum 2^64 bit length)
     *
     * 异或⊕:(A ^ B)
     * 同或⊙:(A ^ B ^ 1)  or  !(A ^ B)
     * A ≡ A ⊕ K ⊕ K
     *
     * https://www.cnblogs.com/scu-cjx/p/6878853.html
     *
     * 安全性:SHA1所产生的摘要比MD5长32位。若两种散列函数在结构上没有任何问题的话,SHA1比MD5更安全。
     * 速度:两种方法都是主要考虑以32位处理器为基础的系统结构。但SHA1的运算步骤比MD5多了16步,而且SHA1记录单元的长度比MD5多了32位。因此若是以硬件来实现SHA1,其速度大约比MD5慢了25%。
     * 简易性:两种方法都是相当的简单,在实现上不需要很复杂的程序或是大量存储空间。然而总体上来讲,SHA1对每一步骤的操作描述比MD5简单与MD5不同的是SHA1的原始报文长度不能超过2的64次方,另外SHA1的明文长度从低位开始填充

    * * 1、按每512bit(64byte)长度进行分组block,可以划分成L份明文分组,我们用Y0,Y1, ...YL-1表示,对于每一个明文分组,都要重复反复的处理 * * 2、最后一组先补一个字节1000 0000(-128),直到长度满足对512取模后余数是448(若已经是56byte即448bit,补后有57byte, * 因此还需要补64-57+56=63byte,会多出一组) * * 3、最后补8byte即64bit的原始数据长度long值(位长),此时为448+64=512bit * * 4、将512位的明文分组划分为16个子明文分组(sub-block),每个子明文分组为32位,使用W[t](t=0,1,...,15)来表示这16份子明文分组 * W[t]存的是int数据,即4个byte为一组的32位的word字 * * 5、16份子明文分组扩展为80份,记为W[t](t=0,1,...,79),扩充的方法: * > W[t] = W[t],当0≤t≤15 * > W[t] = (W[t-3] ⊕ W[t-8] ⊕ W[t-14] ⊕ W [t-16]) << 1,当16≤t≤79 * * 6、分组处理:接下来,对输入分组进行80个步骤的处理,目的是根据输入分组的信息来改变内部状态, * 在对分组处理时,SHA-1中常数Kt如下: * K0 = 0x5A827999 0≤t≤19 * K1 = 0x6ED9EBA1 20≤t≤39 * K2 = 0x8F1BBCDC 40≤t≤59 * K3 = 0xCA62C1D6 60≤t≤79 * * 5个链变量a,b,c,d,e如下: * a = 0x67452301 * b = 0xEFCDAB89 * c = 0x98BADCFE * d = 0x10325476 * e = 0xC3D2E1F0 * * SHA1有4轮运算,每一轮包括20个步骤一共80步,当第1轮运算中的第1步骤开始处理时a、b、c、d、e五个链接变量中的值先赋值到另外 * 5个记录单元a′、b′、c′、d′、e′中,这5个值将保留,用于在第4轮的最后一个步骤完成之后与链接变量a、b、c、d、e进行求和操作 * * 7、SHA-1使用了F0,F1,....,F79这样的一个逻辑函数序列,每一个Ft对3个32位双字b,c,d进行操作,产生一个32位双字的输出。 * Ft(b,c,d) = (b&c)|((~b)&d) 0≤t≤19 * Ft(b,c,d) = b^c^d 20≤t≤39 * Ft(b,c,d) = (b&c)|(b&d)|(c&d) 40≤t≤59 * Ft(b,c,d) = b^c^d 60≤t≤79 * * 8、W[0] ~ W[19]处理:(注:S为循环左移位操作) * for (int t=0; t<20; t++) { * tmp=K0+F0(b,c,d)+S(5,a)+e+(sh->W[t]); // 将Kt+Ft(b,c,d)+(a<<5)+e+W[t]的结果赋值给临时变量tmp * e=d; // 将链接变量d初始值赋值给链接变量e * d=c; // 将链接变量c初始值赋值给链接变量d * c=S(30,b); // 将链接变量b初始值循环左移30位赋值给链接变量c * b=a; a=tmp; // 将链接变量a初始值赋值给链接变量b,再将tmp赋值给a * } * * W[20] ~ W[39]处理: * for (int t=20; t<40; t++) { * tmp=K1+F1(b,c,d)+S(5,a)+e+(sh->W[t]); * e=d; d=c; * c=S(30,b); * b=a; a=tmp; * } * * W[40] ~ W[59]处理: * for (int t=40; t<60; t++) { * tmp=K2+F2(b,c,d)+S(5,a)+e+(sh->W[t]); * e=d; d=c; * c=S(30,b); * b=a; a=tmp; * } * * W[60] ~ W[79]处理: * for (int t=60; t<80; t++) { * tmp=K3+F3(b,c,d)+S(5,a)+e+(sh->W[t]); * e=d; d=c; * c=S(30,b); * b=a; a=tmp; * } * 即:Kt+Ft(b,c,d)+S(5,a)+e+Wt, a, S(30,b), c, d → a, b, c, d, e * * 9、将循环80个步骤后的值a,b,c,d,e与原始链变量a′、b′、c′、d′、e′相加作为下一个明文分组的输入重复进行以上操作 * sh->a′+=a; sh->b′+=b; sh->c′+=c; * sh->d′+=d; sh->e′+=e; * * 10、最后一个分组处理完成后,最终得到的a,b,c,d,e即为160位的消息摘要 *

    * * @author Ponfee */ public class SHA1Digest { /** SHA-1分组中每块的大小 */ private static final int BLOCK_SIZE = 64; /** SHA-1摘要byte大小 */ private static final int DIGEST_SIZE = 20; private static final int WORK_SIZE = 80; /** 填充的边界 */ private static final int PADDING_BOUNDS = 448 >>> 3; // 56,long=8byte=64bit /** 4个常数K */ private static final int K0 = 0x5A827999, K1 = 0x6ED9EBA1, K2 = 0x8F1BBCDC, K3 = 0xCA62C1D6; // ---------------------------------------------fields private final int[] work = new int[WORK_SIZE]; private final byte[] block = new byte[BLOCK_SIZE]; private int H0, H1, H2, H3, H4, blockOffset; private long dataByteCount; private SHA1Digest() { this.reset(); } private SHA1Digest(SHA1Digest d) { this.H0 = d.H0; this.H1 = d.H1; this.H2 = d.H2; this.H3 = d.H3; this.H4 = d.H4; System.arraycopy(d.block, 0, this.block, 0, BLOCK_SIZE); this.blockOffset = d.blockOffset; this.dataByteCount = d.dataByteCount; } public static SHA1Digest getInstance() { return new SHA1Digest(); } public static SHA1Digest getInstance(SHA1Digest d) { return new SHA1Digest(d); } public void update(byte input) { this.block[this.blockOffset++] = input; if (this.blockOffset == BLOCK_SIZE) { this.digestBlock(this.block); this.blockOffset = 0; this.dataByteCount += BLOCK_SIZE; } } public void update(byte[] input) { this.update(input, 0, input.length); } public void update(byte[] input, int offset, int length) { length = Math.min(input.length - offset, length); for (int i = offset, end = offset + length; i < end; i++) { this.update(input[i]); } } public byte[] doFinal(byte[] data) { this.update(data, 0, data.length); return this.doFinal(); } public byte[] doFinal() { this.dataByteCount += this.blockOffset; this.block[this.blockOffset++] = -128; // 填充:先补1000 0000 if (this.blockOffset > PADDING_BOUNDS) { Arrays.fill(this.block, this.blockOffset, BLOCK_SIZE, ZERO_BYTE); // 填充0 this.digestBlock(this.block); // reset a empty block, repadding 0x00 and bit length start 0 this.blockOffset = 0; } Arrays.fill(this.block, this.blockOffset, PADDING_BOUNDS, ZERO_BYTE); long dataLongBitLen = this.dataByteCount << 3; // bitLen=byteCount*8 // dataLongBitLen value to byte array and padding in block tail for (int i = 0, j = (Long.BYTES - 1) << 3; i < Long.BYTES; i++, j -= 8) { this.block[PADDING_BOUNDS + i] = (byte) (dataLongBitLen >>> j); } this.digestBlock(this.block); byte[] digest = new byte[DIGEST_SIZE]; Bytes.put(this.H0, digest, 0); Bytes.put(this.H1, digest, 4); Bytes.put(this.H2, digest, 8); Bytes.put(this.H3, digest, 12); Bytes.put(this.H4, digest, 16); this.reset(); return digest; } public void reset() { this.H0 = 0x67452301; this.H1 = 0xEFCDAB89; this.H2 = 0x98BADCFE; this.H3 = 0x10325476; this.H4 = 0xC3D2E1F0; this.blockOffset = 0; this.dataByteCount = 0; } public static int getDigestSize() { return DIGEST_SIZE; } // --------------------------------------------------private methods private void digestBlock(byte[] block) { int i = 0; // sub-block(子明文分组) for (int j = 0; i < 16; j += 4) { work[i++] = Bytes.toInt(block, j); } // ext-block(扩展明文分组) for (; i < WORK_SIZE; i++) { work[i] = Maths.rotateLeft(work[i - 3] ^ work[i - 8] ^ work[i - 14] ^ work[i - 16], 1); } int A = this.H0, B = this.H1, C = this.H2, D = this.H3, E = this.H4, tmp, t = 0; // round first for (; t < 20; t++) { // temp = Ki + fi(B, C, D) + S5(A) + E + Wt tmp = K0 + f0(B, C, D) + Maths.rotateLeft(A, 5) + E + work[t]; // E = D; D = C; C = S30(B); B = A; A = temp; E = D; D = C; C = Maths.rotateLeft(B, 30); B = A; A = tmp; } // round second for (; t < 40; t++) { tmp = K1 + f1(B, C, D) + Maths.rotateLeft(A, 5) + E + work[t]; E = D; D = C; C = Maths.rotateLeft(B, 30); B = A; A = tmp; } // round third for (; t < 60; t++) { tmp = K2 + f2(B, C, D) + Maths.rotateLeft(A, 5) + E + work[t]; E = D; D = C; C = Maths.rotateLeft(B, 30); B = A; A = tmp; } // round fourth for (; t < WORK_SIZE; t++) { tmp = K3 + f3(B, C, D) + Maths.rotateLeft(A, 5) + E + work[t]; E = D; D = C; C = Maths.rotateLeft(B, 30); B = A; A = tmp; } // add chain variable this.H0 += A; this.H1 += B; this.H2 += C; this.H3 += D; this.H4 += E; } private static int f0(int b, int c, int d) { return (b & c) | ((~b) & d); } private static int f1(int b, int c, int d) { return b ^ c ^ d; } private static int f2(int b, int c, int d) { return (b & c) | (b & d) | (c & d); } private static int f3(int b, int c, int d) { return f1(b, c, d); } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/implementation/ecc/ECCryptor.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.implementation.ecc; import cn.ponfee.commons.jce.HmacAlgorithms; import cn.ponfee.commons.jce.implementation.Cryptor; import cn.ponfee.commons.jce.implementation.Key; import cn.ponfee.commons.util.Bytes; import cn.ponfee.commons.util.SecureRandoms; import java.math.BigInteger; import java.util.Arrays; import static cn.ponfee.commons.jce.Providers.BC; import static cn.ponfee.commons.jce.digest.HmacUtils.crypt; /** * EC Cryptor based xor * * origin ≡ origin ⊕ key ⊕ key * * 一、首先:生成随机数dk,在曲线上计算得到dk的倍点beta point, * beta point(public key) = basePointG(public key) * dk, * beta point(public key)作为公钥,dk作为私钥 * * 二、加密:1)生成随机数rk,在曲线上计算得到rk的倍点gamma point, * gamma point(public key) = basePointG(public key) * rk, * 由椭圆曲线特性可得出:beta point(public key) * rk = ECPoint S = gamma point(public key) * dk * * 2)ECPoint S = beta point(public key) * rk,把ECPoint S作为中间对称密钥, * 通过HASH函数计算对称加密密钥:key = HmacSHA-512(ECPoint S) * * 3)加密:origin ⊕ key = cipher * * 4)打包加密数据Encrypted = {gamma point(public key), cipher} * * 三、解密:1)解析加密数据Encrypted: {gamma point(public key), cipher},得到:gamma point(public key),cipher * * 2)用第一步的私钥dk与gamma point(public key)进行计算得到:ECPoint S = gamma point(public key) * dk * * 3)通过HASH函数计算对称加密密钥:key = HmacSHA-512(ECPoint S) * * 4)解密:cipher ⊕ key = origin * * @author Ponfee */ public class ECCryptor extends Cryptor { private static final HmacAlgorithms HMAC_ALG = HmacAlgorithms.HmacSHA3_512; private final EllipticCurve curve; public ECCryptor(EllipticCurve curve) { this.curve = curve; } /** * 加密数据逻辑: * origin ≡ origin ⊕ data ⊕ data */ @Override public byte[] encrypt(byte[] input, int length, Key ek) { // ek is an Elliptic key (dk=secret, beta=public) ECKey ecKey = (ECKey) ek; // 生成随机数rk BigInteger rk; if (ecKey.curve.getN() != null) { rk = SecureRandoms.random(ecKey.curve.getN()); } else { rk = SecureRandoms.random(ecKey.curve.getP().bitLength() + 17); } // 计算曲线上rk倍点gamma:ECPoint gamma = basePointG(public key) * rk ECPoint gamma = ecKey.curve.getBasePointG().multiply(rk); // PCS is compressed point size. int offset = ecKey.curve.getPCS(); // 导出该rk倍点gamma point(public key) byte[] result = Arrays.copyOf(gamma.compress(), offset + length); // 生成需要hash的数据:ECPoint S = beta point(public key) * rk ECPoint secure = ecKey.beta.multiply(rk); // 用hash值与原文进行xor操作 byte[] keyBytes = Bytes.concat(secure.getX().toByteArray(), secure.getY().toByteArray()); int count = 1; byte[] hashedKey = crypt(keyBytes, Bytes.toBytes(count), HMAC_ALG, BC); for (int i = 0, keyOffset = 0; i < length; i++) { if (keyOffset == HMAC_ALG.byteSize()) { keyOffset = 0; hashedKey = crypt(keyBytes, Bytes.toBytes(++count), HMAC_ALG, BC); } result[i + offset] = (byte) (input[i] ^ hashedKey[keyOffset++]); } return result; } @Override public byte[] decrypt(byte[] input, Key dk) { ECKey ecKey = (ECKey) dk; int offset = ecKey.curve.getPCS(); // 取出gamma point(public key) byte[] gammacom = Arrays.copyOfRange(input, 0, offset); ECPoint gamma = new ECPoint(gammacom, ecKey.curve); // beta point(public key) * rk = ECPoint S = gamma point(public key) * dk // ECPoint S = gamma point(public key) * dk ECPoint secure = gamma.multiply(ecKey.dk); byte[] keyBytes; if (secure.isZero()) { keyBytes = Bytes.concat(BigInteger.ZERO.toByteArray(), BigInteger.ZERO.toByteArray()); } else { keyBytes = Bytes.concat(secure.getX().toByteArray(), secure.getY().toByteArray()); } int count = 1, length = input.length - offset; byte[] hashedKey = crypt(keyBytes, Bytes.toBytes(count), HMAC_ALG, BC), result = new byte[length]; for (int i = 0, keyOffset = 0; i < length; i++) { if (keyOffset == HMAC_ALG.byteSize()) { keyOffset = 0; hashedKey = crypt(keyBytes, Bytes.toBytes(++count), HMAC_ALG, BC); } result[i] = (byte) (input[i + offset] ^ hashedKey[keyOffset++]); } return result; } /** * generate ECKey */ @Override public Key generateKey() { return new ECKey(curve); } @Override public String toString() { return "ECCryptor - " + curve.toString(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/implementation/ecc/ECKey.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.implementation.ecc; import cn.ponfee.commons.jce.implementation.Key; import cn.ponfee.commons.util.SecureRandoms; import java.io.*; import java.math.BigInteger; /** * This is Elliptic Curve key * @author Ponfee */ public class ECKey implements Key { protected boolean secret; // 是否是私钥 protected BigInteger dk; // decrypt key protected ECPoint beta; // the public key of ECPoint protected final EllipticCurve curve; // the Elliptic cCurve /** * ECKey generates a random secret key (contains also the public key) * @param ec */ public ECKey(EllipticCurve ec) { this.curve = ec; this.secret = true; // dk is a random num. if (curve.getN() != null) { this.dk = SecureRandoms.random(this.curve.getN()); } else { this.dk = SecureRandoms.random(ec.getP().bitLength() + 17); } // beta = pointG * dk this.beta = this.curve.getBasePointG().multiply(this.dk); // dk倍点beta this.beta.fastCache(); } @Override public String toString() { String str = ""; if (secret) { str = "Private key: " + dk + ", "; } return str + "Public key: " + beta + ", Curve: " + curve; } @Override public boolean isPublic() { return !secret; } @Override public void writeKey(OutputStream out) throws IOException { DataOutputStream output = new DataOutputStream(out); this.curve.writeCurve(output); output.writeBoolean(this.secret); if (this.secret) { byte[] dk0 = this.dk.toByteArray(); output.writeInt(dk0.length); output.write(dk0); } byte[] beta0 = this.beta.compress(); output.writeInt(beta0.length); output.write(beta0); } @Override public Key readKey(InputStream in) throws IOException { DataInputStream input = new DataInputStream(in); ECKey key = new ECKey(new EllipticCurve(input)); key.secret = input.readBoolean(); if (key.secret) { byte[] dk0 = new byte[input.readInt()]; input.read(dk0); key.dk = new BigInteger(1, dk0); } byte[] beta0 = new byte[input.readInt()]; input.read(beta0); key.beta = new ECPoint(beta0, key.curve); return key; } /** * get the public key */ @Override public Key getPublic() { if (!this.secret) { return this; } ECKey pubKey = new ECKey(curve); pubKey.beta = beta; pubKey.dk = BigInteger.ZERO; pubKey.secret = false; return pubKey; } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/implementation/ecc/ECPoint.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.implementation.ecc; import java.math.BigInteger; /** * The EC point of lie on the curve * * @author Ponfee */ public class ECPoint { private final EllipticCurve curve; private final boolean zero; private BigInteger x; private BigInteger y; // fastcache is an array of ECPoints private ECPoint[] fastcache = null; public void fastCache() { if (fastcache == null) { // fastcache initialised to 256 EC Points. fastcache = new ECPoint[256]; // First point is null. fastcache[0] = new ECPoint(curve); for (int i = 1; i < fastcache.length; i++) { // [1, 256) // add the point repeatedly (Cumulative sum). P,2P,... fastcache[i] = fastcache[i - 1].add(this); } } } /** * Constructs a point on an elliptic curve. * @param curve The elliptic curve on wich the point is surposed to lie * @param x The x coordinate of the point * @param y The y coordinate of the point */ public ECPoint(EllipticCurve curve, BigInteger x, BigInteger y) { this.curve = curve; this.x = x; this.y = y; if (!curve.isOnCurve(this)) { throw new IllegalArgumentException("(x,y) is not on this curve!"); } this.zero = false; } /** * Decompresses a compressed point stored in a byte-array into a new ECPoint. * @param bytes the array of bytes to be decompressed * @param curve the EllipticCurve the decompressed point is supposed to lie on. */ public ECPoint(byte[] bytes, EllipticCurve curve) { this.curve = curve; if (bytes[0] == 2) { this.zero = true; return; } boolean ymt = bytes[0] != 0; bytes[0] = 0; this.x = new BigInteger(1, bytes); this.y = this.x.multiply(this.x).add(curve.getA()).multiply(this.x) .add(curve.getB()).modPow(curve.getPSR2(), curve.getP()); if (ymt != this.y.testBit(0)) { this.y = curve.getP().subtract(this.y); } this.zero = false; } /** * IMPORTANT this renders the values of x and y to be null! * Use this constructor only to create instances of a Zero class! */ public ECPoint(EllipticCurve e) { this.x = this.y = BigInteger.ZERO; this.curve = e; this.zero = true; } /** * compress the point as byte array data * @return byte array data of this point */ public byte[] compress() { // 只导出x坐标,y坐标可由方程计算得到 byte[] cmp = new byte[this.curve.getPCS()]; if (this.zero) { cmp[0] = 2; } byte[] xb = this.x.toByteArray(); System.arraycopy(xb, 0, cmp, this.curve.getPCS() - xb.length, xb.length); if (this.y.testBit(0)) { cmp[0] = 1; } return cmp; } /** * 在曲线上计算两点相加的第三个点:point c = point a + point b * @param q The point to be added * @return the sum of this point on the argument */ public ECPoint add(ECPoint q) { if (!isSameCurve(q)) { throw new IllegalArgumentException( "the q point don't lie on the same elliptic curve."); } if (this.isZero()) { return q; } else if (q.isZero()) { return this; } BigInteger x1 = this.x, y1 = this.y; BigInteger x2 = q.getX(), y2 = q.getY(); BigInteger alpha; if (x2.compareTo(x1) == 0) { if (y2.compareTo(y1) != 0) { return new ECPoint(curve); // return a zero point } else { alpha = ((x1.modPow(EllipticCurve.TWO, curve.getP())).multiply(EllipticCurve.THREE)).add(curve.getA()); alpha = (alpha.multiply((EllipticCurve.TWO.multiply(y1)).modInverse(curve.getP()))).mod(curve.getP()); } } else { BigInteger i = x2.subtract(x1).modInverse(curve.getP()); alpha = y2.subtract(y1).multiply(i).mod(curve.getP()); } BigInteger x3 = (((alpha.modPow(EllipticCurve.TWO, curve.getP())).subtract(x2)).subtract(x1)).mod(curve.getP()); BigInteger y3 = ((alpha.multiply(x1.subtract(x3))).subtract(y1)).mod(curve.getP()); return new ECPoint(curve, x3, y3); } /** * 计算k倍点 * @param k * @return this * k */ public ECPoint multiply(BigInteger k) { ECPoint result = this; for (int i = k.bitCount() - 1; i > 0; i--) { result = result.add(result); if (k.testBit(i)) { result = result.add(this); } } return result; } public boolean isZero() { return zero; } public BigInteger getX() { return x; } public BigInteger getY() { return y; } public EllipticCurve getCurve() { return curve; } @Override public String toString() { return "(" + x.toString() + ", " + y.toString() + ")"; } private boolean isSameCurve(ECPoint p) { return this.curve.equals(p.getCurve()); } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/implementation/ecc/EllipticCurve.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.implementation.ecc; import cn.ponfee.commons.jce.ECParameters; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.math.BigInteger; /** * An implementation of an elliptic curve over a finite field. * * @author Ponfee */ public class EllipticCurve { public static final BigInteger COEFA = new BigInteger("4"); public static final BigInteger COEFB = new BigInteger("27"); public static final BigInteger TWO = new BigInteger("2"); public static final BigInteger THREE = new BigInteger("3"); private static final int PRIME_SECURITY = 500; private final BigInteger a, b, p, n; // n为p的阶 private final String name; // the curve name private final int pcs; // the compressed point size. private final ECPoint basePointG; // base point G private BigInteger psr2; // (p add one) >> 2 /** * Constructs an elliptic curve over the finite field of 'mod' elements. * The equation of the curve is on the form : y^2 = x^3 + ax + b. * @param a the value of 'a' where y^2 = x^3 + ax + b * @param b the value of 'b' where y^2 = x^3 + ax + b * @param p the elliptic curve point in finite field */ public EllipticCurve(BigInteger a, BigInteger b, BigInteger p) { if (!p.isProbablePrime(PRIME_SECURITY)) { throw new IllegalArgumentException("the p is not prime"); } if (isSingular(a, b, p)) { throw new IllegalArgumentException("is singular"); } this.a = a; this.b = b; this.p = p; this.name = "cust"; byte[] p0 = p.toByteArray(); this.pcs = p0[0] == 0 ? p0.length : p0.length + 1; this.n = calculateN(); this.basePointG = calculateBasePointG(); } public EllipticCurve(ECParameters ecp) { if (!ecp.p.isProbablePrime(PRIME_SECURITY)) { throw new IllegalArgumentException("the p is not prime"); } if (isSingular(ecp.a, ecp.b, ecp.p)) { throw new IllegalArgumentException("the ec parameter is singular"); } this.a = ecp.a; this.b = ecp.b; this.p = ecp.p; this.name = ecp.toString(); byte[] p0 = ecp.p.toByteArray(); this.pcs = p0[0] == 0 ? p0.length : p0.length + 1; this.n = ecp.n; this.basePointG = new ECPoint(this, ecp.gx, ecp.gy); // the base point G this.basePointG.fastCache(); } public EllipticCurve(DataInputStream input) throws IOException { byte[] ab = new byte[input.readInt()]; input.read(ab); this.a = new BigInteger(1, ab); byte[] bb = new byte[input.readInt()]; input.read(bb); this.b = new BigInteger(1, bb); byte[] pb = new byte[input.readInt()]; input.read(pb); this.p = new BigInteger(1, pb); byte[] ob = new byte[input.readInt()]; input.read(ob); this.n = new BigInteger(1, ob); byte[] gb = new byte[input.readInt()]; input.read(gb); this.basePointG = new ECPoint(gb, this); byte[] ppb = new byte[input.readInt()]; input.read(ppb); this.psr2 = new BigInteger(1, ppb); this.pcs = input.readInt(); this.name = input.readUTF(); this.basePointG.fastCache(); } public void writeCurve(DataOutputStream output) throws IOException { byte[] a0 = a.toByteArray(); output.writeInt(a0.length); output.write(a0); byte[] b0 = b.toByteArray(); output.writeInt(b0.length); output.write(b0); byte[] p0 = p.toByteArray(); output.writeInt(p0.length); output.write(p0); byte[] n0 = n.toByteArray(); output.writeInt(n0.length); output.write(n0); byte[] pointG0 = basePointG.compress(); output.writeInt(pointG0.length); output.write(pointG0); byte[] ppobf0 = getPSR2().toByteArray(); output.writeInt(ppobf0.length); output.write(ppobf0); output.writeInt(pcs); output.writeUTF(name); } public boolean isOnCurve(ECPoint q) { if (q.isZero()) { return true; } BigInteger ySquare = (q.getY()).modPow(TWO, p); BigInteger xCube = (q.getX()).modPow(THREE, p); BigInteger dum = ((xCube.add(a.multiply(q.getX()))).add(b)).mod(p); return ySquare.compareTo(dum) == 0; } public BigInteger getN() { return n; } public ECPoint getZero() { return new ECPoint(this); } public BigInteger getA() { return a; } public BigInteger getB() { return b; } public BigInteger getP() { return p; } public int getPCS() { return pcs; } public ECPoint getBasePointG() { return basePointG; } public BigInteger getPSR2() { if (this.psr2 == null) { this.psr2 = this.p.add(BigInteger.ONE).shiftRight(2); } return this.psr2; } @Override public String toString() { if (name == null || name.length() == 0) { return "y^2 = x^3 + " + a + "x + " + b + " ( mod " + p + " )"; } else { return name; } } @Override public boolean equals(Object obj) { if (!(obj instanceof EllipticCurve)) { return false; } EllipticCurve o = (EllipticCurve) obj; return new EqualsBuilder().append(this.a, o.a) .append(this.b, o.b) .append(this.p, o.p) .append(this.n, o.n) .isEquals(); } @Override public int hashCode() { return new HashCodeBuilder().append(this.a) .append(this.b) .append(this.p) .append(this.n) .hashCode(); } private static boolean isSingular(BigInteger a, BigInteger b, BigInteger p) { BigInteger a0 = a.pow(3); BigInteger b0 = b.pow(2); BigInteger result = a0.multiply(COEFA).add(b0.multiply(COEFB)).mod(p); return result.compareTo(BigInteger.ZERO) == 0; } /** * calculate mod n * @return */ private BigInteger calculateN() { return null; // TODO } /** * calculate base point G * @return */ private ECPoint calculateBasePointG() { return null; // TODO /*BigInteger x = BigInteger.ONE, y, dum = (x.modPow(THREE, this.p).add(a.multiply(x)).add(b)).mod(p); // x^3 + ax + b long i = 0; do { y = BigInteger.valueOf(i++); } while (y.modPow(TWO, this.p).compareTo(dum) != 0); return new ECPoint(this, x, BigInteger.valueOf(i));*/ } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/implementation/ecc/package-info.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ /** * ECC implementation * @author Ponfee */ package cn.ponfee.commons.jce.implementation.ecc; ================================================ FILE: src/main/java/cn/ponfee/commons/jce/implementation/package-info.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ /** * The crypto implementation by self, include digest, rsa and ecc * * @author Ponfee */ package cn.ponfee.commons.jce.implementation; ================================================ FILE: src/main/java/cn/ponfee/commons/jce/implementation/rsa/AbstractRSACryptor.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.implementation.rsa; import cn.ponfee.commons.io.Files; import cn.ponfee.commons.jce.implementation.Cryptor; import cn.ponfee.commons.jce.implementation.Key; import cn.ponfee.commons.math.Numbers; import cn.ponfee.commons.util.SecureRandoms; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.math.BigInteger; import java.util.Arrays; /** * http://blog.51cto.com/xnuil/1698673 * * RSA Cryptor, Without padding * RSA私钥解密证明:费马小定理(欧拉定理特例) * 等同证明:c^d ≡ m (mod n) * 因为:m^e ≡ c (mod n) * 于是,c可以写成:c = m^e - kn * 将c代入要我们要证明的那个解密规则:(m^e - kn)^d ≡ m (mod n) * 等同证明:m^(ed) ≡ m (mod n) * 由于:ed ≡ 1 (mod φ(n)) * 所以:ed = hφ(n)+1 * 得出:m^(hφ(n)+1) ≡ m (mod n) * * @author Ponfee */ public abstract class AbstractRSACryptor extends Cryptor { private final boolean isPadding; public AbstractRSACryptor(boolean isPadding) { this.isPadding = isPadding; } public int getOriginBlockSize(RSAKey rsaKey) { // 减一个byte为了防止溢出(byte array less than mod) // 此时BigInteger(1, byte[getOriginBlockSize(rsaKey)]) < rsaKey.n return rsaKey.n.bitLength() / 8 - 1; } public int getCipherBlockSize(RSAKey rsaKey) { return rsaKey.n.bitLength() / 8; } // ---------------------------------------------------------------do crypt methods @Override public byte[] encrypt(byte[] input, int length, Key ek) { RSAKey rsaKey = (RSAKey) ek; BigInteger exponent = this.getExponent(rsaKey); //return new BigInteger(1, input).modPow(exponent, rsaKey.n).toByteArray(); int originBlockSize = this.getOriginBlockSize(rsaKey), // 加密前原文数据块的大小 cipherBlockSize = this.getCipherBlockSize(rsaKey); // 加密后密文数据块大小 ByteArrayOutputStream out = new ByteArrayOutputStream(input.length); byte[] origin, encrypted; try { for (int offset = 0, len = input.length, to; offset < len; offset += originBlockSize) { to = Math.min(len, offset + originBlockSize); if (isPadding) { // 切割并填充原文数据块 origin = encodeBlock(input, offset, to, cipherBlockSize, rsaKey); } else { // 切割原文数据块 origin = Arrays.copyOfRange(input, offset, to); } // 加密:encrypted = origin^e mod n encrypted = new BigInteger(1, origin).modPow(exponent, rsaKey.n).toByteArray(); // 固定密文长度 fixedByteArray(encrypted, cipherBlockSize, out); } return out.toByteArray(); } catch (IOException e) { throw new SecurityException(e); // cannot happen } } @Override public byte[] decrypt(byte[] input, Key dk) { RSAKey rsaKey = (RSAKey) dk; BigInteger exponent = this.getExponent(rsaKey); //return new BigInteger(1, input).modPow(exponent, rsaKey.n).toByteArray(); int cipherBlockSize = this.getCipherBlockSize(rsaKey), originBlockSize = this.getOriginBlockSize(rsaKey); ByteArrayOutputStream output = new ByteArrayOutputStream(input.length); byte[] encrypted, origin; try { for (int offset = 0, len = input.length; offset < len; offset += cipherBlockSize) { // 切割密文数据块 encrypted = Arrays.copyOfRange(input, offset, Math.min(len, offset + cipherBlockSize)); // 解密:origin = encrypted^d mod n origin = new BigInteger(1, encrypted).modPow(exponent, rsaKey.n).toByteArray(); if (isPadding) { // 解码数据块 decodeBlock(origin, cipherBlockSize, output); } else { if (offset + cipherBlockSize < len) { // 判断是否是最后一轮循环 // 固定明文长度 fixedByteArray(origin, originBlockSize, output); } else { // 去掉原文前缀0 trimByteArray(origin, output); } } } return output.toByteArray(); } catch (IOException e) { throw new SecurityException(e); // cannot happened } } public void encrypt(InputStream input, Key ek, OutputStream output) { RSAKey rsaKey = (RSAKey) ek; BigInteger exponent = this.getExponent(rsaKey); int cipherBlockSize = this.getCipherBlockSize(rsaKey); byte[] buffer = new byte[getOriginBlockSize(rsaKey)], origin, encrypted; try { for (int len; (len = input.read(buffer)) != Files.EOF;) { if (isPadding) { // 切割并填充原文数据块 origin = encodeBlock(buffer, 0, len, cipherBlockSize, rsaKey); } else { // 切割原文数据块 origin = Arrays.copyOfRange(buffer, 0, len); } // 加密:encrypted = origin^e mod n encrypted = new BigInteger(1, origin).modPow(exponent, rsaKey.n).toByteArray(); // 固定密文长度 fixedByteArray(encrypted, cipherBlockSize, output); } output.flush(); } catch (IOException e) { throw new SecurityException(e); } } public void decrypt(InputStream input, Key dk, OutputStream output) { RSAKey rsaKey = (RSAKey) dk; BigInteger exponent = this.getExponent(rsaKey); int cipherBlockSize = this.getCipherBlockSize(rsaKey), originBlockSize = this.getOriginBlockSize(rsaKey); byte[] buffer = new byte[cipherBlockSize], encrypted, origin; try { int len, offset = 0, inputLen = input.available(); for (; (len = input.read(buffer)) != Files.EOF; offset += cipherBlockSize) { // 切割密文数据块 encrypted = Arrays.copyOfRange(buffer, 0, len); // 解密:origin = encrypted^d mod n origin = new BigInteger(1, encrypted).modPow(exponent, rsaKey.n).toByteArray(); if (isPadding) { // 解码数据块 decodeBlock(origin, cipherBlockSize, output); } else { if (offset + cipherBlockSize < inputLen) { // 固定明文长度 fixedByteArray(origin, originBlockSize, output); } else { // 去掉原文前缀0 trimByteArray(origin, output); } } } output.flush(); } catch (IOException e) { throw new SecurityException(e); } } public final BigInteger getExponent(RSAKey rsaKey) { return rsaKey.secret ? rsaKey.d : rsaKey.e; } /** * This method generates a new key for the crypto. * @return the new key generated */ @Override public final Key generateKey() { return generateKey(2048); } public final Key generateKey(int keySize) { return new RSAKey(keySize); } @Override public final String toString() { return this.getClass().getSimpleName(); } // ---------------------------------------------------------------private methods /** * When the BigInteger convert to byte array, if head more than two zero * then was automatic trim remain one zero, if head has not zreo then automatic * add a zreo. So we should manual control handle it, recover the origin byte * array of this BigInteger. * * @param data the data * @param fixedSize the result of byte array length * @param out the output stream * @throws IOException if occur IOException * @see cn.ponfee.commons.util.Bytes#toBinary(byte...) * @see cn.ponfee.commons.util.Bytes#tailCopy(byte[], int, int, byte[], int, int) */ private static void fixedByteArray(byte[] data, int fixedSize, OutputStream out) throws IOException { if (data.length < fixedSize) { // 当最前面有多个0时,此时会被舍去只留下一个0来充当符号位,所以要加前缀0来补全 // 加前缀0补全到固定字节数:encryptedBlockSize for (int i = 0, heading = fixedSize - data.length; i < heading; i++) { out.write(Numbers.ZERO_BYTE); } out.write(data, 0, data.length); } else { // 当最前面的位为1时,BigInteger会通过加一个byte 0来充当符号位,此时需要手动舍去 out.write(data, data.length - fixedSize, fixedSize); } } /** * 当最前面的位为1时,BigInteger会通过加一个byte 0来充当符号位,此时需要手动舍去 * this method is unsafe, will be lose the prefix byte 0(one or more) * * @param data the decrypted origin data * @param out the output stream * @throws IOException if occur IOException * @see cn.ponfee.commons.util.Bytes#toBinary(byte...) */ private static void trimByteArray(byte[] data, OutputStream out) throws IOException { int i = 0, len = data.length; for (; i < len; i++) { if (data[i] != Numbers.ZERO_BYTE) { break; } } if (i < len) { out.write(data, i, len - i); } } /** * 原文进行编码填充 * * EB = 00 || BT || PS || 00 || D * BT:公钥为0x02;私钥为0x00或0x01 * PS:BT为0则PS全部为0x00;BT为0x01则全部为0xFF;BT为0x02则为随机数,但不能为0 * * 对于BT为00的,数据D就不能以00字节开头,因为这时候PS填充的也是00, * 会分不清哪些是填充数据哪些是明文数据

    * * 如果你使用私钥加密,建议你BT使用01,保证了安全性 * 对于BT为02和01的,PS至少要有8个字节长 * * @param input 数据 * @param from 开始位置 * @param to 结束位置 * @param cipherBlockSize 模字节长(modulo/8) * @param rsaKey 密钥 * @return the encrypting block with pkcs1 padding */ private static byte[] encodeBlock(byte[] input, int from, int to, int cipherBlockSize, RSAKey rsaKey) { int length = to - from; if (length > cipherBlockSize) { throw new IllegalArgumentException("input data too large"); } else if (cipherBlockSize - length - 3 < 8) { throw new IllegalArgumentException("the padding too small"); } ByteArrayOutputStream baos = new ByteArrayOutputStream(cipherBlockSize); baos.write(Numbers.ZERO_BYTE); // 0x00 if (rsaKey.secret) { // 私钥填充 baos.write(0x01); // BT for (int i = 2, pLen = cipherBlockSize - length - 1; i < pLen; i++) { baos.write(0xFF); } } else { // 公钥填充,规定此处至少要8个字节 baos.write(0x02); // BT byte b; for (int i = 2, pLen = cipherBlockSize - length - 1; i < pLen; i++) { do { b = (byte) SecureRandoms.nextInt(); } while (b == Numbers.ZERO_BYTE); baos.write(b); } } baos.write(Numbers.ZERO_BYTE); // 0x00 baos.write(input, from, length); // D return baos.toByteArray(); } /** * 解码原文填充(前缀0被舍去,只有127位) * @param input the input * @param cipherBlockSize the cipherBlockSize * @param out the out * @throws IOException if occur IOException * @see cn.ponfee.commons.util.Bytes#toBinary(byte...) */ private static void decodeBlock(byte[] input, int cipherBlockSize, OutputStream out) throws IOException { // BigInteger to byte array will be removed the prefix 0 (one or more) // or added one byte 0 // 0x00 [0x01 | 0x02], so signum is definite on [0x01 | 0x02] then // was removed the first 0x00 int removedZeroLen; if (input[0] == Numbers.ZERO_BYTE) { removedZeroLen = 0; } else { removedZeroLen = 1; } // 输入数据长度必须等于数据块长 if (input.length != cipherBlockSize - removedZeroLen) { throw new IllegalArgumentException("block incorrect size"); } // check BT byte type = input[1 - removedZeroLen]; if (type != 1 && type != 2) { throw new IllegalArgumentException("unknown block type"); } // PS int start = 2 - removedZeroLen; for (; start != input.length; start++) { byte pad = input[start]; if (pad == 0) { break; } if (type == 1 && pad != (byte) 0xff) { // private key padding throw new IllegalArgumentException("invalid block padding"); } } // get D start++; // data should start at the next byte if (start > input.length || start < 11 - removedZeroLen) { throw new IllegalArgumentException("invalid block data"); } out.write(input, start, input.length - start); } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/implementation/rsa/RSAHashCryptor.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.implementation.rsa; import cn.ponfee.commons.io.Files; import cn.ponfee.commons.jce.HmacAlgorithms; import cn.ponfee.commons.jce.implementation.Key; import cn.ponfee.commons.util.Bytes; import cn.ponfee.commons.util.SecureRandoms; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.math.BigInteger; import java.util.Arrays; import static cn.ponfee.commons.jce.Providers.BC; import static cn.ponfee.commons.jce.digest.HmacUtils.crypt; /** * RSA Cryptor based sha512 xor * @author Ponfee */ public class RSAHashCryptor extends AbstractRSACryptor { public RSAHashCryptor() { super(false); } private static final HmacAlgorithms HMAC_ALG = HmacAlgorithms.HmacSHA3_512; /** * (origin ⊕ passwd) ⊕ passwd = origin * @param input * @param length * @param ek * @return */ @Override public byte[] encrypt(byte[] input, int length, Key ek) { RSAKey rsaKey = (RSAKey) ek; int keyByteLen = rsaKey.n.bitLength() / 8, count = 1; BigInteger exponent = getExponent(rsaKey); // 生成随机对称密钥 BigInteger key = SecureRandoms.random(rsaKey.n); // mod是以1XX开头,key是以01X开头 // 对密钥进行RSA加密,encryptedKey = key^e mod n byte[] encryptedKey = key.modPow(exponent, rsaKey.n).toByteArray(); byte[] result = new byte[keyByteLen + length]; // mod pow之后可能被去0或加0 Bytes.tailCopy(encryptedKey, 0, encryptedKey.length, result, 0, keyByteLen); // 对密钥进行HASH byte[] keyArray = key.toByteArray(); byte[] hashedKey = crypt(keyArray, Bytes.toBytes(count), HMAC_ALG, BC); for (int keyOffset = 0, i = 0; i < length; i++) { if (keyOffset == HMAC_ALG.byteSize()) { keyOffset = 0; hashedKey = crypt(keyArray, Bytes.toBytes(++count), HMAC_ALG, BC); } result[keyByteLen + i] = (byte) (input[i] ^ hashedKey[keyOffset++]); } return result; } @Override public byte[] decrypt(byte[] input, Key dk) { RSAKey rsaKey = (RSAKey) dk; int keyByteLen = rsaKey.n.bitLength() / 8, count = 1; BigInteger exponent = getExponent(rsaKey); // 获取被加密的对称密钥数据 byte[] encryptedKey = Arrays.copyOfRange(input, 0, keyByteLen); // 解密被加密的密钥数据,key = encryptedKey^d mod n BigInteger key = new BigInteger(1, encryptedKey).modPow(exponent, rsaKey.n); // 对密钥进行HASH byte[] keyArray = key.toByteArray(); byte[] hashedKey = crypt(keyArray, Bytes.toBytes(count), HMAC_ALG, BC); byte[] result = new byte[input.length - keyByteLen]; for (int keyOffset = 0, rLen = result.length, i = 0; i < rLen; i++) { if (keyOffset == HMAC_ALG.byteSize()) { keyOffset = 0; hashedKey = crypt(keyArray, Bytes.toBytes(++count), HMAC_ALG, BC); } result[i] = (byte) (input[keyByteLen + i] ^ hashedKey[keyOffset++]); } return result; } @Override public void encrypt(InputStream input, Key ek, OutputStream output) { RSAKey rsaKey = (RSAKey) ek; int keyByteLen = rsaKey.n.bitLength() / 8, count = 1; BigInteger exponent = getExponent(rsaKey); // 生成随机对称密钥 BigInteger key = SecureRandoms.random(rsaKey.n); // 对密钥进行RSA加密,encryptedKey = key^e mod n byte[] encryptedKey = key.modPow(exponent, rsaKey.n).toByteArray(); byte[] encryptedKey0 = new byte[keyByteLen]; // mod pow之后可能被去0或加0 Bytes.tailCopy(encryptedKey, 0, encryptedKey.length, encryptedKey0, 0, keyByteLen); byte[] keyArray = key.toByteArray(); byte[] hashedKey = crypt(keyArray, Bytes.toBytes(count), HMAC_ALG, BC); try { output.write(encryptedKey0); // encrypted key byte[] buffer = new byte[this.getOriginBlockSize(rsaKey)]; for (int keyOffset = 0, len, i; (len = input.read(buffer)) != Files.EOF;) { for (i = 0; i < len; i++) { if (keyOffset == HMAC_ALG.byteSize()) { keyOffset = 0; hashedKey = crypt(keyArray, Bytes.toBytes(++count), HMAC_ALG, BC); } output.write((byte) (buffer[i] ^ hashedKey[keyOffset++])); } } output.flush(); } catch (IOException e) { throw new SecurityException(e); } } @Override public void decrypt(InputStream input, Key dk, OutputStream output) { RSAKey rsaKey = (RSAKey) dk; int keyByteLen = rsaKey.n.bitLength() / 8, count = 1; BigInteger exponent = getExponent(rsaKey); try { if (input.available() < keyByteLen) { throw new IllegalArgumentException("Invalid cipher data"); } // 获取被加密的对称密钥数据 byte[] encryptedKey = new byte[keyByteLen]; input.read(encryptedKey); // 解密被加密的密钥数据,key = encryptedKey^d mod n BigInteger key = new BigInteger(1, encryptedKey).modPow(exponent, rsaKey.n); byte[] buffer = new byte[this.getCipherBlockSize(rsaKey)]; byte[] keyArray = key.toByteArray(); byte[] hashedKey = crypt(keyArray, Bytes.toBytes(count), HMAC_ALG, BC); for (int keyOffset = 0, len, i; (len = input.read(buffer)) != Files.EOF;) { for (i = 0; i < len; i++) { if (keyOffset == HMAC_ALG.byteSize()) { keyOffset = 0; hashedKey = crypt(keyArray, Bytes.toBytes(++count), HMAC_ALG, BC); } output.write((byte) (buffer[i] ^ hashedKey[keyOffset++])); } } output.flush(); } catch (IOException e) { throw new SecurityException(e); } } @Override public int getOriginBlockSize(RSAKey rsaKey) { return 4096; } @Override public int getCipherBlockSize(RSAKey rsaKey) { return this.getOriginBlockSize(rsaKey); } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/implementation/rsa/RSAKey.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.implementation.rsa; import cn.ponfee.commons.jce.implementation.Key; import cn.ponfee.commons.util.SecureRandoms; import com.google.common.base.Preconditions; import org.apache.commons.io.IOUtils; import sun.security.util.DerInputStream; import sun.security.util.DerOutputStream; import sun.security.util.DerValue; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.math.BigInteger; import java.security.SecureRandom; import static java.math.BigInteger.ONE; /** *

     * The RSA Key
     *
     * https://blog.csdn.net/seccloud/article/details/8188812
     * https://blog.csdn.net/zntsbkhhh/article/details/109581852
     * https://www.zhihu.com/question/54779059
     * 
     * (1)选择两个不同的大素数p和q
     * (2)计算公共模数(n=pq)和欧拉数(eular=(p-1)(q-1))
     * (3)选择公钥指数e
     * (4)计算inverse(d)
     * (5)生成公钥和私钥
     * 
     * 
     * 公钥KU:
     *   n:两素数p和q的乘积(p和q必须保密)(n为模值)
     *   e:与(p-1)*(q-1)互质(e称为公钥指数)
     *
     * 私钥KR:
     *   n:两素数p和q的乘积(p和q必须保密)(n为模值)
     *   d:满足(d*e) mod ((p-1)*(q-1)) = 1(d称为私钥指数)
     *
     * 加密过程  C=M^e mod n  (C为密文)
     * 解密过程  M=C^d mod n  (M为明文)
     * 
    * * @see org.bouncycastle.crypto.generators.RSAKeyPairGenerator * @author Ponfee */ @SuppressWarnings("restriction") public class RSAKey implements Key { private static final SecureRandom SECURE_RANDOM = new SecureRandom(SecureRandoms.generateSeed(24)); // RSA公钥指数:Should RSA public exponent be only in (3, 5, 17, 257 or 65537),最常用的是3,17,65537 // 学术界普遍认为绝对不能选用e=3作为RSA公钥指数 // 选取小公钥指数主要是为了提高加密或签名验证的性能 public static final int RSA_F4 = 65537; public final BigInteger n; public final BigInteger e; public final BigInteger d; public final BigInteger p; public final BigInteger q; public final BigInteger pe; public final BigInteger qe; public final BigInteger coeff; public final boolean secret; public RSAKey(int keySize) { this(keySize, RSA_F4); } public RSAKey(int keySize, int e) { this.secret = true; KeyPair pair = generateKey(keySize, e); this.e = pair.e; this.p = pair.p; this.q = pair.q; this.n = pair.n; this.pe = pair.pe; this.qe = pair.qe; this.d = pair.d; this.coeff = pair.coeff; } public RSAKey(BigInteger n, BigInteger e, BigInteger d, BigInteger p, BigInteger q, BigInteger pe, BigInteger qe, BigInteger coeff) { Preconditions.checkArgument(n != null && e != null && d != null && p != null && q != null && pe != null && qe != null && coeff != null); this.secret = true; this.n = n; this.e = e; this.d = d; this.p = p; this.q = q; this.pe = pe; this.qe = qe; this.coeff = coeff; } public RSAKey(BigInteger n, BigInteger e) { Preconditions.checkArgument(n != null && e != null); this.secret = false; this.n = n; this.e = e; this.d = null; this.p = null; this.q = null; this.pe = null; this.qe = null; this.coeff = null; } @Override public boolean isPublic() { return !secret; } public boolean isSecret() { return secret; } /** * get the public key */ @Override public RSAKey getPublic() { return new RSAKey(n, e); } // Secret: (secret, n, e, d, p, q, pe, qe, coeff) // Public: (secret, n, e) @Override public void writeKey(OutputStream out) throws IOException { DerOutputStream der = new DerOutputStream(); der.putInteger(this.secret ? 0 : 1); der.putInteger(this.n); der.putInteger(this.e); if (this.secret) { der.putInteger(this.d); der.putInteger(this.p); der.putInteger(this.q); der.putInteger(this.pe); der.putInteger(this.qe); der.putInteger(this.coeff); } DerValue dervalue = new DerValue((byte) 48, der.toByteArray()); out.write(dervalue.toByteArray()); der.close(); } // Secret: (secret, n, e, d, p, q, pe, qe, coeff) // Public: (secret, n, e) @Override public RSAKey readKey(InputStream in) throws IOException { DerValue der = new DerInputStream(IOUtils.toByteArray(in)).getDerValue(); if (der.getTag() != 48) { throw new IOException("Not a SEQUENCE"); } DerInputStream derIn = der.getData(); boolean secret = der.getInteger() == 0; BigInteger n = getBigInteger(derIn); BigInteger e = getBigInteger(derIn); RSAKey rsaKey; if (secret) { BigInteger d = getBigInteger(derIn); BigInteger p = getBigInteger(derIn); BigInteger q = getBigInteger(derIn); BigInteger pe = getBigInteger(derIn); BigInteger qe = getBigInteger(derIn); BigInteger coeff = getBigInteger(derIn); rsaKey = new RSAKey(n, e, d, p, q, pe, qe, coeff); } else { rsaKey = new RSAKey(n, e); } if (derIn.available() != 0) { throw new IOException("Extra data available"); } return rsaKey; } private static BigInteger getBigInteger(DerInputStream derIn) { BigInteger biginteger; try { biginteger = derIn.getBigInteger(); } catch (IOException e) { throw new IllegalArgumentException(e); } if (biginteger.signum() < 0) { biginteger = new BigInteger(1, biginteger.toByteArray()); } return biginteger; } /** * Generate the key pair * * @param keySize * @param e * @return */ public static KeyPair generateKey(int keySize, int e) { KeyPair keyPair = new KeyPair(); keyPair.e = BigInteger.valueOf(e); int i = (keySize + 1) >> 1; int j = keySize - i; do { keyPair.p = BigInteger.probablePrime(i, SECURE_RANDOM); do { keyPair.q = BigInteger.probablePrime(j, SECURE_RANDOM); if (keyPair.p.compareTo(keyPair.q) < 0) { BigInteger temp = keyPair.p; keyPair.p = keyPair.q; keyPair.q = temp; } keyPair.n = keyPair.p.multiply(keyPair.q); } while (keyPair.n.bitLength() != keySize); keyPair.p1 = keyPair.p.subtract(ONE); keyPair.q1 = keyPair.q.subtract(ONE); keyPair.phi = keyPair.p1.multiply(keyPair.q1); } while (!keyPair.e.gcd(keyPair.phi).equals(ONE)); keyPair.d = keyPair.e.modInverse(keyPair.phi); keyPair.pe = keyPair.d.mod(keyPair.p1); keyPair.qe = keyPair.d.mod(keyPair.q1); keyPair.coeff = keyPair.q.modInverse(keyPair.p); return keyPair; // new sun.security.rsa.RSAPublicKeyImpl(n, e); // new sun.security.rsa.RSAPrivateCrtKeyImpl(n, e, d, p, q, pe, qe, coeff); // return new java.security.KeyPair(RSAPublicKeyImpl, RSAPrivateCrtKeyImpl); } /*public static KeyPair generateKey(int keySize, int e) { KeyPair keyPair = new KeyPair(); int qs = keySize >> 1; keyPair.e = BigInteger.valueOf(e); for (;;) { do { keyPair.p = new BigInteger(keySize - qs, 1, SECURE_RANDOM); } while (keyPair.p.subtract(ONE).gcd(keyPair.e).compareTo(ONE) != 0 || !keyPair.p.isProbablePrime(10)); do { keyPair.q = new BigInteger(qs, 1, SECURE_RANDOM); } while (keyPair.q.subtract(ONE).gcd(keyPair.e).compareTo(ONE) != 0 || !keyPair.q.isProbablePrime(10)); if (keyPair.p.compareTo(keyPair.q) <= 0) { BigInteger t = keyPair.p; keyPair.p = keyPair.q; keyPair.q = t; } keyPair.p1 = keyPair.p.subtract(ONE); keyPair.q1 = keyPair.q.subtract(ONE); keyPair.phi = keyPair.p1.multiply(keyPair.q1); if (keyPair.phi.gcd(keyPair.e).compareTo(ONE) == 0) { keyPair.n = keyPair.p.multiply(keyPair.q); keyPair.d = keyPair.e.modInverse(keyPair.phi); keyPair.pe = keyPair.d.mod(keyPair.p1); keyPair.qe = keyPair.d.mod(keyPair.q1); keyPair.coeff = keyPair.q.modInverse(keyPair.p); break; } } return keyPair; }*/ private static class KeyPair { private BigInteger n; private BigInteger e; private BigInteger d; private BigInteger p; private BigInteger q; private BigInteger pe; private BigInteger qe; private BigInteger coeff; private BigInteger p1; private BigInteger q1; private BigInteger phi; } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/implementation/rsa/RSANoPaddingCryptor.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.implementation.rsa; /** * RSA crypto without padding * * @author Ponfee */ public class RSANoPaddingCryptor extends AbstractRSACryptor { public RSANoPaddingCryptor() { super(false); } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/implementation/rsa/RSAPKCS1PaddingCryptor.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.implementation.rsa; /** * RSA crypto with PKCS1 padding * * @author Ponfee */ public class RSAPKCS1PaddingCryptor extends AbstractRSACryptor { public RSAPKCS1PaddingCryptor() { super(true); } @Override public int getOriginBlockSize(RSAKey rsaKey) { return rsaKey.n.bitLength() / 8 - 11; } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/implementation/rsa/RSASigner.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.implementation.rsa; import cn.ponfee.commons.jce.DigestAlgorithms; import cn.ponfee.commons.jce.digest.DigestUtils; import com.google.common.collect.ImmutableMap; import org.bouncycastle.asn1.ASN1Encoding; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.DERNull; import org.bouncycastle.asn1.nist.NISTObjectIdentifiers; import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.asn1.x509.DigestInfo; import org.bouncycastle.asn1.x509.X509ObjectIdentifiers; import org.bouncycastle.crypto.AsymmetricBlockCipher; import org.bouncycastle.crypto.InvalidCipherTextException; import org.bouncycastle.crypto.encodings.PKCS1Encoding; import org.bouncycastle.crypto.engines.RSABlindedEngine; import org.bouncycastle.crypto.params.RSAKeyParameters; import java.io.IOException; import java.util.Arrays; import java.util.Map; /** * RSA sign * https://www.cnblogs.com/jintianhu/p/5051169.html * * @see org.bouncycastle.crypto.signers.RSADigestSigner * * @author Ponfee */ public class RSASigner { private static final Map HASH_OID_MAPPING = ImmutableMap. builder() // .put("RIPEMD128", TeleTrusTObjectIdentifiers.ripemd128) // .put("RIPEMD160", TeleTrusTObjectIdentifiers.ripemd160) // .put("RIPEMD256", TeleTrusTObjectIdentifiers.ripemd256) // .put("SHA-1", X509ObjectIdentifiers.id_SHA1) // .put("SHA-224", NISTObjectIdentifiers.id_sha224) // .put("SHA-256", NISTObjectIdentifiers.id_sha256) // .put("SHA-384", NISTObjectIdentifiers.id_sha384) // .put("SHA-512", NISTObjectIdentifiers.id_sha512) // .put("SHA-512/224", NISTObjectIdentifiers.id_sha512_224) // .put("SHA-512/256", NISTObjectIdentifiers.id_sha512_256) // .put("SHA3-224", NISTObjectIdentifiers.id_sha3_224) // .put("SHA3-256", NISTObjectIdentifiers.id_sha3_256) // .put("SHA3-384", NISTObjectIdentifiers.id_sha3_384) // .put("SHA3-512", NISTObjectIdentifiers.id_sha3_512) // .put("MD2", PKCSObjectIdentifiers.md2) // .put("MD4", PKCSObjectIdentifiers.md4) // .put("MD5", PKCSObjectIdentifiers.md5) // .build(); private final AsymmetricBlockCipher rsaEngine = new PKCS1Encoding(new RSABlindedEngine()); private final RSAKey rsaKey; public RSASigner(RSAKey rsaKey) { this.rsaKey = rsaKey; if (rsaKey.secret) { // 签名 rsaEngine.init(true, new RSAKeyParameters(true, rsaKey.n, rsaKey.d)); } else { // 验签 rsaEngine.init(false, new RSAKeyParameters(false, rsaKey.n, rsaKey.e)); } } public byte[] signSha1(byte[] data) { return sign(data, DigestAlgorithms.SHA1); } public boolean verifySha1(byte[] data, byte[] signature) { return verify(data, signature, DigestAlgorithms.SHA1); } public byte[] signSha256(byte[] data) { return sign(data, DigestAlgorithms.SHA256); } public boolean verifySha256(byte[] data, byte[] signature) { return verify(data, signature, DigestAlgorithms.SHA256); } public byte[] sign(byte[] data, DigestAlgorithms alg) { if (!this.rsaKey.isSecret()) { throw new IllegalArgumentException("Sign must use private key."); } ASN1ObjectIdentifier oid = HASH_OID_MAPPING.get(alg.algorithm()); if (oid == null) { throw new IllegalArgumentException("Invalid hash algorithm " + alg.name()); } // data hash byte[] hash = DigestUtils.digest(alg, data); try { byte[] result = derEncode(hash, oid); return rsaEngine.processBlock(result, 0, result.length); } catch (InvalidCipherTextException | IOException e) { throw new SecurityException(e); } } public boolean verify(byte[] data, byte[] signature, DigestAlgorithms alg) { if (this.rsaKey.isSecret()) { throw new IllegalArgumentException("Verify signature must use public key."); } ASN1ObjectIdentifier oid = HASH_OID_MAPPING.get(alg.algorithm()); if (oid == null) { throw new IllegalArgumentException("Invalid hash algorithm " + alg.name()); } // hash data byte[] hash = DigestUtils.digest(alg, data); byte[] sig; byte[] expected; try { expected = derEncode(hash, oid); sig = rsaEngine.processBlock(signature, 0, signature.length); } catch (InvalidCipherTextException | IOException e) { return false; } if (sig.length == expected.length) { return Arrays.equals(sig, expected); } else if (sig.length == expected.length - 2) { // NULL left out int sigOffset = sig.length - hash.length - 2; int expOffset = expected.length - hash.length - 2; expected[1] -= 2; // adjust lengths expected[3] -= 2; for (int i = 0; i < sigOffset; i++) { // check header less NULL if (sig[i] != expected[i]) { return false; } } for (int i = 0; i < hash.length; i++) { // check hash data if (sig[sigOffset + i] != expected[expOffset + i]) { return false; } } return true; } else { return false; } } /** * DER :: { * BERTags.SEQUENCE|BERTags.CONSTRUCTED, * totalLength, * BERTags.OBJECT_IDENTIFIER, -- {@link ASN1ObjectIdentifier#encode} * algLength, * algBody, * BERTags.OCTET_STRING, -- {@link org.bouncycastle.asn1.DEROctetString#encode} * digestLength, * digestBody * } * * @param hash * @param digestOid * @return the byte array of der encoded * @throws IOException * @see org.bouncycastle.asn1.DERSequence * @see org.bouncycastle.asn1.x509.DigestInfo * @see org.bouncycastle.asn1.BERTags */ private byte[] derEncode(byte[] hash, ASN1ObjectIdentifier digestOid) throws IOException { AlgorithmIdentifier algId = new AlgorithmIdentifier(digestOid, DERNull.INSTANCE); DigestInfo dInfo = new DigestInfo(algId, hash); return dInfo.getEncoded(ASN1Encoding.DER); } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/implementation/rsa/package-info.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ /** * RSA implementation * @author Ponfee */ package cn.ponfee.commons.jce.implementation.rsa; ================================================ FILE: src/main/java/cn/ponfee/commons/jce/implementation/symmetric/RC4.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.implementation.symmetric; import org.apache.commons.lang3.ArrayUtils; import java.util.Arrays; /** * RC4 implementation * * 给定一个短的密码,储存在key[MAX]数组里,还有一个数组S[256],令S[i]=i。 * 然后利用数组key来对数组S做一个置换,也就是对S数组里的数重新排列,排列算法为 * KSA:密钥调度算法 * j := 0 * for i from 0 to 255 * j := (j + S[i] + key[i mod keyLen]) mod sboxLen * swap values of S[i] and S[j] * endfor * * @author Ponfee */ public class RC4 { private final static int STATE_LENGTH = 256; /** variables to hold the state of the RC4 during encryption and decryption */ private final byte[] sBox; /** * Constructs a RC4 Cryptor, input the specified key byte array * and init the sbox in this methods * * @param keyBytes */ public RC4(byte[] keyBytes) { // KSA:密钥调度算法 // 生成并填充s-box this.sBox = new byte[STATE_LENGTH]; for (int i = 0; i < STATE_LENGTH; i++) { this.sBox[i] = (byte) i; } // 置换s-box for (int i = 0, j = 0, k = 0, keyLen = keyBytes.length; i < STATE_LENGTH; i++) { j = (j + sBox[i] + keyBytes[k++]) & 0xFF; // & 0xFF -> modulo s-box len ArrayUtils.swap(this.sBox, i, j); if (k == keyLen) { k = 0; } } } // -----------------------------------------------------------crypt one byte public byte encrypt(byte in) { byte[] sBox = Arrays.copyOf(this.sBox, this.sBox.length); int x = 1; int y = sBox[x] & 0xFF; ArrayUtils.swap(sBox, x, y); // xor return (byte) (in ^ sBox[(sBox[x] + sBox[y]) & 0xFF]); } public byte decrypt(byte in) { return this.encrypt(in); } // -----------------------------------------------------------crypt byte array public byte[] encrypt(byte[] in) { byte[] out = new byte[in.length]; this.doCrypt(in, 0, in.length, out, 0); return out; } public byte[] decrypt(byte[] in) { return this.encrypt(in); } // -----------------------------------------------------------private methods private void doCrypt(byte[] in, int inOff, int len, byte[] out, int outOff) { byte[] sBox = Arrays.copyOf(this.sBox, this.sBox.length); // RPGA:伪随机生成算法,不断的重排S盒来产生任意长度的密钥流 for (int i = 0, x = 0, y = 0; i < len; i++) { x = (x + 1) & 0xFF; y = (sBox[x] + y) & 0xFF; ArrayUtils.swap(sBox, x, y); // xor out[i + outOff] = (byte) (in[i + inOff] ^ sBox[(sBox[x] + sBox[y]) & 0xFF]); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/package-info.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ /** *
     * Java Cryptography Extension提供用于加密、密钥生成和协商
     * 以及 Message Authentication Code(MAC)算法的实现
     * 
     * http://www.freebuf.com/articles/others-articles/136742.html
     * https://www.jianshu.com/p/b10a892879a0
     * 
     * 1、密码:
     *   你知道什么:口令(密码)、口令摘要、质询/响应
     *   你有什么:认证令牌(质询/响应令牌、时间令牌),PIN双因素认证、SSL与认证令牌、智能卡
     *   你是什么:生物特征认证,FAR(False Accept Ratio),FRR(False Reject Ratio)
     * 
     * 2、对称加密:
     *   优点:效率高
     *   缺点:密钥成几何数增长、需要事先协商密钥
     *   类型:分组密码(DES、3DES、AES),序列密码(RC4)、盐加密(PBE)
     *   分组模式:ECB、CBC、OFB、CFB
     *   填充:NoPadding, PKCS5Padding, PKCS7Padding, PADDING_ISO10126
     *   AES要支持256位密钥:http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html
     *                  解压后替换jre/lib/security/目录下的jar文件即可
     * 
     * 3、非对称加密:
     *   优点:密钥分发安全,公开公钥即可
     *   缺点:效率低
     *   算法:
     *      DH:基于离散对数的实现,主要用于密钥交换
     *      RSA:基于大整数因式分解的实现,Ron [R]ivest, Adi [S]hamir, Leonard [A]dleman(三人)
     *          https://www.kancloud.cn/kancloud/rsa_algorithm/48488
     *      DSA:基于整数有限域离散对数难题(特点是两个素数公开),Digital Signature Algorithm,顾名思义只用于数字签名
     *      ECC:基于椭圆曲线算法,指在取代RSA
     *   填充:RSA_PKCS1_PADDING(blocklen=keysize/8–11)、RSA_PKCS1_OAEP_PADDING(keysize-41)、RSA_NO_PADDING
     *   签名/验签:PKCS1及填充、PKCS7格式(附原文|不附原文)
     *   PKCS: Public-Key Cryptography Standards
     *   PKI: Public-Key Infrastructure
     * 
     * 4、对称与非对称结合:数字信封envelop,结构体,(带签名|不带签名)
     * 
     * 5、数字证书:ASN1、X509、p7b、p7r、p10、p12、PEM、DER等概念
     * 
     * 6、BASE64编码:3个字节切分为4个字节后每字节最高位补00  0 ~ 63, “=”,并与编码表对照
     *         前生:解决邮件只能发ASCII码问题
     *         应用:二进制字节流数据文本化(某些场景的网络传输及文本表示)
     * 
     * 7、哈希算法:指纹、摘要,用于防篡改等
     *    MD5:前身MD2、MD3和MD4,安全性低,算法原理(填充、记录长度、数据处理)
     *    SHA-1:已被严重质疑
     *    SHA-2:SHA-224、SHA-256、SHA-384、SHA-512,算法跟SHA-1基本上仍然相似
     *    SHA-3:之前名为Keccak算法,是一个加密杂凑算法
     *    RIPEMD-160:哈希加密算法,用于替代128位哈希函数 MD4、MD5 和 RIPEMD
     * 
     * 8、密码安全:BCrypt、SCrypt、PBKDF2, Argon2
     * 
     * 9、时间戳、签章
     * 
     * 10、区块链:
     *     https://anders.com/blockchain,https://www.zhihu.com/question/22075219
     *     SHA256:
     *          block_header = version + previous_block_hash + merkle_root + time + target_bits + nonce
     *          for i in range(0, 2^32):
     *              if sha256(sha256(block_header)) < target_bits:
     *                  break
     *              else:
     *                  continue
    
     *          version:block的版本(静态常数)
     *          previous_block_hash:上一个block的hash值(前一区块已经是打包好的)
     *          merkle_root:需要写入的交易记录的hash树的值(根据本次交易包含的交易列表得到)
     *          time:更新时间(utc时间:取打包时的时间,也不需要很精确,前后几十秒也都可以)
     *          target_bits:当前难度
     *          nonce:从0试到最大值2^32
     *
     *          target_bits:TARGET_MAX/difficulty,创世区块时的difficulty=1
     *          TARGET_MAX=0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
     *
     *
     *          https://www.liaoxuefeng.com/article/1124144362997184
     *          ┌─────────────────────────────────────────────┐
     *          │Tx: abcd...1234                              │
     *          ├─────────────────────┬───────────────────────┤
     *          │         TxIn        │         TxOut         │
     *          ├─────────────┬───────┼──────┬────────────────┤
     *          │prev hash    │index  │btc   │pkScript        │
     *          ├─────────────┼───────┼──────┼────────────────┤
     *          │2016...a3c5  │3      │0.15  │OP_DUP a1b2...  │<─┐
     *          ├─────────────┼───────┼──────┼────────────────┤  │
     *          │2015...b6d8  │1      │0.08  │OP_DUP c3d4...  │  │
     *          └─────────────┴───────┴──────┴────────────────┘  │
     *          ┌────────────────────────────────────────────────┘
     *          │  ┌─────────────────────────────────────────────┐
     *          │  │Tx: a1b2...e8f9                              │
     *          │  ├─────────────────────┬───────────────────────┤
     *          │  │         TxIn        │         TxOut         │
     *          │  ├─────────────┬───────┼──────┬────────────────┤
     *          │  │prev hash    │index  │btc   │pkScript        │
     *          │  ├─────────────┼───────┼──────┼────────────────┤
     *          └──│abcd...1234  │0      │0.15  │OP_DUP 3456...  │
     *             ├─────────────┼───────┼──────┼────────────────┤
     *             │cdef...5678  │2      │0.02  │OP_DUP 9876...  │
     *             └─────────────┴───────┴──────┴────────────────┘
     *
     *
     *     ECC:公钥160位的指纹作为钱包地址,一笔交易就是一个地址的比特币转移到另一个地址
     *     Base58:end of 4 bytes long checksum
     * 
     * 11、国密系列:
     *   SM1:为对称加密,其加密强度与AES相当。该算法不公开,调用该算法时,需要通过加密芯片的接口进行调用
     *   SM2:基于ECC的非对称加密算法,该算法已公开。ECC 256位(SM2采用的就是ECC 256位的一种)
     *       安全强度比RSA 2048位高,但运算速度快于RSA
     *       方程:y² = x³ + ax + b
     *       1、用户A选定一条椭圆曲线Ep(a,b),并取椭圆曲线上一点,作为基点G。
     *       2、用户A选择一个私有密钥k,并生成公开密钥K=kG。
     *       3、用户A将Ep(a,b)和点K,G传给用户B。
     *       4、用户B接到信息后 ,将待传输的明文编码到Ep(a,b)上一点M(编码方法很多,这里不作讨论),并产生一个随机整数r(r
     *
     * @author Ponfee
     */
    package cn.ponfee.commons.jce;
    
    
    ================================================
    FILE: src/main/java/cn/ponfee/commons/jce/passwd/BCrypt.java
    ================================================
    package cn.ponfee.commons.jce.passwd;
    
    import cn.ponfee.commons.util.Base64UrlSafe;
    import cn.ponfee.commons.util.SecureRandoms;
    import com.google.common.base.Preconditions;
    
    import java.util.Arrays;
    
    import static java.nio.charset.StandardCharsets.UTF_8;
    
    /**
    * BCrypt implements OpenBSD-style Blowfish password hashing using
    * the scheme described in "A Future-Adaptable Password Scheme" by
    * Niels Provos and David Mazieres.
    * 

    * This password hashing system tries to thwart off-line password * cracking using a computationally-intensive hashing algorithm, * based on Bruce Schneier's Blowfish cipher. The work factor of * the algorithm is parameterised, so it can be increased as * computers get faster. *

    * * Maven position: * * de.svenkubiak * jBCrypt * 0.4.1 * * * http://www.mindrot.org/projects/jBCrypt/#download * https://en.m.wikipedia.org/wiki/Bcrypt * $1$: MD5 * $2$: Bcrypt * $sha1$: SHA-1 * $5$: SHA-256 * $6$: SHA-512 * * Crypt {@link org.apache.commons.codec.digest.Crypt} * * @author Damien Miller * @version 0.5 * * Reference from internet and with optimization */ public final class BCrypt { private BCrypt() {} private static final char SEPARATOR = '$'; // Blowfish parameters private static final int BLOWFISH_NUM_ROUNDS = 16; // Initial contents of key schedule private static final int[] P_ORIGIN = { 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b }; private static final int[] S_ORIGIN = { 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a, 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7, 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0, 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 }; // bcrypt IV: "OrpheanBeholderScryDoubt". The C implementation calls // this "ciphertext", but it is really plaintext or an IV. We keep // the name to make code comparison easier. private static final int[] CIPHERTEXT = { 0x4f727068, 0x65616e42, 0x65686f6c, 0x64657253, 0x63727944, 0x6f756274 }; public static String create(String passwd) { return create(passwd.getBytes(UTF_8), 10); } public static String create(byte[] passwd) { return create(passwd, 10); } public static String create(String passwd, int logrounds) { return create(passwd.getBytes(UTF_8), logrounds); } /** * Hash a password using the OpenBSD bcrypt scheme * @param passwd the password to hash * @param logrounds the binary logarithm of the number * Math.pow(2,logrounds) * @return the hashed password (fixed 62 length string) */ public static String create(byte[] passwd, int logrounds) { Preconditions.checkArgument(logrounds >= 2 && logrounds <= 20, "logrounds must between 2 and 20."); StringBuilder builder = new StringBuilder(62).append(SEPARATOR) .append("2a").append(SEPARATOR); if (logrounds < 10) { builder.append('0'); } builder.append(logrounds).append(SEPARATOR); byte[] salt = SecureRandoms.nextBytes(16); builder.append(Base64UrlSafe.encode(salt)).append(SEPARATOR); byte[] hashed = crypt(passwd, salt, logrounds); return builder.append(Base64UrlSafe.encode(hashed)).toString(); } public static boolean check(String passwd, String hashed) { return check(passwd.getBytes(UTF_8), hashed); } /** * Check that a plaintext password matches a previously hashed one * @param passwd the plaintext password to verify * @param hashed the previously-hashed password * @return true if the passwords match, false otherwise */ public static boolean check(byte[] passwd, String hashed) { String[] parts = hashed.split("\\" + SEPARATOR); if (parts.length != 5 || !"2a".equals(parts[1])) { throw new IllegalArgumentException("Invalid hashed value: " + hashed); } int logrounds = Integer.parseInt(parts[2]); byte[] salt = Base64UrlSafe.decode(parts[3]); byte[] actual = Base64UrlSafe.decode(parts[4]); // crypt passwd by salt byte[] expect = crypt(passwd, salt, logrounds); return Arrays.equals(actual, expect); } public static byte[] crypt(byte[] passwd, byte[] salt, int logrounds) { int[] ciphertext = Arrays.copyOf(CIPHERTEXT, CIPHERTEXT.length); return crypt(passwd, salt, logrounds, ciphertext); } // ---------------------------------------------------------------------------private methods /** * Perform the central password hashing step in the bcrypt scheme * @param passwd the password to hash * @param salt the binary salt to hash with the password * @param logrounds the binary logarithm of the number * of rounds for hashing to apply * @param cdata the plaintext to encrypt * @return an array containing the binary hashed password */ public static byte[] crypt(byte[] passwd, byte[] salt, int logrounds, int[] cdata) { if (logrounds < 2 || logrounds > 20) { throw new IllegalArgumentException("logrounds must between 2 and 20."); } if (salt.length != 16) { throw new IllegalArgumentException("bad salt length"); } int[] P = Arrays.copyOf(P_ORIGIN, P_ORIGIN.length), S = Arrays.copyOf(S_ORIGIN, S_ORIGIN.length); ekskey(P, S, salt, passwd); // Math.pow(2,5), left move logrounds bit int i, j, clen = cdata.length, rounds = 1 << logrounds; for (i = 0; i < rounds; i++) { key(P, S, passwd); key(P, S, salt); } for (i = 0; i < 64; i++) { for (j = 0; j < (clen >> 1); j++) { encipher(P, S, cdata, j << 1); } } byte[] result = new byte[clen << 2]; for (i = 0, j = 0; i < clen; i++) { result[j++] = (byte) ((cdata[i] >> 24) & 0xff); result[j++] = (byte) ((cdata[i] >> 16) & 0xff); result[j++] = (byte) ((cdata[i] >> 8) & 0xff); result[j++] = (byte) ( cdata[i] & 0xff); } return result; } /** * Perform the "enhanced key schedule" step described by * Provos and Mazieres in "A Future-Adaptable Password Scheme" * http://www.openbsd.org/papers/bcrypt-paper.ps * @param P Mix password into the internal P-array of state * Encrypt state using the lower 8 bytes of salt, * and store the 8 byte result in P1|P2 * @param S Mix encrypted state into the internal S-boxes of state * @param data salt information * @param key password information */ private static void ekskey(int[] P, int[] S, byte[] data, byte[] key) { int[] koffp = { 0 }, doffp = { 0 }, lr = { 0, 0 }; int i, plen = P.length, slen = S.length; for (i = 0; i < plen; i++) { P[i] = P[i] ^ streamtoword(key, koffp); } for (i = 0; i < plen; i += 2) { lr[0] ^= streamtoword(data, doffp); lr[1] ^= streamtoword(data, doffp); encipher(P, S, lr, 0); P[i] = lr[0]; P[i + 1] = lr[1]; } for (i = 0; i < slen; i += 2) { lr[0] ^= streamtoword(data, doffp); lr[1] ^= streamtoword(data, doffp); encipher(P, S, lr, 0); S[i] = lr[0]; S[i + 1] = lr[1]; } } /** * Cycically extract a word of key material * @param data the string to extract the data from * @param offp a "pointer" (as a one-entry array) to the * current offset into data * @return the next word of material from data */ private static int streamtoword(byte[] data, int[] offp) { int word = 0, off = offp[0]; for (int i = 0; i < 4; i++) { word = (word << 8) | (data[off] & 0xff); off = (off + 1) % data.length; } offp[0] = off; return word; } /** * Key the Blowfish cipher * @param P Mix password into the internal P-array of state * Encrypt state using the lower 8 bytes of salt, * and store the 8 byte result in P1|P2 * @param S Mix encrypted state into the internal S-boxes of state * @param key an array containing the key */ private static void key(int[] P, int[] S, byte[] key) { int[] koffp = { 0 }, lr = { 0, 0 }; int i, plen = P.length, slen = S.length; for (i = 0; i < plen; i++) { P[i] = P[i] ^ streamtoword(key, koffp); } for (i = 0; i < plen; i += 2) { encipher(P, S, lr, 0); P[i] = lr[0]; P[i + 1] = lr[1]; } for (i = 0; i < slen; i += 2) { encipher(P, S, lr, 0); S[i] = lr[0]; S[i + 1] = lr[1]; } } /** * Blowfish encipher a single 64-bit block encoded as * two 32-bit halves * @param P Mix password into the internal P-array of state * Encrypt state using the lower 8 bytes of salt, * and store the 8 byte result in P1|P2 * @param S Mix encrypted state into the internal S-boxes of state * @param lr an array containing the two 32-bit half blocks * @param off the position in the array of the blocks */ private static void encipher(int[] P, int[] S, int[] lr, int off) { int k = lr[off] ^ P[0], r = lr[off + 1]; for (int i = 0, n; i <= BLOWFISH_NUM_ROUNDS - 2;) { // Feistel substitution on left word n = S[(k >> 24) & 0xff]; n += S[0x100 | ((k >> 16) & 0xff)]; n ^= S[0x200 | ((k >> 8) & 0xff)]; n += S[0x300 | (k & 0xff)]; r ^= n ^ P[++i]; // Feistel substitution on right word n = S[(r >> 24) & 0xff]; n += S[0x100 | ((r >> 16) & 0xff)]; n ^= S[0x200 | ((r >> 8) & 0xff)]; n += S[0x300 | (r & 0xff)]; k ^= n ^ P[++i]; } lr[off] = r ^ P[BLOWFISH_NUM_ROUNDS + 1]; lr[off + 1] = k; } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/passwd/Crypt.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.passwd; import cn.ponfee.commons.jce.HmacAlgorithms; import cn.ponfee.commons.jce.Providers; import cn.ponfee.commons.jce.digest.HmacUtils; import cn.ponfee.commons.util.Base64UrlSafe; import cn.ponfee.commons.util.Bytes; import cn.ponfee.commons.util.SecureRandoms; import com.google.common.base.Preconditions; import javax.crypto.Mac; import java.security.Provider; import java.util.Arrays; import static cn.ponfee.commons.jce.HmacAlgorithms.ALGORITHM_MAPPING; /** * The passwd crypt based hmac * * @author Ponfee */ public class Crypt { private static final char SEPARATOR = '$'; public static String create(String passwd) { return create(HmacAlgorithms.HmacSHA256, passwd, 32, Providers.BC); } /** * create crypt * @param alg * @param passwd * @param rounds the loop hmac count, between 1 and 255 * @param provider * @return */ public static String create(HmacAlgorithms alg, String passwd, int rounds, Provider provider) { Preconditions.checkArgument(rounds >= 1 && rounds <= 0xFF, "iterations must between 1 and 255"); byte[] salt = SecureRandoms.nextBytes(16); int algIdx = ALGORITHM_MAPPING.inverse().get(alg) & 0xF; // maximum is 0xf byte[] hashed = crypt(alg, passwd.getBytes(), salt, rounds, provider); return new StringBuilder(6 + ((salt.length + hashed.length) << 2) / 3 + 4) .append(SEPARATOR).append(Integer.toString((algIdx << 8) | rounds, 16)) .append(SEPARATOR).append(Base64UrlSafe.encode(salt)) .append(SEPARATOR).append(Base64UrlSafe.encode(hashed)) .toString(); } public static boolean check(String passwd, String hashed) { return check(passwd, hashed, null); } /** * check the passwd crypt * @param passwd * @param hashed * @param provider * @return {@code true} is success */ public static boolean check(String passwd, String hashed, Provider provider) { String[] parts = hashed.split("\\" + SEPARATOR); if (parts.length != 4) { throw new IllegalArgumentException("Invalid hashed value"); } int params = Integer.parseInt(parts[1], 16); HmacAlgorithms alg = ALGORITHM_MAPPING.get(params >> 8 & 0xF); byte[] salt = Base64UrlSafe.decode(parts[2]); byte[] testHash = crypt(alg, passwd.getBytes(), salt, params & 0xFF, provider); // compare return Arrays.equals(Base64UrlSafe.decode(parts[3]), testHash); } /** * crypt with hmac * @param alg * @param password * @param salt * @param rounds * @param provider * @return */ private static byte[] crypt(HmacAlgorithms alg, byte[] password, byte[] salt, int rounds, Provider provider) { Mac mac = HmacUtils.getInitializedMac(alg, provider, salt); password = mac.doFinal(password); for (int i = 1; i < rounds; i++) { mac.update(Bytes.toBytes(i)); password = mac.doFinal(password); } return password; } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/passwd/PBKDF2.java ================================================ package cn.ponfee.commons.jce.passwd; import cn.ponfee.commons.jce.HmacAlgorithms; import cn.ponfee.commons.jce.Providers; import cn.ponfee.commons.util.Base64UrlSafe; import cn.ponfee.commons.util.SecureRandoms; import com.google.common.base.Preconditions; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import java.security.spec.InvalidKeySpecException; import java.util.Arrays; import static cn.ponfee.commons.jce.HmacAlgorithms.ALGORITHM_MAPPING; /** * PBKDF2 salted password hashing. * Author: havoc AT defuse.ca * www: http://crackstation.net/hashing-security.htm * * The OpenJDK implementation does only provide a PBKDF2HmacSHA1Factory.java which has the "HmacSHA1" * digest harcoded. As far as I tested, the Oracle JDK is not different in that sense. * * @author havoc AT defuse.ca * Reference from internet and with optimization * * Password-Based Key Derivation Function 2 */ public final class PBKDF2 { private PBKDF2() {} private static final char SEPARATOR = '$'; /** * * @param password * @return */ public static String create(String password) { return create(HmacAlgorithms.HmacSHA256, password.toCharArray()); } public static String create(HmacAlgorithms alg, String password) { return create(alg, password.toCharArray()); } /** * fix salt 16 byte * iterationCount 32 * dkLen 32 byte * @param alg * @param password * @return */ public static String create(HmacAlgorithms alg, char[] password) { return create(alg, password, 16, 32, 32); } /** * Returns a salted PBKDF2 hash of the password. * @param alg HmacAlgorithm, HmacAlgorithm.HmacMD5 is invalid * @param password the password to hash * @param saltByteSize the byte length of random slat * @param iterationCount the iteration count (slowness factor) * @param dkLen Intended length, in octets, of the derived key. * @return a salted PBKDF2 hash of the password */ public static String create(HmacAlgorithms alg, char[] password, int saltByteSize, int iterationCount, int dkLen) { Preconditions.checkArgument(iterationCount >= 1 && iterationCount <= 0xffff, "iterations must between 1 and 65535"); // Generate a random salt byte[] salt = SecureRandoms.nextBytes(saltByteSize); // Hash the password byte[] hash = pbkdf2(alg, password, salt, iterationCount, dkLen); int algIdx = ALGORITHM_MAPPING.inverse().get(alg) & 0xF; // maximum is 0xf String params = Integer.toString(algIdx << 16L | iterationCount, 16); // format iterations:salt:hash return new StringBuilder(8 + ((salt.length + hash.length) << 2) / 3 + 4) .append(SEPARATOR).append(params) .append(SEPARATOR).append(Base64UrlSafe.encode(salt)) .append(SEPARATOR).append(Base64UrlSafe.encode(hash)) .toString(); } /** * Validates a password using a hash. * @param password the password to check * @param correctHash the hash of the valid password * @return true if the password is correct, false if not */ public static boolean check(String password, String correctHash) { return check(password.toCharArray(), correctHash); } /** * Validates a password using a hash. * @param password the password to check * @param correctHash the hash of the valid password * @return true if the password is correct, false if not */ public static boolean check(char[] password, String correctHash) { // Decode the hash into its parameters String[] parts = correctHash.split("\\" + SEPARATOR); if (parts.length != 4) { throw new IllegalArgumentException("Invalid hashed value"); } int params = Integer.parseInt(parts[1], 16); HmacAlgorithms alg = ALGORITHM_MAPPING.get(params >> 16 & 0xf); int iterations = params & 0xffff; byte[] salt = Base64UrlSafe.decode(parts[2]); byte[] hash = Base64UrlSafe.decode(parts[3]); // Compute the hash of the provided password, using the same salt, // iteration count, and hash length byte[] testHash = pbkdf2(alg, password, salt, iterations, hash.length); // Compare the hashes in constant time. The password is correct if // both hashes match. return Arrays.equals(hash, testHash); } /** * Computes the PBKDF2 hash of a password. * @param alg the HmacAlgorithm * @param password the password to hash * @param salt the salt * @param iterationCount the iteration count (slowness factor) * @param dkLen the length of the hash to compute in bytes * @return the PBDKF2 hash of the password */ public static byte[] pbkdf2(HmacAlgorithms alg, char[] password, byte[] salt, int iterationCount, int dkLen) { PBEKeySpec spec = new PBEKeySpec(password, salt, iterationCount, dkLen << 3); try { SecretKeyFactory skf = Providers.getSecretKeyFactory("PBKDF2With" + alg.algorithm()); return skf.generateSecret(spec).getEncoded(); } catch (InvalidKeySpecException e) { throw new SecurityException(e); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/passwd/SCrypt.java ================================================ // Copyright (C) 2011 - Will Glozer. All rights reserved. package cn.ponfee.commons.jce.passwd; import cn.ponfee.commons.jce.HmacAlgorithms; import cn.ponfee.commons.jce.Providers; import cn.ponfee.commons.jce.digest.HmacUtils; import cn.ponfee.commons.util.Base64UrlSafe; import cn.ponfee.commons.util.SecureRandoms; import com.google.common.base.Preconditions; import javax.crypto.Mac; import javax.crypto.ShortBufferException; import java.util.Arrays; import static cn.ponfee.commons.jce.HmacAlgorithms.ALGORITHM_MAPPING; import static java.lang.Integer.MAX_VALUE; import static java.lang.System.arraycopy; import static java.nio.charset.StandardCharsets.UTF_8; /** * An implementation of the scrypt * key derivation function. This class will attempt to load a native * library containing the optimized C implementation from * http://www.tarsnap.com/scrypt.html * and fall back to the pure Java version if that fails. * * @author Will Glozer * * Reference from internet and with optimization */ public final class SCrypt { private SCrypt() {} private static final char SEPARATOR = '$'; // -------------------------------------------------------------------scrypt public static String create(String passwd, int N, int r, int p) { return create(HmacAlgorithms.HmacSHA256, passwd, N, r, p, 32); } /** * Hash the supplied plaintext password and generate output in the format * described in {@link #scrypt(HmacAlgorithms, byte[], byte[], int, int, int, int)}. * * @param alg HmacAlgorithm. * @param passwd Password. * @param N CPU cost parameter. between 0x01 and 0x0F, 2^15=32768 * @param r Memory cost parameter. between 0x01 and 0xFF * @param p Parallelization parameter. between 0x01 and 0xFF * @param dkLen Intended length, in octets, of the derived key. * @return The hashed password. */ public static String create(HmacAlgorithms alg, String passwd, int N, int r, int p, int dkLen) { Preconditions.checkArgument(N > 0 && N <= 0xF, "N must between 1 and 15"); Preconditions.checkArgument(r > 0 && r <= 0xFF, "r must between 1 and 255"); Preconditions.checkArgument(p > 0 && p <= 0xFF, "p must between 1 and 255"); int algIdx = ALGORITHM_MAPPING.inverse().get(alg) & 0xF; // maximum is 0xF byte[] salt = SecureRandoms.nextBytes(16); byte[] derived = scrypt(alg, passwd.getBytes(UTF_8), salt, 1 << N, r, p, dkLen); String params = Integer.toString(algIdx << 20 | N << 16 | r << 8 | p, 16); return new StringBuilder(12 + ((salt.length + derived.length) << 2) / 3 + 4) .append(SEPARATOR).append("s0").append(SEPARATOR) .append(params).append(SEPARATOR) .append(Base64UrlSafe.encode(salt)).append(SEPARATOR) .append(Base64UrlSafe.encode(derived)).toString(); } /** * Compare the supplied plaintext password to a hashed password. * * @param passwd Plaintext password. * @param hashed scrypt hashed password. * @return true if passwd matches hashed value. */ public static boolean check(String passwd, String hashed) { String[] parts = hashed.split("\\" + SEPARATOR); if (parts.length != 5 || !"s0".equals(parts[1])) { throw new IllegalArgumentException("Invalid hashed value"); } int params = Integer.parseInt(parts[2], 16); byte[] salt = Base64UrlSafe.decode(parts[3]); byte[] actual = Base64UrlSafe.decode(parts[4]); int algIdx = (params >> 20) & 0xF , N = (params >> 16) & 0xF , r = (params >> 8) & 0xFF, p = (params ) & 0xFF; byte[] except = scrypt(ALGORITHM_MAPPING.get(algIdx), passwd.getBytes(UTF_8), salt, 1 << N, r, p, actual.length); return Arrays.equals(actual, except); } /** * Implementation of PBKDF2 (RFC2898). * * @param alg HmacAlgorithm. * @param P password of byte array. * @param S Salt. * @param c Iteration count. * @param dkLen Intended length, in octets, of the derived key. * @return the byte array of DK */ public static byte[] pbkdf2(HmacAlgorithms alg, byte[] P, byte[] S, int c, int dkLen) { Mac mac = HmacUtils.getInitializedMac(alg, Providers.BC, P); int hLen = mac.getMacLength(); // ((long) 1 << 32) - 1 == 4294967295L if (dkLen > 4294967295L * hLen) { throw new SecurityException("Requested key length too long"); } byte[] U = new byte[hLen]; byte[] T = new byte[hLen]; byte[] block = new byte[S.length + 4]; int n = (int) Math.ceil((double) dkLen / hLen); int r = dkLen - (n - 1) * hLen; arraycopy(S, 0, block, 0, S.length); byte[] DK = new byte[dkLen]; for (int i = 1; i <= n; i++) { block[S.length ] = (byte) (i >> 24 & 0xff); block[S.length + 1] = (byte) (i >> 16 & 0xff); block[S.length + 2] = (byte) (i >> 8 & 0xff); block[S.length + 3] = (byte) (i & 0xff); mac.update(block); try { mac.doFinal(U, 0); arraycopy(U, 0, T, 0, hLen); for (int j = 1, k; j < c; j++) { mac.update(U); mac.doFinal(U, 0); for (k = 0; k < hLen; k++) { T[k] ^= U[k]; } } } catch (ShortBufferException | IllegalStateException e) { throw new SecurityException(e); } arraycopy(T, 0, DK, (i - 1) * hLen, (i == n ? r : hLen)); } return DK; } /** * Pure Java implementation of the * scrypt KDF. * * @param alg HmacAlgorithm. * @param P Password. * @param S Salt. * @param N CPU cost parameter. * @param r Memory cost parameter. * @param p Parallelization parameter. * @param dkLen Intended length of the derived key. * @return The derived key. */ public static byte[] scrypt(HmacAlgorithms alg, byte[] P, byte[] S, int N, int r, int p, int dkLen) { if (r > MAX_VALUE / 128 / p) { throw new IllegalArgumentException("Parameter r is too large"); } if (N > MAX_VALUE / 128 / r) { throw new IllegalArgumentException("Parameter N is too large"); } byte[] B = pbkdf2(alg, P, S, 1, (p << 7) * r); byte[] XY = new byte[r << 8], V = new byte[(r << 7) * N]; for (int i = 0; i < p; i++) { smix(B, (i << 7) * r, r, N, V, XY); } return pbkdf2(alg, P, B, 1, dkLen); } // ------------------------------------------------------------------------------private methods private static void smix(byte[] B, int Bi, int r, int N, byte[] V, byte[] XY) { int i, Xi = 0, Yi = r << 7; arraycopy(B, Bi, XY, Xi, Yi); for (i = 0; i < N; i++) { arraycopy(XY, Xi, V, i * Yi, Yi); blockmix_salsa8(XY, Xi, Yi, r); } for (i = 0; i < N; i++) { int j = integerify(XY, Xi, r) & (N - 1); blockxor(V, j * Yi, XY, Xi, Yi); blockmix_salsa8(XY, Xi, Yi, r); } arraycopy(XY, Xi, B, Bi, Yi); } private static void blockmix_salsa8(byte[] BY, int Bi, int Yi, int r) { byte[] X = new byte[64]; arraycopy(BY, Bi + ((2 * r - 1) << 6), X, 0, 64); int i, n, m; for (i = 0, n = r << 1; i < n; i++) { m = i << 6; blockxor(BY, m, X, 0, 64); salsa20_8(X); arraycopy(X, 0, BY, Yi + m, 64); } for (i = 0; i < r; i++) { arraycopy(BY, Yi + (i << 7), BY, Bi + (i << 6), 64); } for (i = 0; i < r; i++) { arraycopy(BY, Yi + (((i << 1) + 1) << 6), BY, Bi + ((i + r) << 6), 64); } } private static int R(int a, int b) { return (a << b) | (a >>> (32 - b)); } private static void salsa20_8(byte[] B) { int[] B32 = new int[16], x = new int[16]; int i; for (i = 0; i < 16; i++) { B32[i] = (B[(i << 2) ] & 0xff) ; B32[i] |= (B[(i << 2) + 1] & 0xff) << 8; B32[i] |= (B[(i << 2) + 2] & 0xff) << 16; B32[i] |= (B[(i << 2) + 3] & 0xff) << 24; } arraycopy(B32, 0, x, 0, 16); for (i = 8; i > 0; i -= 2) { x[4] ^= R(x[0] + x[12], 7); x[8] ^= R(x[4] + x[0], 9); x[12] ^= R(x[8] + x[4], 13); x[0] ^= R(x[12] + x[8], 18); x[9] ^= R(x[5] + x[1], 7); x[13] ^= R(x[9] + x[5], 9); x[1] ^= R(x[13] + x[9], 13); x[5] ^= R(x[1] + x[13], 18); x[14] ^= R(x[10] + x[6], 7); x[2] ^= R(x[14] + x[10], 9); x[6] ^= R(x[2] + x[14], 13); x[10] ^= R(x[6] + x[2], 18); x[3] ^= R(x[15] + x[11], 7); x[7] ^= R(x[3] + x[15], 9); x[11] ^= R(x[7] + x[3], 13); x[15] ^= R(x[11] + x[7], 18); x[1] ^= R(x[0] + x[3], 7); x[2] ^= R(x[1] + x[0], 9); x[3] ^= R(x[2] + x[1], 13); x[0] ^= R(x[3] + x[2], 18); x[6] ^= R(x[5] + x[4], 7); x[7] ^= R(x[6] + x[5], 9); x[4] ^= R(x[7] + x[6], 13); x[5] ^= R(x[4] + x[7], 18); x[11] ^= R(x[10] + x[9], 7); x[8] ^= R(x[11] + x[10], 9); x[9] ^= R(x[8] + x[11], 13); x[10] ^= R(x[9] + x[8], 18); x[12] ^= R(x[15] + x[14], 7); x[13] ^= R(x[12] + x[15], 9); x[14] ^= R(x[13] + x[12], 13); x[15] ^= R(x[14] + x[13], 18); } for (i = 0; i < 16; ++i) { B32[i] = x[i] + B32[i]; } for (i = 0; i < 16; i++) { B[(i << 2) ] = (byte) (B32[i] & 0xff); B[(i << 2) + 1] = (byte) (B32[i] >> 8 & 0xff); B[(i << 2) + 2] = (byte) (B32[i] >> 16 & 0xff); B[(i << 2) + 3] = (byte) (B32[i] >> 24 & 0xff); } } private static void blockxor(byte[] S, int Si, byte[] D, int Di, int len) { for (int i = 0; i < len; i++) { D[Di + i] ^= S[Si + i]; } } private static int integerify(byte[] B, int Bi, int r) { Bi += ((2 * r - 1) << 6); return ((B[Bi ] & 0xff) ) | ((B[Bi + 1] & 0xff) << 8) | ((B[Bi + 2] & 0xff) << 16) | ((B[Bi + 3] & 0xff) << 24); } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/pkcs/CryptoMessageSyntax.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.pkcs; import org.apache.commons.codec.binary.Hex; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.jcajce.JcaCertStore; import org.bouncycastle.cms.*; import org.bouncycastle.cms.jcajce.*; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.DigestCalculatorProvider; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; import org.bouncycastle.util.Store; import java.io.IOException; import java.security.PrivateKey; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import static cn.ponfee.commons.jce.Providers.BC; /** * 加密消息语法:Cryptography Message Syntax * * @author Ponfee */ public final class CryptoMessageSyntax { // ---------------------------------------------------------------------------sign/verify /** * 附原文签名(单人) * @param data * @param key * @param certChain * @return */ public static byte[] sign(byte[] data, PrivateKey key, X509Certificate[] certChain) { return sign(data, Collections.singletonList(key), Collections.singletonList(certChain)); } /** * 附原文签名(多人) * @param data * @param keys * @param certs 证书链(多人list) * @return */ public static byte[] sign(byte[] data, List keys, List certs) { try { CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); DigestCalculatorProvider dcp = new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(); for (int i = 0; i < keys.size(); i++) { gen.addCertificates(new JcaCertStore(Arrays.asList(certs.get(i)))); ContentSigner signer = new JcaContentSignerBuilder(certs.get(i)[0].getSigAlgName()) .setProvider(BC).build(keys.get(i)); JcaSignerInfoGeneratorBuilder jsBuilder = new JcaSignerInfoGeneratorBuilder(dcp); gen.addSignerInfoGenerator(jsBuilder.build(signer, certs.get(i)[0])); } return gen.generate(new CMSProcessableByteArray(data), true).getEncoded(); // true附原文 } catch (OperatorCreationException | CertificateEncodingException | CMSException | IOException e) { throw new SecurityException(e); } } /** * 验签(附原文) * @param signed * @return */ public static void verify(byte[] signed) { try { CMSSignedData sign = new CMSSignedData(signed); // 构建PKCS#7签名数据处理对象 Store store = sign.getCertificates(); JcaSimpleSignerInfoVerifierBuilder builder = new JcaSimpleSignerInfoVerifierBuilder() .setProvider(BC); for (SignerInformation signer : sign.getSignerInfos()) { @SuppressWarnings("unchecked") Collection chain = store.getMatches(signer.getSID()); // 证书链 X509CertificateHolder cert = chain.iterator().next(); // 证书链的第一个为subject cert if (!signer.verify(builder.build(cert))) { String sn = Hex.encodeHexString(cert.getSerialNumber().toByteArray()); String dn = cert.getSubject().toString(); throw new SecurityException("signature verify fail[" + sn + ", " + dn + "]"); } } } catch (OperatorCreationException | CertificateException | CMSException e) { throw new SecurityException(e); } } // ---------------------------------------------------------------------------envelop/unenvelop /** * 构造数字信封 * * PKCSObjectIdentifiers#des_EDE3_CBC * PKCSObjectIdentifiers.RC2_CBC * * new ASN1ObjectIdentifier("1.2.840.113549.3.2"); // RSA_RC2 * new ASN1ObjectIdentifier("1.2.840.113549.3.4"); // RSA_RC4 * * new ASN1ObjectIdentifier("1.3.14.3.2.7"); // DES_CBC * new ASN1ObjectIdentifier("1.2.840.113549.3.7"); // DESede_CBC * * new ASN1ObjectIdentifier("2.16.840.1.101.3.4.1.2"); // AES128_CBC * * @param data * @param cert * @param alg * @return */ public static byte[] envelop(byte[] data, X509Certificate cert, ASN1ObjectIdentifier alg) { try { //添加数字信封 CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator(); edGen.addRecipientInfoGenerator( new JceKeyTransRecipientInfoGenerator(cert).setProvider(BC) ); return edGen.generate( new CMSProcessableByteArray(data), new JceCMSContentEncryptorBuilder(alg).setProvider(BC).build() ).getEncoded(); } catch (CertificateEncodingException | CMSException | IOException e) { throw new SecurityException(e); } } /** * 解数字信封 * @param enveloped * @param privateKey * @return */ public static byte[] unenvelop(byte[] enveloped, X509Certificate cert, PrivateKey privateKey) { try { RecipientInformationStore ris = new CMSEnvelopedData(enveloped).getRecipientInfos(); for (RecipientInformation rin : ris.getRecipients()) { KeyTransRecipientId rid = (KeyTransRecipientId) rin.getRID(); // 匹配 if (cert.getSerialNumber().equals(rid.getSerialNumber())) { // 解密 return rin.getContent( new JceKeyTransEnvelopedRecipient(privateKey).setProvider(BC) ); } } return null; } catch (CMSException e) { throw new SecurityException(e); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/pkcs/PKCS1Signature.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.pkcs; import cn.ponfee.commons.jce.Providers; import java.security.GeneralSecurityException; import java.security.PrivateKey; import java.security.Signature; import java.security.cert.X509Certificate; /** * pkcs1方式的签名/验签工具类 * * @author Ponfee */ public class PKCS1Signature { /** * 签名 * @param data the byte array data to sign * @param privateKey the private key * @param cert the certificate * @return the signature of private key signed */ public static byte[] sign(byte[] data, PrivateKey privateKey, X509Certificate cert) { Signature signature = Providers.getSignature(cert.getSigAlgName()); try { signature.initSign(privateKey); signature.update(data); return signature.sign(); } catch (GeneralSecurityException e) { throw new SecurityException(e); } } /** * 验签 * @param data the origin byte array data * @param signed the byte array of signature * @param cert the certificate * @return {@code true} is success */ public static boolean verify(byte[] data, byte[] signed, X509Certificate cert) { Signature sign = Providers.getSignature(cert.getSigAlgName()); try { sign.initVerify(cert.getPublicKey()); sign.update(data); return sign.verify(signed); } catch (GeneralSecurityException e) { throw new SecurityException(e); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/pkcs/PKCS7Signature.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.pkcs; import cn.ponfee.commons.jce.Providers; import org.apache.commons.codec.binary.Hex; import sun.security.pkcs.ContentInfo; import sun.security.pkcs.PKCS7; import sun.security.pkcs.ParsingException; import sun.security.pkcs.SignerInfo; import sun.security.util.DerValue; import sun.security.x509.AlgorithmId; import sun.security.x509.X500Name; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.Signature; import java.security.SignatureException; import java.security.cert.X509Certificate; /** * pkcs7工具类 * * @author Ponfee */ @SuppressWarnings("restriction") public class PKCS7Signature { /*private static final Map HASH_SIGN_ALG = ImmutableMap.builder() .put("1.2.840.113549.1.1.4", "MD5") .put("1.2.840.113549.1.1.5", "SHA-1") .put("1.2.840.113549.1.1.11", "SHA-256") .put("1.2.840.113549.1.1.12", "SHA-384") .put("1.2.840.113549.1.1.13", "SHA-512") .build();*/ /** * byte流数据签名(单人) * @param privKey * @param cert * @param data * @param attach 是否附原文 * @return */ public static byte[] sign(PrivateKey privKey, X509Certificate cert, byte[] data, boolean attach) { return sign(new PrivateKey[] { privKey }, new X509Certificate[] { cert }, data, attach); } /** * byte流数据签名(多人) * @param privKeys * @param certs * @param data * @param attach * @return */ public static byte[] sign(PrivateKey[] privKeys, X509Certificate[] certs, byte[] data, boolean attach) { ContentInfo contentInfo; if (attach) { contentInfo = new ContentInfo(data); } else { contentInfo = new ContentInfo(ContentInfo.DATA_OID, null); } return sign(contentInfo, data, certs, privKeys); } /** * 文本签名(单人) * @param privKey * @param cert * @param data * @param attach 是否附原文 * @return */ public static byte[] sign(PrivateKey privKey, X509Certificate cert, String data, boolean attach) { return sign(new PrivateKey[] { privKey }, new X509Certificate[] { cert }, data, attach); } /** * 文本签名(多人) * @param privKeys * @param certs * @param data * @param attach * @return */ public static byte[] sign(PrivateKey[] privKeys, X509Certificate[] certs, String data, boolean attach) { try { DerValue dv = null; if (attach) { dv = new DerValue(data); } ContentInfo contentInfo = new ContentInfo(ContentInfo.DATA_OID, dv); return sign(contentInfo, data.getBytes(), certs, privKeys); } catch (IOException e) { throw new RuntimeException(e); } } /** * 附原文的验签(pkcs7方式验签,可验证CMS格式签名) * @param pkcs7Data the pkcs7 byte array data, with origin * @return the origin byte data */ public static byte[] verify(byte[] pkcs7Data) { PKCS7 pkcs7 = getPkcs7(pkcs7Data); byte[] data = getContent(pkcs7); verify(pkcs7, data); return data; } /** * 不附原文的验签(pkcs7方式验签,可验证CMS格式签名) * @param pkcs7Data the pkcs7 byte array data, without origin * @param data the origin byte data * @return */ public static void verify(byte[] pkcs7Data, byte[] data) { verify(getPkcs7(pkcs7Data), data); } public static void verify(PKCS7 pkcs7, byte[] data) { if (data == null || data.length == 0) { throw new IllegalArgumentException("the origin data cannot be null."); } try { for (SignerInfo signed : pkcs7.getSignerInfos()) { if (pkcs7.verify(signed, data) == null) { String certSN = Hex.encodeHexString(signed.getCertificateSerialNumber().toByteArray()); String subjectDN = signed.getCertificate(pkcs7).getSubjectX500Principal().getName(); throw new SecurityException("验签失败[certSN:" + certSN + ";subjectDN:" + subjectDN + "]"); } } } catch (NoSuchAlgorithmException | SignatureException | IOException e) { throw new RuntimeException(e); } } /** * 签名方法体 * @param contentInfo * @param certs * @param keys * @return */ private static byte[] sign(ContentInfo contentInfo, byte[] data, X509Certificate[] certs, PrivateKey[] keys) { SignerInfo[] signs = new SignerInfo[keys.length]; AlgorithmId[] digestAlgorithmIds = new AlgorithmId[keys.length]; for (int i = 0; i < keys.length; i++) { X509Certificate cert = certs[i]; PrivateKey privKey = keys[i]; try { /*AlgorithmId digAlg = AlgorithmId.get(HASH_SIGN_ALG.get(cert.getSigAlgOID())); AlgorithmId encAlg = new AlgorithmId(AlgorithmId.RSAEncryption_oid);*/ AlgorithmId digAlg = AlgorithmId.get(AlgorithmId.getDigAlgFromSigAlg(cert.getSigAlgName())); AlgorithmId encAlg = AlgorithmId.get(AlgorithmId.getEncAlgFromSigAlg(cert.getSigAlgName())); digestAlgorithmIds[i] = digAlg; X500Name name = new X500Name(cert.getIssuerX500Principal().getEncoded()); Signature signer = Providers.getSignature(cert.getSigAlgName()); signer.initSign(privKey); signer.update(data); // signer.update(data, 0, data.length); signs[i] = new SignerInfo(name, cert.getSerialNumber(), digAlg, encAlg, signer.sign()); } catch (Exception e) { throw new RuntimeException(e); } } // 构造PKCS7数据 PKCS7 pkcs7 = new PKCS7(digestAlgorithmIds, contentInfo, certs, signs); try { ByteArrayOutputStream out = new ByteArrayOutputStream(); pkcs7.encodeSignedData(out); out.flush(); return out.toByteArray(); } catch (IOException e) { throw new RuntimeException(e); } } /** * get the pkcs7 from byte array data * @param pkcs7Data * @return */ public static PKCS7 getPkcs7(byte[] pkcs7Data) { try { return new PKCS7(pkcs7Data); } catch (ParsingException e) { throw new IllegalArgumentException("Invalid pacs7 data", e); } } /** * get the origin byte array data from pkcs7 * @param pkcs7 * @return */ public static byte[] getContent(PKCS7 pkcs7) { ContentInfo contentInfo = pkcs7.getContentInfo(); try { byte[] data; if (contentInfo.getContent() == null) { data = contentInfo.getData(); } else { try { data = contentInfo.getContent().getOctetString(); } catch (Exception e) { data = contentInfo.getContent().getDataBytes(); } } return data; } catch (IOException e) { throw new SecurityException("Get content from pkcs7 occur error", e); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/security/DHKeyExchanger.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.security; import cn.ponfee.commons.jce.Providers; import cn.ponfee.commons.jce.symmetric.Algorithm; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import javax.crypto.Cipher; import javax.crypto.KeyAgreement; import javax.crypto.SecretKey; import javax.crypto.interfaces.DHPrivateKey; import javax.crypto.interfaces.DHPublicKey; import java.security.*; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; /** * Diffie-Hellman Key Exchange * Key-Agreement * * 1、生成质数p * 2、找到p的原根,满足: g mod p, g^2 mod p, ..., g^(p-1) mod p * 是各不相同的整数,并且以某种排列方式组成了从1到p-1的所有整数 * 3、对于一个整数b和质数p的一个原根g,可以找到惟一的指数i,使得 * b=g^i mod p, 0<=i<=p-1,指数i称为b的以g为基数的模p的离散对数或者指数, * 该值被记为ind(g ,p(b)) * * 4、用户A选择一个随机数作为私钥XA (a^b) mod p = ((a mod p)^b) mod p * = (g^XA mod p)^XB mod p * = (YA)^XB mod p * * g^(a*b) mod p = g^(b*a) mod p * * @author Ponfee */ public final class DHKeyExchanger { private static final String ALGORITHM = "DH"; // DH算法名称 // -------------------------------------------------------------初始化甲方密钥对 public static Pair initPartAKey() { return initPartAKey(1024); } /** * 初始化甲方密钥 * @param keySize must be a multiple of 64, ranging from 512 to 1024 (inclusive). * @return */ public static Pair initPartAKey(int keySize) { KeyPairGenerator keyPairGenerator = Providers.getKeyPairGenerator(ALGORITHM); keyPairGenerator.initialize(keySize); KeyPair pair = keyPairGenerator.generateKeyPair(); return ImmutablePair.of( (DHPublicKey) pair.getPublic(), // 甲方公钥 (DHPrivateKey) pair.getPrivate() // // 甲方私钥 ); } // -------------------------------------------------------------初始化已方密钥对 /** * 初始化乙方密钥 * @param partAPubKey 甲方公钥 * @return */ public static Pair initPartBKey(byte[] partAPubKey) { return initPartBKey(decodePublicKey(partAPubKey)); } /** * 初始化乙方密钥 * * @param partAPublicKey 甲方公钥 * @return */ public static Pair initPartBKey(DHPublicKey partAPublicKey) { // 由甲方公钥构建乙方密钥 KeyPairGenerator keyPairGen = Providers.getKeyPairGenerator(partAPublicKey.getAlgorithm()); try { keyPairGen.initialize(partAPublicKey.getParams()); } catch (InvalidAlgorithmParameterException e) { throw new SecurityException(e); } KeyPair keyPair = keyPairGen.generateKeyPair(); return ImmutablePair.of( (DHPublicKey) keyPair.getPublic(), // 乙方公钥 (DHPrivateKey) keyPair.getPrivate() // // 乙方私钥 ); } // -------------------------------------------------------------密钥序列化 /** * DHPublicKey convert to byte array * @param key the DHPublicKey * @return byte array encoded of DHPublicKey */ public static byte[] encode(DHPublicKey key) { return key.getEncoded(); } /** * DHPrivateKey convert to byte array * @param key the DHPrivateKey * @return byte array encoded of DHPrivateKey */ public static byte[] encode(DHPrivateKey key) { return key.getEncoded(); } // -------------------------------------------------------------密钥反序列化 /** * 取得私钥 * @param privateKey * @return */ public static DHPrivateKey decodePrivateKey(byte[] privateKey) { KeyFactory keyFactory = Providers.getKeyFactory(ALGORITHM); try { PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(privateKey); return (DHPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec); } catch (InvalidKeySpecException e) { throw new SecurityException(e); } } /** * 取得公钥 * @param publicKey * @return */ public static DHPublicKey decodePublicKey(byte[] publicKey) { KeyFactory keyFactory = Providers.getKeyFactory(ALGORITHM); try { X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(publicKey); return (DHPublicKey) keyFactory.generatePublic(x509KeySpec); } catch (InvalidKeySpecException e) { throw new SecurityException(e); } } /** * 双方公私钥生成(协商)对称密钥 * @param bPriKey * @param aPubKey * @return */ public static SecretKey genSecretKey(byte[] bPriKey, byte[] aPubKey) { return genSecretKey(decodePrivateKey(bPriKey), decodePublicKey(aPubKey)); } /** * 双方公私钥生成(协商)对称密钥 * @param bPriKey * @param aPubKey * @return */ public static SecretKey genSecretKey(DHPrivateKey bPriKey, DHPublicKey aPubKey) { KeyAgreement keyAgree = Providers.getKeyAgreement(aPubKey.getAlgorithm()); try { keyAgree.init(bPriKey); keyAgree.doPhase(aPubKey, true); // 生成对称密钥,使用3DES对称加密,192位的AES被限制出口 return keyAgree.generateSecret(Algorithm.DESede.name()); } catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e) { throw new SecurityException(e); } } /** * 加密
    * @param data 待加密数据 * @param secretKey 双方公私钥协商的对称密钥 * @return */ public static byte[] encrypt(byte[] data, SecretKey secretKey) { Cipher cipher = Providers.getCipher(secretKey.getAlgorithm()); try { cipher.init(Cipher.ENCRYPT_MODE, secretKey); return cipher.doFinal(data); } catch (Exception e) { throw new SecurityException(e); } } /** * 解密
    * @param data 待解密数据 * @param secretKey 双方公私钥协商的对称密钥 * @return */ public static byte[] decrypt(byte[] data, SecretKey secretKey) { Cipher cipher = Providers.getCipher(secretKey.getAlgorithm()); try { cipher.init(Cipher.DECRYPT_MODE, secretKey); return cipher.doFinal(data); } catch (Exception e) { throw new SecurityException(e); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/security/DSASigner.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.security; import cn.ponfee.commons.jce.Providers; import cn.ponfee.commons.util.ObjectUtils; import cn.ponfee.commons.util.UuidUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import java.security.*; import java.security.interfaces.DSAPrivateKey; import java.security.interfaces.DSAPublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; /** * 基于整数有限域离散对数难题 * * DSA签名/验签(只用于数字签名) * * @author Ponfee */ public final class DSASigner { private static final String ALGORITHM = "DSA"; /** * 默认生成密钥 * @return 密钥对象 */ public static Pair initKey() { return initKey(UuidUtils.uuid32(), 1024); } /** * 生成密钥 * @param seed 种子 * @param keySize must be a multiple of 64, r * anging from 512 to 1024 (inclusive). * @return 密钥对象 */ public static Pair initKey(String seed, int keySize) { KeyPairGenerator keygen = Providers.getKeyPairGenerator(ALGORITHM); // 初始化随机产生器 SecureRandom secureRandom = new SecureRandom(); secureRandom.setSeed(seed.getBytes()); keygen.initialize(keySize, secureRandom); KeyPair pair = keygen.genKeyPair(); return ImmutablePair.of( (DSAPublicKey) pair.getPublic(), (DSAPrivateKey) pair.getPrivate() ); } public static DSAPrivateKey decodePrivateKey(byte[] privateKey) { KeyFactory keyFactory = Providers.getKeyFactory(ALGORITHM); try { return (DSAPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKey)); } catch (InvalidKeySpecException e) { throw new SecurityException(e); } } public static DSAPublicKey decodePublicKey(byte[] publicKey) { KeyFactory keyFactory = Providers.getKeyFactory(ALGORITHM); try { return (DSAPublicKey) keyFactory.generatePublic(new X509EncodedKeySpec(publicKey)); } catch (InvalidKeySpecException e) { throw new SecurityException(e); } } /** * 用私钥对信息生成数字签名 * @param data 原文数据 * @param privateKey 私钥 * @return 签名结果 */ public static byte[] sign(byte[] data, byte[] privateKey) { return sign(data, decodePrivateKey(privateKey)); } public static byte[] sign(byte[] data, DSAPrivateKey privateKey) { Signature signature = Providers.getSignature(privateKey.getAlgorithm()); try { signature.initSign(privateKey); signature.update(data); return signature.sign(); } catch (InvalidKeyException | SignatureException e) { throw new SecurityException(e); } } /** * 校验数字签名 * @param origin 原文数据 * @param publicKey 公钥 * @param signed 签名数据 * @return 校验成功返回true 失败返回false * */ public static boolean verify(byte[] origin, byte[] publicKey, byte[] signed) { return verify(origin, decodePublicKey(publicKey), signed); } public static boolean verify(byte[] origin, DSAPublicKey publicKey, byte[] signed) { Signature signature = Providers.getSignature(publicKey.getAlgorithm()); try { signature.initVerify(publicKey); signature.update(origin); return signature.verify(signed); } catch (InvalidKeyException | SignatureException e) { throw new SecurityException(e); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/security/ECDHKeyExchanger.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.security; import cn.ponfee.commons.jce.Providers; import cn.ponfee.commons.jce.symmetric.Algorithm; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import javax.crypto.Cipher; import javax.crypto.KeyAgreement; import javax.crypto.SecretKey; import java.security.*; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; /** * ECDH Key Exchange * Key-Agreement * * @author Ponfee */ public final class ECDHKeyExchanger { private static final String ALGORITHM = "ECDH"; // ECDH算法名称 public static Pair initPartAKey() { return initPartAKey(256); } /** * 初始化甲方密钥 * * @param keySize the key size: 192/224/256/384/521 * @return the key map */ public static Pair initPartAKey(int keySize) { KeyPairGenerator keyPairGenerator = Providers.getKeyPairGenerator(ALGORITHM); keyPairGenerator.initialize(keySize); // must be 256 KeyPair pair = keyPairGenerator.generateKeyPair(); return ImmutablePair.of( (ECPublicKey) pair.getPublic(), // 甲方公钥 (ECPrivateKey) pair.getPrivate() // 甲方私钥 ); } public static Pair initPartBKey(byte[] partAPubKey) { return initPartBKey(decodePublicKey(partAPubKey)); } /** * 初始化乙方密钥 * @param partAPublicKey 甲方公钥 * @return */ public static Pair initPartBKey(ECPublicKey partAPublicKey) { // 由甲方公钥构建乙方密钥 KeyPairGenerator keyPairGen = Providers.getKeyPairGenerator(partAPublicKey.getAlgorithm()); try { keyPairGen.initialize(partAPublicKey.getParams()); } catch (InvalidAlgorithmParameterException e) { throw new SecurityException(e); } KeyPair keyPair = keyPairGen.generateKeyPair(); return ImmutablePair.of( (ECPublicKey) keyPair.getPublic(), // 乙方公钥 (ECPrivateKey) keyPair.getPrivate() // 乙方私钥 ); } /** * ECPublicKey convert to byte array * @param key the ECPublicKey * @return byte array encoded of ECPublicKey */ public static byte[] encode(ECPublicKey key) { return key.getEncoded(); } /** * ECPrivateKey convert to byte array * @param key the ECPrivateKey * @return byte array encoded of ECPrivateKey */ public static byte[] encode(ECPrivateKey key) { return key.getEncoded(); } /** * 取得私钥 * @param privateKey * @return */ public static ECPrivateKey decodePrivateKey(byte[] privateKey) { KeyFactory keyFactory = Providers.getKeyFactory(ALGORITHM); try { PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(privateKey); return (ECPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec); } catch (InvalidKeySpecException e) { throw new SecurityException(e); } } /** * 取得公钥 * @param publicKey * @return */ public static ECPublicKey decodePublicKey(byte[] publicKey) { KeyFactory keyFactory = Providers.getKeyFactory(ALGORITHM); try { X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(publicKey); return (ECPublicKey) keyFactory.generatePublic(x509KeySpec); } catch (InvalidKeySpecException e) { throw new SecurityException(e); } } /** * 双方公私钥生成(协商)对称密钥 * @param bPriKey * @param aPubKey * @return */ public static SecretKey genSecretKey(byte[] bPriKey, byte[] aPubKey) { return genSecretKey(decodePrivateKey(bPriKey), decodePublicKey(aPubKey)); } /** * 双方公私钥生成(协商)对称密钥 * @param bPriKey * @param aPubKey * @return */ public static SecretKey genSecretKey(ECPrivateKey bPriKey, ECPublicKey aPubKey) { KeyAgreement keyAgree = Providers.getKeyAgreement(aPubKey.getAlgorithm()); try { keyAgree.init(bPriKey); keyAgree.doPhase(aPubKey, true); // 生成对称密钥,使用3DES对称加密,192位的AES被限制出口 return keyAgree.generateSecret(Algorithm.DESede.name()); } catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e) { throw new SecurityException(e); } } /** * 加密
    * @param data 待加密数据 * @param secretKey 双方公私钥协商的对称密钥 * @return */ public static byte[] encrypt(byte[] data, SecretKey secretKey) { Cipher cipher = Providers.getCipher(secretKey.getAlgorithm()); try { cipher.init(Cipher.ENCRYPT_MODE, secretKey); return cipher.doFinal(data); } catch (Exception e) { throw new SecurityException(e); } } /** * 解密
    * @param data 待解密数据 * @param secretKey 双方公私钥协商的对称密钥 * @return */ public static byte[] decrypt(byte[] data, SecretKey secretKey) { Cipher cipher = Providers.getCipher(secretKey.getAlgorithm()); try { cipher.init(Cipher.DECRYPT_MODE, secretKey); return cipher.doFinal(data); } catch (Exception e) { throw new SecurityException(e); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/security/ECDSASigner.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.security; import cn.ponfee.commons.jce.Providers; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import java.security.*; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; /** * ECDSA签名算法工具类 * http://blog.csdn.net/qq_30866297/article/details/51465439 * * @author Ponfee */ public final class ECDSASigner { public enum ECDSASignAlgorithms { SHA1withECDSA, SHA256withECDSA, // SHA384withECDSA, SHA512withECDSA } private static final String ALGORITHM = "EC"; public static Pair generateKeyPair() { return generateKeyPair(256); } /** * 密钥生成 * @param keySize the key size: 192/224/256/384/521/571 * @return ec key map */ public static Pair generateKeyPair(int keySize) { KeyPairGenerator keyPairGen = Providers.getKeyPairGenerator(ALGORITHM); keyPairGen.initialize(keySize); KeyPair keyPair = keyPairGen.generateKeyPair(); return ImmutablePair.of( (ECPublicKey) keyPair.getPublic(), (ECPrivateKey) keyPair.getPrivate() ); } /** * ECPublicKey convert to byte array * @param key the ECPublicKey * @return byte array encoded of ECPublicKey */ public static byte[] encode(ECPublicKey key) { return key.getEncoded(); } /** * ECPrivateKey convert to byte array * @param key the ECPrivateKey * @return byte array encoded of ECPrivateKey */ public static byte[] encode(ECPrivateKey key) { return key.getEncoded(); } /** * get ECPublicKey from byte array * @param publicKey * @return */ public static ECPublicKey decodePublicKey(byte[] publicKey) { KeyFactory keyFactory = Providers.getKeyFactory(ALGORITHM); try { return (ECPublicKey) keyFactory.generatePublic(new X509EncodedKeySpec(publicKey)); } catch (InvalidKeySpecException e) { throw new SecurityException(e); } } /** * get ECPrivateKey from byte array * @param privateKey * @return */ public static ECPrivateKey decodePrivateKey(byte[] privateKey) { KeyFactory keyFactory = Providers.getKeyFactory(ALGORITHM); try { return (ECPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKey)); } catch (InvalidKeySpecException e) { throw new SecurityException(e); } } public static byte[] signSha1(byte[] data, ECPrivateKey privateKey) { return sign(data, privateKey, ECDSASignAlgorithms.SHA1withECDSA); } public static boolean verifySha1(byte[] data, byte[] signed, ECPublicKey publicKey) { return verify(data, signed, publicKey, ECDSASignAlgorithms.SHA1withECDSA); } public static byte[] signSha256(byte[] data, ECPrivateKey privateKey) { return sign(data, privateKey, ECDSASignAlgorithms.SHA256withECDSA); } public static boolean verifySha256(byte[] data, byte[] signed, ECPublicKey publicKey) { return verify(data, signed, publicKey, ECDSASignAlgorithms.SHA256withECDSA); } public static byte[] signSha512(byte[] data, ECPrivateKey privateKey) { return sign(data, privateKey, ECDSASignAlgorithms.SHA512withECDSA); } public static boolean verifySha512(byte[] data, byte[] signed, ECPublicKey publicKey) { return verify(data, signed, publicKey, ECDSASignAlgorithms.SHA512withECDSA); } private static byte[] sign(byte[] data, ECPrivateKey privateKey, ECDSASignAlgorithms algorithm) { Signature signature = Providers.getSignature(algorithm.name()); try { signature.initSign(privateKey); signature.update(data); return signature.sign(); } catch (InvalidKeyException | SignatureException e) { throw new SecurityException(e); } } private static boolean verify(byte[] data, byte[] signed, ECPublicKey publicKey, ECDSASignAlgorithms algorithm) { Signature signature = Providers.getSignature(algorithm.name()); try { signature.initVerify(publicKey); signature.update(data); return signature.verify(signed); } catch (InvalidKeyException | SignatureException e) { throw new SecurityException(e); } } /*public static byte[] encrypt(byte[] data, T key) { return docrypt(data, key, Cipher.ENCRYPT_MODE); } public static byte[] decrypt(byte[] encrypted, T key) { return docrypt(encrypted, key, Cipher.DECRYPT_MODE); } private static byte[] docrypt(byte[] data, Key key, int cryptMode) { try { Cipher cipher = Providers.getCipher(key.getAlgorithm()); cipher.init(cryptMode, key); return cipher.doFinal(data); } catch (Exception e) { throw new SecurityException(e); } }*/ } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/security/KeyStoreResolver.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.security; import cn.ponfee.commons.jce.Providers; import cn.ponfee.commons.jce.cert.X509CertUtils; import cn.ponfee.commons.jce.digest.DigestUtils; import cn.ponfee.commons.util.SecureRandoms; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.security.*; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; /** * 密钥库解析类 * * @author Ponfee */ public class KeyStoreResolver { private static final SecureRandom SECURE_RANDOM = new SecureRandom(SecureRandoms.generateSeed(20)); public enum KeyStoreType { JKS, PKCS12 } private final KeyStore keyStore; public KeyStoreResolver(KeyStoreType type) { this(type, null); } public KeyStoreResolver(KeyStoreType type, String storePassword) { this(type, (InputStream) null, storePassword); } public KeyStoreResolver(KeyStoreType type, byte[] keyStore, String storePassword) { this(type, new ByteArrayInputStream(keyStore), storePassword); } /** * 创建密钥库 * @param type 密钥库类型 * @param input 密钥库输入流数据 * @param storePassword 用于解锁密钥库 */ public KeyStoreResolver(KeyStoreType type, InputStream input, String storePassword) { this.keyStore = Providers.getKeyStore(type.name()); try (InputStream inputStream = input) { this.keyStore.load(inputStream, toCharArray(storePassword)); } catch (Exception e) { throw new SecurityException(e); } } /** * 添加证书 * @param alias 别名 * @param cert 证书 */ public void setCertificateEntry(String alias, Certificate cert) { try { checkAliasNotExists(alias); this.keyStore.setCertificateEntry(alias, cert); } catch (KeyStoreException e) { throw new SecurityException(e); } } /** * 设置私钥 * @param alias 别名 * @param key 私钥 * @param keyPassword 私钥加锁密码 * @param chain */ public final void setKeyEntry(String alias, PrivateKey key, String keyPassword, Certificate[] chain) { try { checkAliasNotExists(alias); this.keyStore.setKeyEntry(alias, key, keyPassword.toCharArray(), chain); } catch (KeyStoreException e) { throw new SecurityException(e); } } /** * set key entry * * @param alias * @param encryptedPkcs8Key * @param chain * @see RSAPrivateKeys#toEncryptedPkcs8(java.security.interfaces.RSAPrivateKey, String) */ public final void setKeyEntry(String alias, byte[] encryptedPkcs8Key, Certificate[] chain) { try { checkAliasNotExists(alias); this.keyStore.setKeyEntry(alias, encryptedPkcs8Key, chain); } catch (KeyStoreException e) { throw new SecurityException(e); } } public byte[] export(String storePassword) { ByteArrayOutputStream out = new ByteArrayOutputStream(); export(out, storePassword); return out.toByteArray(); } /** * 导出密钥库 * @param out 目标输出流 * @param storePassword 设置要导出密钥库的密码 */ public void export(OutputStream out, String storePassword) { try { keyStore.store(out, toCharArray(storePassword)); out.flush(); } catch (Exception e) { throw new SecurityException(e); } } /** * 枚举密钥库条目 * @return */ public List listAlias() { try { List alias = new ArrayList<>(); Enumeration e = keyStore.aliases(); while (e.hasMoreElements()) { alias.add(e.nextElement()); } return alias; } catch (KeyStoreException e) { throw new SecurityException(e); } } public void delAlias(String alias) { try { if (keyStore.containsAlias(alias)) { keyStore.deleteEntry(alias);// 删除别名对应的条目 } } catch (KeyStoreException e) { throw new SecurityException(e); } } public String getFirstAlias() { try { return keyStore.aliases().nextElement(); } catch (KeyStoreException e) { throw new SecurityException(e); } } public Certificate getCertificate() { return getCertificate(getFirstAlias()); } /** * 获取证书 * * @param alias * @return */ public Certificate getCertificate(String alias) { try { //if (!keyStore.isCertificateEntry(alias)) { // pfx cert isNotCertificateEntry // throw new SecurityException(alias + " is not certificate entry."); //} return keyStore.getCertificate(alias); } catch (KeyStoreException e) { throw new SecurityException(e); } } public PrivateKey getPrivateKey(String keyPassword) { return getPrivateKey(getFirstAlias(), keyPassword); } /** * 获取私钥 * @param alias 别名 * @param keyPassword the password for recovering the PrivateKey * @return */ public PrivateKey getPrivateKey(String alias, String keyPassword) { try { if (!keyStore.isKeyEntry(alias)) { throw new SecurityException("alias[" + alias + "] is not key entry."); } return (PrivateKey) keyStore.getKey(alias, toCharArray(keyPassword)); } catch (UnrecoverableKeyException e) { throw new SecurityException("invalid key password: " + keyPassword, e); } catch (KeyStoreException | NoSuchAlgorithmException e) { throw new SecurityException(e); } } public X509Certificate[] getX509CertChain() { return getX509CertChain(getFirstAlias()); } /** * 获取证书链 * @param alias * @return */ public X509Certificate[] getX509CertChain(String alias) { try { if (!keyStore.isKeyEntry(alias)) { throw new SecurityException("alias[" + alias + "] is not key entry."); } Certificate[] certs = keyStore.getCertificateChain(alias); X509Certificate[] x509Certchain = new X509Certificate[certs.length]; for (int i = 0; i < certs.length; i++) { x509Certchain[i] = (X509Certificate) certs[i]; } return x509Certchain; } catch (KeyStoreException e) { throw new SecurityException(e); } } public SSLContext getSSLContext(String keyPassword) { return this.getSSLContext(keyPassword, null); } /** * 获取SSLContext * @param keyPassword the password for recovering the PrivateKey * @param trustStore 受信任的证书库 * @return */ public SSLContext getSSLContext(String keyPassword, KeyStore trustStore) { String algorithm = "SunX509"; try { TrustManager[] trusts = null; if (trustStore != null) { TrustManagerFactory tmf = Providers.getTrustManagerFactory(algorithm); tmf.init(trustStore); trusts = tmf.getTrustManagers(); } KeyManagerFactory kmf = Providers.getKeyManagerFactory(algorithm); kmf.init(this.keyStore, toCharArray(keyPassword)); SSLContext context = Providers.getSSLContext("TLS"); context.init(kmf.getKeyManagers(), trusts, SECURE_RANDOM); return context; } catch (KeyStoreException | UnrecoverableKeyException | NoSuchAlgorithmException | KeyManagementException e) { throw new SecurityException(e); } } public KeyStore getKeyStore() { return keyStore; } public static KeyStoreResolver loadFromPem(String pem) { KeyStoreResolver resolver = new KeyStoreResolver(KeyStoreType.JKS); // X509CertUtils.loadFromPem(pem) <==> X509CertUtils.loadX509Cert(pem.getBytes()) resolver.setCertificateEntry(DigestUtils.md5Hex(pem), X509CertUtils.loadPemCert(pem)); return resolver; } private void checkAliasNotExists(String alias) throws KeyStoreException { if (keyStore.containsAlias(alias)) { throw new SecurityException("alias[" + alias + "] is exists."); } } private static char[] toCharArray(String str) { if (null == str || str.length() == 0) { return null; } else { return str.toCharArray(); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/security/RSACryptor.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.security; import cn.ponfee.commons.io.Closeables; import cn.ponfee.commons.io.Files; import cn.ponfee.commons.jce.Providers; import cn.ponfee.commons.jce.RSASignAlgorithms; import javax.crypto.Cipher; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; import java.security.*; import java.security.interfaces.RSAKey; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import static cn.ponfee.commons.jce.RSACipherPaddings.ECB_PKCS1PADDING; import static cn.ponfee.commons.jce.RSACipherPaddings.NONE_NOPADDING; /** *

     * 基于大整数因式分解的数学难题(费马小定理)
     * n=p*q, p,q互质
     *
     * RSA Cryptor
     * 加/解密
     * 签名/验签
     * 
    * * @author Ponfee */ public final class RSACryptor { private RSACryptor() {} static final String ALG_RSA = "RSA"; public static RSAKeyPair generateKeyPair() { return generateKeyPair(1024); } /** * 密钥生成 * @param keySize the RSA key size, optional is 512 or 1028 * or 2048 or 4096 * @return RSAKeyPair */ public static RSAKeyPair generateKeyPair(int keySize) { KeyPairGenerator keyPairGen = Providers.getKeyPairGenerator(ALG_RSA); keyPairGen.initialize(keySize); KeyPair pair = keyPairGen.generateKeyPair(); return new RSAKeyPair( (RSAPrivateKey) pair.getPrivate(), (RSAPublicKey) pair.getPublic() ); } // ---------------------------------------sign/verify--------------------------------------- /** * MD5 sign * @param data * @param privateKey * @return */ public static byte[] signMd5(byte[] data, RSAPrivateKey privateKey) { return sign(data, privateKey, RSASignAlgorithms.MD5withRSA); } /** * SHA1 sign * @param data * @param privateKey * @return */ public static byte[] signSha1(byte[] data, RSAPrivateKey privateKey) { return sign(data, privateKey, RSASignAlgorithms.SHA1withRSA); } /** * SHA256 sign * @param data * @param privateKey * @return */ public static byte[] signSha256(byte[] data, RSAPrivateKey privateKey) { return sign(data, privateKey, RSASignAlgorithms.SHA256withRSA); } /** * verify MD5 signature * @param data * @param publicKey * @param signed * @return */ public static boolean verifyMd5(byte[] data, RSAPublicKey publicKey, byte[] signed) { return verify(data, publicKey, signed, RSASignAlgorithms.MD5withRSA); } /** * verify SHA1 signature * @param data * @param publicKey * @param signed * @return */ public static boolean verifySha1(byte[] data, RSAPublicKey publicKey, byte[] signed) { return verify(data, publicKey, signed, RSASignAlgorithms.SHA1withRSA); } /** * verify SHA256 signature * @param data * @param publicKey * @param signed * @return */ public static boolean verifySha256(byte[] data, RSAPublicKey publicKey, byte[] signed) { return verify(data, publicKey, signed, RSASignAlgorithms.SHA256withRSA); } /** *
         *   1、可以通过修改生成密钥的长度来调整密文长度
         *   2、不管明文长度是多少,RSA生成的密文长度总是固定的
         *   3、明文长度不能超过密钥长度:
         *     1)SUN JDK默认的RSA加密实现不允许明文长度超过密钥长度减去11字节(byte):比如1024位(bit)的密钥,
         *        则待加密的明文最长为1024/8-11=117(byte)
         *     2)BouncyCastle提供的加密算法能够支持到的RSA明文长度最长为密钥长度
         *   4、每次生成的密文都不一致证明加密算法安全:这是因为在加密前使用RSA/None/PKCS1Padding对明文信息进行了
         *      随机数填充,为了防止已知明文攻击,随机长度的填充来防止攻击者知道明文的长度。
         *   5、javax.crypto.Cipher是线程不安全的
         * 
    * * 大数据分块加密 * @param data 源数据 * @param key * @return */ public static byte[] encrypt(byte[] data, T key) { return docrypt(data, key, Cipher.ENCRYPT_MODE, true); } public static byte[] encryptNoPadding(byte[] data, T key) { return docrypt(data, key, Cipher.ENCRYPT_MODE, false); } public static void encrypt(InputStream input, T key, OutputStream out) { docrypt(input, key, out, Cipher.ENCRYPT_MODE, true); } public static void encryptNoPadding(InputStream input, T key, OutputStream out) { docrypt(input, key, out, Cipher.ENCRYPT_MODE, false); } /** * 大数据分块解密 * @param encrypted * @param key * @return */ public static byte[] decrypt(byte[] encrypted, T key) { return docrypt(encrypted, key, Cipher.DECRYPT_MODE, true); } public static byte[] decryptNoPadding(byte[] encrypted, T key) { return docrypt(encrypted, key, Cipher.DECRYPT_MODE, false); } public static void decrypt(InputStream input, T key, OutputStream out) { docrypt(input, key, out, Cipher.DECRYPT_MODE, true); } public static void decryptNoPadding(InputStream input, T key, OutputStream out) { docrypt(input, key, out, Cipher.DECRYPT_MODE, false); } // -----------------------------------private methods------------------------------------- private static int getBlockSize(int cryptMode, T key) { return (cryptMode == Cipher.ENCRYPT_MODE) ? key.getModulus().bitLength() / 8 - 11 : key.getModulus().bitLength() / 8; } private static void docrypt(InputStream input, T key, OutputStream out, int cryptMode, boolean isPadding) { // Providers.getCipher(key.getAlgorithm()) Cipher cipher = Providers.getCipher( key.getAlgorithm() + (isPadding ? ECB_PKCS1PADDING.transform() : NONE_NOPADDING.transform()) ); try { cipher.init(cryptMode, key); byte[] buffer = new byte[getBlockSize(cryptMode, key)]; for (int len; (len = input.read(buffer)) != Files.EOF;) { out.write(cipher.doFinal(buffer, 0, len)); } out.flush(); } catch (Exception e) { throw new SecurityException(e); } finally { Closeables.console(input); } } /** * JDK supported: * Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); * * BC supported: * Cipher.getInstance("RSA/None/NoPadding", Providers.BC); * Cipher.getInstance("RSA/ECB/NOPADDING", Providers.BC); * ... * @param data * @param key * @param cryptMode * @param isPadding * @return */ private static byte[] docrypt(byte[] data, T key, int cryptMode, boolean isPadding) { int blockSize = getBlockSize(cryptMode, key); // Providers.getCipher(key.getAlgorithm()) Cipher cipher = Providers.getCipher( key.getAlgorithm() + (isPadding ? ECB_PKCS1PADDING.transform() : NONE_NOPADDING.transform()) ); try { cipher.init(cryptMode, key); ByteArrayOutputStream out = new ByteArrayOutputStream(data.length); byte[] block; for (int offSet = 0, len = data.length; offSet < len; offSet += blockSize) { block = cipher.doFinal(data, offSet, Math.min(blockSize, len - offSet)); out.write(block, 0, block.length); } out.flush(); return out.toByteArray(); } catch (Exception e) { throw new SecurityException(e); } } /** * 数据签名 * @param data * @param privateKey * @param alg * @return */ private static byte[] sign(byte[] data, RSAPrivateKey privateKey, RSASignAlgorithms alg) { Signature signature = Providers.getSignature(alg.name()); try { signature.initSign(privateKey); signature.update(data); return signature.sign(); } catch (SignatureException | InvalidKeyException e) { throw new SecurityException(e); } } /** * 验证签名 * @param data * @param publicKey * @param signed * @param alg * @return */ private static boolean verify(byte[] data, RSAPublicKey publicKey, byte[] signed, RSASignAlgorithms alg) { Signature signature = Providers.getSignature(alg.name()); try { signature.initVerify(publicKey); signature.update(data); return signature.verify(signed); } catch (InvalidKeyException | SignatureException e) { throw new SecurityException(e); } } /** * RSA密钥对 */ public static final class RSAKeyPair implements Serializable { private static final long serialVersionUID = -1592700389671199076L; private final RSAPrivateKey privateKey; private final RSAPublicKey publicKey; private RSAKeyPair(RSAPrivateKey privateKey, RSAPublicKey publicKey) { this.privateKey = privateKey; this.publicKey = publicKey; } public RSAPrivateKey getPrivateKey() { return privateKey; } public RSAPublicKey getPublicKey() { return publicKey; } public String toPkcs8PrivateKey() { return RSAPrivateKeys.toPkcs8(privateKey); } public String toPkcs1PrivateKey() { return RSAPrivateKeys.toPkcs1(privateKey); } public String toPkcs8PublicKey() { return RSAPublicKeys.toPkcs8(publicKey); } public String toPkcs1PublicKey() { return RSAPublicKeys.toPkcs1(publicKey); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/security/RSAPrivateKeys.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.security; import cn.ponfee.commons.jce.Providers; import cn.ponfee.commons.jce.cert.X509CertUtils; import org.bouncycastle.asn1.*; import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.PKCS8Generator; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.bouncycastle.openssl.jcajce.JcaPEMWriter; import org.bouncycastle.operator.InputDecryptorProvider; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.OutputEncryptor; import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; import org.bouncycastle.pkcs.PKCSException; import org.bouncycastle.pkcs.jcajce.JcePKCSPBEInputDecryptorProviderBuilder; import org.bouncycastle.pkcs.jcajce.JcePKCSPBEOutputEncryptorBuilder; import org.bouncycastle.util.io.pem.PemObject; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.io.StringWriter; import java.math.BigInteger; import java.security.KeyFactory; import java.security.interfaces.RSAPrivateCrtKey; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.RSAPrivateKeySpec; import java.util.Base64; /** *
     *   PEM格式:
     *    PKCS#1:RSA PRIVATE KEY
     *    PKCS#8:PRIVATE KEY
     *    PKCS#8加密:ENCRYPTED PRIVATE KEY
     * 
     * @see org.bouncycastle.openssl.PEMParser
     *   ("CERTIFICATE REQUEST",      new PKCS10CertificationRequestParser());
     *   ("NEW CERTIFICATE REQUEST",  new PKCS10CertificationRequestParser());
     *   ("CERTIFICATE",              new X509CertificateParser());
     *   ("TRUSTED CERTIFICATE",      new X509TrustedCertificateParser());
     *   ("X509 CERTIFICATE",         new X509CertificateParser());
     *   ("X509 CRL",                 new X509CRLParser());
     *   ("PKCS7",                    new PKCS7Parser());
     *   ("CMS",                      new PKCS7Parser());
     *   ("ATTRIBUTE CERTIFICATE",    new X509AttributeCertificateParser());
     *   ("EC PARAMETERS",            new ECCurveParamsParser());
     *   ("PUBLIC KEY",               new PublicKeyParser());
     *   ("RSA PUBLIC KEY",           new RSAPublicKeyParser());
     *   ("RSA PRIVATE KEY",          new KeyPairParser(new RSAKeyPairParser()));
     *   ("DSA PRIVATE KEY",          new KeyPairParser(new DSAKeyPairParser()));
     *   ("EC PRIVATE KEY",           new KeyPairParser(new ECDSAKeyPairParser()));
     *   ("ENCRYPTED PRIVATE KEY",    new EncryptedPrivateKeyParser());
     *   ("PRIVATE KEY",              new PrivateKeyParser());
     * 
    * * RSA Private Key Convert * * @author Ponfee */ public final class RSAPrivateKeys { private RSAPrivateKeys() {} /** * Build RSAPrivateKey with modulus and privateExponent * * @param modulus * @param privateExponent * @return the RSAPrivateKey */ public static RSAPrivateKey toRSAPrivateKey(BigInteger modulus, BigInteger privateExponent) { try { return (RSAPrivateKey) Providers.getKeyFactory(RSACryptor.ALG_RSA).generatePrivate( new RSAPrivateKeySpec(modulus, privateExponent) // RSAPrivateCrtKeySpec ); } catch (Exception ex) { throw new SecurityException(ex); } } /** * 对于某些jdk不支持私钥加密及验签,所以要反转私钥为公钥 * 私钥伪造公钥来支持加密及验签 * * @param privateKey * @return */ public static RSAPublicKey inverse(RSAPrivateKey privateKey) { return RSAPublicKeys.toRSAPublicKey( privateKey.getModulus(), privateKey.getPrivateExponent() ); } // ------------------------------------------------------------EXTRACT PUBLIC KEY FROM PRIVATE KEY /** * extract public key from private key * * @param privateKey * @return */ public static RSAPublicKey extractPublicKey(RSAPrivateKey privateKey) { if (!(privateKey instanceof RSAPrivateCrtKey)) { throw new ClassCastException("The key expect a java.security.interfaces.RSAPrivateCrtKey, " + "but is " + privateKey.getClass().getCanonicalName()); } RSAPrivateCrtKey key = (RSAPrivateCrtKey) privateKey; return RSAPublicKeys.toRSAPublicKey(key.getModulus(), key.getPublicExponent()); } // ------------------------------------------------------------PRIVATE KEY PKCS1 FORMAT /** * MIICXAIBAAKBgQCo20qAU4iyZIInpu2XzNXYHhFv6FVC/N1vsfz4ZrwX3VQaFsXf720QBkuP34Y31jy/6B+OB7DzklDBTnJXltCX2XdHyBY5WQYMX9rsQrfbvUL47u676FD1T8o1/e+cEOGS75mKQIQjyt1zCZOl26Hy6x4TPeBSdVzFNYSr7KNjLQIDAQABAoGALFd51v0YtpACRdtmJSjbNyeeOJ7wVOkGVWCOJ8UCu9mZTkiQqd+76itdCGkQW/VceqDAOJH4e93+auTozeuC1w/srrUuPASUsE/5VLwPBvR90kToC28B59wAdl31nD0KM8COq/9EdrkVkz6XO7KAik9gr3PLHCXu4i7tzf9djlkCQQDhagX7hsjJZ554Pr0uBhXHwMmhiLPOK1b3884Wc1rHTMShVGF3DJH6stJV5hXwzjXBwSA8zCbxGDsqVdmbQBkPAkEAv8Sv4GtdXCucN0GsZcRhvOmGhNkhQU7W3qkPqLaAvBzfCzT/Kty4YEWTlF+sCP1+/Chl7AHf4FQ+3ivNkftoAwJAEZ0YRJQ+okY/gsPcQnllQEuXNdEZw7VtQUjCxMxUvpgIEVcnmobX7VAF0YJ+GmfymWY+36FQNaygCunUbCYxDwJAQaKxS8+Tmbt3cVYyCnbnuP/4wbmLb03rrzQQHv+wGjKLiMtv1pzLInBN7ce9Gyqgbu/oypltpdtP1T0K1D9HPwJBAKskq4+amIGnJ7FxGiPXAi0+Y96QPbAR/WjXiIaLRvwRa4Jwy8U6E6HHfYYTeuuB7h1ga6kyzfB7nUeGyeWSSkI= * * convert private key to base64 pkcs1 format * @param privateKey * @return pkcs1 encoded base64 pkcs1 format private key */ public static String toPkcs1(RSAPrivateKey privateKey) { PrivateKeyInfo privKeyInfo = PrivateKeyInfo.getInstance(privateKey.getEncoded()); try { return Base64.getEncoder().encodeToString( privKeyInfo.parsePrivateKey().toASN1Primitive().getEncoded() ); } catch (IOException e) { throw new SecurityException(e); } } /** * parse private key from pkcs1 format * @param pkcs1PrivateKey * @return RSAPrivateKey */ public static RSAPrivateKey fromPkcs1(String pkcs1PrivateKey) { ASN1EncodableVector v1 = new ASN1EncodableVector(); v1.add(new ASN1ObjectIdentifier(PKCSObjectIdentifiers.rsaEncryption.getId())); v1.add(DERNull.INSTANCE); ASN1EncodableVector v2 = new ASN1EncodableVector(); v2.add(new ASN1Integer(0)); v2.add(new DERSequence(v1)); v2.add(new DEROctetString(Base64.getDecoder().decode(pkcs1PrivateKey))); ASN1Sequence seq = new DERSequence(v2); try { return fromPkcs8(Base64.getEncoder().encodeToString(seq.getEncoded())); } catch (IOException e) { throw new SecurityException(e); } } // ------------------------------------------------------------PRIVATE KEY PKCS1 PEM FORMAT /** * -----BEGIN RSA PRIVATE KEY----- * MIICXAIBAAKBgQCo20qAU4iyZIInpu2XzNXYHhFv6FVC/N1vsfz4ZrwX3VQaFsXf * 720QBkuP34Y31jy/6B+OB7DzklDBTnJXltCX2XdHyBY5WQYMX9rsQrfbvUL47u67 * 6FD1T8o1/e+cEOGS75mKQIQjyt1zCZOl26Hy6x4TPeBSdVzFNYSr7KNjLQIDAQAB * AoGALFd51v0YtpACRdtmJSjbNyeeOJ7wVOkGVWCOJ8UCu9mZTkiQqd+76itdCGkQ * W/VceqDAOJH4e93+auTozeuC1w/srrUuPASUsE/5VLwPBvR90kToC28B59wAdl31 * nD0KM8COq/9EdrkVkz6XO7KAik9gr3PLHCXu4i7tzf9djlkCQQDhagX7hsjJZ554 * Pr0uBhXHwMmhiLPOK1b3884Wc1rHTMShVGF3DJH6stJV5hXwzjXBwSA8zCbxGDsq * VdmbQBkPAkEAv8Sv4GtdXCucN0GsZcRhvOmGhNkhQU7W3qkPqLaAvBzfCzT/Kty4 * YEWTlF+sCP1+/Chl7AHf4FQ+3ivNkftoAwJAEZ0YRJQ+okY/gsPcQnllQEuXNdEZ * w7VtQUjCxMxUvpgIEVcnmobX7VAF0YJ+GmfymWY+36FQNaygCunUbCYxDwJAQaKx * S8+Tmbt3cVYyCnbnuP/4wbmLb03rrzQQHv+wGjKLiMtv1pzLInBN7ce9Gyqgbu/o * ypltpdtP1T0K1D9HPwJBAKskq4+amIGnJ7FxGiPXAi0+Y96QPbAR/WjXiIaLRvwR * a4Jwy8U6E6HHfYYTeuuB7h1ga6kyzfB7nUeGyeWSSkI= * -----END RSA PRIVATE KEY----- *

    * * new PemObject("RSA PRIVATE KEY", toPkcs1Encode(privateKey)) * * convert private key to pem format * @param privateKey * @return encoded base64 pkcs1 pem fromat private key */ public static String toPkcs1Pem(RSAPrivateKey privateKey) { return X509CertUtils.exportToPem(privateKey); } /** * parse private key from pem format * @param pemPrivateKey encoded pem format private key * @return */ public static RSAPrivateKey fromPkcs1Pem(String pemPrivateKey) { try (Reader reader = new StringReader(pemPrivateKey); PEMParser pemParser = new PEMParser(reader) ) { PEMKeyPair keyPair = (PEMKeyPair) pemParser.readObject(); JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(Providers.BC); //PublicKey publicKey = converter.getPublicKey(keyPair.getPublicKeyInfo()); return (RSAPrivateKey) converter.getPrivateKey(keyPair.getPrivateKeyInfo()); } catch (IOException e) { throw new SecurityException(e); } } // ------------------------------------------------------------PRIVATE KEY PKCS8 FORMAT /** * MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKjbSoBTiLJkgiem7ZfM1dgeEW/oVUL83W+x/PhmvBfdVBoWxd/vbRAGS4/fhjfWPL/oH44HsPOSUMFOcleW0JfZd0fIFjlZBgxf2uxCt9u9Qvju7rvoUPVPyjX975wQ4ZLvmYpAhCPK3XMJk6XbofLrHhM94FJ1XMU1hKvso2MtAgMBAAECgYAsV3nW/Ri2kAJF22YlKNs3J544nvBU6QZVYI4nxQK72ZlOSJCp37vqK10IaRBb9Vx6oMA4kfh73f5q5OjN64LXD+yutS48BJSwT/lUvA8G9H3SROgLbwHn3AB2XfWcPQozwI6r/0R2uRWTPpc7soCKT2Cvc8scJe7iLu3N/12OWQJBAOFqBfuGyMlnnng+vS4GFcfAyaGIs84rVvfzzhZzWsdMxKFUYXcMkfqy0lXmFfDONcHBIDzMJvEYOypV2ZtAGQ8CQQC/xK/ga11cK5w3QaxlxGG86YaE2SFBTtbeqQ+otoC8HN8LNP8q3LhgRZOUX6wI/X78KGXsAd/gVD7eK82R+2gDAkARnRhElD6iRj+Cw9xCeWVAS5c10RnDtW1BSMLEzFS+mAgRVyeahtftUAXRgn4aZ/KZZj7foVA1rKAK6dRsJjEPAkBBorFLz5OZu3dxVjIKdue4//jBuYtvTeuvNBAe/7AaMouIy2/WnMsicE3tx70bKqBu7+jKmW2l20/VPQrUP0c/AkEAqySrj5qYgacnsXEaI9cCLT5j3pA9sBH9aNeIhotG/BFrgnDLxToTocd9hhN664HuHWBrqTLN8HudR4bJ5ZJKQg== * * convert private key to pkcs8 format * @param privateKey * @return pkcs8 base64 pkcs8 format private key */ public static String toPkcs8(RSAPrivateKey privateKey) { return Base64.getEncoder().encodeToString(privateKey.getEncoded()); } /** * parse private key from pkcs8 format * @param pkcs8PrivateKey encoded base64 pkcs8 fromat private key * @return RSAPrivateKey */ public static RSAPrivateKey fromPkcs8(String pkcs8PrivateKey) { byte[] bytes = Base64.getDecoder().decode(pkcs8PrivateKey); KeyFactory keyFactory = Providers.getKeyFactory(RSACryptor.ALG_RSA); try { return (RSAPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(bytes)); } catch (InvalidKeySpecException e) { throw new SecurityException(e); } } // ------------------------------------------------------------Transform PRIVATE KEY ENCRYPTED PKCS8 PEM FORMAT /** * -----BEGIN ENCRYPTED PRIVATE KEY----- * MIICrjAoBgoqhkiG9w0BDAEDMBoEFA4ymXPHyGS9n5BRIibZHRkJ+idqAgIEAASC * AoA5TutO4A/F9MX4h3278DBIihEEetPGU7GxbmCySVsJraL8tXueMEZrLSDWC9rl * StJR82Umv0H8fpiMKlzYyHQjmqclY7e367+tQ87EqEenZNCCC1uveYLfBM7kZ+4/ * m276IY6sNuJqYp3k8RrIBfYG9KCCQ7ywWiORuKvGbNAytFW9H+Z9JAyO5E70ysWe * pvfeLCFbRgJ0BOkm1aSyk81MN/alZ9d6a3d/UJLnnV42u7dS++mONVi66y6gKoON * Y5xVX2ICPRGoIvZmXeeQYzH02XFpTn9+vQ//KG4iGErXjNaWLSWpLyCY2qHQWwQ1 * YB43aX7xYOiKUN24PMiwGKwjhBaKzakMt4lcmGNd6ZcwPC+ghhkmpPcCu4gSabQk * Etv5tmkChBLIjRgxmmnlEYLDl68e8vth5RquJvwB4zBOQkDo9tPcwWnOk4vbvGJP * w1WXfwHU4X4oy+FOiOTe7+lOTN6CeXxfx8a91h5zS1tA16bQLAgTA7oJGPD3yHpF * aBYXPNzwIOpAUkEqCSbZuuYZ5uidjrm7rV8nSjXu0fkYxzpXbQAps+NBtlKHFXNC * JWvj2g6UFtk/RoT4ghgtTx11DZUh+GsKbCjLM52omDDNLK8K+GOEFzElWiZIEWfh * yoUAN9KuuCprC3RsqV4K70nQewuiX5NBt9xYcaKVBQ5jRgL0xnVpquyFeFXrY0Ge * EDgOZTSUVVxlbNQ+iwBb52cD2cFmPcIszSNpQ85cS8eISdYiwaW42yUa7LYpO98S * jyTNnLzOMRt04gcBcp71EOWEheE9Ui6AqweSA6LUHulSbOK4+4oKtdKH5KjdcWYI * WDgTEoIGOC3se36z3v5Mlr8h * -----END ENCRYPTED PRIVATE KEY----- *

    * * convert private key to encrypted pem format * @param privateKey the private key * @param outEncryptor the out encryptor * @return */ public static String toEncryptedPkcs8Pem(RSAPrivateKey privateKey, OutputEncryptor outEncryptor) { try (StringWriter stringWriter = new StringWriter(); JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter) ) { PrivateKeyInfo privKeyInfo = PrivateKeyInfo.getInstance(privateKey.getEncoded()); pemWriter.writeObject(new PKCS8Generator(privKeyInfo, outEncryptor)); pemWriter.flush(); return stringWriter.toString(); } catch (IOException e) { throw new SecurityException(e); } } /** * convert private key to encrypted pem format * default {@link PKCSObjectIdentifiers#pbeWithSHAAnd3_KeyTripleDES_CBC} * algorithm for encrypt * * @param privateKey * @param password * @return private key pkcs8 pem format */ public static String toEncryptedPkcs8Pem(RSAPrivateKey privateKey, String password) { JcePKCSPBEOutputEncryptorBuilder builder = new JcePKCSPBEOutputEncryptorBuilder( PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC ); try { return toEncryptedPkcs8Pem(privateKey, builder.build(password.toCharArray())); } catch (OperatorCreationException e) { throw new SecurityException(e); } } /** * Encrypts private key to pkcs#8 format * * @param privateKey the private key * @param password the password * @return a string of encrypted private key pkcs#8 format */ public static String toEncryptedPkcs8(RSAPrivateKey privateKey, String password) { JcePKCSPBEOutputEncryptorBuilder builder = new JcePKCSPBEOutputEncryptorBuilder( PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC ); try { return toEncryptedPkcs8(privateKey, builder.build(password.toCharArray())); } catch (OperatorCreationException e) { throw new SecurityException(e); } } /** * Encrypts private key to pkcs#8 format * * @param privateKey the private key * @param outEncryptor the OutputEncryptor * @return a string of encrypted private key pkcs#8 format */ public static String toEncryptedPkcs8(RSAPrivateKey privateKey, OutputEncryptor outEncryptor) { try { PrivateKeyInfo privKeyInfo = PrivateKeyInfo.getInstance(privateKey.getEncoded()); PemObject pem = new PKCS8Generator(privKeyInfo, outEncryptor).generate(); return Base64.getEncoder().encodeToString(pem.getContent()); } catch (IOException e) { throw new SecurityException(e); } } // ------------------------------------------------------------Parse PRIVATE KEY ENCRYPTED PKCS8 PEM FORMAT /** * Parse private key from encrypted pem format * * @param encryptedPem he encrypted pem * @param inputDecryptor the InputDecryptorProvider * @return RSAPrivateKey */ public static RSAPrivateKey fromEncryptedPkcs8Pem(String encryptedPem, InputDecryptorProvider inputDecryptor) { try (Reader reader = new StringReader(encryptedPem); PEMParser pemParser = new PEMParser(reader) ) { PKCS8EncryptedPrivateKeyInfo encrypted = (PKCS8EncryptedPrivateKeyInfo) pemParser.readObject(); PrivateKeyInfo pkInfo = encrypted.decryptPrivateKeyInfo(inputDecryptor); JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(Providers.BC); return (RSAPrivateKey) converter.getPrivateKey(pkInfo); } catch (IOException | PKCSException e) { throw new SecurityException(e); } } /** * Parse private key from encrypted pem format

    * default {@link JcePKCSPBEInputDecryptorProviderBuilder} input decryptor

    * * @param encryptedPem the encrypted pem * @param password the password * @return RSAPrivateKey */ public static RSAPrivateKey fromEncryptedPkcs8Pem(String encryptedPem, String password) { JcePKCSPBEInputDecryptorProviderBuilder builder = new JcePKCSPBEInputDecryptorProviderBuilder(); return fromEncryptedPkcs8Pem(encryptedPem, builder.build(password.toCharArray())); } public static RSAPrivateKey fromEncryptedPkcs8(String encryptedPrivateKey, String password) { return fromEncryptedPkcs8(Base64.getDecoder().decode(encryptedPrivateKey), password); } /** * Parse private key from encrypted format * * @param encryptedPrivateKey the encryptedPrivateKey * @param password the password * @return RSAPrivateKey */ public static RSAPrivateKey fromEncryptedPkcs8(byte[] encryptedPrivateKey, String password) { JcePKCSPBEInputDecryptorProviderBuilder builder = new JcePKCSPBEInputDecryptorProviderBuilder(); return fromEncryptedPkcs8(encryptedPrivateKey, builder.build(password.toCharArray())); } public static RSAPrivateKey fromEncryptedPkcs8(String encryptedPrivateKey, InputDecryptorProvider inputDecryptor) { return fromEncryptedPkcs8(Base64.getDecoder().decode(encryptedPrivateKey), inputDecryptor); } /** * Parse private key from encrypted format * * @param encryptedPrivateKey the encryptedPrivateKey * @param inputDecryptor the InputDecryptorProvider * @return RSAPrivateKey */ public static RSAPrivateKey fromEncryptedPkcs8(byte[] encryptedPrivateKey, InputDecryptorProvider inputDecryptor) { try { PKCS8EncryptedPrivateKeyInfo encrypted = new PKCS8EncryptedPrivateKeyInfo(encryptedPrivateKey); PrivateKeyInfo pkInfo = encrypted.decryptPrivateKeyInfo(inputDecryptor); JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(Providers.BC); return (RSAPrivateKey) converter.getPrivateKey(pkInfo); } catch (IOException | PKCSException e) { throw new SecurityException(e); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/security/RSAPublicKeys.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.security; import cn.ponfee.commons.jce.Providers; import cn.ponfee.commons.jce.cert.X509CertUtils; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.crypto.params.RSAKeyParameters; import org.bouncycastle.crypto.util.PublicKeyFactory; import org.bouncycastle.openssl.PEMParser; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.math.BigInteger; import java.security.KeyFactory; import java.security.cert.Certificate; import java.security.interfaces.RSAKey; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.RSAPublicKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Base64; /** * PKCS#8 PEM:PUBLIC KEY * * RSA Public Key convert * * @author Ponfee */ public final class RSAPublicKeys { private RSAPublicKeys() {} /** * Build RSAPublicKey with modulus and publicExponent * * @param modulus * @param publicExponent * @return the RSAPublicKey */ public static RSAPublicKey toRSAPublicKey(BigInteger modulus, BigInteger publicExponent) { try { return (RSAPublicKey) Providers.getKeyFactory(RSACryptor.ALG_RSA).generatePublic( new RSAPublicKeySpec(modulus, publicExponent) ); } catch (Exception ex) { throw new SecurityException(ex); } } /** * 证书中获取公钥 * @param cert * @return */ public static RSAPublicKey getPublicKey(Certificate cert) { return (RSAPublicKey) cert.getPublicKey(); } /** * 对于某些jdk不支持公钥解密及签名,所以要反转公钥为私钥 * 公钥伪造成私钥来支持解密及签名 * * @param publicKey * @return */ public static RSAPrivateKey inverse(RSAPublicKey publicKey) { return RSAPrivateKeys.toRSAPrivateKey( publicKey.getModulus(), publicKey.getPublicExponent() ); } // ------------------------------------------------------------PUBLIC KEY PKCS1 FORMAT /** * MIGJAoGBAKVpbo/Wum3G5ciustuKNGvPX/rgkdZw33QGqBR5UOKUoD5/h/IeQlS7ladX+oa+ciVCXyP854Zq+0RVQ7x87DfAohLmyXlIGOJ7KLJZkUWDYSG0WsPbnTOEmxQcRzqEV5g9pVHIjgPH6N/j6HHKRs5xDEd3pVpoRBZKEncbZ85xAgMBAAE= *

    * * ASN1 Encode * * The RSA Public key PEM file is specific for RSA keys.

    * convert public key to pkcs1 format

    * @param publicKey * @return */ public static String toPkcs1(RSAPublicKey publicKey) { SubjectPublicKeyInfo spkInfo = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()); try { return Base64.getEncoder().encodeToString(spkInfo.parsePublicKey().getEncoded()); } catch (IOException e) { throw new SecurityException(e); } } /** * parse public key from pkcs1 format * @param pkcs1PublicKey encoded base64 pkcs1 public key * @return */ public static RSAPublicKey fromPkcs1(String pkcs1PublicKey) { byte[] bytes = Base64.getDecoder().decode(pkcs1PublicKey); try { org.bouncycastle.asn1.pkcs.RSAPublicKey pk = org.bouncycastle.asn1.pkcs.RSAPublicKey.getInstance(bytes); RSAPublicKeySpec keySpec = new RSAPublicKeySpec(pk.getModulus(), pk.getPublicExponent()); return (RSAPublicKey) Providers.getKeyFactory(RSACryptor.ALG_RSA).generatePublic(keySpec); } catch (InvalidKeySpecException e) { throw new SecurityException(e); } } // ------------------------------------------------------------PUBLIC KEY X509 PKCS8 FORMAT /** * MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQClaW6P1rptxuXIrrLbijRrz1/64JHWcN90BqgUeVDilKA+f4fyHkJUu5WnV/qGvnIlQl8j/OeGavtEVUO8fOw3wKIS5sl5SBjieyiyWZFFg2EhtFrD250zhJsUHEc6hFeYPaVRyI4Dx+jf4+hxykbOcQxHd6VaaEQWShJ3G2fOcQIDAQAB * * DER Encode * * convert public key to x509 pkcs8 fromat * @param publicKey * @return */ public static String toPkcs8(RSAPublicKey publicKey) { return Base64.getEncoder().encodeToString(publicKey.getEncoded()); } /** * parse public key from base64 X509 pkcs8 fromat * @param pkcs8PublicKey encoded base64 x509 pkcs8 fromat * @return RSAPublicKey */ public static RSAPublicKey fromPkcs8(String pkcs8PublicKey) { byte[] keyBytes = Base64.getDecoder().decode(pkcs8PublicKey); X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); KeyFactory keyFactory = Providers.getKeyFactory(RSACryptor.ALG_RSA); try { return (RSAPublicKey) keyFactory.generatePublic(x509KeySpec); } catch (InvalidKeySpecException e) { throw new SecurityException(e); } } // ------------------------------------------------------------PUBLIC KEY X509 PKCS8 PEM FORMAT /** * -----BEGIN PUBLIC KEY----- * MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQClaW6P1rptxuXIrrLbijRrz1/6 * 4JHWcN90BqgUeVDilKA+f4fyHkJUu5WnV/qGvnIlQl8j/OeGavtEVUO8fOw3wKIS * 5sl5SBjieyiyWZFFg2EhtFrD250zhJsUHEc6hFeYPaVRyI4Dx+jf4+hxykbOcQxH * d6VaaEQWShJ3G2fOcQIDAQAB * -----END PUBLIC KEY----- *

    * * new PemObject("RSA PUBLIC KEY", toPkcs8Encode(publicKey)) * * convert public key to pem fromat (pkcs8) * @param publicKey * @return */ public static String toPkcs8Pem(RSAPublicKey publicKey) { return X509CertUtils.exportToPem(publicKey); } /** * parse public key from pem format * @param pemPublicKey encoded pem public key * @return */ public static RSAPublicKey fromPkcs8Pem(String pemPublicKey) { try (Reader reader = new StringReader(pemPublicKey); PEMParser pemParser = new PEMParser(reader) ) { SubjectPublicKeyInfo subPkInfo = (SubjectPublicKeyInfo) pemParser.readObject(); RSAKeyParameters param = (RSAKeyParameters) PublicKeyFactory.createKey(subPkInfo); RSAPublicKeySpec keySpec = new RSAPublicKeySpec(param.getModulus(), param.getExponent()); return (RSAPublicKey) Providers.getKeyFactory(RSACryptor.ALG_RSA).generatePublic(keySpec); } catch (IOException | InvalidKeySpecException e) { throw new SecurityException(e); } } /** * Gets the rsa key length * * @param rsaKey the rsa key * @return a int number of key bit length */ public static int getKeyLength(RSAKey rsaKey) { return rsaKey.getModulus().bitLength(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/sm/SM2.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.sm; import cn.ponfee.commons.jce.ECParameters; import cn.ponfee.commons.util.Base64UrlSafe; import cn.ponfee.commons.util.Bytes; import cn.ponfee.commons.util.SecureRandoms; import com.google.common.collect.ImmutableMap; import org.apache.commons.lang3.ArrayUtils; import org.bouncycastle.crypto.AsymmetricCipherKeyPair; import org.bouncycastle.crypto.params.ECPrivateKeyParameters; import org.bouncycastle.crypto.params.ECPublicKeyParameters; import org.bouncycastle.math.ec.ECPoint; import java.math.BigInteger; import java.util.Arrays; import java.util.Map; import java.util.Objects; /** * 基于椭圆曲线 * * new BigInteger("0") // 0为十进制数字符串表示 * SM2 asymmetric cipher implementation * support encrypt/decrypt * and sign/verify signature * * reference the internet code and refactor optimization * * @author Ponfee */ public final class SM2 { public static final String PRIVATE_KEY = "SM2PrivateKey"; public static final String PUBLIC_KEY = "SM2PublicKey"; private static final int KEY_LENGTH = SM3Digest.getDigestSize(); private final byte[] key = new byte[KEY_LENGTH]; private final SM3Digest sm3keybase = SM3Digest.getInstance(); // sm3 keybase private final SM3Digest sm3c3 = SM3Digest.getInstance(); // sm3 c3 private final byte[] x, y; private byte keyOffset; private int count; private SM2(ECPoint publicKey, BigInteger privateKey, BigInteger n) { Objects.requireNonNull(publicKey, "public key cannot be null."); Objects.requireNonNull(privateKey, "private key cannot be null."); ECPoint point = publicKey.multiply(privateKey); // S = [h]point byte[] x1 = point.normalize().getXCoord().toBigInteger().toByteArray(); byte[] y1 = point.normalize().getYCoord().toBigInteger().toByteArray(); int byteCount = (int) Math.ceil(n.bitLength() / 8.0D); this.x = new byte[byteCount]; this.y = new byte[byteCount]; Bytes.tailCopy(x1, 0, x1.length, this.x, 0, this.x.length); Bytes.tailCopy(y1, 0, y1.length, this.y, 0, this.y.length); this.reset(); } private void reset() { this.sm3keybase.reset(); this.sm3keybase.update(this.x); this.sm3keybase.update(this.y); this.sm3c3.reset(); this.sm3c3.update(this.x); this.count = 1; nextKey(); } private void nextKey() { SM3Digest sm3keycur = SM3Digest.getInstance(this.sm3keybase); sm3keycur.update(Bytes.toBytes(count)); sm3keycur.doFinal(key, 0); // update key this.keyOffset = 0; this.count++; } private void encrypt(byte[] data) { this.sm3c3.update(data); for (int i = 0, len = data.length; i < len;) { if (keyOffset == KEY_LENGTH) { nextKey(); } data[i++] ^= key[keyOffset++]; } } private void decrypt(byte[] data) { for (int i = 0, len = data.length; i < len;) { if (keyOffset == KEY_LENGTH) { nextKey(); } data[i++] ^= key[keyOffset++]; } this.sm3c3.update(data); } private byte[] doFinal() { this.sm3c3.update(this.y); byte[] digest = this.sm3c3.doFinal(); reset(); return digest; } public static Map generateKeyPair() { return generateKeyPair(ECParameters.SM2_BEST); } /** * generate the SM2 key pair * a public key and a private key * @param ecParam the ec parameter * @return sm2 key store the map */ public static Map generateKeyPair(ECParameters ecParam) { AsymmetricCipherKeyPair key = ecParam.keyPairGenerator.generateKeyPair(); ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters) key.getPrivate(); ECPublicKeyParameters ecpub = (ECPublicKeyParameters) key.getPublic(); BigInteger priKey = ecpriv.getD(); // k ECPoint pubKey = ecpub.getQ(); // K = [k]POINT_G return ImmutableMap.of(PRIVATE_KEY, priKey.toByteArray(), PUBLIC_KEY, pubKey.getEncoded(false)); } public static byte[] getPublicKey(Map keyMap) { return keyMap.get(PUBLIC_KEY); } public static byte[] getPrivateKey(Map keyMap) { return keyMap.get(PRIVATE_KEY); } public static ECPoint getPublicKey(byte[] publicKey) { return getPublicKey(ECParameters.SM2_BEST, publicKey); } public static ECPoint getPublicKey(ECParameters ecParam, byte[] publicKey) { return ecParam.curve.decodePoint(publicKey); } public static BigInteger getPrivateKey(byte[] privateKey) { return new BigInteger(1, privateKey); } public static byte[] encrypt(byte[] publicKey, byte[] data) { return encrypt(ECParameters.SM2_BEST, publicKey, data); } /** * encrypt data by public key * @param ecParam the ec parameter * @param publicKey SM2 public key, point K = [k]POINT_G * @param data the data to be encrypt * @return encrypted byte array */ public static byte[] encrypt(ECParameters ecParam, byte[] publicKey, byte[] data) { if (ArrayUtils.isEmpty(publicKey) || ArrayUtils.isEmpty(data)) { return null; } // create C1 point AsymmetricCipherKeyPair key = ecParam.keyPairGenerator.generateKeyPair(); // point M ECPublicKeyParameters ecPub = (ECPublicKeyParameters) key.getPublic(); ECPrivateKeyParameters ecPri = (ECPrivateKeyParameters) key.getPrivate(); SM2 sm2 = new SM2(ecParam.curve.decodePoint(publicKey), ecPri.getD(), ecParam.n); byte[] c1 = ecPub.getQ().getEncoded(false); // generate random r, C1=M+rK byte[] c2 = Arrays.copyOf(data, data.length); // C2=rG sm2.encrypt(c2); // 加密数据 byte[] c3 = sm2.doFinal(); // 摘要 // return: C1(65) + C2(data.length) + C3(32) // C1 = {0x04, X byte array, Y byte array} return Bytes.concat(c1, c2, c3); } public static byte[] decrypt(byte[] privateKey, byte[] encrypted) { return decrypt(ECParameters.SM2_BEST, privateKey, encrypted); } /** * decrypt the encrypted byte array data by private key * @param ecParam the ec parameter * @param privateKey SM2 private key * @param encrypted the encrypted byte array data * @return the origin byte array data */ public static byte[] decrypt(ECParameters ecParam, byte[] privateKey, byte[] encrypted) { if (ArrayUtils.isEmpty(privateKey) || ArrayUtils.isEmpty(encrypted)) { return null; } // 分解加密数据 // C1公钥 = 1位标志位+64位公钥(共65位) // C2数据 = encrypted.length-C1-C3 // C3摘要 = 32 int c1Len = 65, c3Len = 32, c2Len = encrypted.length - (c1Len + c3Len); byte[] c1 = Arrays.copyOf(encrypted, c1Len); byte[] c2 = Arrays.copyOfRange(encrypted, c1Len, c1Len + c2Len); byte[] c3 = Arrays.copyOfRange(encrypted, c1Len + c2Len, encrypted.length); SM2 sm2 = new SM2(getPublicKey(ecParam, c1), getPrivateKey(privateKey), ecParam.n); sm2.decrypt(c2); // 解密 if (!Arrays.equals(c3, sm2.doFinal())) { throw new SecurityException("Invalid SM3 digest."); } //返回解密结果 return c2; } // ----------------------------------------------------------------signature sign public static byte[] sign(byte[] data, byte[] publicKey, byte[] privateKey) { return sign(data, null, publicKey, privateKey); } public static byte[] sign(byte[] data, byte[] ida, byte[] publicKey, byte[] privateKey) { return sign(ECParameters.SM2_BEST, data, ida, publicKey, privateKey); } public static byte[] sign(ECParameters ecParam, byte[] data, byte[] publicKey, byte[] privateKey) { return sign(ecParam, data, null, publicKey, privateKey); } /** * sm2 sign * @param ecParam the ec parameter * @param data 签名信息 * @param ida 签名方唯一标识,如:Alice@gmail.com * @param publicKey 公钥 * @param privateKey 私钥 * @return 签名信息 */ public static byte[] sign(ECParameters ecParam, byte[] data, byte[] ida, byte[] publicKey, byte[] privateKey) { ECPoint pubKey = getPublicKey(ecParam, publicKey); BigInteger priKey = getPrivateKey(privateKey); SM3Digest sm3 = SM3Digest.getInstance(); sm3.update(calcZ(sm3, ecParam, ida, pubKey)); sm3.update(data); BigInteger e = new BigInteger(1, sm3.doFinal()), k ,r; do { k = SecureRandoms.random(ecParam.n); ECPoint p = ecParam.pointG.multiply(k).normalize(); r = e.add(p.getXCoord().toBigInteger()).mod(ecParam.n); } while (r.equals(BigInteger.ZERO) || r.add(k).equals(ecParam.n)); BigInteger n1 = priKey.add(BigInteger.ONE).modInverse(ecParam.n); BigInteger n2 = k.subtract(r.multiply(priKey)); BigInteger n3 = n1.multiply(n2.mod(ecParam.n)); BigInteger s = n3.mod(ecParam.n); return new Signature(r, s, ecParam.n).toByteArray(); } // ----------------------------------------------------------------signature verify public static boolean verify(byte[] data, byte[] signed, byte[] publicKey) { return verify(data, null, signed, publicKey); } public static boolean verify(byte[] data, byte[] ida, byte[] signed, byte[] publicKey) { return verify(ECParameters.SM2_BEST, data, ida, signed, publicKey); } public static boolean verify(ECParameters ecParam, byte[] data, byte[] signed, byte[] publicKey) { return verify(ecParam, data, null, signed, publicKey); } /** * verify signature * @param ecParam the ec parameter * @param data * @param ida * @param signed * @param publicKey * @return */ public static boolean verify(ECParameters ecParam, byte[] data, byte[] ida, byte[] signed, byte[] publicKey) { Signature signature = new Signature(signed, ecParam.n); if ( isNotBetween(signature.r, BigInteger.ONE, ecParam.n) || isNotBetween(signature.s, BigInteger.ONE, ecParam.n)) { return false; } ECPoint pubKey = getPublicKey(ecParam, publicKey); SM3Digest sm3 = SM3Digest.getInstance(); sm3.update(calcZ(sm3, ecParam, ida, pubKey)); sm3.update(data); BigInteger e = new BigInteger(1, sm3.doFinal()); BigInteger t = signature.r.add(signature.s).mod(ecParam.n); if (t.equals(BigInteger.ZERO)) { return false; } ECPoint p1 = ecParam.pointG.multiply(signature.s).normalize(); ECPoint p2 = pubKey.multiply(t).normalize(); BigInteger x1 = p1.add(p2).normalize().getXCoord().toBigInteger(); BigInteger r = e.add(x1).mod(ecParam.n); return r.equals(signature.r); } public static boolean checkPublicKey(byte[] publicKey) { return checkPublicKey(getPublicKey(publicKey)); } public static boolean checkPublicKey(ECParameters ecParam, byte[] publicKey) { return checkPublicKey(ecParam, getPublicKey(ecParam, publicKey)); } public static boolean checkPublicKey(ECPoint publicKey) { return checkPublicKey(ECParameters.SM2_BEST, publicKey); } public static boolean checkPublicKey(ECParameters ecParam, ECPoint publicKey) { if (publicKey.isInfinity()) { return false; } BigInteger x = publicKey.getXCoord().toBigInteger(); BigInteger y = publicKey.getYCoord().toBigInteger(); if ( isNotBetween(x, BigInteger.ZERO, ecParam.p) || isNotBetween(y, BigInteger.ZERO, ecParam.p)) { return false; } BigInteger x1 = x.pow(3).add(ecParam.a.multiply(x)) .add(ecParam.b).mod(ecParam.p); BigInteger y1 = y.pow(2).mod(ecParam.p); return y1.equals(x1) && publicKey.multiply(ecParam.n).isInfinity(); } static byte[] calcZ(SM3Digest sm3, ECParameters ecParam, ECPoint pubKey) { return calcZ(sm3, ecParam, null, pubKey); } /** * 取得用户标识字节数组 * @param ecParam the ec parameter * @param ida * @param pubKey * @return */ static byte[] calcZ(SM3Digest sm3, ECParameters ecParam, byte[] ida, ECPoint pubKey) { sm3.reset(); if (ida != null && ida.length > 0) { int idaBitLen = ida.length << 3; // ida.length*8 sm3.update((byte) (idaBitLen & 0xFF00)); sm3.update((byte) (idaBitLen & 0x00FF)); sm3.update(ida); } sm3.update(ecParam.a.toByteArray()); sm3.update(ecParam.b.toByteArray()); sm3.update(ecParam.gx.toByteArray()); sm3.update(ecParam.gy.toByteArray()); sm3.update(pubKey.getXCoord().toBigInteger().toByteArray()); sm3.update(pubKey.getYCoord().toBigInteger().toByteArray()); return sm3.doFinal(); } // -------------------------------------------------------------------private methods /** * check the number is not between min(inclusion) and max(exclusion) * @param number the value * @param min the minimum number, inclusion * @param max the maximum number, exclusion * @return {@code true} is not between */ private static boolean isNotBetween(BigInteger number, BigInteger min, BigInteger max) { return number.compareTo(min) < 0 || number.compareTo(max) >= 0; } /** * SM2WithSM3 signature */ private static class Signature implements java.io.Serializable { private static final long serialVersionUID = -2732762291362285185L; final BigInteger r; final BigInteger s; final BigInteger n; Signature(BigInteger r, BigInteger s, BigInteger n) { this.r = r; this.s = s; this.n = n; } Signature(byte[] signed, BigInteger n) { this.n = n; int byteCount = (int) Math.ceil(this.n.bitLength() / 8.0D); this.r = new BigInteger(1, Arrays.copyOfRange(signed, 0, byteCount)); this.s = new BigInteger(1, Arrays.copyOfRange(signed, byteCount, byteCount << 1)); } byte[] toByteArray() { int byteCount = (int) Math.ceil(this.n.bitLength() / 8.0D); byte[] out = new byte[byteCount << 1]; byte[] r1 = this.r.toByteArray(); byte[] s1 = this.s.toByteArray(); Bytes.tailCopy(r1, 0, r1.length, out, 0, byteCount); Bytes.tailCopy(s1, 0, s1.length, out, byteCount, byteCount); return out; } @Override public String toString() { return Base64UrlSafe.encode(toByteArray()); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/sm/SM2KeyExchanger.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.sm; import cn.ponfee.commons.jce.ECParameters; import cn.ponfee.commons.util.Bytes; import cn.ponfee.commons.util.SecureRandoms; import org.bouncycastle.math.ec.ECPoint; import java.io.ByteArrayOutputStream; import java.io.Serializable; import java.math.BigInteger; import java.util.Arrays; /** * SM2 key exchange implementation * the final, partA and partB get the same symmetric key * * reference the internet code and refactor optimization * * @author Ponfee */ public class SM2KeyExchanger implements Serializable { private static final long serialVersionUID = 8553046425593791291L; private static final BigInteger TWO = BigInteger.valueOf(2); private BigInteger rA; private ECPoint RA; private ECPoint V; private byte[] key; private final ECParameters ecParam; private final BigInteger w; private final ECPoint publicKey; private final BigInteger privateKey; private final byte[] Z; public SM2KeyExchanger(ECPoint publicKey, BigInteger privateKey) { this(null, publicKey, privateKey, ECParameters.SM2_BEST); } public SM2KeyExchanger(byte[] ida, ECPoint publicKey, BigInteger privateKey) { this(ida, publicKey, privateKey, ECParameters.SM2_BEST); } public SM2KeyExchanger(ECPoint publicKey, BigInteger privateKey, ECParameters ecParam) { this(null, publicKey, privateKey, ecParam); } public SM2KeyExchanger(byte[] ida, ECPoint publicKey, BigInteger privateKey, ECParameters ecParam) { this.ecParam = ecParam; this.w = TWO.pow((int) Math.ceil(ecParam.n.bitLength() * 1.0 / 2) - 1); this.publicKey = publicKey; this.privateKey = privateKey; this.Z = SM2.calcZ(SM3Digest.getInstance(), ecParam, ida, publicKey); } /** * 密钥协商第一步(甲方) * @return TransportEntity */ public TransportEntity step1PartA() { rA = SecureRandoms.random(ecParam.n); RA = ecParam.pointG.multiply(rA).normalize(); return new TransportEntity(RA.getEncoded(false), null, Z, publicKey); } /** * 密钥协商第二步(乙方) * @param entity1 传输实体 * @return TransportEntity */ public TransportEntity step2PartB(TransportEntity entity1) { BigInteger rB = SecureRandoms.random(ecParam.n); ECPoint RB = ecParam.pointG.multiply(rB).normalize(); this.rA = rB; this.RA = RB; BigInteger x2 = RB.getXCoord().toBigInteger(); x2 = w.add(x2.and(w.subtract(BigInteger.ONE))); BigInteger tB = privateKey.add(x2.multiply(rB)).mod(ecParam.n); ECPoint RA = ecParam.curve.decodePoint(entity1.R).normalize(); BigInteger x1 = RA.getXCoord().toBigInteger(); x1 = w.add(x1.and(w.subtract(BigInteger.ONE))); ECPoint aPublicKey = ecParam.curve.decodePoint(entity1.K).normalize(); ECPoint temp = aPublicKey.add(RA.multiply(x1).normalize()).normalize(); ECPoint V = temp.multiply(ecParam.bcSpec.getH().multiply(tB)).normalize(); if (V.isInfinity()) { throw new IllegalStateException(); } this.V = V; byte[] xV = V.getXCoord().toBigInteger().toByteArray(); byte[] yV = V.getYCoord().toBigInteger().toByteArray(); key = kdf(Bytes.concat(xV, yV, entity1.Z, this.Z), 16); SM3Digest sm3 = SM3Digest.getInstance(); byte[] data = digest(sm3, xV, entity1.Z, this.Z, RA, RB); sm3.update((byte) 0x02); sm3.update(yV); sm3.update(data); byte[] sB = sm3.doFinal(); return new TransportEntity(RB.getEncoded(false), sB, this.Z, publicKey); } /** * 密钥协商第三步(甲方) * @param entity2 传输实体 * @return TransportEntity */ public TransportEntity step3PartA(TransportEntity entity2) { BigInteger x1 = RA.getXCoord().toBigInteger(); x1 = w.add(x1.and(w.subtract(BigInteger.ONE))); BigInteger tA = privateKey.add(x1.multiply(rA)).mod(ecParam.n); ECPoint RB = ecParam.curve.decodePoint(entity2.R).normalize(); BigInteger x2 = RB.getXCoord().toBigInteger(); x2 = w.add(x2.and(w.subtract(BigInteger.ONE))); ECPoint bPublicKey = ecParam.curve.decodePoint(entity2.K).normalize(); ECPoint temp = bPublicKey.add(RB.multiply(x2).normalize()).normalize(); ECPoint U = temp.multiply(ecParam.bcSpec.getH().multiply(tA)).normalize(); if (U.isInfinity()) { throw new IllegalStateException(); } this.V = U; byte[] xU = U.getXCoord().toBigInteger().toByteArray(); byte[] yU = U.getYCoord().toBigInteger().toByteArray(); key = kdf(Bytes.concat(xU, yU, this.Z, entity2.Z), 16); SM3Digest sm3 = SM3Digest.getInstance(); byte[] data = digest(sm3, xU, this.Z, entity2.Z, RA, RB); sm3.update((byte) 0x02); sm3.update(yU); sm3.update(data); data = sm3.doFinal(); if (!Arrays.equals(entity2.S, data)) { return null; } data = digest(sm3, xU, this.Z, entity2.Z, RA, RB); sm3.update((byte) 0x03); sm3.update(yU); sm3.update(data); byte[] sA = sm3.doFinal(); return new TransportEntity(RA.getEncoded(false), sA, this.Z, publicKey); } /** * 密钥协商最后一(第四)步(乙方) * @param entity3 传输实体 */ public boolean step4PartB(TransportEntity entity3) { byte[] xV = V.getXCoord().toBigInteger().toByteArray(); byte[] yV = V.getYCoord().toBigInteger().toByteArray(); ECPoint RA = ecParam.curve.decodePoint(entity3.R).normalize(); SM3Digest sm3 = SM3Digest.getInstance(); byte[] data = digest(sm3, xV, entity3.Z, this.Z, RA, this.RA); sm3.update((byte) 0x03); sm3.update(yV); sm3.update(data); return Arrays.equals(entity3.S, sm3.doFinal()); } public byte[] getKey() { return key; } /** * 传输实体类 */ public static class TransportEntity implements Serializable { private static final long serialVersionUID = 3657694935421411649L; private final byte[] R; // R点 private final byte[] S; // 验证S private final byte[] Z; // 用户标识 private final byte[] K; // 公钥 TransportEntity(byte[] r, byte[] s, byte[] z, ECPoint pKey) { this(r, s, z, pKey.getEncoded(false)); } TransportEntity(byte[] r, byte[] s, byte[] z, byte[] publicKey) { R = r; S = s; Z = z; K = publicKey; } public byte[] getR() { return R; } public byte[] getS() { return S; } public byte[] getZ() { return Z; } public byte[] getK() { return K; } } /** * 连接数据 * @param x * @param z1 * @param z2 * @param a * @param b * @return */ private static byte[] digest(SM3Digest sm3, byte[] x, byte[] z1, byte[] z2, ECPoint a, ECPoint b) { sm3.reset(); sm3.update(x); sm3.update(z1); sm3.update(z2); sm3.update(a.getXCoord().toBigInteger().toByteArray()); sm3.update(a.getYCoord().toBigInteger().toByteArray()); sm3.update(b.getXCoord().toBigInteger().toByteArray()); sm3.update(b.getYCoord().toBigInteger().toByteArray()); return sm3.doFinal(); } /** * 密钥派生函数 * @param Z * @param klen 生成klen字节数长度的密钥 * @return */ private static byte[] kdf(byte[] Z, int klen) { int ct = 1, end = (int) Math.ceil(klen * 1.0D / 32.0D); SM3Digest sm3 = SM3Digest.getInstance(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); for (int i = 1; i < end; i++) { sm3.update(Z); byte[] data = sm3.doFinal(Bytes.toBytes(ct)); baos.write(data, 0, data.length); ct++; } sm3.update(Z); sm3.update(Bytes.toBytes(ct)); byte[] last = sm3.doFinal(); int len = klen & 0x1F; // klen % 32 baos.write(last, 0, (len == 0) ? last.length : len); return baos.toByteArray(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/sm/SM3Digest.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.sm; import java.util.Arrays; /** * SM3 digest implementation * * reference the internet code and refactor optimization * * @author Ponfee */ public class SM3Digest { /** SM3值的长度 */ private static final int DIGEST_SIZE = 32; /** SM3分组块大小 */ private static final int BLOCK_SIZE = 64; /** 缓冲区长度 */ private static final int BUFFER_LENGTH = BLOCK_SIZE; private final byte[] xBuf = new byte[BUFFER_LENGTH], // 缓冲区 iv = new byte[SM3.IV.length]; // 初始向量 private int xBufOffset, // 缓冲区偏移量 cntBlock; // block数量 private SM3Digest() { this.reset(); } private SM3Digest(SM3Digest t) { System.arraycopy(t.xBuf, 0, this.xBuf, 0, t.xBuf.length); System.arraycopy(t.iv, 0, this.iv, 0, t.iv.length); this.xBufOffset = t.xBufOffset; this.cntBlock = t.cntBlock; } public static SM3Digest getInstance() { return new SM3Digest(); } public static SM3Digest getInstance(SM3Digest t) { return new SM3Digest(t); } public void update(byte[] in) { this.update(in, 0, in.length); } /** * 明文输入 * @param input 明文输入缓冲区 * @param inputOffset 缓冲区偏移量 * @param len 明文长度 */ public void update(byte[] input, int inputOffset, int len) { int partLen = BUFFER_LENGTH - xBufOffset, dPos = inputOffset; if (partLen < len) { System.arraycopy(input, dPos, xBuf, xBufOffset, partLen); len -= partLen; dPos += partLen; doUpdate(); while (len > BUFFER_LENGTH) { System.arraycopy(input, dPos, xBuf, 0, BUFFER_LENGTH); len -= BUFFER_LENGTH; dPos += BUFFER_LENGTH; doUpdate(); } } System.arraycopy(input, dPos, xBuf, xBufOffset, len); xBufOffset += len; } public void update(byte in) { update(new byte[] { in }, 0, 1); } /** * SM3结果输出 * @param out 保存SM3结构的缓冲区 * @param outOffset 缓冲区偏移量 */ public void doFinal(byte[] out, int outOffset) { byte[] tmp = this.doFinal(); System.arraycopy(tmp, 0, out, outOffset, tmp.length); } public byte[] doFinal(byte[] in) { this.update(in, 0, in.length); return this.doFinal(); } public byte[] doFinal() { byte[] B = new byte[BLOCK_SIZE]; byte[] buffer = new byte[xBufOffset]; System.arraycopy(xBuf, 0, buffer, 0, buffer.length); byte[] tmp = SM3.padding(buffer, cntBlock); for (int i = 0; i < tmp.length; i += BLOCK_SIZE) { System.arraycopy(tmp, i, B, 0, B.length); this.doHash(B); } byte[] v = Arrays.copyOf(iv, iv.length); this.reset(); return v; } public void reset() { xBufOffset = 0; cntBlock = 0; System.arraycopy(SM3.IV, 0, iv, 0, SM3.IV.length); } public static int getDigestSize() { return DIGEST_SIZE; } private void doUpdate() { byte[] B = new byte[BLOCK_SIZE]; for (int i = 0; i < BUFFER_LENGTH; i += BLOCK_SIZE) { System.arraycopy(xBuf, i, B, 0, B.length); doHash(B); } xBufOffset = 0; } private void doHash(byte[] B) { byte[] tmp = SM3.cf(iv, B); System.arraycopy(tmp, 0, iv, 0, iv.length); cntBlock++; } private static class SM3 { static final byte[] IV = { (byte) 0x73, (byte) 0x80, (byte) 0x16, (byte) 0x6f, (byte) 0x49, (byte) 0x14, (byte) 0xb2, (byte) 0xb9, (byte) 0x17, (byte) 0x24, (byte) 0x42, (byte) 0xd7, (byte) 0xda, (byte) 0x8a, (byte) 0x06, (byte) 0x00, (byte) 0xa9, (byte) 0x6f, (byte) 0x30, (byte) 0xbc, (byte) 0x16, (byte) 0x31, (byte) 0x38, (byte) 0xaa, (byte) 0xe3, (byte) 0x8d, (byte) 0xee, (byte) 0x4d, (byte) 0xb0, (byte) 0xfb, (byte) 0x0e, (byte) 0x4e }; static final int[] T_J = new int[64]; static { for (int i = 0; i < 16; i++) { T_J[i] = 0x79cc4519; } for (int i = 16; i < 64; i++) { T_J[i] = 0x7a879d8a; } } static byte[] cf(byte[] V, byte[] B) { return convert(cf(convert(V), convert(B))); } /** * 对最后一个分组字节数据padding * @param in * @param bLen 分组个数 * @return */ static byte[] padding(byte[] in, int bLen) { int k = 448 - (((in.length << 3) + 1) & 0x1FF); // % 512 if (k < 0) { k = 960 - (((in.length << 3) + 1) & 0x1FF); } k += 1; byte[] padd = new byte[k / 8]; padd[0] = (byte) 0x80; long n = (in.length << 3) + (bLen << 9); byte[] out = new byte[in.length + k / 8 + 8]; int pos = 0; System.arraycopy(in, 0, out, 0, in.length); pos += in.length; System.arraycopy(padd, 0, out, pos, padd.length); pos += padd.length; byte[] tmp = back(longToByteArray(n)); System.arraycopy(tmp, 0, out, pos, tmp.length); return out; } static int[] convert(byte[] arr) { int[] out = new int[arr.length >>> 2]; byte[] tmp = new byte[4]; for (int i = 0; i < arr.length; i += 4) { System.arraycopy(arr, i, tmp, 0, 4); out[i >>> 2] = bigEndianByteToInt(tmp); } return out; } static byte[] convert(int[] arr) { byte[] out = new byte[arr.length << 2]; byte[] tmp; for (int i = 0; i < arr.length; i++) { tmp = bigEndianIntToByte(arr[i]); System.arraycopy(tmp, 0, out, i << 2, 4); } return out; } static int[] cf(int[] V, int[] B) { int a = V[0], b = V[1], c = V[2], d = V[3], e = V[4], f = V[5], g = V[6], h = V[7]; int ss1, ss2, tt1, tt2; int[][] arr = expand(B); int[] w = arr[0], w1 = arr[1]; for (int j = 0; j < 64; j++) { ss1 = (bitCycleLeft(a, 12) + e + bitCycleLeft(T_J[j], j)); ss1 = bitCycleLeft(ss1, 7); ss2 = ss1 ^ bitCycleLeft(a, 12); tt1 = FFj(a, b, c, j) + d + ss2 + w1[j]; tt2 = GGj(e, f, g, j) + h + ss1 + w[j]; d = c; c = bitCycleLeft(b, 9); b = a; a = tt1; h = g; g = bitCycleLeft(f, 19); f = e; e = P0(tt2); } int[] out = new int[8]; out[0] = a ^ V[0]; out[1] = b ^ V[1]; out[2] = c ^ V[2]; out[3] = d ^ V[3]; out[4] = e ^ V[4]; out[5] = f ^ V[5]; out[6] = g ^ V[6]; out[7] = h ^ V[7]; return out; } static int[][] expand(int[] B) { int[] W = new int[68]; int[] W1 = new int[64]; System.arraycopy(B, 0, W, 0, B.length); for (int i = 16; i < 68; i++) { W[i] = P1(W[i - 16] ^ W[i - 9] ^ bitCycleLeft(W[i - 3], 15)) ^ bitCycleLeft(W[i - 13], 7) ^ W[i - 6]; } for (int i = 0; i < 64; i++) { W1[i] = W[i] ^ W[i + 4]; } return new int[][] { W, W1 }; } static byte[] bigEndianIntToByte(int num) { return back(intToByteArray(num)); } static int bigEndianByteToInt(byte[] bytes) { return byteArrayToInt(back(bytes)); } static int FFj(int X, int Y, int Z, int j) { if (j >= 0 && j <= 15) { return FF1j(X, Y, Z); } else { return FF2j(X, Y, Z); } } static int GGj(int X, int Y, int Z, int j) { if (j >= 0 && j <= 15) { return GG1j(X, Y, Z); } else { return GG2j(X, Y, Z); } } // 逻辑位运算函数 static int FF1j(int X, int Y, int Z) { return X ^ Y ^ Z; } static int FF2j(int X, int Y, int Z) { return ((X & Y) | (X & Z) | (Y & Z)); } static int GG1j(int X, int Y, int Z) { return X ^ Y ^ Z; } static int GG2j(int X, int Y, int Z) { return (X & Y) | (~X & Z); } static int P0(int X) { return X ^ bitCycleLeft(X, 9) ^ bitCycleLeft(X, 17); } static int P1(int X) { return X ^ bitCycleLeft(X, 15) ^ bitCycleLeft(X, 23); } /** * 字节数组逆序 * * @param in * @return */ static byte[] back(byte[] in) { byte[] out = new byte[in.length]; for (int i = 0; i < out.length; i++) { out[i] = in[out.length - i - 1]; } return out; } static int bitCycleLeft(int n, int bitLen) { bitLen &= 0x1F; // bitLen %= 32; byte[] tmp = bigEndianIntToByte(n); int byteLen = bitLen / 8; int len = bitLen & 0x07; // bitLen % 8 if (byteLen > 0) { tmp = byteCycleLeft(tmp, byteLen); } if (len > 0) { tmp = bitSmall8CycleLeft(tmp, len); } return bigEndianByteToInt(tmp); } static byte[] bitSmall8CycleLeft(byte[] in, int len) { byte[] tmp = new byte[in.length]; int t1, t2, t3; for (int i = 0; i < tmp.length; i++) { t1 = (byte) ((in[i] & 0x000000ff) << len); t2 = (byte) ((in[(i + 1) % tmp.length] & 0x000000ff) >> (8 - len)); t3 = (byte) (t1 | t2); tmp[i] = (byte) t3; } return tmp; } static byte[] byteCycleLeft(byte[] in, int byteLen) { byte[] tmp = new byte[in.length]; System.arraycopy(in, byteLen, tmp, 0, in.length - byteLen); System.arraycopy(in, 0, tmp, in.length - byteLen, byteLen); return tmp; } /** * 整形转换成网络传输的字节流(字节数组)型数据 * @param num 一个整型数据 * @return 4个字节的自己数组 */ static byte[] intToByteArray(int num) { return new byte[] { (byte) (num ), (byte) (num >>> 8), (byte) (num >>> 16), (byte) (num >>> 24) }; } /** * 四个字节的字节数据转换成一个整形数据 * @param bytes 4个字节的字节数组 * @return 一个整型数据 */ static int byteArrayToInt(byte[] bytes) { return (bytes[3] ) << 24 // 转int后左移24位,刚好剩下原来的8位,故不用&0xFF | (bytes[2] & 0xFF) << 16 // 默认转int | (bytes[1] & 0xFF) << 8 | (bytes[0] & 0xFF); } /** * 长整形转换成网络传输的字节流(字节数组)型数据 * @param value 一个长整型数据 * @return 4个字节的自己数组 */ static byte[] longToByteArray(long value) { return new byte[] { (byte) (value ), (byte) (value >>> 8), (byte) (value >>> 16), (byte) (value >>> 24), (byte) (value >>> 32), (byte) (value >>> 40), (byte) (value >>> 48), (byte) (value >>> 56) }; } } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/sm/SM4.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.sm; import com.google.common.base.Preconditions; import org.apache.commons.lang3.ArrayUtils; import org.bouncycastle.util.Arrays; import javax.annotation.Nonnull; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; /** * SM4 symmetric cryptor implementation * * reference the internet code and refactor optimization * * @author Ponfee */ public final class SM4 { private SM4(){} private static final int ENCRYPT_MODE = 1; private static final int DECRYPT_MODE = 2; // S盒 private static final byte[] SBOX_TABLE = { (byte) 0xd6, (byte) 0x90, (byte) 0xe9, (byte) 0xfe, (byte) 0xcc, (byte) 0xe1, (byte) 0x3d, (byte) 0xb7, (byte) 0x16, (byte) 0xb6, (byte) 0x14, (byte) 0xc2, (byte) 0x28, (byte) 0xfb, (byte) 0x2c, (byte) 0x05, (byte) 0x2b, (byte) 0x67, (byte) 0x9a, (byte) 0x76, (byte) 0x2a, (byte) 0xbe, (byte) 0x04, (byte) 0xc3, (byte) 0xaa, (byte) 0x44, (byte) 0x13, (byte) 0x26, (byte) 0x49, (byte) 0x86, (byte) 0x06, (byte) 0x99, (byte) 0x9c, (byte) 0x42, (byte) 0x50, (byte) 0xf4, (byte) 0x91, (byte) 0xef, (byte) 0x98, (byte) 0x7a, (byte) 0x33, (byte) 0x54, (byte) 0x0b, (byte) 0x43, (byte) 0xed, (byte) 0xcf, (byte) 0xac, (byte) 0x62, (byte) 0xe4, (byte) 0xb3, (byte) 0x1c, (byte) 0xa9, (byte) 0xc9, (byte) 0x08, (byte) 0xe8, (byte) 0x95, (byte) 0x80, (byte) 0xdf, (byte) 0x94, (byte) 0xfa, (byte) 0x75, (byte) 0x8f, (byte) 0x3f, (byte) 0xa6, (byte) 0x47, (byte) 0x07, (byte) 0xa7, (byte) 0xfc, (byte) 0xf3, (byte) 0x73, (byte) 0x17, (byte) 0xba, (byte) 0x83, (byte) 0x59, (byte) 0x3c, (byte) 0x19, (byte) 0xe6, (byte) 0x85, (byte) 0x4f, (byte) 0xa8, (byte) 0x68, (byte) 0x6b, (byte) 0x81, (byte) 0xb2, (byte) 0x71, (byte) 0x64, (byte) 0xda, (byte) 0x8b, (byte) 0xf8, (byte) 0xeb, (byte) 0x0f, (byte) 0x4b, (byte) 0x70, (byte) 0x56, (byte) 0x9d, (byte) 0x35, (byte) 0x1e, (byte) 0x24, (byte) 0x0e, (byte) 0x5e, (byte) 0x63, (byte) 0x58, (byte) 0xd1, (byte) 0xa2, (byte) 0x25, (byte) 0x22, (byte) 0x7c, (byte) 0x3b, (byte) 0x01, (byte) 0x21, (byte) 0x78, (byte) 0x87, (byte) 0xd4, (byte) 0x00, (byte) 0x46, (byte) 0x57, (byte) 0x9f, (byte) 0xd3, (byte) 0x27, (byte) 0x52, (byte) 0x4c, (byte) 0x36, (byte) 0x02, (byte) 0xe7, (byte) 0xa0, (byte) 0xc4, (byte) 0xc8, (byte) 0x9e, (byte) 0xea, (byte) 0xbf, (byte) 0x8a, (byte) 0xd2, (byte) 0x40, (byte) 0xc7, (byte) 0x38, (byte) 0xb5, (byte) 0xa3, (byte) 0xf7, (byte) 0xf2, (byte) 0xce, (byte) 0xf9, (byte) 0x61, (byte) 0x15, (byte) 0xa1, (byte) 0xe0, (byte) 0xae, (byte) 0x5d, (byte) 0xa4, (byte) 0x9b, (byte) 0x34, (byte) 0x1a, (byte) 0x55, (byte) 0xad, (byte) 0x93, (byte) 0x32, (byte) 0x30, (byte) 0xf5, (byte) 0x8c, (byte) 0xb1, (byte) 0xe3, (byte) 0x1d, (byte) 0xf6, (byte) 0xe2, (byte) 0x2e, (byte) 0x82, (byte) 0x66, (byte) 0xca, (byte) 0x60, (byte) 0xc0, (byte) 0x29, (byte) 0x23, (byte) 0xab, (byte) 0x0d, (byte) 0x53, (byte) 0x4e, (byte) 0x6f, (byte) 0xd5, (byte) 0xdb, (byte) 0x37, (byte) 0x45, (byte) 0xde, (byte) 0xfd, (byte) 0x8e, (byte) 0x2f, (byte) 0x03, (byte) 0xff, (byte) 0x6a, (byte) 0x72, (byte) 0x6d, (byte) 0x6c, (byte) 0x5b, (byte) 0x51, (byte) 0x8d, (byte) 0x1b, (byte) 0xaf, (byte) 0x92, (byte) 0xbb, (byte) 0xdd, (byte) 0xbc, (byte) 0x7f, (byte) 0x11, (byte) 0xd9, (byte) 0x5c, (byte) 0x41, (byte) 0x1f, (byte) 0x10, (byte) 0x5a, (byte) 0xd8, (byte) 0x0a, (byte) 0xc1, (byte) 0x31, (byte) 0x88, (byte) 0xa5, (byte) 0xcd, (byte) 0x7b, (byte) 0xbd, (byte) 0x2d, (byte) 0x74, (byte) 0xd0, (byte) 0x12, (byte) 0xb8, (byte) 0xe5, (byte) 0xb4, (byte) 0xb0, (byte) 0x89, (byte) 0x69, (byte) 0x97, (byte) 0x4a, (byte) 0x0c, (byte) 0x96, (byte) 0x77, (byte) 0x7e, (byte) 0x65, (byte) 0xb9, (byte) 0xf1, (byte) 0x09, (byte) 0xc5, (byte) 0x6e, (byte) 0xc6, (byte) 0x84, (byte) 0x18, (byte) 0xf0, (byte) 0x7d, (byte) 0xec, (byte) 0x3a, (byte) 0xdc, (byte) 0x4d, (byte) 0x20, (byte) 0x79, (byte) 0xee, (byte) 0x5f, (byte) 0x3e, (byte) 0xd7, (byte) 0xcb, (byte) 0x39, (byte) 0x48 }; private static final int[] FK = { 0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc }; private static final int[] CK = { 0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269, 0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9, 0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249, 0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9, 0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229, 0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299, 0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209, 0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279 }; public static byte[] encrypt(byte[] key, byte[] input) { return crypt(ENCRYPT_MODE, true, key, input); } public static byte[] encrypt(boolean isPadding, byte[] key, byte[] input) { return crypt(ENCRYPT_MODE, isPadding, key, input); } public static byte[] decrypt(byte[] key, byte[] input) { return crypt(DECRYPT_MODE, true, key, input); } public static byte[] decrypt(boolean isPadding, byte[] key, byte[] input) { return crypt(DECRYPT_MODE, isPadding, key, input); } public static byte[] encrypt(byte[] key, byte[] iv, byte[] input) { return crypt(ENCRYPT_MODE, true, key, iv, input); } public static byte[] encrypt(boolean isPadding, byte[] key, byte[] iv, byte[] input) { return crypt(ENCRYPT_MODE, isPadding, key, iv, input); } public static byte[] decrypt(byte[] key, byte[] iv, byte[] input) { return crypt(DECRYPT_MODE, true, key, iv, input); } public static byte[] decrypt(boolean isPadding, byte[] key, byte[] iv, byte[] input) { return crypt(DECRYPT_MODE, isPadding, key, iv, input); } /** * 不带向量的加密(ECB模式) * * @param mode * @param isPadding * @param key * @param input * @return */ private static byte[] crypt(int mode, boolean isPadding, byte[] key, byte[] input) { Preconditions.checkArgument(ArrayUtils.isNotEmpty(input), "input cannot not null."); long[] sk = setKey(mode, key); if (isPadding && (mode == ENCRYPT_MODE)) { input = padding(input, ENCRYPT_MODE); } ByteArrayInputStream bins = new ByteArrayInputStream(input); ByteArrayOutputStream bous = new ByteArrayOutputStream(); for (int length = input.length; length > 0; length -= 16) { byte[] in = new byte[16]; byte[] out = new byte[16]; bins.read(in, 0, in.length); oneRound(sk, in, out); bous.write(out, 0, out.length); } byte[] output = bous.toByteArray(); if (isPadding && mode == DECRYPT_MODE) { output = padding(output, DECRYPT_MODE); } return output; } /** * 带向量的加密(CBC模式) * * @param mode * @param isPadding * @param key * @param iv * @param input * @return */ private static byte[] crypt(int mode, boolean isPadding, byte[] key, @Nonnull byte[] iv, byte[] input) { if (ArrayUtils.isEmpty(input)) { throw new IllegalArgumentException("Input cannot not null."); } if (iv == null || iv.length != 16) { throw new IllegalArgumentException("Iv must be 16 byte array."); } long[] sk = setKey(mode, key); iv = Arrays.copyOf(iv, iv.length); if (isPadding && mode == ENCRYPT_MODE) { input = padding(input, ENCRYPT_MODE); } ByteArrayInputStream bins = new ByteArrayInputStream(input); ByteArrayOutputStream bous = new ByteArrayOutputStream(); int i, length = input.length; if (mode == ENCRYPT_MODE) { for (; length > 0; length -= 16) { byte[] in = new byte[16]; byte[] out = new byte[16]; byte[] out1 = new byte[16]; bins.read(in, 0, in.length); for (i = 0; i < 16; i++) { out[i] = ((byte) (in[i] ^ iv[i])); } oneRound(sk, out, out1); System.arraycopy(out1, 0, iv, 0, 16); bous.write(out1, 0, out1.length); } } else { byte[] temp = new byte[16]; for (; length > 0; length -= 16) { byte[] in = new byte[16]; byte[] out = new byte[16]; byte[] out1 = new byte[16]; bins.read(in, 0, in.length); System.arraycopy(in, 0, temp, 0, 16); oneRound(sk, in, out); for (i = 0; i < 16; i++) { out1[i] = ((byte) (out[i] ^ iv[i])); } System.arraycopy(temp, 0, iv, 0, 16); bous.write(out1, 0, out1.length); } } byte[] output = bous.toByteArray(); if (isPadding && mode == DECRYPT_MODE) { output = padding(output, DECRYPT_MODE); } return output; } private static long toLong(byte[] bytes, int offset) { return ((long) (bytes[ offset] & 0xFF) << 24) | ((long) (bytes[++offset] & 0xFF) << 16) | ((long) (bytes[++offset] & 0xFF) << 8) | ((long) (bytes[++offset] & 0xFF) ) & ( 0xFFFFFFFFL ); } private static void toByteArray(long n, byte[] bytes, int offset) { bytes[ offset] = (byte) (n >>> 24); bytes[++offset] = (byte) (n >>> 16); bytes[++offset] = (byte) (n >>> 8); bytes[++offset] = (byte) (n ); } /** * shift left round * @param x * @param n * @return */ private static long rotateLeft(long x, int n) { // ((x & 0xFFFFFFFF) << n) | (x >>> (32 - n)); return (x << n) | (x >>> (32 - n)); } private static void swap(long[] sk, int i) { int j = 31 - i; long t = sk[i]; sk[i] = sk[j]; sk[j] = t; } private static byte sm4Sbox(byte inch) { return SBOX_TABLE[inch & 0xFF]; } private static long sm4Lt(long ka) { byte[] a = new byte[4]; toByteArray(ka, a, 0); byte[] b = { sm4Sbox(a[0]), sm4Sbox(a[1]), sm4Sbox(a[2]), sm4Sbox(a[3]), }; long x = toLong(b, 0); return x ^ rotateLeft(x, 2) ^ rotateLeft(x, 10) ^ rotateLeft(x, 18) ^ rotateLeft(x, 24); } private static long sm4F(long x0, long x1, long x2, long x3, long rk) { return x0 ^ sm4Lt(x1 ^ x2 ^ x3 ^ rk); } private static long sm4CalciRK(long ka) { byte[] a = new byte[4]; toByteArray(ka, a, 0); byte[] b = { sm4Sbox(a[0]), sm4Sbox(a[1]), sm4Sbox(a[2]), sm4Sbox(a[3]) }; long x = toLong(b, 0); return x ^ rotateLeft(x, 13) ^ rotateLeft(x, 23); } private static long[] setKey(int mode, @Nonnull byte[] key) { if (key == null || key.length != 16) { throw new IllegalArgumentException("Key must be 16 byte array."); } key = Arrays.copyOf(key, key.length); long[] MK = { toLong(key, 0), toLong(key, 4), toLong(key, 8), toLong(key, 12) }; long[] k = new long[36]; k[0] = MK[0] ^ (long) FK[0]; k[1] = MK[1] ^ (long) FK[1]; k[2] = MK[2] ^ (long) FK[2]; k[3] = MK[3] ^ (long) FK[3]; long[] SK = new long[32]; int i = 0; for (; i < 32; i++) { k[(i + 4)] = k[i] ^ sm4CalciRK(k[(i + 1)] ^ k[(i + 2)] ^ k[(i + 3)] ^ (long) CK[i]); SK[i] = k[(i + 4)]; } if (mode == DECRYPT_MODE) { for (i = 0; i < 16; i++) { swap(SK, i); } } return SK; } private static void oneRound(long[] sk, byte[] input, byte[] output) { long[] ulbuf = new long[36]; ulbuf[0] = toLong(input, 0); ulbuf[1] = toLong(input, 4); ulbuf[2] = toLong(input, 8); ulbuf[3] = toLong(input, 12); for (int i = 0; i < 32; i++) { ulbuf[(i + 4)] = sm4F(ulbuf[i], ulbuf[(i + 1)], ulbuf[(i + 2)], ulbuf[(i + 3)], sk[i]); } toByteArray(ulbuf[35], output, 0); toByteArray(ulbuf[34], output, 4); toByteArray(ulbuf[33], output, 8); toByteArray(ulbuf[32], output, 12); } private static byte[] padding(byte[] input, int mode) { if (input == null) { return null; } byte[] result; if (mode == ENCRYPT_MODE) { int p = 16 - input.length & 0xF; // % 16 result = new byte[input.length + p]; System.arraycopy(input, 0, result, 0, input.length); for (int i = 0; i < p; i++) { result[input.length + i] = (byte) p; } } else { int p = input[input.length - 1]; result = new byte[input.length - p]; System.arraycopy(input, 0, result, 0, input.length - p); } return result; } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/symmetric/Algorithm.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.symmetric; /** *

     *   对称密钥算法bit(位)
     *     DES                  key size must be equal to 64
     *     DESede(TripleDES)    key size must be equal to 112 or 168
     *     AES                  key size must be equal to 128, 192 or 256, but 192 and 256 bits may be unsupport
     *     Blowfish             key size must be multiple of 8, and can only range from 32 to 448 (inclusive)
     *     RC2                  key size must be between 40 and 1024 bits(block cipher, 曾经被考虑作为DES算法的替代品, 比DES快)
     *     RC4(ARCFOUR)         key size must be between 40 and 1024 bits(stream cipher)
     * 
    * * AES进入最后一轮候选算法有:Rijndael/Serpent/Twofish/RC6/MARS,最终Rijndael算法获胜 * * 速度排名:IDEA < DES < GASTI28 < GOST < AES < RC4 < TEA < Blowfish * * 1、DES(Data Encryption Standard):对称算法,数据加密标准,速度较快,适用于加密大量数据的场合; * 2、3DES(Triple DES):是基于DES的对称算法,对一块数据用三个不同的密钥进行三次加密,强度更高; * 3、RC2和RC4:对称算法,用变长密钥对大量数据进行加密,比 DES快; * 4、IDEA(International Data Encryption Algorithm)国际数据加密算法,使用128位密钥提供非常强的安全性; * 5、RSA:由 RSA 公司发明,是一个支持变长密钥的公共密钥算法,需要加密的文件块的长度也是可变的,非对称算法; * 6、DSA(Digital Signature Algorithm):数字签名算法,是一种标准的 DSS(数字签名标准),严格来说不算加密算法; * 7、AES(Advanced Encryption Standard):高级加密标准,对称算法,是下一代的加密算法标准,速度快,安全级别高,在21世纪AES标准的一个实现是Rijndael算法; * 8、BLOWFISH,它使用变长的密钥,长度可达448位,运行速度很快; * 10、PKCS:The Public-Key Cryptography Standards (PKCS)是由美国RSA数据安全公司及其合作伙伴制定的一组公钥密码学标准, * 其中包括证书申请、证书更新、证书作废表发布、扩展证书内容以及数字签名、数字信封的格式等方面的一系列相关协议。 * 11、SSF33,SSF28,SCB2(SM1):国家密码局的隐蔽不公开的商用算法,在国内民用和商用的,除这些都不容许使用外,其他的都可以使用; * 12、ECC(Elliptic Curves Cryptography):椭圆曲线密码编码学。 * 13、TEA(Tiny Encryption Algorithm)简单高效的加密算法,加密解密速度快,实现简单。但安全性不如DES,QQ一直用tea加密 * * https://bouncycastle.org/documentation.html * https://downloads.bouncycastle.org/fips-java/BC-FJA-UserGuide-1.0.2.pdf * https://downloads.bouncycastle.org/fips-java/BC-FJA-(D)TLSUserGuide-1.0.9.pdf * * @see org.bouncycastle.jcajce.provider.symmetric.ARC4 * * @author Ponfee */ public enum Algorithm { AES, DES, DESede, Blowfish, RC2, RC4, // RC4: ARC4, ARCFOUR RC5, IDEA, TEA, TDEA, Camellia, CAST5, // GOST, GOST3411, GOST28147, SEED, // Serpent, SHACAL2, Twofish, SM4 // } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/symmetric/Mode.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.symmetric; /** * 对称加密分组模式

    * 推荐使用CBC和CTR模式

    * CFB,OFB,CTR模式不需要padding

    * * ECB:最基本的加密模式,也就是通常理解的加密,相同的明文将永远加密成相同的密文,无初始向量,容易受到密码本重放攻击,一般情况下很少用。 * * CBC:明文被加密前要与前面的密文进行异或运算后再加密,因此只要选择不同的初始向量,相同的密文加密后会形成不同的密文,这是目前应用最广泛的模式。 * CBC加密后的密文是上下文相关的,但明文的错误不会传递到后续分组,但如果一个分组丢失,后面的分组将全部作废(同步错误)。 * * CFB:类似于自同步序列密码,分组加密后,按8位分组将密文和明文进行移位异或后得到输出同时反馈回移位寄存器,优点最小可以按字节进行加解密, * 也可以是n位的,CFB也是上下文相关的,CFB模式下,明文的一个错误会影响后面的密文(错误扩散)。 * * OFB:将分组密码作为同步序列密码运行,和CFB相似,不过OFB用的是前一个n位密文输出分组反馈回移位寄存器,OFB没有错误扩散问题。 * * @author Ponfee */ public enum Mode { ECB, CBC, CFB, OFB, CTR, // EAX, OCB, CFB8, CFB64, // CFB128, OpenPGPCFB, GCM, CCM // } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/symmetric/PBECryptor.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.symmetric; import javax.crypto.SecretKey; import javax.crypto.spec.PBEParameterSpec; import java.security.Provider; import java.security.spec.AlgorithmParameterSpec; /** *

     *  |---------------------------------------|-------------------|---------------------------|
     *  |               Algorithm               | secret key length | default secret key length |
     *  |---------------------------------------|-------------------|---------------------------|
     *  | PBEWithMD5AndDES                      |        56         |            56             |
     *  |---------------------------------------|-------------------|---------------------------|
     *  | PBEWithMD5AndTripleDES                |      112,168      |            168            |
     *  |---------------------------------------|-------------------|---------------------------|
     *  | PBEWithSHA1AndDESede                  |      112,168      |            168            |
     *  |---------------------------------------|-------------------|---------------------------|
     *  | PBEWithSHA1AndRC2_40                  |     40 to 1024    |            128            |
     *  |---------------------------------------|-------------------|---------------------------|
     * 
    * * String是常量(即创建之后就无法更改),会保存到常量池中,如果有其他进程 * 可以dump这个进程的内存,那么密码就会随着常量池被dump出去从而泄露。 * 而char[]可以写入其他的信息从而改变,即是被dump了也会减少泄露密码的风险。 *

    * * PBE Cryptors * * @author Ponfee */ public class PBECryptor extends SymmetricCryptor { public enum PBEAlgorithm { PBEWithMD5AndDES, // PBEWithSHA1AndDESede, // best PBEWithSHA1AndRC2_40, // PBEWithMD5AndTripleDES, // } public PBECryptor(SecretKey secretKey, Mode mode, Padding padding, AlgorithmParameterSpec parameter, Provider provider) { // (provider == null) ? Providers.SunJCE : provider super(secretKey, mode, padding, parameter, provider); } // --------------------------getter public char[] getPass() { return new String(getKey()).toCharArray(); } public byte[] getSalt() { return ((PBEParameterSpec) parameter).getSalt(); } public int getIterations() { return ((PBEParameterSpec) parameter).getIterationCount(); } @Override public byte[] getParameterAsBytes() { throw new UnsupportedOperationException(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/symmetric/PBECryptorBuilder.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.symmetric; import cn.ponfee.commons.jce.Providers; import cn.ponfee.commons.jce.symmetric.PBECryptor.PBEAlgorithm; import org.apache.commons.text.RandomStringGenerator; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.PBEParameterSpec; import java.security.Provider; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.InvalidKeySpecException; /** * PBE Cryptor builder * * @author Ponfee */ public class PBECryptorBuilder { private static final RandomStringGenerator GENERATOR = new RandomStringGenerator.Builder().withinRange('!', '~').build(); private final SecretKey secretKey; // 密钥 private final Provider provider; private Mode mode; // 分组加密模式 private Padding padding; // 填充 private AlgorithmParameterSpec parameter; // 填充向量 private PBECryptorBuilder(PBEAlgorithm algorithm, char[] pass, Provider provider) { try { // new SecretKeySpec(new String(pass).getBytes(), algName); // 也可用此方法来构造具体的密钥 SecretKeyFactory factory = Providers.getSecretKeyFactory(algorithm.name(), provider); this.secretKey = factory.generateSecret(new PBEKeySpec(pass)); this.provider = provider; } catch (InvalidKeySpecException e) { throw new SecurityException(e); } } public static PBECryptorBuilder newBuilder(PBEAlgorithm algorithm) { return newBuilder(algorithm, 24); } public static PBECryptorBuilder newBuilder(PBEAlgorithm algorithm, int passSize) { return newBuilder(algorithm, GENERATOR.generate(passSize).toCharArray()); } public static PBECryptorBuilder newBuilder(PBEAlgorithm algorithm, char[] pass) { return newBuilder(algorithm, pass, null); } public static PBECryptorBuilder newBuilder(PBEAlgorithm algorithm, char[] pass, Provider provider) { return new PBECryptorBuilder(algorithm, pass, provider); } public PBECryptorBuilder mode(Mode mode) { this.mode = mode; return this; } public PBECryptorBuilder padding(Padding padding) { this.padding = padding; return this; } public PBECryptorBuilder parameter(byte[] salt, int iterations) { this.parameter = new PBEParameterSpec(salt, iterations); return this; } public PBECryptor build() { if (mode != null && padding == null) { // 设置了mode必须指定padding throw new IllegalArgumentException("padding cannot be null within mode crypto."); } else if (mode == null && padding != null) { // 没有设置mode,不能指定padding throw new IllegalArgumentException("padding must be null without mode crypto."); } return new PBECryptor(secretKey, mode, padding, parameter, provider); } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/symmetric/Padding.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.symmetric; /** * encrypt padding * pkcs7Padding must be has BouncyCastleProvider support * PKCS7Padding:缺几个字节就补几个字节的0 * PKCS5Padding:缺几个字节就补充几个字节的几,如缺6个字节就补充6个字节的6 * * @author Ponfee */ public enum Padding { NoPadding, PKCS5Padding, PKCS7Padding, // ISO10126_Padding("ISO10126Padding"), // ISO10126_2Padding("ISO10126-2Padding"), // ISO7816_4Padding("ISO7816-4Padding"), // X9_23Padding("X9.23Padding"), TBCPadding, // CS1Padding, CS2Padding, CS3Padding // CS1Padding, CS2Padding may cannot support ; private final String padding; Padding() { this.padding = this.name(); } Padding(String padding) { this.padding = padding; } public String padding() { return this.padding; } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/symmetric/SymmetricCryptor.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.symmetric; import cn.ponfee.commons.jce.Providers; import cn.ponfee.commons.util.Base64UrlSafe; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import java.security.GeneralSecurityException; import java.security.Provider; import java.security.spec.AlgorithmParameterSpec; /** * AES * http://blog.csdn.net/qq_28205153/article/details/55798628 * http://blog.csdn.net/lrwwll/article/details/78069013 * https://coolshell.cn//wp-content/uploads/2010/10/rijndael_ingles2004.swf * 对称加密 * 加密:C = E(K, P) * 解密:P = D(K, C) * * AES 密钥长度(32位比特字) 分组长度(32位比特字) 加密轮数 * AES-128 4 4 10 * AES-192 6 4 12 * AES-256 8 4 14 * * 1、明文按16字节(4个32位比特字)分组,P[0],P[1],...,P[15],不足则填充 * 2、16字节(4个32位比特字)密钥进行分组,分成44组每组1个比特字(W[0],W[1],...,W[43]), * 前4组为原始密钥用于初始密钥加:W[0]=K[0] K[1] K[2] K[3],...,W[3]=K[12] K[13] K[14] K[15]。 * 后面40个字分为10组,每组4个字(128比特)分别用于10轮加密运算中的轮密钥加 * 3、W[4]~W[43]通过轮密钥加来生成,将128位轮密钥Ki同状态矩阵中的数据进行逐位异或操作, * 密钥Ki中每个字W[4i],W[4i+1],W[4i+2],W[4i+3]为32位比特字,包含4个字节 * 4、先将明文和原始密钥进行一次异或加密操作 * 5、加密的第1轮到第9轮的轮函数一样,包括4个操作:字节代换、行位移、列混合和轮密钥加。最后一轮迭代不执行列混合。 * 6、字节代换:把该字节的高4位作为行值,低4位作为列值,取出S盒或者逆S盒中对应的行的元素作为输出 * * @author Ponfee */ public class SymmetricCryptor { /** * 分组对称加密模式时padding不能为null */ private final Mode mode; /** * 1、RC2、RC4分组对称加密模式时padding必须为NoPadding * 2、无分组模式时padding必须为null * 3、其它算法无限制 */ private final Padding padding; /** * 1、ECB模式时iv必须为null * 2、无分组对称加密模式时iv必须为null * 3、有分组对称加密模式时必须要有iv * 4、iv must be 16 bytes long */ protected final AlgorithmParameterSpec parameter; /** 加密提供方 */ private final Provider provider; /** 密钥 */ private final SecretKey secretKey; /** the cipher transformation */ private final String transformation; protected SymmetricCryptor(SecretKey secretKey, Mode mode, Padding padding, AlgorithmParameterSpec parameter, Provider provider) { this.secretKey = secretKey; this.mode = mode; this.padding = padding; this.parameter = parameter; this.provider = provider; if (mode != null) { this.transformation = new StringBuilder(getAlgorithm()) .append("/").append(mode.name()) .append("/").append(padding.padding()) .toString(); } else { this.transformation = getAlgorithm(); } } public final byte[] encrypt(byte[] data) { return this.docrypt(data, Cipher.ENCRYPT_MODE); } public final byte[] decrypt(byte[] encrypted) { return this.docrypt(encrypted, Cipher.DECRYPT_MODE); } /** * 加密解 * @param bytes 待加密/密文数据 * @param cryptMode 密码模式:1加密;2解密; * @return */ private byte[] docrypt(byte[] bytes, int cryptMode) { try { Cipher cipher = Providers.getCipher(transformation, provider); cipher.init(cryptMode, secretKey, parameter); return cipher.doFinal(bytes); } catch (GeneralSecurityException e) { throw new SecurityException(e); } } // ----------------------------------getter /** * Returns encrypt algorithm string * * @return algorithm string */ public final String getAlgorithm() { return secretKey.getAlgorithm(); } /** * Returns key byte array data * * @return key byte array data */ public final byte[] getKey() { return secretKey.getEncoded(); } public final String getKeyAsBase64() { return Base64UrlSafe.encode(getKey()); } /** * Returns iv parameter byte array data * * @return iv parameter byte array data */ public byte[] getParameterAsBytes() { return ((IvParameterSpec) parameter).getIV(); } public final String getParameterAsBase64() { return Base64UrlSafe.encode(getParameterAsBytes()); } public final Mode getMode() { return mode; } public final Padding getPadding() { return padding; } public final Provider getProvider() { return provider; } } ================================================ FILE: src/main/java/cn/ponfee/commons/jce/symmetric/SymmetricCryptorBuilder.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.jce.symmetric; import cn.ponfee.commons.jce.Providers; import cn.ponfee.commons.util.SecureRandoms; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.security.Provider; /** * 对称加密构建类 * * @author Ponfee */ public final class SymmetricCryptorBuilder { private final SecretKey secretKey; // 密钥 /**加密服务提供方 {@link cn.ponfee.commons.jce.Providers} */ private final Provider provider; private Mode mode; // 分组加密模式 private Padding padding; // 填充 private IvParameterSpec parameter; // 填充向量 private SymmetricCryptorBuilder(Algorithm alg, byte[] key, Provider provider) { if (key == null) { KeyGenerator keyGenerator = Providers.getKeyGenerator(alg.name(), provider); this.secretKey = keyGenerator.generateKey(); } else { this.secretKey = new SecretKeySpec(key, alg.name()); } this.provider = provider; } public static SymmetricCryptorBuilder newBuilder(Algorithm algorithm) { return newBuilder(algorithm, null, null); } public static SymmetricCryptorBuilder newBuilder(Algorithm algorithm, Provider provider) { return newBuilder(algorithm, null, provider); } public static SymmetricCryptorBuilder newBuilder(Algorithm algorithm, int keySize) { return newBuilder(algorithm, SecureRandoms.nextBytes(keySize), null); } public static SymmetricCryptorBuilder newBuilder(Algorithm algorithm, int keySize, Provider provider) { return newBuilder(algorithm, SecureRandoms.nextBytes(keySize), provider); } public static SymmetricCryptorBuilder newBuilder(Algorithm algorithm, byte[] key) { return newBuilder(algorithm, key, null); } public static SymmetricCryptorBuilder newBuilder(Algorithm algorithm, byte[] key, Provider provider) { return new SymmetricCryptorBuilder(algorithm, key, provider); } public SymmetricCryptorBuilder mode(Mode mode) { this.mode = mode; return this; } public SymmetricCryptorBuilder padding(Padding padding) { this.padding = padding; return this; } public SymmetricCryptorBuilder parameter(byte[] parameter) { this.parameter = new IvParameterSpec(parameter); return this; } public SymmetricCryptor build() { if (mode != null && padding == null) { // 设置了mode必须指定padding throw new IllegalArgumentException("padding cannot be null within mode crypto."); } else if (mode == null && padding != null) { // 没有设置mode,不能指定padding throw new IllegalArgumentException("padding must be null without mode crypto."); } return new SymmetricCryptor(secretKey, mode, padding, parameter, provider); } } ================================================ FILE: src/main/java/cn/ponfee/commons/json/FastjsonMoney.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.json; import cn.ponfee.commons.reflect.GenericUtils; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.parser.DefaultJSONParser; import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer; import com.alibaba.fastjson.serializer.JSONSerializer; import com.alibaba.fastjson.serializer.ObjectSerializer; import com.alibaba.fastjson.serializer.SerializeWriter; import org.javamoney.moneta.Money; import javax.money.Monetary; import java.io.IOException; import java.lang.reflect.Type; /** * The Fastjson Money Serializer & Deserializer * * @author Ponfee */ public class FastjsonMoney implements ObjectSerializer, ObjectDeserializer { private static final String CURRENCY = "currency"; private static final String NUMBER = "number"; @Override public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException { SerializeWriter writer = serializer.getWriter(); if (object == null) { serializer.writeNull(); } else { Money money = (Money) object; writer.write("{\"" + CURRENCY + "\":\""); writer.write(money.getCurrency().getCurrencyCode()); writer.write("\",\"" + NUMBER + "\":"); writer.writeLong(money.getNumberStripped().movePointRight(money.getCurrency().getDefaultFractionDigits()).longValueExact()); writer.write("}"); } } @Override public Money deserialze(DefaultJSONParser parser, Type type, Object fieldName) { if (GenericUtils.getRawType(type) != Money.class) { throw new UnsupportedOperationException("Cannot supported deserialize type: " + type); } JSONObject jsonObject = parser.parseObject(); String currencyCode = jsonObject.getString(CURRENCY); long number = jsonObject.getLongValue(NUMBER); return Money.ofMinor(Monetary.getCurrency(currencyCode), number); } @Override public int getFastMatchToken() { return 0 /*JSONToken.RBRACKET*/; } } ================================================ FILE: src/main/java/cn/ponfee/commons/json/FastjsonPropertyFilter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.json; import com.alibaba.fastjson.serializer.PropertyFilter; import org.apache.commons.lang3.ArrayUtils; import javax.annotation.Nonnull; /** *

     * Object to json specified fields whether includes or excludes
     * 
     * {@code
     *   Map map = ImmutableMap.of("a", 1, "b", true, "c", "x");
     *   JSON.toJSONString(map, JsonPropertyFilter.include("a", "b"))
     *   JSON.toJSONString(map, JsonPropertyFilter.exclude("a", "b"))
     *   
     *   OR 
     *   
     *   JSON.toJSONString(map, new SimplePropertyPreFilter("a", "b"))
     * }
     * 
    * * @author Ponfee */ public class FastjsonPropertyFilter implements PropertyFilter { private final boolean isIncludes; private final boolean forceNonNull; // if true, then non null field will be serialize private final String[] fields; public FastjsonPropertyFilter(@Nonnull PropertyFilterType type, boolean forceNonNull, @Nonnull String... fields) { this.isIncludes = type == PropertyFilterType.INCLUDES; this.forceNonNull = forceNonNull; this.fields = fields; } @Override public boolean apply(Object source, String name, Object value) { if (forceNonNull && value != null) { return true; } // 异或:(A ^ B) // 同或:(A ^ B ^ 1) or !(A ^ B) //return isIncludes ^ ArrayUtils.contains(fields, name) ^ true; //return !(isIncludes ^ ArrayUtils.contains(fields, name)); return isIncludes == ArrayUtils.contains(fields, name); } // ----------------------------------------------------------------------static methods public static FastjsonPropertyFilter exclude(@Nonnull String... fields) { return exclude(false, fields); } public static FastjsonPropertyFilter exclude(boolean forceNonNull, @Nonnull String... fields) { return new FastjsonPropertyFilter(PropertyFilterType.EXCLUDES, forceNonNull, fields); } public static FastjsonPropertyFilter include(@Nonnull String... fields) { return include(false, fields); } public static FastjsonPropertyFilter include(boolean forceNonNull, @Nonnull String... fields) { return new FastjsonPropertyFilter(PropertyFilterType.INCLUDES, forceNonNull, fields); } private enum PropertyFilterType { INCLUDES, EXCLUDES } } ================================================ FILE: src/main/java/cn/ponfee/commons/json/FastjsonTypeReferences.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.json; import com.alibaba.fastjson.TypeReference; import java.util.List; import java.util.Map; import java.util.Set; /** * The Fastjson TypeReference holder * * @author Ponfee * @see sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl * @see org.springframework.core.ParameterizedTypeReference */ public final class FastjsonTypeReferences { public static final TypeReference> MAP_NORMAL = new TypeReference>() {}; public static final TypeReference> MAP_STRING = new TypeReference>() {}; public static final TypeReference> LIST_OBJECT = new TypeReference>() {}; public static final TypeReference> LIST_STRING = new TypeReference>() {}; public static final TypeReference> SET_OBJECT = new TypeReference>() {}; public static final TypeReference> SET_STRING = new TypeReference>() {}; public static final TypeReference>> LIST_MAP_NORMAL = new TypeReference>>() {}; public static final TypeReference>> LIST_MAP_STRING = new TypeReference>>() {}; public static final TypeReference>> SET_MAP_NORMAL = new TypeReference>>() {}; public static final TypeReference>> SET_MAP_STRING = new TypeReference>>() {}; } ================================================ FILE: src/main/java/cn/ponfee/commons/json/JacksonCurrencyUnit.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.json; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import org.apache.commons.lang3.StringUtils; import javax.money.CurrencyUnit; import javax.money.Monetary; import java.io.IOException; /** * The Jackson Currency unit Serializer & Deserializer * * @author Ponfee */ public class JacksonCurrencyUnit { public static final JacksonCurrencyUnit INSTANCE = new JacksonCurrencyUnit(); private final JsonSerializer serializer; private final JsonDeserializer deserializer; private JacksonCurrencyUnit() { this.serializer = new Serializer(); this.deserializer = new Deserializer(); } public JsonSerializer serializer() { return this.serializer; } public JsonDeserializer deserializer() { return this.deserializer; } private static class Serializer extends JsonSerializer { @Override public void serialize(CurrencyUnit currencyUnit, JsonGenerator generator, SerializerProvider provider) throws IOException { if (currencyUnit == null) { generator.writeNull(); } else { generator.writeString(currencyUnit.getCurrencyCode()); } } } private static class Deserializer extends JsonDeserializer { @Override public CurrencyUnit deserialize(JsonParser parser, DeserializationContext context) throws IOException { String currencyCode = parser.getValueAsString(); if (StringUtils.isBlank(currencyCode)) { return null; } return Monetary.getCurrency(currencyCode); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/json/JacksonDate.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.json; import cn.ponfee.commons.date.JavaUtilDateFormat; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import org.apache.commons.lang3.StringUtils; import java.io.IOException; import java.text.DateFormat; import java.text.ParseException; import java.util.Date; /** * The Jackson Money Serializer & Deserializer * * @author Ponfee */ public class JacksonDate { public static final JacksonDate INSTANCE = new JacksonDate(JavaUtilDateFormat.DEFAULT); private final JsonSerializer serializer; private final JsonDeserializer deserializer; public JacksonDate(DateFormat format) { this.serializer = new Serializer(format); this.deserializer = new Deserializer(format); } public JsonSerializer serializer() { return this.serializer; } public JsonDeserializer deserializer() { return this.deserializer; } private static class Serializer extends JsonSerializer { private final DateFormat format; private Serializer(DateFormat format) { this.format = format; } @Override public void serialize(Date date, JsonGenerator generator, SerializerProvider provider) throws IOException { if (date == null) { generator.writeNull(); } else { generator.writeString(format.format(date)); } } } private static class Deserializer extends JsonDeserializer { private final DateFormat format; private Deserializer(DateFormat format) { this.format = format; } @Override public Date deserialize(JsonParser p, DeserializationContext ctx) throws IOException { String text = p.getText(); if (StringUtils.isBlank(text)) { return null; } try { return format.parse(text); } catch (ParseException e) { throw new IllegalArgumentException("Invalid date format: " + text); } } } } ================================================ FILE: src/main/java/cn/ponfee/commons/json/JacksonMoney.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.json; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.node.BaseJsonNode; import com.fasterxml.jackson.databind.node.NullNode; import org.javamoney.moneta.Money; import javax.money.CurrencyUnit; import javax.money.Monetary; import java.io.IOException; /** * The Jackson Money Serializer & Deserializer * * @author Ponfee */ public class JacksonMoney { public static final JacksonMoney INSTANCE = new JacksonMoney(); private static final String CURRENCY = "currency"; private static final String NUMBER = "number"; private final JsonSerializer serializer; private final JsonDeserializer deserializer; private JacksonMoney() { this.serializer = new Serializer(); this.deserializer = new Deserializer(); } public JsonSerializer serializer() { return this.serializer; } public JsonDeserializer deserializer() { return this.deserializer; } private static class Serializer extends JsonSerializer { @Override public void serialize(Money money, JsonGenerator generator, SerializerProvider provider) throws IOException { if (money == null) { generator.writeNull(); } else { CurrencyUnit currency = money.getCurrency(); generator.writeStartObject(); generator.writeStringField(CURRENCY, currency.getCurrencyCode()); generator.writeNumberField(NUMBER, money.getNumberStripped().movePointRight(currency.getDefaultFractionDigits()).longValueExact()); generator.writeEndObject(); } } } private static class Deserializer extends JsonDeserializer { @Override public Money deserialize(JsonParser parser, DeserializationContext context) throws IOException { BaseJsonNode jsonNode = parser.readValueAsTree(); if (jsonNode == null || jsonNode instanceof NullNode) { return null; } String currency = jsonNode.required(CURRENCY).textValue(); long number = jsonNode.required(NUMBER).longValue(); return Money.ofMinor(Monetary.getCurrency(currency), number); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/json/JacksonTypeReferences.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.json; import com.fasterxml.jackson.core.type.TypeReference; import java.util.List; import java.util.Map; import java.util.Set; /** * The Jackson TypeReference holder * * @author Ponfee */ public final class JacksonTypeReferences { public static final TypeReference> MAP_NORMAL = new TypeReference>() {}; public static final TypeReference> MAP_STRING = new TypeReference>() {}; public static final TypeReference> LIST_OBJECT = new TypeReference>() {}; public static final TypeReference> LIST_STRING = new TypeReference>() {}; public static final TypeReference> SET_OBJECT = new TypeReference>() {}; public static final TypeReference> SET_STRING = new TypeReference>() {}; public static final TypeReference>> LIST_MAP_NORMAL = new TypeReference>>() {}; public static final TypeReference>> LIST_MAP_STRING = new TypeReference>>() {}; public static final TypeReference>> SET_MAP_NORMAL = new TypeReference>>() {}; public static final TypeReference>> SET_MAP_STRING = new TypeReference>>() {}; } ================================================ FILE: src/main/java/cn/ponfee/commons/json/Jsons.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.json; import cn.ponfee.commons.date.*; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.core.json.JsonWriteFeature; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.springframework.util.Assert; import javax.annotation.concurrent.ThreadSafe; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.util.Date; import java.util.Map; /** * The json utility based jackson * * @author Ponfee * @ThreadSafe */ @ThreadSafe public final class Jsons { public static final TypeReference> MAP_NORMAL = new TypeReference>() {}; /** * 标准:忽略对象中值为null的属性 */ public static final Jsons NORMAL = new Jsons(JsonInclude.Include.NON_NULL); /** * 不排除任何属性 */ public static final Jsons ALL = new Jsons(null); /** * Jackson ObjectMapper(thread safe) */ private final ObjectMapper mapper; private Jsons(JsonInclude.Include include) { this.mapper = createObjectMapper(include); } // --------------------------------------------------------serialization /** * Converts object to json, and write to output stream * * @param output the output stream * @param target the target object */ public void write(OutputStream output, Object target) { try { mapper.writeValue(output, target); } catch (IOException e) { ExceptionUtils.rethrow(e); } } /** * Converts an object(POJO, Array, Collection, ...) to json string * * @param target target object * @return json string */ public String string(Object target) { try { return mapper.writeValueAsString(target); } catch (IOException e) { return ExceptionUtils.rethrow(e); } } /** * Serialize the byte array of json * * @param target object * @return byte[] array */ public byte[] bytes(Object target) { try { return mapper.writeValueAsBytes(target); } catch (IOException e) { return ExceptionUtils.rethrow(e); } } // --------------------------------------------------------deserialization /** * Deserialize the json string to java object * * @param json json string * @param javaType JavaType * @return the javaType's object * @see ObjectMapper#getTypeFactory() * @see ObjectMapper#constructType(Type) * @see com.fasterxml.jackson.databind.type.TypeFactory#constructGeneralizedType(JavaType, Class) */ public T parse(String json, JavaType javaType) { if (StringUtils.isEmpty(json)) { return null; } try { return mapper.readValue(json, javaType); } catch (Exception e) { return ExceptionUtils.rethrow(e); } } /** * Deserialize the json byte array to java object * * @param json json byte array * @param javaType JavaType * @return the javaType's object */ public T parse(byte[] json, JavaType javaType) { if (json == null || json.length == 0) { return null; } try { return mapper.readValue(json, javaType); } catch (Exception e) { return ExceptionUtils.rethrow(e); } } public T parse(String json, Class target) { return parse(json, mapper.constructType(target)); } public T parse(byte[] json, Class target) { return parse(json, mapper.constructType(target)); } public T parse(String json, Type type) { return parse(json, mapper.constructType(type)); } public T parse(byte[] json, Type type) { return parse(json, mapper.constructType(type)); } public T parse(String json, TypeReference type) { return parse(json, mapper.constructType(type)); } public T parse(byte[] json, TypeReference type) { return parse(json, mapper.constructType(type)); } // ----------------------------------------------------static methods public static String toJson(Object target) { return NORMAL.string(target); } public static byte[] toBytes(Object target) { return NORMAL.bytes(target); } public static Object[] parseArray(String body, Class... types) { if (body == null) { return null; } ObjectMapper mapper = NORMAL.mapper; JsonNode rootNode = readTree(mapper, body); Assert.isTrue(rootNode.isArray(), "Not array json data."); ArrayNode arrayNode = (ArrayNode) rootNode; if (types.length == 1 && arrayNode.size() > 1) { return new Object[]{parse(mapper, arrayNode, types[0])}; } Object[] result = new Object[types.length]; for (int i = 0; i < types.length; i++) { result[i] = parse(mapper, arrayNode.get(i), types[i]); } return result; } public static Object[] parseMethodArgs(String body, Method method) { if (body == null) { return null; } // 不推荐使用fastjson,项目中尽量统一使用一种JSON序列化方式 //return com.alibaba.fastjson.JSON.parseArray(body, method.getGenericParameterTypes()).toArray(); Type[] genericArgumentTypes = method.getGenericParameterTypes(); int argumentCount = genericArgumentTypes.length; if (/*method.getParameterCount()*/argumentCount == 0) { return null; } ObjectMapper mapper = NORMAL.mapper; JsonNode rootNode = readTree(mapper, body); if (rootNode.isArray()) { ArrayNode arrayNode = (ArrayNode) rootNode; // 方法只有一个参数,但请求参数长度大于1 // ["a", "b"] -> method(Object[] arg) -> arg=["a", "b"] // [["a"], ["b"]] -> method(Object[] arg) -> arg=[["a"], ["b"]] if (argumentCount == 1 && arrayNode.size() > 1) { return new Object[]{parse(mapper, arrayNode, genericArgumentTypes[0])}; } // 其它情况,在调用方将参数(requestParameters)用数组包一层:new Object[]{ arg-1, arg-2, ..., arg-n } // [["a", "b"]] -> method(Object[] arg) -> arg =["a", "b"] // [["a"], ["b"]] -> method(Object[] arg1, Object[] arg2) -> arg1=["a"], arg2=["b"] // ["a", "b"] -> method(Object[] arg1, Object[] arg2) -> arg1=["a"], arg2=["b"] # ACCEPT_SINGLE_VALUE_AS_ARRAY作用:将字符串“a”转为数组arg1[] Assert.isTrue( argumentCount == arrayNode.size(), () -> "Method arguments size: " + argumentCount + ", but actual size: " + arrayNode.size() ); Object[] methodArguments = new Object[argumentCount]; for (int i = 0; i < argumentCount; i++) { methodArguments[i] = parse(mapper, arrayNode.get(i), genericArgumentTypes[i]); } return methodArguments; } else { Assert.isTrue(argumentCount == 1, "Single object request parameter not support multiple arguments method."); return new Object[]{parse(mapper, rootNode, genericArgumentTypes[0])}; } } public static T fromJson(String json, JavaType javaType) { return NORMAL.parse(json, javaType); } public static T fromJson(byte[] json, JavaType javaType) { return NORMAL.parse(json, javaType); } public static T fromJson(String json, Class target) { return NORMAL.parse(json, target); } public static T fromJson(byte[] json, Class target) { return NORMAL.parse(json, target); } public static T fromJson(String json, Type target) { return NORMAL.parse(json, target); } public static T fromJson(byte[] json, Type target) { return NORMAL.parse(json, target); } public static T fromJson(String json, TypeReference type) { return NORMAL.parse(json, type); } public static T fromJson(byte[] json, TypeReference type) { return NORMAL.parse(json, type); } public static ObjectMapper createObjectMapper(JsonInclude.Include include) { JsonFactory jsonFactory = new JsonFactoryBuilder() .disable(JsonFactory.Feature.INTERN_FIELD_NAMES) .build(); ObjectMapper mapper = new ObjectMapper(jsonFactory); // 设置序列化时的特性 if (include != null) { mapper.setSerializationInclusion(include); } configObjectMapper(mapper); return mapper; } public static void configObjectMapper(ObjectMapper mapper) { mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // 反序列化时忽略未知属性 mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); // Date不序列化为时间戳 mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); // 解决报错:No serializer found for class XXX and no properties discovered to create BeanSerializer mapper.configure(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN, true); // BigDecimal禁用科学计数格式输出 mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, false); // 禁止无双引号字段 mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, false); // 禁止单引号字段 mapper.configure(JsonWriteFeature.QUOTE_FIELD_NAMES.mappedFeature(), true); // 字段加双引号 // java.util.Date:registerModule > JsonFormat(会使用setTimeZone) > setDateFormat(会使用setTimeZone) // 1)如果同时配置了setDateFormat和registerModule,则使用registerModule // 2)如果设置了setTimeZone,则会调用setDateFormat#setTimeZone(注:setTimeZone对registerModule无影响) // 3)如果实体字段使用了JsonFormat注解,则setDateFormat不生效(会使用jackson内置的格式化器,默认为0时区,此时要setTimeZone) // 4)JsonFormat注解对registerModule无影响(registerModule优先级最高) mapper.setTimeZone(JavaUtilDateFormat.DEFAULT.getTimeZone()); // TimeZone.getDefault() mapper.setDateFormat(JavaUtilDateFormat.DEFAULT); //mapper.setConfig(mapper.getDeserializationConfig().with(mapper.getDateFormat())); //mapper.setConfig(mapper.getSerializationConfig().with(mapper.getDateFormat())); SimpleModule module = new SimpleModule(); module.addSerializer(Date.class, JacksonDate.INSTANCE.serializer()); module.addDeserializer(Date.class, JacksonDate.INSTANCE.deserializer()); //module.addSerializer(Money.class, JacksonMoney.INSTANCE.serializer()); //module.addDeserializer(Money.class, JacksonMoney.INSTANCE.deserializer()); mapper.registerModule(module); // java.time.LocalDateTime DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(Dates.DATE_PATTERN); DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss"); JavaTimeModule javaTimeModule = new JavaTimeModule(); javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(LocalDateTimeFormat.PATTERN_11)); javaTimeModule.addDeserializer(LocalDateTime.class, CustomLocalDateTimeDeserializer.INSTANCE); javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(dateFormatter)); javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(dateFormatter)); javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(timeFormatter)); javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(timeFormatter)); mapper.registerModule(javaTimeModule); //mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); } private static JsonNode readTree(ObjectMapper mapper, String body) { try { return mapper.readTree(body); } catch (JsonProcessingException e) { return ExceptionUtils.rethrow(e); } } private static Object parse(ObjectMapper mapper, JsonNode jsonNode, Type type) { try { return mapper .readerFor(mapper.getTypeFactory().constructType(type)) .with(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) .readValue(mapper.treeAsTokens(jsonNode)); } catch (IOException e) { return ExceptionUtils.rethrow(e); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/limit/current/CurrentLimiter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.limit.current; import java.util.Date; /** * 流量限制:限流器(隔板) * * https://www.cnblogs.com/softidea/p/6229543.html * * @author Ponfee */ public interface CurrentLimiter { /** * 校验并追踪 * @param key * @return */ boolean checkpoint(String key); /** * 校验并追踪 * @param key * @param requestThreshold * @return */ boolean checkpoint(String key, long requestThreshold); /** * 按区间统计 * @param key * @param from * @param to * @return */ long countByRange(String key, Date from, Date to); /** * 设置一分钟(60s)的访问限制量 * @param key * @param threshold * @return */ void setRequestThreshold(String key, long threshold); /** * 获取配置的访问量 * @param key * @return */ long getRequestThreshold(String key); } ================================================ FILE: src/main/java/cn/ponfee/commons/limit/current/GuavaCurrentLimiter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.limit.current; import cn.ponfee.commons.util.SynchronizedCaches; import com.google.common.util.concurrent.RateLimiter; import java.util.Date; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * The rate limiter based guava RateLimiter * * @author Ponfee */ public class GuavaCurrentLimiter implements CurrentLimiter { private static final ConcurrentMap LIMITER_MAP = new ConcurrentHashMap<>(); @Override public boolean checkpoint(String key) { RateLimiter limiter = LIMITER_MAP.get(key); return limiter == null || limiter.tryAcquire(); } @Override public boolean checkpoint(String key, long requestThreshold) { if (requestThreshold < 0) { return true; // 小于0表示无限制 } else if (requestThreshold == 0) { return false; // 禁止访问 } RateLimiter limiter = SynchronizedCaches.get(key, LIMITER_MAP, () -> RateLimiter.create(requestThreshold)); if (((Double) limiter.getRate()).longValue() != requestThreshold) { synchronized (limiter) { if (((Double) limiter.getRate()).longValue() != requestThreshold) { limiter.setRate(requestThreshold); } } } return limiter.tryAcquire(); } @Override public long countByRange(String key, Date from, Date to) { throw new UnsupportedOperationException(); } @Override public void setRequestThreshold(String key, long threshold) { if (threshold < -1) { LIMITER_MAP.remove(key); } SynchronizedCaches.get(key, LIMITER_MAP, () -> RateLimiter.create(threshold)).setRate(threshold); } @Override public long getRequestThreshold(String key) { RateLimiter limiter = LIMITER_MAP.get(key); if (limiter == null) { return -1; } return ((Double) limiter.getRate()).longValue(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/limit/request/ConcurrentMapRequestLimiter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.limit.request; import cn.ponfee.commons.util.Asserts; import org.apache.commons.lang3.StringUtils; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * The request limiter based ConcurrentHashMap * * Warning: distribute depoly with multiple server nodes maybe occur problem * * @author Ponfee */ public final class ConcurrentMapRequestLimiter extends RequestLimiter { private final ConcurrentMap> cache = new ConcurrentHashMap<>(); private final Lock lock = new ReentrantLock(); // 定时清理加锁 private ConcurrentMapRequestLimiter(ScheduledExecutorService scheduler) { Asserts.notNull(scheduler, "Scheduler cannot be null."); scheduler.scheduleAtFixedRate(() -> { if (!lock.tryLock()) { return; } try { long now = System.currentTimeMillis(); cache.entrySet().removeIf(x -> x.getValue().isExpire(now)); } finally { lock.unlock(); } }, 60, 120, TimeUnit.SECONDS); } // ---------------------------------------------------------------------request limit @Override public ConcurrentMapRequestLimiter limitFrequency(String key, int period, String message) throws RequestLimitException { checkLimit(CHECK_FREQ_KEY + key, period, 1, message); return this; } @Override public ConcurrentMapRequestLimiter limitThreshold(String key, int period, int limit, String message) throws RequestLimitException { checkLimit(CHECK_THRE_KEY + key, period, limit, message); return this; } // ---------------------------------------------------------------------cache sms code @Override public void cacheCode(String key, String code, int ttl) { add(CACHE_CODE_KEY + key, code, ttl); remove(CHECK_CODE_KEY + key); } @Override public ConcurrentMapRequestLimiter checkCode(String key, String code, int limit) throws RequestLimitException { if (StringUtils.isEmpty(code)) { throw new RequestLimitException("验证码不能为空!"); } String cacheKey = CACHE_CODE_KEY + key; // 1、判断验证码是否已失效 CacheValue actual = get(cacheKey); if (actual == null || actual.get() == null) { throw new RequestLimitException("验证码失效,请重新获取!"); } String checkKey = CHECK_CODE_KEY + key; // 2、检查是否验证超过限定次数 CacheValue times = incrementAndGet(checkKey, actual.expireTimeMillis); if (times.count() > limit) { remove(cacheKey, checkKey); // 超过验证次数,删除缓存中的验证码 throw new RequestLimitException("验证错误次数过多,请重新获取!"); } // 3、检查验证码是否匹配 if (!actual.get().equals(code)) { throw new RequestLimitException("验证码错误!"); } // 验证成功,删除缓存key remove(cacheKey, checkKey); return this; } // ---------------------------------------------------------------------cache captcha @Override public void cacheCaptcha(String key, String captcha, int expire) { add(CACHE_CAPTCHA_KEY + key, captcha, expire); } @Override public boolean checkCaptcha(String key, String captcha, boolean caseSensitive) { CacheValue value = getAndRemove(CACHE_CAPTCHA_KEY + key); if (value == null || value.get() == null) { return false; } return caseSensitive ? value.get().equals(captcha) : value.get().equalsIgnoreCase(captcha); } // ---------------------------------------------------------------------action @Override public void recordAction(String key, int period) { incrementAndGet(TRACE_ACTION_KEY + key, expire(period)); } @Override public long countAction(String key) { CacheValue cache = get(TRACE_ACTION_KEY + key); return cache == null ? 0 : cache.count(); } @Override public void resetAction(String key) { remove(TRACE_ACTION_KEY + key); } // ---------------------------------------------------------------------private methods private void checkLimit(String key, int ttl, int limit, String message) throws RequestLimitException { CacheValue cache = incrementAndGet(key, expire(ttl)); if (cache.count() > limit) { throw new RequestLimitException(message); } } private CacheValue incrementAndGet(String key, long expireTimeMillis) { CacheValue value = cache.get(key); if (value == null || value.isExpire()) { synchronized (cache) { value = cache.get(key); if (value == null || value.isExpire()) { // 失效则重置 value = new CacheValue<>(null, expireTimeMillis); cache.put(key, value); return value; } } } value.increment(); return value; } private void remove(String... keys) { for (String key : keys) { cache.remove(key); } } private CacheValue getAndRemove(String key) { CacheValue value = (CacheValue) cache.remove(key); return value == null || value.isExpire() ? null : value; } private void add(String key, T value, int ttl) { cache.put(key, new CacheValue<>(value, expire(ttl))); } private CacheValue get(String key) { CacheValue value = (CacheValue) cache.get(key); if (value == null) { return null; } else if (value.isExpire()) { cache.remove(key); return null; } else { return value; } } } ================================================ FILE: src/main/java/cn/ponfee/commons/limit/request/HttpSessionRequestLimiter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.limit.request; import org.apache.commons.lang3.StringUtils; import javax.servlet.http.HttpSession; /** * The request limiter based http session * * Warning: User clear cookie maybe occur problem * * @author Ponfee */ @SuppressWarnings("unchecked") public class HttpSessionRequestLimiter extends RequestLimiter { private final HttpSession session; private HttpSessionRequestLimiter(HttpSession session) { this.session = session; } public static HttpSessionRequestLimiter create(HttpSession session) { return new HttpSessionRequestLimiter(session); } // ---------------------------------------------------------------------request limit /** * Client user (web browser) can clear session(cookie), * so this limit can't really effect * * @deprecated */ @Override @Deprecated public HttpSessionRequestLimiter limitFrequency(String key, int period, String message) throws RequestLimitException { checkLimit(CHECK_FREQ_KEY + key, period, 1, message); return this; //throw new UnsupportedOperationException(); } @Override @Deprecated public HttpSessionRequestLimiter limitThreshold(String key, int period, int limit, String message) throws RequestLimitException { checkLimit(CHECK_THRE_KEY + key, period, limit, message); return this; //throw new UnsupportedOperationException(); } // ---------------------------------------------------------------------cache sms code @Override public void cacheCode(String key, String code, int ttl) { add(CACHE_CODE_KEY + key, code, ttl); remove(CHECK_CODE_KEY + key); } @Override public HttpSessionRequestLimiter checkCode(String key, String code, int limit) throws RequestLimitException { if (StringUtils.isEmpty(code)) { throw new RequestLimitException("验证码不能为空!"); } String cacheKey = CACHE_CODE_KEY + key; // 1、判断验证码是否已失效 CacheValue actual = get(cacheKey); if (actual == null || actual.get() == null) { throw new RequestLimitException("验证码失效,请重新获取!"); } String checkKey = CHECK_CODE_KEY + key; // 2、检查是否验证超过限定次数 CacheValue times = incrementAndGet(checkKey, actual.expireTimeMillis); if (times.count() > limit) { remove(cacheKey, checkKey); // 超过验证次数,删除缓存中的验证码 throw new RequestLimitException("验证错误次数过多,请重新获取!"); } // 3、检查验证码是否匹配 if (!actual.get().equals(code)) { throw new RequestLimitException("验证码错误!"); } // 验证成功,删除缓存key remove(cacheKey, checkKey); return this; } // ---------------------------------------------------------------------cache captcha @Override public void cacheCaptcha(String key, String captcha, int expire) { add(CACHE_CAPTCHA_KEY + key, captcha, expire); } @Override public boolean checkCaptcha(String key, String captcha, boolean caseSensitive) { CacheValue value = getAndRemove(CACHE_CAPTCHA_KEY + key); if (value == null || value.get() == null) { return false; } return caseSensitive ? value.get().equals(captcha) : value.get().equalsIgnoreCase(captcha); } // ---------------------------------------------------------------------action /** * Client user (web browser) can clear session(cookie), * so this limit can't really effect * * @deprecated */ @Override @Deprecated public void recordAction(String key, int period) { incrementAndGet(TRACE_ACTION_KEY + key, expire(period)); } @Override @Deprecated public long countAction(String key) { CacheValue cache = get(TRACE_ACTION_KEY + key); return cache == null ? 0 : cache.count(); } @Override @Deprecated public void resetAction(String key) { remove(TRACE_ACTION_KEY + key); } // ---------------------------------------------------------------------private methods private void checkLimit(String key, int ttl, int limit, String message) throws RequestLimitException { CacheValue cache = incrementAndGet(key, expire(ttl)); if (cache.count() > limit) { throw new RequestLimitException(message); } } private CacheValue incrementAndGet(String key, long expireTimeMillis) { synchronized (session) { CacheValue cache = (CacheValue) session.getAttribute(key); if (cache == null || cache.isExpire()) { // 失效则重置 cache = new CacheValue<>(null, expireTimeMillis); session.setAttribute(key, cache); } else { cache.increment(); } return cache; } } private void remove(String... keys) { for (String key : keys) { session.removeAttribute(key); } } private CacheValue getAndRemove(String key) { CacheValue cache = (CacheValue) session.getAttribute(key); if (cache == null) { return null; } else { session.removeAttribute(key); return cache.isExpire() ? null : cache; } } private void add(String key, T value, int ttl) { session.setAttribute(key, new CacheValue<>(value, expire(ttl))); } private CacheValue get(String key) { CacheValue cache = (CacheValue) session.getAttribute(key); if (cache == null) { return null; } else if (cache.isExpire()) { session.removeAttribute(key); return null; } else { return cache; } } } ================================================ FILE: src/main/java/cn/ponfee/commons/limit/request/RequestLimitException.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.limit.request; /** * 请求超限异常 * @author Ponfee */ public class RequestLimitException extends Exception { private static final long serialVersionUID = 2493768018114069549L; /** * @param message 错误信息 */ public RequestLimitException(String message) { super(message); } } ================================================ FILE: src/main/java/cn/ponfee/commons/limit/request/RequestLimiter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.limit.request; import cn.ponfee.commons.jce.HmacAlgorithms; import cn.ponfee.commons.jce.digest.HmacUtils; import org.apache.commons.codec.binary.Hex; import org.apache.commons.lang3.StringUtils; import javax.crypto.Mac; import java.io.Serializable; import java.util.concurrent.atomic.AtomicInteger; /** * Request limiter, like as send sms and so on * * @author Ponfee */ public abstract class RequestLimiter { private static final byte[] SALT_PREFIX = "{;a*9)p * 比如短信60秒内只能发送一次 * * @param key the key * @param period the period * @param message the message * * @return the caller, chain program * * @throws RequestLimitException if over limit occurs */ public abstract RequestLimiter limitFrequency(String key, int period, String message) throws RequestLimitException; public final RequestLimiter limitThreshold(String key, int period, int limit) throws RequestLimitException { return limitThreshold(key, period, limit, "请求超限,请" + RequestLimiter.format(period) + "后再试!"); } /** * 访问次数限制:一个周期内最多允许访问limit次 * 比如一个手机号一天只能发10次 * * @param key the key * @param period the period * @param limit the limit * @param message the message * * @return the caller, chain program * * @throws RequestLimitException if over limit occurs */ public abstract RequestLimiter limitThreshold(String key, int period, int limit, String message) throws RequestLimitException; // ----------------------------------------------------------------用于验证码校验(如手机验证码) /** * cache for the server generate validation code * * @param key the cache key * @param code the validation code of server generate * @param ttl the expire time */ public abstract void cacheCode(String key, String code, int ttl); /** * check the validation code of user input is equals server cache * * @param key the cache key * @param code the validation code of user input * @param limit the maximum fail input times * * @return the caller, chain program * * @throws RequestLimitException if over limit occurs */ public abstract RequestLimiter checkCode(String key, String code, int limit) throws RequestLimitException; // ----------------------------------------------------------------用于缓存图片验证码 /** * cache captcha of server generate * * @param key * @param captcha the image captcha code of server generate * @param expire 缓存有效时间 */ public abstract void cacheCaptcha(String key, String captcha, int expire); public final boolean checkCaptcha(String key, String captcha) { return this.checkCaptcha(key, captcha, false); } /** * check captcha of user input * * @param key the cache key * @param captcha the captcha * @param caseSensitive is case sensitive * * @return true|false */ public abstract boolean checkCaptcha(String key, String captcha, boolean caseSensitive); // ------------------------------------------------------------------------行为计数(用于登录失败限制) /** * 计数周期内的行为

    * 用于登录失败达到一定次数后锁定账户等场景

    * * @param key * @param period */ public abstract void recordAction(String key, int period); /** * 统计周期内的行为量

    * 用于登录失败达到一定次数后锁定账户等场景

    * * @param key the key * @return action count number */ public abstract long countAction(String key); /** * 重置行为 * * @param key the key */ public abstract void resetAction(String key); // ----------------------------------------------------------------用于验证码校验 /** * 生成nonce校验码(返回到用户端) * * @param code a string like as captcha code * @param salt a string like as mobile phone * * @return a check code */ public static String buildNonce(String code, String salt) { //byte[] key = Bytes.fromLong(new Random(code.hashCode()).nextLong()); // 第一个nextLong值是固定的 byte[] key = code.getBytes(); Mac mac = HmacUtils.getInitializedMac(HmacAlgorithms.HmacMD5, key); mac.update(SALT_PREFIX); return Hex.encodeHexString(mac.doFinal(salt.getBytes())); } /** * 校验nonce * * @param nonce the nonce * @param code the code * @param salt the salt * * @return {@code true} is verify success */ public static boolean verifyNonce(String nonce, String code, String salt) { return StringUtils.isNotEmpty(nonce) && nonce.equals(buildNonce(code, salt)); } /** * 时间格式化,5/6 rate * * @param seconds * @return */ public static String format(int seconds) { int days = seconds / 86400; // 年 if (days > 365) { return (days / 365 + ((days % 365) / 30 + 10) / 12) + "年"; } // 月 if (days > 30) { return (days / 30 + (days % 30 + 25) / 30) + "个月"; } // 日 seconds %= 86400; int hours = seconds / 3600; if (days > 0) { return (days + (hours + 20) / 24) + "天"; } // 时 seconds %= 3600; int minutes = seconds / 60; if (hours > 0) { return (hours + (minutes + 50) / 60) + "小时"; } // 分 seconds %= 60; if (minutes > 0) { return (minutes + (seconds + 50) / 60) + "分钟"; } // 秒 return seconds + "秒"; } static long expire(int ttl) { return System.currentTimeMillis() + ttl * 1000L; } static class CacheValue implements Serializable { private static final long serialVersionUID = 8615157453929878610L; final T value; final long expireTimeMillis; final AtomicInteger count; CacheValue(T value, long expireTimeMillis) { this.value = value; this.expireTimeMillis = expireTimeMillis; this.count = new AtomicInteger(1); } int increment() { return count.incrementAndGet(); } int count() { return count.get(); } T get() { return value; } boolean isExpire() { return expireTimeMillis < System.currentTimeMillis(); } boolean isExpire(long timeMillis) { return expireTimeMillis < timeMillis; } } } ================================================ FILE: src/main/java/cn/ponfee/commons/log/LogAnnotation.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.log; import java.lang.annotation.*; /** * 如果是日志入库,则无法用在service的只读事务方法上,需要新开启嵌套的事务 *

    * * Ali开发手册: * 应用中的扩展日志(如打点、临时监控、访问日志等)命名方式:appName_logType_logName.log。 * logType:日志类型,推荐分类有stats/desc/monitor/visit等;logName:日志描述。 * 这种命名的好处:通过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找。 * * 可以使用warn日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适从。 * 注意日志输出的级别,error级别只记录系统逻辑出错、异常等重要的错误信息。 * 如非必要,请不要在此场景打出error级别。 *

    * * 日志注解 * * @author Ponfee */ @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface LogAnnotation { LogType type() default LogType.UNDEFINED; boolean enabled() default false; // 是否开启熔断 String desc() default ""; enum LogType { UNDEFINED(0x0, null), ADD(0x1, "新增"), UPDATE(0x2, "更新"), DELETE(0x3, "删除"), QUERY(0x4, "查询"); private final int type; private final String comment; LogType(int type, String comment) { this.type = type; this.comment = comment; } public String comment() { return comment; } public int type() { return type; } } } ================================================ FILE: src/main/java/cn/ponfee/commons/log/LogInfo.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.log; import cn.ponfee.commons.log.LogAnnotation.LogType; import cn.ponfee.commons.model.ToJsonString; /** * 日志信息 * * @author Ponfee */ public class LogInfo extends ToJsonString implements java.io.Serializable { private static final long serialVersionUID = -4824757481106145723L; private LogType type; // 日志类型 private String desc; // 日志描述 private String methodName; // 方法名称 private Object args; // 调用参数 private Object retVal; // 返回值 private String exception; // 异常信息 private int costTime; // 调用耗时(毫秒) public LogInfo() {} public LogInfo(String methodName) { this.methodName = methodName; } public LogType getType() { return type; } public void setType(LogType type) { this.type = type; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } public String getMethodName() { return methodName; } public void setMethodName(String methodName) { this.methodName = methodName; } public Object getArgs() { return args; } public void setArgs(Object args) { this.args = args; } public Object getRetVal() { return retVal; } public void setRetVal(Object retVal) { this.retVal = retVal; } public String getException() { return exception; } public void setException(String exception) { this.exception = exception; } public int getCostTime() { return costTime; } public void setCostTime(int costTime) { this.costTime = costTime; } } ================================================ FILE: src/main/java/cn/ponfee/commons/log/LogRecorder.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.log; import cn.ponfee.commons.exception.Throwables; import cn.ponfee.commons.limit.current.CurrentLimiter; import cn.ponfee.commons.util.ObjectUtils; import com.google.common.base.Preconditions; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** *

     *   1.开启spring切面特性:
     *   2.编写子类:
     *     `@Component
     *     `@Aspect
     *     public class TestLogger extends LogRecorder {
     *         `@Around(value = "execution(public * cn.xxx.service.impl..*Impl..*(..)) 
     *                  && `@annotation(log)", argNames = "pjp,log")
     *         `@Override
     *         public Object around(ProceedingJoinPoint pjp, LogAnnotation log) throws Throwable {
     *             return super.around(pjp, log);
     *         }
     *     }
     * 
    * * 日志记录切处理 * * @author Ponfee */ public abstract class LogRecorder { private static final int DEFAULT_ALARM_THRESHOLD_MILLIS = 2000; private static final Logger logger = LoggerFactory.getLogger(LogRecorder.class); private final int alarmThresholdMillis; // 告警阀值 private final CurrentLimiter limiter; // 访问频率限制 public LogRecorder() { this(DEFAULT_ALARM_THRESHOLD_MILLIS); } public LogRecorder(int alarmThresholdMillis) { this(alarmThresholdMillis, null); } public LogRecorder(CurrentLimiter circuitBreaker) { this(DEFAULT_ALARM_THRESHOLD_MILLIS, circuitBreaker); } public LogRecorder(int alarmThresholdMillis, CurrentLimiter circuitBreaker) { Preconditions.checkArgument(alarmThresholdMillis > 0); this.alarmThresholdMillis = alarmThresholdMillis; this.limiter = circuitBreaker; } /** * 日志拦截 * @param pjp * @return * @throws Throwable */ public Object around(ProceedingJoinPoint pjp) throws Throwable { return this.around(pjp, null); } /** * 日志拦截 * @param pjp * @param log * @return * @throws Throwable */ public Object around(ProceedingJoinPoint pjp, LogAnnotation log) throws Throwable { MethodSignature ms = (MethodSignature) pjp.getSignature(); String methodName = ms.getMethod().toGenericString(); // request volume threshold if (limiter != null && log != null && log.enabled() && !limiter.checkpoint(methodName)) { throw new IllegalStateException("request denied"); } LogInfo logInfo = new LogInfo(methodName); if (log != null) { logInfo.setType(log.type()); logInfo.setDesc(log.desc()); } String logs = getLogs(log); logInfo.setArgs(pjp.getArgs()); if (logger.isInfoEnabled()) { logger.info("[exec-before]-[{}]{}-{}", methodName, logs, ObjectUtils.toString(logInfo.getArgs())); } long start = System.currentTimeMillis(); try { Object retVal = pjp.proceed(); logInfo.setCostTime((int) (System.currentTimeMillis() - start)); logInfo.setRetVal(retVal); if (logger.isInfoEnabled()) { logger.info("[exec-after]-[{}]{}-[{}]", methodName, logs, ObjectUtils.toString(retVal)); } if (logger.isWarnEnabled() && logInfo.getCostTime() > alarmThresholdMillis) { logger.warn("[exec-time]-[{}]{}-[cost {}]", methodName, logs, logInfo.getCostTime()); // 执行时间告警 } return retVal; } catch (Throwable e) { logger.error("[exec-throw]-[{}]{}-{}", methodName, logs, ObjectUtils.toString(logInfo.getArgs()), e); logInfo.setCostTime((int) (System.currentTimeMillis() - start)); logInfo.setException(Throwables.getRootCauseStackTrace(e)); throw e; // 向外抛 } finally { try { log(logInfo); } catch (Throwable ex) { logger.error("Handle log info occur error.", ex); } } } /** * 日志记录(可用于记录到日志表) * @param logInfo */ protected void log(LogInfo logInfo) { // no-thing to do } private String getLogs(LogAnnotation log) { if (log == null) { return ""; } StringBuilder builder = new StringBuilder("-["); builder.append(log.type()); if (log.desc() != null) { builder.append(',').append(log.desc()); } return builder.append(']').toString(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/math/FailureRatioActuary.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.math; import java.util.BitSet; import java.util.List; import java.util.stream.IntStream; /** * Failure ratio * * @author Ponfee */ public class FailureRatioActuary { private final BitSet bitset; private final int size; private int position = 0; public FailureRatioActuary(int size) { this.size = (size + 63) / 64 * 64; this.bitset = new BitSet(size); IntStream.range(0, size).forEach(bitset::set); } public int size() { //Assert.state(size == bitset.size(), () -> "Illegal size, except: " + size + ", actual: " + bitset.size()); return size; } public void set(boolean value) { bitset.set(position++, value); if (position == size) { position = 0; } } public void set(Boolean value) { bitset.set(position++, value != null && value); if (position == size) { position = 0; } } public double ratio(T[] array, ToBooleanFunction mapper) { for (T val : array) { set(mapper.apply(val)); } return ratio(); } public double set(List array, ToBooleanFunction mapper) { for (T val : array) { set(mapper.apply(val)); } return ratio(); } public double ratio(boolean[] array) { for (boolean val : array) { set(val); } return ratio(); } public double ratio() { return ((double) (size - bitset.cardinality())) / size; } @Override public String toString() { return "(" + position + ", " + bitset + ")"; } @FunctionalInterface public interface ToBooleanFunction { boolean apply(T value); } } ================================================ FILE: src/main/java/cn/ponfee/commons/math/Maths.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.math; import cn.ponfee.commons.util.Asserts; import org.springframework.util.Assert; /** * 数学算术 * 取模:Modulo Operation * * @author Ponfee */ public class Maths { /** * 以2为底n的对数 * * @param n the value * @return a value of log(n)/log(2) */ public static strictfp double log2(double n) { return log(n, 2); } /** * 求以base为底n的对数 * {@link java.lang.Math#log10(double) } 求以10为底n的对数(lg) * {@link java.lang.Math#log(double) } 以e为底n的对数(自然对数,ln) * {@link java.lang.Math#log1p(double) } 以e为底n+1的对数 * * @param n a value * @param base 底数 * @return a double of logarithm */ public static strictfp double log(double n, double base) { return Math.log(n) / Math.log(base); } /** * rotate shift left,循环左移位操作:0<=n<=32 * * @param x the value * @param n shift bit len * @return a number of rotate left result */ public static int rotateLeft(int x, int n) { Assert.isTrue(n >= 0 && n <= 32, "N must be range [0, 32]."); return (x << n) | (x >>> (32 - n)); } /** *
         * Returns a long value of bit count mask
         * calculate the bit counts mask long value
         *   a: (1 << bits) - 1
         *   b: -1L ^ (-1L << bits)
         *   c: ~(-1L << bits)
         *   d: Long.MAX_VALUE >>> (63 - bits)
         *
         *  bitsMask(0)  -> 0                   -> 0000000000000000000000000000000000000000000000000000000000000000
         *  bitsMask(1)  -> 1                   -> 0000000000000000000000000000000000000000000000000000000000000001
         *  bitsMask(2)  -> 3                   -> 0000000000000000000000000000000000000000000000000000000000000011
         *  bitsMask(10) -> 1023                -> 0000000000000000000000000000000000000000000000000000001111111111
         *  bitsMask(20) -> 1048575             -> 0000000000000000000000000000000000000000000011111111111111111111
         *  bitsMask(63) -> 9223372036854775807 -> 0111111111111111111111111111111111111111111111111111111111111111
         *  bitsMask(64) -> -1                  -> 1111111111111111111111111111111111111111111111111111111111111111
         * 
    * * @param bits the bit count * @return a long value */ public static long bitsMask(int bits) { Asserts.range(bits, 0, Long.SIZE, "bits must range [0,64]."); return bits == Long.SIZE ? -1 : ~(-1L << bits); } /** * Returns a long value for {@code base}{@code exponent}. * * @param base the base * @param exponent the exponent * @return a long value for {@code base}{@code exponent}. */ public static long pow(long base, int exponent) { Assert.isTrue(base >= 1, "Base number cannot less than 1."); Assert.isTrue(exponent >= 0, "Exponent number cannot less than 1."); if (exponent == 0) { return 1; } long result = base; while (--exponent > 0) { result *= base; } return result; } public static int abs(int a) { // Integer.MIN_VALUE & 0x7FFFFFFF = 0 return (a == Integer.MIN_VALUE) ? Integer.MAX_VALUE : (a < 0) ? -a : a; } public static long abs(long a) { return (a == Long.MIN_VALUE) ? Long.MAX_VALUE : (a < 0) ? -a : a; } /** * Returns square root of specified double value

    * Use binary search method * * @param value the value * @return square root */ public static strictfp double sqrtBinary(double value) { if (value < 0.0D) { return Double.NaN; } if (value == 0.0D || value == 1.0D) { return value; } double lower, upper, root, square; if (value > 1.0D) { lower = 1.0D; upper = value; } else { lower = value; upper = 1.0D; } while ((root = lower + (upper - lower) / 2) != lower && root != upper && (square = root * root) != value) { if (square > value) { upper = root; } else { lower = root; } } // cannot find a more rounded value return root; } /** * Returns square root of specified double value

    * Use newton iteration method: X(n+1)=[X(n)+p/Xn]/2 * * @param value the value * @return square root */ public static strictfp double sqrtNewton(double value) { if (value < 0) { return Double.NaN; } if (value == 0.0D) { return value; } double r = value / 2, t; do { t = r; r = (t + value / t) / 2; } while (t != r); return r; } // ------------------------------------------------------------------------int plus/minus public static int plus(int a, int b) { if (a > 0 && b > 0) { return Integer.MAX_VALUE - b < a ? Integer.MAX_VALUE : a + b; } else if (a < 0 && b < 0) { return Integer.MIN_VALUE - b > a ? Integer.MIN_VALUE : a + b; } else { return a + b; } } public static int minus(int a, int b) { if (a > 0 && b < 0) { return Integer.MAX_VALUE + b < a ? Integer.MAX_VALUE : a - b; } else if (a < 0 && b > 0) { return Integer.MIN_VALUE + b > a ? Integer.MIN_VALUE : a - b; } else { return a - b; } } // ------------------------------------------------------------------------long plus/minus public static long plus(long a, long b) { if (a > 0 && b > 0) { return Long.MAX_VALUE - b < a ? Long.MAX_VALUE : a + b; } else if (a < 0 && b < 0) { return Long.MIN_VALUE - b > a ? Long.MIN_VALUE : a + b; } else { return a + b; } } public static long minus(long a, long b) { if (a > 0 && b < 0) { return Long.MAX_VALUE + b < a ? Long.MAX_VALUE : a - b; } else if (a < 0 && b > 0) { return Long.MIN_VALUE + b > a ? Long.MIN_VALUE : a - b; } else { return a - b; } } /** * Returns the greatest common divisor * * @param a the first number * @param b the second number * @return gcd */ public static int gcd(int a, int b) { if (a < 0 || b < 0) { throw new ArithmeticException(); } if (a == 0 || b == 0) { return Math.abs(a - b); } for (int c; (c = a % b) != 0;) { a = b; b = c; } return b; } /** * Returns the greatest common divisor in array * * @param array the int array * @return gcd */ public static int gcd(int[] array) { int result = array[0]; for (int i = 1; i < array.length; i++) { result = gcd(result, array[i]); } return result; } } ================================================ FILE: src/main/java/cn/ponfee/commons/math/Numbers.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.math; import cn.ponfee.commons.base.Symbol; import cn.ponfee.commons.base.tuple.Tuple2; import com.google.common.base.Strings; import com.google.common.primitives.Chars; import org.apache.commons.codec.binary.Hex; import org.springframework.util.Assert; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.stream.LongStream; import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; /** *

     * Number utility
     *
     * 十进制:10
     * 二进制:0B10
     * 八进制:010
     * 十六进制:0X10
     * 小数点:1e-9
     * 
    * * @author Ponfee */ public final class Numbers { public static final int ZERO_INT = 0; public static final Integer ZERO_INTEGER = ZERO_INT; public static final byte ZERO_BYTE = 0x00; public static final double ZERO_DOUBLE = 0.0D; public static final double ONE_DOUBLE = 1.0D; // --------------------------------------------------------------character convert public static char toChar(Object obj) { return toChar(obj, Symbol.Char.ZERO); } public static char toChar(Object obj, char defaultVal) { Character value = toWrapChar(obj); return value == null ? defaultVal : value; } public static Character toWrapChar(Object obj) { if (obj == null) { return null; } else if (obj instanceof Character) { return (Character) obj; } else if (obj instanceof Number) { return (char) ((Number) obj).intValue(); } else if (obj instanceof byte[]) { return Chars.fromByteArray((byte[]) obj); } else if (obj instanceof Boolean) { return (char) (((boolean) obj) ? 0xFF : 0x00); } else { String str = obj.toString(); return str.length() == 1 ? str.charAt(0) : null; } } // -----------------------------------------------------------------boolean convert public static boolean toBoolean(Object obj) { return toBoolean(obj, false); } public static boolean toBoolean(Object obj, boolean defaultVal) { Boolean value = toWrapBoolean(obj); return value == null ? defaultVal : value; } public static Boolean toWrapBoolean(Object obj) { if (obj == null) { return null; } else if (obj instanceof Boolean) { return (Boolean) obj; } else if (obj instanceof Number) { return ((Number) obj).byteValue() != ZERO_BYTE; } else { return Boolean.parseBoolean(obj.toString()); } } // -----------------------------------------------------------------byte convert public static byte toByte(Object obj) { return toByte(obj, (byte) 0); } public static byte toByte(Object obj, byte defaultVal) { if (obj instanceof Number) { return ((Number) obj).byteValue(); } Long value = parseLong(obj); return value == null ? defaultVal : value.byteValue(); } public static Byte toWrapByte(Object obj) { if (obj instanceof Byte) { return (Byte) obj; } if (obj instanceof Number) { return ((Number) obj).byteValue(); } Long value = parseLong(obj); return value == null ? null : value.byteValue(); } // -----------------------------------------------------------------short convert public static short toShort(Object obj) { return toShort(obj, (short) 0); } public static short toShort(Object obj, short defaultVal) { if (obj instanceof Number) { return ((Number) obj).shortValue(); } Long value = parseLong(obj); return value == null ? defaultVal : value.shortValue(); } public static Short toWrapShort(Object obj) { if (obj instanceof Short) { return (Short) obj; } if (obj instanceof Number) { return ((Number) obj).shortValue(); } Long value = parseLong(obj); return value == null ? null : value.shortValue(); } // -----------------------------------------------------------------int convert public static int toInt(Object obj) { return toInt(obj, 0); } public static int toInt(Object obj, int defaultVal) { if (obj instanceof Number) { return ((Number) obj).intValue(); } Long value = parseLong(obj); return value == null ? defaultVal : value.intValue(); } public static Integer toWrapInt(Object obj) { if (obj instanceof Integer) { return (Integer) obj; } if (obj instanceof Number) { return ((Number) obj).intValue(); } Long value = parseLong(obj); return value == null ? null : value.intValue(); } // -----------------------------------------------------------------long convert public static long toLong(Object obj) { return toLong(obj, 0L); } public static long toLong(Object obj, long defaultVal) { if (obj instanceof Number) { return ((Number) obj).longValue(); } Long value = parseLong(obj); return value == null ? defaultVal : value; } public static Long toWrapLong(Object obj) { if (obj instanceof Long) { return (Long) obj; } if (obj instanceof Number) { return ((Number) obj).longValue(); } return parseLong(obj); } // -----------------------------------------------------------------float convert public static float toFloat(Object obj) { return toFloat(obj, 0.0F); } public static float toFloat(Object obj, float defaultVal) { if (obj instanceof Number) { return ((Number) obj).floatValue(); } Double value = parseDouble(obj); return value == null ? defaultVal : value.floatValue(); } public static Float toWrapFloat(Object obj) { if (obj instanceof Float) { return (Float) obj; } if (obj instanceof Number) { return ((Number) obj).floatValue(); } Double value = parseDouble(obj); return value == null ? null : value.floatValue(); } // -----------------------------------------------------------------double convert public static double toDouble(Object obj) { return toDouble(obj, 0.0D); } public static double toDouble(Object obj, double defaultVal) { if (obj instanceof Number) { return ((Number) obj).doubleValue(); } Double value = parseDouble(obj); return value == null ? defaultVal : value; } public static Double toWrapDouble(Object obj) { if (obj instanceof Double) { return (Double) obj; } if (obj instanceof Number) { return ((Number) obj).doubleValue(); } return parseDouble(obj); } // ---------------------------------------------------------------------number format /** * 数字精度化 * * @param value * @param scale * @return */ public static double scale(Object value, int scale) { double val = toDouble(value); if (scale < 0) { return val; } return BigDecimal.valueOf(val) .setScale(scale, RoundingMode.HALF_UP) .doubleValue(); } /** * 向下转单位 * * @param value * @param pow * @return */ public static double lower(double value, int pow) { return BigDecimal.valueOf(value / Math.pow(10, pow)).doubleValue(); } public static double lower(double value, int pow, int scale) { return BigDecimal.valueOf(value / Math.pow(10, pow)) .setScale(scale, RoundingMode.HALF_UP) .doubleValue(); } /** * 向上转单位 * * @param value * @param pow * @return */ public static double upper(double value, int pow) { return BigDecimal.valueOf(value * Math.pow(10, pow)).doubleValue(); } public static double upper(double value, int pow, int scale) { return BigDecimal.valueOf(value * Math.pow(10, pow)) .setScale(scale, RoundingMode.HALF_UP) .doubleValue(); } /** * 百分比 * * @param numerator * @param denominator * @param scale * @return */ public static String percent(double numerator, double denominator, int scale) { if (denominator == 0.0D) { return "--"; } return percent(numerator / denominator, scale); } /** * 百分比 * * @param value * @param scale * @return */ public static String percent(double value, int scale) { if (Double.isNaN(value) || Double.isInfinite(value)) { return "--"; } String format = "#,##0"; if (scale > 0) { // StringUtils.leftPad("", scale, '0'); String.format("%0" + scale + "d", 0); format += "." + Strings.repeat("0", scale); } return new DecimalFormat(format + "%").format(value); } /** * 数字格式化 * * @param obj * @return */ public static String format(Object obj) { return format(obj, "###,###.###"); } /** * 数字格式化 * * @param obj * @param format * @return */ public static String format(Object obj, String format) { NumberFormat fmt = new DecimalFormat(format); if (obj instanceof CharSequence) { String str = obj.toString().replace(",", ""); if (str.endsWith("%")) { str = str.substring(0, str.length() - 1); return fmt.format(Double.parseDouble(str)) + "%"; } else { return fmt.format(Double.parseDouble(str)); } } else { return fmt.format(obj); } } /** * Returns a string value of double * * @param d the double value * @param scale the scale * @return a string */ public static String format(double d, int scale) { NumberFormat nf = NumberFormat.getInstance(); nf.setMaximumFractionDigits(scale); nf.setGroupingUsed(false); return nf.format(d); } /** * 区间取值 * * @param value * @param min * @param max * @return */ public static int bounds(Integer value, int min, int max) { if (value == null || value < min) { return min; } else if (value > max) { return max; } else { return value; } } public static int sum(Integer a, Integer b) { return defaultIfNull(a, 0) + defaultIfNull(b, 0); } public static long sum(Long a, Long b) { return defaultIfNull(a, 0L) + defaultIfNull(b, 0L); } public static double sum(Double a, Double b) { return defaultIfNull(a, 0.0D) + defaultIfNull(b, 0.0D); } public static boolean isNullOrZero(Long value) { return value == null || value == 0L; } public static boolean isNullOrZero(Integer value) { return value == null || value == 0; } /** * 分片 * *
         *   slice(0 , 2)  ->  [0, 0]
         *   slice(2 , 3)  ->  [1, 1, 0]
         *   slice(3 , 1)  ->  [3]
         *   slice(9 , 3)  ->  [3, 3, 3]
         *   slice(10, 3)  ->  [4, 3, 3]
         *   slice(11, 3)  ->  [4, 4, 3]
         *   slice(12, 3)  ->  [4, 4, 4]
         * 
    * * @param quantity * @param segment * @return */ public static int[] slice(int quantity, int segment) { int[] result = new int[segment]; int quotient = quantity / segment; int remainder = quantity % segment; int moreValue = quotient + 1; Arrays.fill(result, 0, remainder, moreValue); Arrays.fill(result, remainder, segment, quotient); return result; } /** * Partition the number *
         *   partition( 0, 2)  ->  [(0, 0)]
         *   partition( 2, 3)  ->  [(0, 0), (1, 1)]
         *   partition( 3, 1)  ->  [(0, 2)]
         *   partition( 9, 3)  ->  [(0, 2), (3, 5), (6, 8)]
         *   partition(10, 3)  ->  [(0, 3), (4, 6), (7, 9)]
         *   partition(11, 3)  ->  [(0, 3), (4, 7), (8, 10)]
         *   partition(12, 3)  ->  [(0, 3), (4, 7), (8, 11)]
         * 
    * * @param number the number * @param size the size * @return array */ public static List> partition(int number, int size) { Assert.isTrue(number >= 0, "Number must be greater than 0."); Assert.isTrue(size > 0, "Size must be greater than 0."); if (number == 0) { return Collections.singletonList(Tuple2.of(0, 0)); } List> result = new ArrayList<>(size); int last = -1; for (int a : slice(number, size)) { if (a == 0) { break; } result.add(Tuple2.of(last += 1, last += a - 1)); } return result; } /** * Split the bill for coupon amount
    * split(new int[]{249, 249, 249, 3}, 748) -> [249, 249, 248, 2] * * @param bills the bills * @param value the coupon amount value * @return split result */ public static long[] split(long[] bills, long value) { long total = LongStream.of(bills).sum(); if (total < value) { throw new IllegalArgumentException("Total bill amount[" + total + "] cannot less than coupon amount[" + value + "]"); } long[] result = new long[bills.length]; if (bills.length == 0 || value == 0) { return result; } double rate; int i = 0, n = bills.length - 1; for (; i < n; i++) { // rate <= 1.0 rate = value / (double) total; // 不能用Math.round:面值为748分钱的券 去平摊账单 [249, 249, 249, 3],最后金额为3分钱的账单项要平摊掉4分钱 //result[i] = Math.min(Math.round(bills[i] * rate), value); // 因为result[i]是ceil后的结果,所以按比率上来算value减得会更多,即rate只会递减,所以不会出现溢出(后面的费用项不够抵扣)的情况 result[i] = Math.min((int) Math.ceil(bills[i] * rate), value); value -= result[i]; total -= bills[i]; if (value == 0) { break; } } // the last bill item if (i == n) { result[i] = value; } return result; } /** * Returns the Long object is equals the Integer object * * @param a the Long a * @param b the Integer b * @return if is equals then return {@code true} */ public static boolean equals(Long a, Integer b) { if (a == null && b == null) { return true; } return a != null && b != null && a.longValue() == b.intValue(); } /** * To upper hex string and remove prefix 0 * * @param num the BigInteger * @return upper hex string */ public static String toHex(BigInteger num) { String hex = Hex.encodeHexString(num.toByteArray(), false); if (hex.matches("^0+$")) { return "0"; } return hex.replaceFirst("^0*", ""); } // --------------------------------------------------------------------------金额汉化 private static final String[] CN_UPPER_NUMBER = { "零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖" }; private static final String[] CN_UPPER_MONETARY_UNIT = { "分", "角", "元", "拾", "佰", "仟", "万", "拾", "佰", "仟", "亿", "拾", "佰", "仟", "兆", "拾", "佰", "仟" }; private static final BigDecimal MAX_VALUE = new BigDecimal("9999999999999999.995"); /** * 金额汉化(单位元) * * @param amount * @return a string of chineseize amount */ public static String chinesize(BigDecimal amount) { if (amount.compareTo(MAX_VALUE) >= 0) { throw new IllegalArgumentException("The amount value too large."); } int signum = amount.signum(); // 正负数:0,1,-1 if (signum == 0) { return "零元整"; } // * 100 long number = amount.movePointRight(2).setScale(0, RoundingMode.HALF_UP) .abs().longValue(); int scale = (int) (number % 100), numIndex; if (scale == 0) { numIndex = 2; number = number / 100; } else if (scale % 10 == 0) { numIndex = 1; number = number / 10; } else { numIndex = 0; } boolean getZero = numIndex != 0; StringBuilder builder = new StringBuilder(); for (int zeroSize = 0, numUnit; number > 0; number = number / 10, ++numIndex) { numUnit = (int) (number % 10); // get the last number if (numUnit > 0) { if ((numIndex == 9) && (zeroSize >= 3)) { builder.insert(0, CN_UPPER_MONETARY_UNIT[6]); } if ((numIndex == 13) && (zeroSize >= 3)) { builder.insert(0, CN_UPPER_MONETARY_UNIT[10]); } builder.insert(0, CN_UPPER_MONETARY_UNIT[numIndex]); builder.insert(0, CN_UPPER_NUMBER[numUnit]); getZero = false; zeroSize = 0; } else { ++zeroSize; if (!getZero) { builder.insert(0, CN_UPPER_NUMBER[numUnit]); } if (numIndex == 2) { if (number > 0) { builder.insert(0, CN_UPPER_MONETARY_UNIT[numIndex]); } } else if ( (((numIndex - 2) & 0x03) == 0) && (number % 1000 > 0) ) { builder.insert(0, CN_UPPER_MONETARY_UNIT[numIndex]); } getZero = true; } } if (signum == -1) { builder.insert(0, "负"); // 负数 } if (scale == 0) { builder.append("整"); // 整数 } return builder.toString(); } // -------------------------------------------------------private methods private static Long parseLong(Object obj) { if (obj == null) { return null; } try { String val = obj.toString(); return val.indexOf('.') == -1 ? Long.parseLong(val) : (long) Double.parseDouble(val); } catch (NumberFormatException ignored) { return null; } } private static Double parseDouble(Object obj) { if (obj == null) { return null; } try { return Double.parseDouble(obj.toString()); } catch (NumberFormatException ignored) { return null; } } } ================================================ FILE: src/main/java/cn/ponfee/commons/math/WrappedBigDecimal.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.math; import java.math.BigDecimal; import java.math.MathContext; /** * 包装BigDecimal,用于lamda方法体内计算 * * @author Ponfee */ public class WrappedBigDecimal { private BigDecimal decimal; public WrappedBigDecimal(Number num) { this.decimal = BigDecimal.valueOf(num.doubleValue()); } public synchronized void add(Number num) { this.decimal = this.decimal.add(BigDecimal.valueOf(num.doubleValue())); } public synchronized void divide(BigDecimal divisor) { this.decimal = this.decimal.divide(divisor); } public synchronized void remainder(BigDecimal divisor) { this.decimal = this.decimal.remainder(divisor); } public synchronized void abs(MathContext mc) { this.decimal = this.decimal.abs(mc); } public double getDouble() { return this.decimal.doubleValue(); } public int getInt() { return this.decimal.intValue(); } public long getLong() { return this.decimal.longValue(); } public float getFloat() { return this.decimal.floatValue(); } @Override public String toString() { return this.decimal.toString(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/model/AbstractDataConverter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.model; import cn.ponfee.commons.reflect.BeanCopiers; import cn.ponfee.commons.reflect.BeanMaps; import org.springframework.cglib.beans.BeanCopier; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; import static cn.ponfee.commons.reflect.GenericUtils.getActualTypeArgument; import static cn.ponfee.commons.util.ObjectUtils.isNotBeanType; import static cn.ponfee.commons.util.ObjectUtils.newInstance; /** * Converts model object to the data transfer object * * @param source * @param target * * @author Ponfee */ public abstract class AbstractDataConverter implements Function { private final Class targetType; private final BeanCopier copier; public AbstractDataConverter() { this.copier = createBeanCopier( getActualTypeArgument(getClass(), 0), this.targetType = getActualTypeArgument(getClass(), 1) ); } /** * Returns an target object copy source the argument object

    * * Sub class can override this method

    * * @param source the object * @return a target object */ public T convert(S source) { if (source == null) { return null; } return convert(source, this.targetType, this.copier); } // -------------------------------------------------------------final methods public final void copyProperties(S source, T target) { copy(source, target, this.copier); } public final List convert(List list) { if (list == null) { return null; } return list.stream().map(this).collect(Collectors.toList()); } public final Page convert(Page page) { if (page == null) { return null; } return page.map(this); } public final Result convertResultBean(Result result) { if (result == null) { return null; } return result.from(convert(result.getData())); } public final Result> convertResultList(Result> result) { if (result == null) { return null; } return result.from(convert(result.getData())); } public final Result> convertResultPage(Result> result) { if (result == null) { return null; } return result.from(convert(result.getData())); } // ----------------------------------------------other methods @Override public final T apply(S source) { return this.convert(source); } // -----------------------------------------------static methods public static T convert(S source, Class targetType) { return convert(source, targetType, null); } @SuppressWarnings({ "unchecked" }) public static T convert(S source, Class targetType, BeanCopier copier) { if (source == null || targetType.isInstance(source)) { return (T) source; } // convert if (Map.class.isAssignableFrom(targetType)) { return (T) (source instanceof Map ? source : BeanMaps.CGLIB.toMap(source)); } else if (source instanceof Map) { return BeanMaps.CGLIB.toBean((Map) source, targetType); } else { T target = newInstance(targetType); if (copier != null) { copier.copy(source, target, null); } else { BeanCopiers.copy(source, target); } return target; } } public static void copy(S source, T target) { copy(source, target, null); } @SuppressWarnings({ "unchecked", "rawtypes" }) public static void copy(S source, T target, BeanCopier copier) { if (source == null || target == null) { return; } // convert the source object from source type to target type if (target instanceof Map) { if (source instanceof Map) { ((Map) target).putAll((Map) source); } else { ((Map) target).putAll(BeanMaps.CGLIB.toMap(source)); } } else if (source instanceof Map) { BeanMaps.CGLIB.copyFromMap((Map) source, target); } else if (copier != null) { copier.copy(source, target, null); } else { BeanCopiers.copy(source, target); } } public static T convert(S source, Function converter) { if (source == null) { return null; } return converter.apply(source); } public static List convert(List list, Function converter) { if (list == null) { return null; } return list.stream().map(converter).collect(Collectors.toList()); } public static Page convert(Page page, Function converter) { if (page == null) { return null; } return page.map(converter); } public static Result convertResultBean(Result result, Function converter) { if (result == null) { return null; } return result.from(converter.apply(result.getData())); } public static Result> convertResultList(Result> result, Function converter) { if (result == null) { return null; } return result.from(convert(result.getData(), converter)); } public static Result> convertResultPage(Result> result, Function converter) { if (result == null) { return null; } return result.from(convert(result.getData(), converter)); } // -----------------------------------------------------------------------------------private methods private static BeanCopier createBeanCopier(Class sourceType, Class targetType) { if (isNotBeanType(sourceType) || isNotBeanType(targetType)) { return null; } try { return BeanCopiers.get(sourceType, targetType); } catch (Exception e) { throw new UnsupportedOperationException("Create BeanCopier occur error.", e); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/model/BaseEntity.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.model; import lombok.Getter; import lombok.Setter; import java.util.Date; /** * Base class for Persistent Object(PO or DO) * * @author Ponfee */ @Getter @Setter public abstract class BaseEntity implements java.io.Serializable { private static final long serialVersionUID = -3387171222355207376L; private Long id; // database table primary key private Integer version = 1; // data version private Date createdAt; // created time private Date updatedAt; // last updated time /** * Base entity with biz-no filed * * @param biz-no field type */ @Getter @Setter public static abstract class Number extends BaseEntity { private static final long serialVersionUID = -6907471185117485946L; private N no; // biz-no } /** * Base entity with creator filed * * @param creator field type */ @Getter @Setter public static abstract class Creator extends BaseEntity { private static final long serialVersionUID = -812853678840369113L; private U createdBy; // created user } /** * Base entity with creator and updater filed * * @param updater field type(userid or username) */ @Getter @Setter public static abstract class Updater extends Creator { private static final long serialVersionUID = 5333847915253038118L; private U updatedBy; // last updated user } /** * Base entity with biz-no, creator and updater filed * * @param biz-no field type * @param creator and updater field type(userid or username) */ @Getter @Setter public static abstract class All extends Updater { private static final long serialVersionUID = -939331524501110803L; private N no; // biz-no } } ================================================ FILE: src/main/java/cn/ponfee/commons/model/CodeMsg.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.model; /** * The code and message for {@link Result} * * @author Ponfee */ public interface CodeMsg { int getCode(); boolean isSuccess(); String getMsg(); /** * 中止当前运行的Java虚拟机,返回值给调用方(如bash) *

    0正常退出;非0异常退出; * * @see System#exit(int) */ enum SystemExit implements CodeMsg { SUCCESS(0), FAILURE(1), ; private final int code; private final boolean success; SystemExit(int code) { this.code = code; this.success = code == 0; } @Override public int getCode() { return code; } @Override public boolean isSuccess() { return success; } @Override public String getMsg() { return super.name(); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/model/Form.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.model; import java.util.List; /** * 表单 * * @author Ponfee */ public class Form implements java.io.Serializable { private static final long serialVersionUID = 3335254023919017587L; private List parameters; public List getParameters() { return parameters; } public void setParameters(List parameters) { this.parameters = parameters; } public static class Parameter implements java.io.Serializable { private static final long serialVersionUID = 4322704347383719451L; private String name; // 参数名 private Type type; // 表单类型 private String label; // 标签名 private boolean required; // 是否必填 private boolean multiple; // 是否可多选 public String getName() { return name; } public void setName(String name) { this.name = name; } public Type getType() { return type; } public void setType(Type type) { this.type = type; } public String getLabel() { return label; } public void setLabel(String label) { this.label = label; } public boolean isRequired() { return required; } public void setRequired(boolean required) { this.required = required; } public boolean isMultiple() { return multiple; } public void setMultiple(boolean multiple) { this.multiple = multiple; } } public enum Type { INPUT, PASSWORD, TEXTAREA, RADIO, // CHECKBOX, SELECT, COMBOX, DATEBOX, // } } ================================================ FILE: src/main/java/cn/ponfee/commons/model/MapDataConverter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.model; import cn.ponfee.commons.reflect.Fields; import java.util.Arrays; import java.util.Dictionary; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; /** * Converts model object to map, specified source fields * * @param source * * @author Ponfee */ public class MapDataConverter extends AbstractDataConverter> { private final String[] fields; public MapDataConverter(String... fields) { this.fields = fields; } @Override public Map convert(S source) { return convert(source, fields); } @SuppressWarnings("unchecked") public static Map convert(S source, String... fields) { Function vm; if (source instanceof Map) { vm = ((Map) source)::get; } else if (source instanceof Dictionary) { vm = ((Dictionary) source)::get; } else { vm = field -> Fields.get(source, field); } return Arrays.stream(fields).collect(Collectors.toMap(Function.identity(), vm)); } public static List> convert(List list, String... fields) { if (list == null) { return null; } return list.stream().map(e -> convert(e, fields)).collect(Collectors.toList()); } public static Page> convert(Page page, String... fields) { if (page == null) { return null; } return page.map(x -> convert(x, fields)); } public static Result> convertResultBean(Result result, String... fields) { if (result == null) { return null; } return result.from(convert(result.getData(), fields)); } public static Result>> convertResultList(Result> result, String... fields) { if (result == null) { return null; } return result.from(convert(result.getData(), fields)); } public static Result>> convertResultPage(Result> result, String... fields) { if (result == null) { return null; } return result.from(convert(result.getData(), fields)); } } ================================================ FILE: src/main/java/cn/ponfee/commons/model/Null.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.model; import java.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.Method; /** * The {@code Null} class is representing unable instance object * * @author Ponfee */ public final class Null implements Serializable { private static final long serialVersionUID = 1L; public static final Constructor BROKEN_CONSTRUCTOR; public static final Method BROKEN_METHOD; static { try { BROKEN_CONSTRUCTOR = Null.class.getDeclaredConstructor(); BROKEN_METHOD = Null.class.getDeclaredMethod("broken"); } catch (Exception e) { // cannot happen throw new Error(e); } } private Null() { throw new AssertionError("Null cannot create instance."); } private void broken() { throw new AssertionError("Forbid invoke this method."); } } ================================================ FILE: src/main/java/cn/ponfee/commons/model/Page.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.model; import cn.ponfee.commons.reflect.ClassUtils; import com.google.common.collect.Lists; import lombok.Data; import org.apache.commons.collections4.CollectionUtils; import org.springframework.cglib.beans.BeanCopier; import java.beans.Transient; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; /** *

     * 参考guthub开源的mybatis分页工具
     *   项目地址: 
     *     http://git.oschina.net/free/Mybatis_PageHelper
     *     https://github.com/pagehelper/Mybatis-PageHelper
     * 
     *   属性配置:
     *     http://bbs.csdn.net/topics/360010907
     * 
     *   Mapper插件:
     *     https://github.com/abel533/Mapper
     * 
     *   Spring Boot集成 MyBatis, 分页插件 PageHelper, 通用 Mapper
     *     https://github.com/abel533/MyBatis-Spring-Boot
     * 
    * * @author Ponfee */ @Data public class Page implements java.io.Serializable { private static final long serialVersionUID = 1313118491812094979L; /** * https://mapstruct.org/ */ private static final BeanCopier COPIER = BeanCopier.create(Page.class, Page.class, false); private int pageNum; // 当前页(start 1) private int pageSize; // 每页的数量 private int size; // 当前页的数量 private long startRow; // 当前页面第一个元素在数据库中的行号(start 1) private long endRow; // 当前页面最后一个元素在数据库中的行号 private long total; // 总记录数 private int pages; // 总页数 private List rows; // 结果集 private int prePage; // 前一页 private int nextPage; // 下一页 private Boolean firstPage = Boolean.TRUE; // 是否为第一页(Pojo中bool类型一律不加is且用Boolean包装类型) private Boolean lastPage = Boolean.FALSE; // 是否为最后一页 private Boolean hasPreviousPage = Boolean.FALSE; // 是否有前一页 private Boolean hasNextPage = Boolean.FALSE; // 是否有下一页 private int navigatePages; // 导航页码数 private int[] navigatePageNums; // 所有导航页号 private int navigateFirstPage; // 导航条上的第一页 private int navigateLastPage; // 导航条上的最后一页 public static Page empty() { return new Page<>(); } public static Page of(List list) { return new Page<>(list); } public static Page of(List list, int navigatePages) { return new Page<>(list, navigatePages); } public Page() { this(new ArrayList<>()); } /** * 包装Page对象 * @param list */ public Page(List list) { this(list, 8); } /** * 包装Page对象 * @param list page结果 * @param navigatePages 页码数量 */ public Page(List list, int navigatePages) { if (list instanceof com.github.pagehelper.Page) { com.github.pagehelper.Page page = (com.github.pagehelper.Page) list; this.pageNum = page.getPageNum(); this.pageSize = page.getPageSize(); this.pages = page.getPages(); this.rows = copy(page); this.size = page.size(); this.total = page.getTotal(); if (this.size == 0) { this.startRow = 0; this.endRow = 0; } else { this.startRow = page.getStartRow() + 1; // 由于结果是>startRow的,所以实际的需要+1 this.endRow = this.startRow - 1 + this.size; // 计算实际的endRow(最后一页的时候特殊) } } else { if (list == null) { list = new ArrayList<>(); } this.pageNum = 1; this.pageSize = list.size(); this.pages = this.pageSize > 0 ? 1 : 0; this.rows = list; this.size = list.size(); this.total = list.size(); this.startRow = 0; this.endRow = list.size() > 0 ? list.size() - 1 : 0; } this.navigatePages = navigatePages; calcNavigatePageNums(); // 计算导航页 calcPage(); // 计算前后页,第一页,最后一页 judgePageBoudary(); // 判断页面边界 } private List copy(com.github.pagehelper.Page page) { return Lists.newArrayList(page); } /** * 计算导航页 */ private void calcNavigatePageNums() { if (pages <= navigatePages) { // 当总页数小于或等于导航页码数时 navigatePageNums = new int[pages]; for (int i = 0; i < pages; i++) { navigatePageNums[i] = i + 1; } } else { // 当总页数大于导航页码数时 navigatePageNums = new int[navigatePages]; int startNum = pageNum - navigatePages / 2; int endNum = pageNum + navigatePages / 2; if (startNum < 1) { startNum = 1; // (最前navigatePages页 for (int i = 0; i < navigatePages; i++) { navigatePageNums[i] = startNum++; } } else if (endNum > pages) { endNum = pages; // 最后navigatePages页 for (int i = navigatePages - 1; i >= 0; i--) { navigatePageNums[i] = endNum--; } } else { // 所有中间页 for (int i = 0; i < navigatePages; i++) { navigatePageNums[i] = startNum++; } } } } /** * 计算前后页,第一页,最后一页 */ private void calcPage() { if (navigatePageNums != null && navigatePageNums.length > 0) { navigateFirstPage = navigatePageNums[0]; navigateLastPage = navigatePageNums[navigatePageNums.length - 1]; if (pageNum > 1) { prePage = pageNum - 1; } if (pageNum < pages) { nextPage = pageNum + 1; } } } /** * 判定页面边界 */ private void judgePageBoudary() { firstPage = pageNum == 1; lastPage = pageNum == pages; hasPreviousPage = pageNum > 1; hasNextPage = pageNum < pages; } /** * 判断是否无数据 * * @return */ @Transient public boolean isEmpty() { return CollectionUtils.isEmpty(rows); } /** * 处理 * @param action */ public void forEach(Consumer action) { if (isEmpty()) { return; } rows.forEach(action); } /** * 转换 * * @param mapper * @return */ public Page map(Function mapper) { Objects.requireNonNull(mapper); Page page = this.copy(); if (isEmpty()) { return page; } page.setRows(rows.stream().map(mapper).collect(Collectors.toList())); return page; } public Page copy() { Page page = new Page<>(); COPIER.copy(this, page, null); return page; } @Override public String toString() { return new StringBuilder(280) .append(getClass().getCanonicalName()).append("@") .append(Integer.toHexString(hashCode())).append("{") .append("pageNum=").append(pageNum) .append(",pageSize=").append(pageSize) .append(",size=").append(size) .append(",startRow=").append(startRow) .append(",endRow=").append(endRow) .append(",total=").append(total) .append(",pages=").append(pages) .append(",rows=").append(rowsToString()) .append(",prePage=").append(prePage) .append(",nextPage=").append(nextPage) .append(",navigatePages=").append(navigatePages) .append(",navigatePageNums=").append(Arrays.toString(navigatePageNums)) .append("}").toString(); } // ------------------------------------------------------------------private methods private String rowsToString() { // GenericUtils.getFieldGenericType(ClassUtils.getField(Page.class, "rows")) if (rows.isEmpty()) { return "List<>(0)"; } T row = rows.stream()/*.limit(10)*/.filter(Objects::nonNull).findAny().orElse(null); if (row == null) { return "List<>(" + rows.size() + ")"; } return "List<" + ClassUtils.getClassName(row.getClass()) + ">(" + rows.size() + ")"; } } ================================================ FILE: src/main/java/cn/ponfee/commons/model/PageBoundsResolver.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.model; import java.util.ArrayList; import java.util.List; /** * 分页解析器 * * @author Ponfee */ public final class PageBoundsResolver { private PageBoundsResolver() {} /** * 多个数据源查询结果分页 * * @param pageNum 页号 * @param pageSize 页大小 * @param subTotalCounts 各数据源查询结果集行总计 * @return a list of page bounds */ public static List resolve(int pageNum, int pageSize, long... subTotalCounts) { if (subTotalCounts == null || subTotalCounts.length == 0) { return null; } // 总记录数 long totalCounts = 0; for (long subTotalCount : subTotalCounts) { totalCounts += subTotalCount; } if (totalCounts < 1) { return null; } // pageSize小于1时表示查询全部 if (pageSize < 1) { List bounds = new ArrayList<>(); for (int i = 0; i < subTotalCounts.length; i++) { // index, subTotalCounts, offset=0, limit=subTotalCounts bounds.add(new PageBounds(i, subTotalCounts[i], 0, (int) subTotalCounts[i])); } return bounds; } // normalize pageNum, offset value if (pageNum < 1) { pageNum = 1; } long offset = PageHandler.computeOffset(pageNum, pageSize); if (offset >= totalCounts) { // 超出总记录数,则取最后一页 pageNum = PageHandler.computeTotalPages(totalCounts, pageSize); offset = PageHandler.computeOffset(pageNum, pageSize); } // 分页计算 List bounds = new ArrayList<>(); long start = offset, end = start + pageSize, cursor = 0; for (int limit, i = 0; i < subTotalCounts.length; cursor += subTotalCounts[i], i++) { if (start >= cursor + subTotalCounts[i]) { continue; } offset = start - cursor; if (end > cursor + subTotalCounts[i]) { limit = (int) (cursor + subTotalCounts[i] - start); bounds.add(new PageBounds(i, subTotalCounts[i], offset, limit)); start = cursor + subTotalCounts[i]; } else { limit = (int) (end - start); bounds.add(new PageBounds(i, subTotalCounts[i], offset, limit)); break; } } return bounds; } /** * 单个数据源查询结果分页 * * @param pageNum the page number * @param pageSize the page size * @param totalCounts the total counts * @return a page bounds */ public static PageBounds resolve(int pageNum, int pageSize, long totalCounts) { List list = resolve(pageNum, pageSize, new long[] { totalCounts }); if (list == null || list.isEmpty()) { return null; } else { return list.get(0); } } /** * 分页对象 */ public static final class PageBounds { private final int index; // 数据源下标(start 0) private final long total; // 总记录数 private final long offset; // 偏移量(start 0) private final int limit; // 数据行数 PageBounds(int index, long total, long offset, int limit) { this.index = index; this.total = total; this.offset = offset; this.limit = limit; } public int getIndex() { return index; } public long getTotal() { return total; } public long getOffset() { return offset; } public int getLimit() { return limit; } @Override public String toString() { return "PageBounds [index=" + index + ", total=" + total + ", offset=" + offset + ", limit=" + limit + "]"; } } } ================================================ FILE: src/main/java/cn/ponfee/commons/model/PageHandler.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.model; import cn.ponfee.commons.math.Numbers; import cn.ponfee.commons.reflect.Fields; import com.github.pagehelper.PageHelper; import com.google.common.collect.ImmutableMap; import javax.annotation.Nonnull; import java.util.Dictionary; import java.util.Map; /** * 分页参数处理类 * 基于github上的mybatis分页工具 * https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md * * @author Ponfee */ public final class PageHandler { public static final String DEFAULT_PAGE_NUM = "pageNum"; public static final String DEFAULT_PAGE_SIZE = "pageSize"; public static final String DEFAULT_OFFSET = "offset"; public static final String DEFAULT_LIMIT = "limit"; public static final Map QUERY_ALL_PARAMS = ImmutableMap.of( DEFAULT_PAGE_NUM, 1, DEFAULT_PAGE_SIZE, 0, // start page number is 1 DEFAULT_OFFSET, 0, DEFAULT_LIMIT, 0 // start offset is 0 ); public static final PageHandler NORMAL = new PageHandler( DEFAULT_PAGE_NUM, DEFAULT_PAGE_SIZE, DEFAULT_OFFSET, DEFAULT_LIMIT ); public static final int MAX_SIZE = 1000; private final String paramPageNum; private final String paramPageSize; private final String paramOffset; private final String paramLimit; public PageHandler(String paramPageNum, String paramPageSize, String paramOffset, String paramLimit) { this.paramPageNum = paramPageNum; this.paramPageSize = paramPageSize; this.paramOffset = paramOffset; this.paramLimit = paramLimit; } /** * Handles the page parameters * * @param params the params */ public void handle(@Nonnull Object params) { Integer pageSize = getInt(params, paramPageSize); Integer limit = getInt(params, paramLimit); // 默认通过pageSize查询 if (nullOrNegative(pageSize) && nullOrNegative(limit)) { pageSize = 0; } // 分页处理,pageSizeZero:默认值为false,当该参数设置为true时,如果pageSize=0或 // RowBounds.limit=0就会查询出全部的结果(相当于没有执行分页查询,但是返回结果仍然是Page类型) if (pageSize != null && pageSize > -1) { // first priority use page size startPage( getInt(params, paramPageNum), Numbers.bounds(pageSize, 0, MAX_SIZE) ); } else { offsetPage( getInt(params, paramOffset), Numbers.bounds(limit, 0, MAX_SIZE) ); } } /** * Page query with pageNum and pageSize * pageSize=0时查询全部数据 * * @param pageNum the pageNum * @param pageSize the pageSize */ public static void startPage(Integer pageNum, Integer pageSize) { if (pageNum == null || pageNum < 1) { pageNum = 1; } if (nullOrNegative(pageSize)) { pageSize = 0; } PageHelper.startPage(pageNum, pageSize); } /** * Page query with offset and limit * RowBounds.limit=0则会查询出全部的结果 * * @param offset the offset * @param limit the limit */ public static void offsetPage(Integer offset, Integer limit) { if (nullOrNegative(offset)) { offset = 0; } if (nullOrNegative(limit)) { limit = 0; } PageHelper.offsetPage(offset, limit); } public static int computeTotalPages(long totalRecords, int pageSize) { return pageSize == 0 ? 0 : (int) ((totalRecords + pageSize - 1) / pageSize); } public static int computePageNum(long offset, int limit) { return limit == 0 ? 0 : (int) (offset / limit + 1); } public static int computeOffset(long pageNum, int pageSize) { return (int) ((pageNum - 1) * pageSize); } /** * Gets page number from java bean or map or dictionary * * @param params the params collect object * @param name the param name * @return a value of name */ private static Integer getInt(Object params, String name) { try { Object value; if (params instanceof Map) { value = ((Map) params).get(name); } else if (params instanceof Dictionary) { value = ((Dictionary) params).get(name); } else { value = Fields.get(params, name); // as java bean } return Numbers.toWrapInt(value); } catch (Exception e) { return null; } } private static boolean nullOrNegative(Integer val) { return val == null || val < 0; } } ================================================ FILE: src/main/java/cn/ponfee/commons/model/PageParameter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.model; import com.google.common.collect.ImmutableList; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import static cn.ponfee.commons.model.PageHandler.*; /** * 分页请求参数封装类(不能继承Map,否则会被内置Map解析器优先处理) * * @see org.springframework.web.method.annotation.MapMethodProcessor#supportsParameter(org.springframework.core.MethodParameter) * @see org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter) * * @author Ponfee */ public class PageParameter extends TypedParameter { private static final long serialVersionUID = 6176654946390797217L; public static final List PAGE_PARAMS = ImmutableList.of( DEFAULT_PAGE_NUM, DEFAULT_PAGE_SIZE, DEFAULT_OFFSET, DEFAULT_LIMIT ); public static final String SORT_PARAM = "sort"; private static final String[] ORDER_DIRECTION = { "ASC", "DESC" }; private int pageNum = -1; private int pageSize = -1; private int offset = -1; private int limit = -1; private String sort = null; // -----------------------------------------------------constructors public PageParameter() { this(new LinkedHashMap<>()); } public PageParameter(int initialCapacity) { this(new LinkedHashMap<>(initialCapacity)); } public PageParameter(int initialCapacity, int loadFactor) { this(new LinkedHashMap<>(initialCapacity, loadFactor)); } public PageParameter(Map params) { super(params); } // ----------------------------------------------------- methods public PageParameter searchAll() { this.setPageNum(1); this.setPageSize(0); this.setLimit(0); this.setOffset(0); return this; } // prevent sql inject public void validateSort(String... allows) { if (ArrayUtils.isEmpty(allows)) { return; } String sort = this.getString(SORT_PARAM); if (StringUtils.isBlank(sort)) { return; } String[] orders = sort.split(","); for (String order : orders) { if (StringUtils.isBlank(order)) { continue; } String[] array = order.trim()/*.replaceAll("\\s{2,}", " ")*/.split(" ", 2); if ( !ArrayUtils.contains(allows, array[0].trim()) || (array.length == 2 && !ArrayUtils.contains(ORDER_DIRECTION, array[1].trim().toUpperCase())) ) { throw new IllegalArgumentException("Illegal sort param: " + sort); } } } // ----------------------------------------------page operators public int getPageNum() { return this.pageNum; } public void setPageNum(int pageNum) { this.pageNum = pageNum; this.put(DEFAULT_PAGE_NUM, pageNum); } public int getPageSize() { return this.pageSize; } public void setPageSize(int pageSize) { this.pageSize = pageSize; this.put(DEFAULT_PAGE_SIZE, pageSize); } public int getOffset() { return this.offset; } public void setOffset(int offset) { this.offset = offset; this.put(DEFAULT_OFFSET, offset); } public int getLimit() { return this.limit; } public void setLimit(int limit) { this.limit = limit; this.put(DEFAULT_LIMIT, limit); } public String getSort() { return this.sort; } public void setSort(String sort) { this.sort = sort; this.put(SORT_PARAM, sort); } } ================================================ FILE: src/main/java/cn/ponfee/commons/model/PaginationHtmlBuilder.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.model; import cn.ponfee.commons.http.HttpParams; import org.apache.commons.lang3.StringUtils; import java.text.MessageFormat; import java.util.HashMap; import java.util.Map; import java.util.Optional; import static org.apache.commons.lang3.StringUtils.EMPTY; /** * Pagination html builder * * @author Ponfee */ public final class PaginationHtmlBuilder { public static final String CDN_JQUERY = ""; public static final String CDN_BASE64 = ""; private final String title; private final String url; private final int pageNum; private final int pageSize; private final long totalRecords; private final int totalPages; private String scripts = EMPTY; private String form = EMPTY; private String table = EMPTY; private String params = EMPTY; private String foot = EMPTY; private PaginationHtmlBuilder(String title, String url, int pageNum, int pageSize, long totalRecords, int totalPages) { this.title = Optional.ofNullable(title).orElse(EMPTY); this.url = Optional.ofNullable(url).orElse(EMPTY); this.pageNum = pageNum; this.pageSize = pageSize; this.totalRecords = totalRecords; this.totalPages = totalPages; } public static PaginationHtmlBuilder newBuilder(String title, String url, int pageNum, int pageSize, long totalRecords, int totalPages) { return new PaginationHtmlBuilder(title, url, pageNum, pageSize, totalRecords, totalPages); } public static PaginationHtmlBuilder newBuilder(String title, String url, Page page) { return new PaginationHtmlBuilder(title, url, page.getPageNum(), page.getPageSize(), page.getTotal(), page.getPages()); } public PaginationHtmlBuilder scripts(String scripts) { this.scripts = Optional.ofNullable(scripts).orElse(EMPTY); return this; } public PaginationHtmlBuilder form(String form) { this.form = Optional.ofNullable(form).orElse(EMPTY); return this; } public PaginationHtmlBuilder table(String table) { this.table = Optional.ofNullable(table).orElse(EMPTY); return this; } public PaginationHtmlBuilder params(String params) { this.params = Optional.ofNullable(params).orElse(EMPTY); return this; } public PaginationHtmlBuilder params(Map params) { params = new HashMap<>(params); params.remove(PageHandler.DEFAULT_PAGE_NUM); params.remove(PageHandler.DEFAULT_PAGE_SIZE); return this.params(HttpParams.buildParams(params)); } public PaginationHtmlBuilder params(PageParameter pageParams) { return this.params(pageParams.params()); } public PaginationHtmlBuilder foot(String foot) { this.foot = Optional.ofNullable(foot).orElse(EMPTY); return this; } public String build() { return MessageFormat.format( PAGINATION_HTML, title, scripts, url, form, table, buildPageArrow(url, pageNum - 1, pageSize, totalPages, params), buildInputBox(PageHandler.DEFAULT_PAGE_NUM, pageNum), buildPageArrow(url, pageNum + 1, pageSize, totalPages, params), totalRecords, totalPages, buildInputBox(PageHandler.DEFAULT_PAGE_SIZE, pageSize), foot ); } // ------------------------------------------------------------------------------- private static final String PAGINATION_HTML = new StringBuilder(8192) .append(" \n") .append(" \n") .append(" \n") .append(" \n") .append(" {0} \n") .append(" '' \n") .append(" {1} \n") .append(" \n") .append(" \n") .append("
    \n") .append(" {3} \n") .append(" {4} \n") .append("
    \n") .append("
    \n") .append("
      \n") .append(" {5} \n") .append(" {6} \n") .append(" {7} \n") .append("
    \n") .append(" ([ {8} ]records, [ {9} ]pages, [{10}]records/page) \n") .append("
    \n") .append("
    \n") .append("
    \n") .append(" {11} \n") .append(" \n") .append(" \n") .toString().replaceAll("\\s+\n", "\n"); // ------------------------------------------------------------------------------- private static final String INPUT_BOX = ""; private static String buildInputBox(String name, Object value) { return MessageFormat.format(INPUT_BOX, name, value); } // ------------------------------------------------------------------------------- private static final String PAGE_ARROW = "
  • "; private static String buildPageArrow(String url, int pageNum, int pageSize, int totalPages, String params) { if (pageNum < 1 || pageNum > totalPages) { return EMPTY; } params = StringUtils.isBlank(params) ? "" : "&" + params; return MessageFormat.format(PAGE_ARROW, url, pageNum, pageSize, params); } } ================================================ FILE: src/main/java/cn/ponfee/commons/model/RemovableTypedKeyValue.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.model; import cn.ponfee.commons.math.Numbers; import java.util.Objects; /** * Removable typed dictionary key-value * * @param the key type * @param the value type * @author Ponfee */ public interface RemovableTypedKeyValue extends TypedKeyValue { V removeKey(K key); default String removeString(K key) { return removeString(key, null); } default String removeString(K key, String defaultVal) { return Objects.toString(removeKey(key), defaultVal); } default boolean removeBoolean(K key, boolean defaultValue) { return Numbers.toBoolean(removeKey(key), defaultValue); } default Boolean removeBoolean(K key) { return Numbers.toWrapBoolean(removeKey(key)); } default int removeInt(K key, int defaultValue) { return Numbers.toInt(removeKey(key), defaultValue); } default Integer removeInt(K key) { return Numbers.toWrapInt(removeKey(key)); } default long removeLong(K key, long defaultValue) { return Numbers.toLong(removeKey(key), defaultValue); } default Long removeLong(K key) { return Numbers.toWrapLong(removeKey(key)); } default float removeFloat(K key, float defaultValue) { return Numbers.toFloat(removeKey(key), defaultValue); } default Float removeFloat(K key) { return Numbers.toWrapFloat(removeKey(key)); } default double removeDouble(K key, double defaultValue) { return Numbers.toDouble(removeKey(key), defaultValue); } default Double removeDouble(K key) { return Numbers.toWrapDouble(removeKey(key)); } } ================================================ FILE: src/main/java/cn/ponfee/commons/model/RemovableTypedMap.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.model; import java.util.Map; /** * Removable typed for {@link Map} * * @param the key type * @param the value type * @author Ponfee */ public interface RemovableTypedMap extends Map, RemovableTypedKeyValue { @Override default V getValue(K key) { return this.get(key); } @Override default V removeKey(K key) { return this.remove(key); } } ================================================ FILE: src/main/java/cn/ponfee/commons/model/Result.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.model; import java.beans.Transient; import java.util.function.Function; /** * Representing the result-data structure * * @see org.springframework.http.ResponseEntity#status(org.springframework.http.HttpStatus) * * @param the data type * @author Ponfee */ public class Result extends ToJsonString implements CodeMsg, java.io.Serializable { private static final long serialVersionUID = -2804195259517755050L; private Integer code; // 状态码 private boolean success; // 是否成功 private String msg; // 返回信息 private T data; // 结果数据 // -----------------------------------------------constructor methods public Result() { // No operation: retain no-arg constructor for help deserialization } public Result(int code, boolean success, String msg, T data) { this.code = code; this.success = success; this.msg = msg; this.data = data; } // -----------------------------------------------others methods @SuppressWarnings("unchecked") public Result cast() { return (Result) this; } public Result from(E data) { return new Result<>(code, success, msg, data); } public Result map(Function mapper) { return new Result<>(code, success, msg, mapper.apply(data)); } // -----------------------------------------------static success methods /** * Returns success 200 code * * @return Result success object */ public static Result success() { return Success.INSTANCE; } /** * Returns success 200 code * * @param data the data * @param the data type * @return Result success object */ public static Result success(T data) { return new Result<>(Success.CODE, true, Success.MSG, data); } // -----------------------------------------------static failure methods public static Result failure(CodeMsg cm) { if (cm.isSuccess()) { throw new IllegalStateException("Failure state must be 'false'."); } return new Result<>(cm.getCode(), cm.isSuccess(), cm.getMsg(), null); } public static Result failure(int code) { return new Result<>(code, false, null, null); } public static Result failure(int code, String msg) { return new Result<>(code, false, msg, null); } // -----------------------------------------------of operations public static Result of(CodeMsg cm) { return new Result<>(cm.getCode(), cm.isSuccess(), cm.getMsg(), null); } public static Result of(CodeMsg cm, T data) { return new Result<>(cm.getCode(), cm.isSuccess(), cm.getMsg(), data); } public static Result of(int code, boolean success, String msg) { return new Result<>(code, success, msg, null); } public static Result of(int code, boolean success, String msg, T data) { return new Result<>(code, success, msg, data); } // -----------------------------------------------database update or delete affected rows public static Result assertAffectedOne(int actualAffectedRows) { return assertAffectedRows(actualAffectedRows, 1); } public static Result assertAffectedRows(int actualAffectedRows, int exceptAffectedRows) { return actualAffectedRows == exceptAffectedRows ? (Result) Success.INSTANCE : failure(ResultCode.OPS_CONFLICT); } public static Result assertOperatedState(boolean state) { return state ? (Result) Success.INSTANCE : failure(ResultCode.OPS_CONFLICT); } // -----------------------------------------------getter/setter @Override public int getCode() { return code; } @Override public boolean isSuccess() { return success; } @Transient public boolean isFailure() { return !success; } @Override public String getMsg() { return msg; } public T getData() { return data; } public void setCode(int code) { this.code = code; } public void setSuccess(boolean success) { this.success = success; } public void setMsg(String msg) { this.msg = msg; } public void setData(T data) { this.data = data; } // -----------------------------------------------static class /** * Success result */ private static final class Success extends Result { private static final long serialVersionUID = 6740650053476768729L; private static final int CODE = ResultCode.OK.getCode(); private static final String MSG = "OK"; private static final Result INSTANCE = new Success(); private Success() { super(CODE, true, MSG, null); } @Override public void setCode(int code) { throw new UnsupportedOperationException(); } @Override public void setMsg(String msg) { throw new UnsupportedOperationException(); } @Override public void setData(Void data) { throw new UnsupportedOperationException(); } private Object readResolve() { return INSTANCE; } } } ================================================ FILE: src/main/java/cn/ponfee/commons/model/ResultCode.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.model; import cn.ponfee.commons.http.HttpStatus; import static java.net.HttpURLConnection.*; /** *
     * Http response code: {@link java.net.HttpURLConnection#HTTP_OK}
     *  100 => "HTTP/1.1 100 Continue",
     *  101 => "HTTP/1.1 101 Switching Protocols",
     *  200 => "HTTP/1.1 200 OK",
     *  201 => "HTTP/1.1 201 Created",
     *  202 => "HTTP/1.1 202 Accepted",
     *  203 => "HTTP/1.1 203 Non-Authoritative Information",
     *  204 => "HTTP/1.1 204 No Content",
     *  205 => "HTTP/1.1 205 Reset Content",
     *  206 => "HTTP/1.1 206 Partial Content",
     *  300 => "HTTP/1.1 300 Multiple Choices",
     *  301 => "HTTP/1.1 301 Moved Permanently",
     *  302 => "HTTP/1.1 302 Found",
     *  303 => "HTTP/1.1 303 See Other",
     *  304 => "HTTP/1.1 304 Not Modified",
     *  305 => "HTTP/1.1 305 Use Proxy",
     *  307 => "HTTP/1.1 307 Temporary Redirect",
     *  400 => "HTTP/1.1 400 Bad Request",
     *  401 => "HTTP/1.1 401 Unauthorized",
     *  402 => "HTTP/1.1 402 Payment Required",
     *  403 => "HTTP/1.1 403 Forbidden",
     *  404 => "HTTP/1.1 404 Not Found",
     *  405 => "HTTP/1.1 405 Method Not Allowed",
     *  406 => "HTTP/1.1 406 Not Acceptable",
     *  407 => "HTTP/1.1 407 Proxy Authentication Required",
     *  408 => "HTTP/1.1 408 Request Time-out",
     *  409 => "HTTP/1.1 409 Conflict",
     *  410 => "HTTP/1.1 410 Gone",
     *  411 => "HTTP/1.1 411 Length Required",
     *  412 => "HTTP/1.1 412 Precondition Failed",
     *  413 => "HTTP/1.1 413 Request Entity Too Large",
     *  414 => "HTTP/1.1 414 Request-URI Too Large",
     *  415 => "HTTP/1.1 415 Unsupported Media Type",
     *  416 => "HTTP/1.1 416 Requested range not satisfiable",
     *  417 => "HTTP/1.1 417 Expectation Failed",
     *  500 => "HTTP/1.1 500 Internal Server Error",
     *  501 => "HTTP/1.1 501 Not Implemented",
     *  502 => "HTTP/1.1 502 Bad Gateway",
     *  503 => "HTTP/1.1 503 Service Unavailable",
     *  504 => "HTTP/1.1 504 Gateway Time-out"
     * 
     * 2开头-请求成功-表示成功处理了请求的状态代码。
     *  200 (成功)       服务器已成功处理了请求。通常,这表示服务器提供了请求的网页。
     *  201 (已创建)     请求成功并且服务器创建了新的资源。
     *  202 (已接受)     服务器已接受请求,但尚未处理。
     *  203 (非授权信息)  服务器已成功处理了请求,但返回的信息可能来自另一来源。
     *  204 (无内容)     服务器成功处理了请求,但没有返回任何内容。
     *  205 (重置内容)   没有新的内容,但浏览器应该重置它所显示的内容(用来强制浏览器清除表单输入内容)。
     *  206 (部分内容)   服务器成功处理了部分GET请求。
     * 
     * 3开头 (请求被重定向)表示要完成请求,需要进一步操作。通常,这些状态代码用来重定向。
     *  300   (多种选择)  针对请求,服务器可执行多种操作。服务器可根据请求者 (user agent) 选择一项操作,或提供操作列表供请求者选择。
     *  301   (永久移动)  请求的网页已永久移动到新位置。服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置。
     *  302   (临时移动)  服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。
     *  303   (查看其他位置) 请求者应当对不同的位置使用单独的 GET 请求来检索响应时,服务器返回此代码。
     *  304   (未修改) 自从上次请求后,请求的网页未修改过。服务器返回此响应时,不会返回网页内容。
     *  305   (使用代理) 请求者只能使用代理访问请求的网页。如果服务器返回此响应,还表示请求者应使用代理。
     *  307   (临时重定向)  服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。
     * 
     * 4开头 (请求错误)这些状态代码表示请求可能出错,妨碍了服务器的处理。
     *  400   (错误请求) 服务器不理解请求的语法。
     *  401   (未授权) 请求要求身份验证。对于需要登录的网页,服务器可能返回此响应。
     *  403   (禁止) 服务器拒绝请求。
     *  404   (未找到) 服务器找不到请求的网页。
     *  405   (方法禁用) 禁用请求中指定的方法。
     *  406   (不接受) 无法使用请求的内容特性响应请求的网页。
     *  407   (需要代理授权) 此状态代码与 401(未授权)类似,但指定请求者应当授权使用代理。
     *  408   (请求超时)  服务器等候请求时发生超时。
     *  409   (冲突)  服务器在完成请求时发生冲突。服务器必须在响应中包含有关冲突的信息。
     *  410   (已删除)  如果请求的资源已永久删除,服务器就会返回此响应。
     *  411   (需要有效长度) 服务器不接受不含有效内容长度标头字段的请求。
     *  412   (未满足前提条件) 服务器未满足请求者在请求中设置的其中一个前提条件。
     *  413   (请求实体过大) 服务器无法处理请求,因为请求实体过大,超出服务器的处理能力。
     *  414   (请求的 URI 过长) 请求的 URI(通常为网址)过长,服务器无法处理。
     *  415   (不支持的媒体类型) 请求的格式不受请求页面的支持。
     *  416   (请求范围不符合要求) 如果页面无法提供请求的范围,则服务器会返回此状态代码。
     *  417   (未满足期望值) 服务器未满足"期望"请求标头字段的要求。
     * 
     * 5开头(服务器错误)这些状态代码表示服务器在尝试处理请求时发生内部错误。这些错误可能是服务器本身的错误,而不是请求出错。
     *  500   (服务器内部错误)  服务器遇到错误,无法完成请求。
     *  501   (尚未实施) 服务器不具备完成请求的功能。例如,服务器无法识别请求方法时可能会返回此代码。
     *  502   (错误网关) 服务器作为网关或代理,从上游服务器收到无效响应。
     *  503   (服务不可用) 服务器目前无法使用(由于超载或停机维护)。通常,这只是暂时状态。
     *  504   (网关超时)  服务器作为网关或代理,但是没有及时从上游服务器收到请求。
     *  505   (HTTP 版本不受支持) 服务器不支持请求中所用的 HTTP 协议版本。
     *
     * 公用错误码区间[000 ~ 999]
     * 
    * * @see org.springframework.http.HttpStatus * @see cn.ponfee.commons.http.HttpStatus * @see java.net.HttpURLConnection#HTTP_OK * * @author Ponfee */ public final class ResultCode implements CodeMsg { private static final int SYS_CODE_MIN = 100; private static final int SYS_CODE_MAX = 599; private static final String SYS_ERROR = "Must in predefine reserved code [" + SYS_CODE_MIN + ", " + SYS_CODE_MAX + "]"; private static final String BIZ_ERROR = "Cannot defined in reserved code [" + SYS_CODE_MIN + ", " + SYS_CODE_MAX + "]"; /** 公用结果码 */ public static final ResultCode OK = of0(HTTP_OK, "OK"); public static final ResultCode CREATED = of0(HTTP_CREATED, "已创建"); // POST public static final ResultCode ACCEPTED = of0(HTTP_ACCEPTED, "已接受,等待处理"); public static final ResultCode NOT_AUTHORITATIVE = of0(HTTP_NOT_AUTHORITATIVE, "非授权信息"); public static final ResultCode NO_CONTENT = of0(HTTP_NO_CONTENT, "已处理,无返回内容"); // PUT, PATCH, DELETE public static final ResultCode REST_CONTENT = of0(HTTP_RESET, "重置内容"); public static final ResultCode PARTIAL_CONTENT = of0(HTTP_PARTIAL, "部分内容"); public static final ResultCode REDIRECT = of0(300, "Multiple Choices"); public static final ResultCode BAD_REQUEST = of0(400, "参数错误"); public static final ResultCode UNAUTHORIZED = of0(401, "未授权"); // public static final ResultCode FORBIDDEN = of0(403, "拒绝访问"); // BLACKLIST public static final ResultCode NOT_FOUND = of0(404, "资源未找到"); // GET return null public static final ResultCode NOT_ALLOWED = of0(405, "方法不允许"); public static final ResultCode NOT_ACCEPTABLE = of0(406, "请求格式错误"); public static final ResultCode REQUEST_TIMEOUT = of0(408, "请求超时"); public static final ResultCode OPS_CONFLICT = of0(409, "数据不存在或版本冲突"); // DATABASE UPDATE DELETE FAIL public static final ResultCode UNSUPPORT_MEDIA = of0(415, "格式不支持"); public static final ResultCode SERVER_ERROR = of0(500, "服务器错误"); public static final ResultCode BAD_GATEWAY = of0(502, "网关错误"); public static final ResultCode SERVER_UNAVAILABLE = of0(503, "服务不可用"); public static final ResultCode GATEWAY_TIMEOUT = of0(504, "网关超时"); public static final ResultCode SERVER_UNSUPPORTED = of0(505, "服务不支持"); private final int code; private final boolean success; private final String msg; private ResultCode(int code, String msg) { this.code = code; this.success = HttpStatus.Series.valueOf(code) == HttpStatus.Series.SUCCESSFUL; this.msg = msg; } /** * inner create, only call in this class * use in assign the commons code * @param code * @param msg * @return */ private static ResultCode of0(int code, String msg) { if (code < SYS_CODE_MIN || code > SYS_CODE_MAX) { throw new IllegalArgumentException(SYS_ERROR); } return new ResultCode(code, msg); } /** * others place cannot set the code in commons code range[000 ~ 999] * * @param code * @param msg * @return */ public static ResultCode of(int code, String msg) { if (code >= SYS_CODE_MIN && code <= SYS_CODE_MAX) { throw new IllegalArgumentException(BIZ_ERROR); } return new ResultCode(code, msg); } @Override public int getCode() { return code; } @Override public boolean isSuccess() { return success; } @Override public String getMsg() { return msg; } } ================================================ FILE: src/main/java/cn/ponfee/commons/model/SearchAfter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.model; /** * Search after * * @author Ponfee */ public class SearchAfter implements java.io.Serializable { private static final long serialVersionUID = 4870755106055211046L; private final SortField sortField; private final T value; public SearchAfter(SortField sortField, T value) { this.sortField = sortField; this.value = value; } public SortField getSortField() { return sortField; } public T getValue() { return value; } public SearchAfter copy(T value) { return new SearchAfter<>(this.sortField, value); } } ================================================ FILE: src/main/java/cn/ponfee/commons/model/SortField.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.model; import cn.ponfee.commons.model.SortOrder.NullHandling; /** * SortOrder and Field * * @author Ponfee */ public class SortField implements java.io.Serializable { private static final long serialVersionUID = -2400506091734529951L; private final String field; private final SortOrder sortOrder; private final boolean ignoreCase; private final NullHandling nullHandling; public SortField(String field, SortOrder sortOrder) { this(field, sortOrder, false, null); } public SortField(String field, SortOrder sortOrder, boolean ignoreCase, NullHandling nullHandling) { this.field = field; this.sortOrder = sortOrder; this.ignoreCase = ignoreCase; this.nullHandling = nullHandling; } public String getField() { return field; } public SortOrder getSortOrder() { return sortOrder; } public boolean isIgnoreCase() { return ignoreCase; } public NullHandling getNullHandling() { return nullHandling; } } ================================================ FILE: src/main/java/cn/ponfee/commons/model/SortOrder.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.model; /** * Query Order by * * @author Ponfee */ public enum SortOrder { ASC, DESC; public static SortOrder of(String name) { return "ASC".equalsIgnoreCase(name) ? ASC : DESC; } public enum NullHandling { NATIVE, NULLS_FIRST, NULLS_LAST } } ================================================ FILE: src/main/java/cn/ponfee/commons/model/ToJsonString.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.model; import cn.ponfee.commons.json.Jsons; /** * Override {@code Object#toString()} method, implemented to json string. * * @author Ponfee */ public abstract class ToJsonString { @Override public String toString() { return Jsons.toJson(this); } } ================================================ FILE: src/main/java/cn/ponfee/commons/model/TypedHashMap.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.model; import java.util.HashMap; import java.util.Map; /** * Typed {@link HashMap} with pre-defined get methods * * @author Ponfee */ public class TypedHashMap extends HashMap implements TypedMap { private static final long serialVersionUID = -4207327688392334942L; public TypedHashMap() { super(); } public TypedHashMap(int initialCapacity) { super(initialCapacity); } public TypedHashMap(Map m) { super(m); } } ================================================ FILE: src/main/java/cn/ponfee/commons/model/TypedKeyValue.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.model; import cn.ponfee.commons.math.Numbers; import java.util.Objects; import java.util.function.Function; /** * Get the value with typed for dictionary key-value * * @param the key type * @param the value type * @author Ponfee */ public interface TypedKeyValue { V getValue(K key); default boolean hasKey(K key) { return getValue(key) != null; } // --------------------------------------------------------object default V getRequired(K key) { return getRequired(key, Function.identity()); } default V get(K key, V defaultVal) { V value = getValue(key); return value == null ? defaultVal : value; } // --------------------------------------------------------string default String getRequiredString(K key) { return getRequired(key, Object::toString); } default String getString(K key) { return getString(key, null); } default String getString(K key, String defaultVal) { return Objects.toString(getValue(key), defaultVal); } // --------------------------------------------------------boolean default boolean getRequiredBoolean(K key) { Object value = getValue(key); if (value instanceof Boolean) { return (boolean) value; } if (value == null) { throw new IllegalArgumentException("Not presented value of '" + key + "'"); } switch (value.toString()) { case "TRUE" : case "True" : case "true" : return true; case "FALSE": case "False": case "false": return false; default: throw new IllegalArgumentException("Invalid boolean value: " + value); } } default boolean getBoolean(K key, boolean defaultValue) { return Numbers.toBoolean(getValue(key), defaultValue); } default Boolean getBoolean(K key) { return Numbers.toWrapBoolean(getValue(key)); } // --------------------------------------------------------------int default int getRequiredInt(K key) { return getRequired(key, Numbers::toInt); } default int getInt(K key, int defaultValue) { return Numbers.toInt(getValue(key), defaultValue); } default Integer getInt(K key) { return Numbers.toWrapInt(getValue(key)); } // --------------------------------------------------------------long default long getRequiredLong(K key) { return getRequired(key, Numbers::toLong); } default long getLong(K key, long defaultValue) { return Numbers.toLong(getValue(key), defaultValue); } default Long getLong(K key) { return Numbers.toWrapLong(getValue(key)); } // --------------------------------------------------------------float default float getRequiredFloat(K key) { return getRequired(key, Numbers::toFloat); } default float getFloat(K key, float defaultValue) { return Numbers.toFloat(getValue(key), defaultValue); } default Float getFloat(K key) { return Numbers.toWrapFloat(getValue(key)); } // --------------------------------------------------------------double default double getRequiredDouble(K key) { return getRequired(key, Numbers::toDouble); } default double getDouble(K key, double defaultValue) { return Numbers.toDouble(getValue(key), defaultValue); } default Double getDouble(K key) { return Numbers.toWrapDouble(getValue(key)); } // ---------------------------------------------------- methods default R getRequired(K key, Function mapper) { V value = getValue(key); if (value == null) { throw new IllegalArgumentException("Not presented value of '" + key + "'"); } return mapper.apply(value); } } ================================================ FILE: src/main/java/cn/ponfee/commons/model/TypedLinkedHashMap.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.model; import java.util.LinkedHashMap; import java.util.Map; /** * Typed {@link LinkedHashMap} with pre-defined get methods * * @author Ponfee */ public class TypedLinkedHashMap extends LinkedHashMap implements TypedMap { private static final long serialVersionUID = -4207327688392334942L; public TypedLinkedHashMap() { super(); } public TypedLinkedHashMap(int initialCapacity) { super(initialCapacity); } public TypedLinkedHashMap(Map m) { super(m); } } ================================================ FILE: src/main/java/cn/ponfee/commons/model/TypedLinkedMultiValueMap.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.model; import org.apache.commons.collections4.CollectionUtils; import org.springframework.util.LinkedMultiValueMap; import java.util.List; import java.util.Map; /** * Typed {@link LinkedMultiValueMap} with pre-defined get methods * * @author Ponfee */ public class TypedLinkedMultiValueMap extends LinkedMultiValueMap implements RemovableTypedKeyValue { private static final long serialVersionUID = 4369022038293264189L; public TypedLinkedMultiValueMap() { super(); } public TypedLinkedMultiValueMap(int initialCapacity) { super(initialCapacity); } public TypedLinkedMultiValueMap(Map> otherMap) { super(otherMap); } @Override public V getValue(K key) { return getFirst(key); } @Override public V removeKey(K key) { List values = remove(key); return CollectionUtils.isEmpty(values) ? null : values.get(0); } } ================================================ FILE: src/main/java/cn/ponfee/commons/model/TypedMap.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.model; import java.util.Map; /** * Get the value with typed for {@link Map} * * @author Ponfee * @param the key type * @param the value type */ public interface TypedMap extends Map, TypedKeyValue { @Override default V getValue(K key) { return this.get(key); } } ================================================ FILE: src/main/java/cn/ponfee/commons/model/TypedMapWrapper.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.model; import java.io.Serializable; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; /** * Wrapped the {@link Map} for with gets typed value trait * * @param * @param * @author Ponfee */ public class TypedMapWrapper implements TypedMap, Serializable, Cloneable { private static final long serialVersionUID = 6899012847958938043L; private final Map target; public TypedMapWrapper(Map otherMap) { this.target = otherMap == null ? Collections.emptyMap() : otherMap; } @Override public int size() { return this.target.size(); } @Override public boolean isEmpty() { return this.target.isEmpty(); } @Override public boolean containsKey(Object key) { return this.target.containsKey(key); } @Override public boolean containsValue(Object value) { return this.target.containsValue(value); } @Override public V get(Object key) { return this.target.get(key); } @Override public V put(K key, V value) { return this.target.put(key, value); } @Override public V remove(Object key) { return this.target.remove(key); } @Override public void putAll(Map m) { this.target.putAll(m); } @Override public void clear() { this.target.clear(); } @Override public Set keySet() { return this.target.keySet(); } @Override public Collection values() { return this.target.values(); } @Override public Set> entrySet() { return this.target.entrySet(); } @Override public boolean equals(Object obj) { return this.target.equals(obj); } @Override public int hashCode() { return this.target.hashCode(); } @Override public String toString() { return this.target.toString(); } public static TypedMapWrapper empty() { return new TypedMapWrapper<>(Collections.emptyMap()); } @Override public TypedMapWrapper clone() { return new TypedMapWrapper<>(this.target); } public TypedMapWrapper copy() { return new TypedMapWrapper<>(target.entrySet().stream().collect(Collectors.toMap(Entry::getKey, Entry::getValue))); } } ================================================ FILE: src/main/java/cn/ponfee/commons/model/TypedParameter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.model; import java.util.Map; /** * 通用Map请求参数类(不继承Map是为了阻断被内置的Map解析器先一步处理) * * @author Ponfee */ public class TypedParameter implements RemovableTypedKeyValue, java.io.Serializable { private static final long serialVersionUID = 1898625104491344717L; private final Map params; public TypedParameter(Map params) { this.params = params; } public Object put(String key, Object value) { return this.params.put(key, value); } @Override public Object getValue(String key) { return this.params.get(key); } @Override public Object removeKey(String key) { return this.params.remove(key); } public Map params() { return this.params; } } ================================================ FILE: src/main/java/cn/ponfee/commons/mybatis/MultipleSqlSessionTemplate.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.mybatis; import cn.ponfee.commons.data.lookup.MultipleDataSourceContext; import org.apache.ibatis.cursor.Cursor; import org.apache.ibatis.exceptions.PersistenceException; import org.apache.ibatis.executor.BatchResult; import org.apache.ibatis.session.*; import org.mybatis.spring.MyBatisExceptionTranslator; import org.mybatis.spring.SqlSessionTemplate; import org.springframework.dao.support.PersistenceExceptionTranslator; import java.lang.reflect.Proxy; import java.sql.Connection; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import static org.apache.ibatis.reflection.ExceptionUtil.unwrapThrowable; import static org.mybatis.spring.SqlSessionUtils.*; /** * Mutiple datasource for SqlSessionTemplate * * @author Ponfee */ public class MultipleSqlSessionTemplate extends SqlSessionTemplate { private final SqlSessionFactory defaultTargetSqlSessionFactory; private final ExecutorType defaultTargetExecutorType; private final PersistenceExceptionTranslator defaultTargetExceptionTranslator; private final Map targetSqlSessionFactories; private final SqlSession sqlSessionProxy; public MultipleSqlSessionTemplate(SqlSessionFactory defaultTargetSqlSessionFactory, Map targetSqlSessionFactories) { super(defaultTargetSqlSessionFactory); this.targetSqlSessionFactories = Objects.requireNonNull(targetSqlSessionFactories); this.defaultTargetSqlSessionFactory = defaultTargetSqlSessionFactory; this.defaultTargetExecutorType = defaultTargetSqlSessionFactory.getConfiguration().getDefaultExecutorType(); this.defaultTargetExceptionTranslator = new MyBatisExceptionTranslator( defaultTargetSqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true ); this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance( SqlSessionFactory.class.getClassLoader(), new Class[] {SqlSession.class }, (proxy, method, args) -> { SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(); SqlSession sqlSession = getSqlSession( sqlSessionFactory, defaultTargetExecutorType, defaultTargetExceptionTranslator ); try { Object result = method.invoke(sqlSession, args); if (!isSqlSessionTransactional(sqlSession, sqlSessionFactory)) { // force commit even on non-dirty sessions because some databases require // a commit/rollback before calling close() sqlSession.commit(true); } return result; } catch (Throwable t) { Throwable unwrapped = unwrapThrowable(t); if (defaultTargetExceptionTranslator != null && unwrapped instanceof PersistenceException) { // release the connection to avoid a deadlock if the translator is no loaded. See issue #22 closeSqlSession(sqlSession, sqlSessionFactory); sqlSession = null; Throwable translated = defaultTargetExceptionTranslator.translateExceptionIfPossible( (PersistenceException) unwrapped ); if (translated != null) { unwrapped = translated; } } throw unwrapped; } finally { if (sqlSession != null) { closeSqlSession(sqlSession, sqlSessionFactory); } } } ); } @Override public SqlSessionFactory getSqlSessionFactory() { return Optional.ofNullable( targetSqlSessionFactories.get(MultipleDataSourceContext.get()) ).orElse( defaultTargetSqlSessionFactory ); } @Override public ExecutorType getExecutorType() { return this.defaultTargetExecutorType; } @Override public PersistenceExceptionTranslator getPersistenceExceptionTranslator() { return this.defaultTargetExceptionTranslator; } @Override public T selectOne(String statement) { return this.sqlSessionProxy.selectOne(statement); } @Override public T selectOne(String statement, Object parameter) { return this.sqlSessionProxy.selectOne(statement, parameter); } @Override public Map selectMap(String statement, String mapKey) { return this.sqlSessionProxy.selectMap(statement, mapKey); } @Override public Map selectMap(String statement, Object parameter, String mapKey) { return this.sqlSessionProxy.selectMap(statement, parameter, mapKey); } @Override public Map selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) { return this.sqlSessionProxy.selectMap(statement, parameter, mapKey, rowBounds); } @Override public Cursor selectCursor(String statement) { return this.sqlSessionProxy.selectCursor(statement); } @Override public Cursor selectCursor(String statement, Object parameter) { return this.sqlSessionProxy.selectCursor(statement, parameter); } @Override public Cursor selectCursor(String statement, Object parameter, RowBounds rowBounds) { return this.sqlSessionProxy.selectCursor(statement, parameter, rowBounds); } @Override public List selectList(String statement) { return this.sqlSessionProxy.selectList(statement); } @Override public List selectList(String statement, Object parameter) { return this.sqlSessionProxy.selectList(statement, parameter); } @Override public List selectList(String statement, Object parameter, RowBounds rowBounds) { return this.sqlSessionProxy.selectList(statement, parameter, rowBounds); } @Override public void select(String statement, ResultHandler handler) { this.sqlSessionProxy.select(statement, handler); } @Override public void select(String statement, Object parameter, ResultHandler handler) { this.sqlSessionProxy.select(statement, parameter, handler); } @Override public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) { this.sqlSessionProxy.select(statement, parameter, rowBounds, handler); } @Override public int insert(String statement) { return this.sqlSessionProxy.insert(statement); } @Override public int insert(String statement, Object parameter) { return this.sqlSessionProxy.insert(statement, parameter); } @Override public int update(String statement) { return this.sqlSessionProxy.update(statement); } @Override public int update(String statement, Object parameter) { return this.sqlSessionProxy.update(statement, parameter); } @Override public int delete(String statement) { return this.sqlSessionProxy.delete(statement); } @Override public int delete(String statement, Object parameter) { return this.sqlSessionProxy.delete(statement, parameter); } @Override public T getMapper(Class type) { return getConfiguration().getMapper(type, this); } @Override public void commit() { throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession"); } @Override public void commit(boolean force) { throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession"); } @Override public void rollback() { throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession"); } @Override public void rollback(boolean force) { throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession"); } @Override public void close() { throw new UnsupportedOperationException("Manual close is not allowed over a Spring managed SqlSession"); } @Override public void clearCache() { this.sqlSessionProxy.clearCache(); } @Override public Configuration getConfiguration() { return this.defaultTargetSqlSessionFactory.getConfiguration(); } @Override public Connection getConnection() { return this.sqlSessionProxy.getConnection(); } @Override public List flushStatements() { return this.sqlSessionProxy.flushStatements(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/mybatis/PackagesSqlSessionFactoryBean.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.mybatis; import cn.ponfee.commons.resource.ResourceScanner; import org.mybatis.spring.SqlSessionFactoryBean; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.HashSet; import java.util.Set; /** * Mybatis typeAliasesPackage config * * p:typeAliasesPackage="cn.ponfee.data.**.model" * * @author Ponfee */ public class PackagesSqlSessionFactoryBean extends SqlSessionFactoryBean { private static final Logger logger = LoggerFactory.getLogger(PackagesSqlSessionFactoryBean.class); @Override public void setTypeAliasesPackage(String typeAliasesPackage) { Set result = new HashSet<>(); for (Class type : new ResourceScanner(typeAliasesPackage.replace('.', '/')).scan4class()) { result.add(type.getPackage().getName()); } if (result.isEmpty()) { logger.warn("TypeAliasesPackage not scanned: " + typeAliasesPackage); } else { super.setTypeAliasesPackage(String.join(",", result)); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/mybatis/SqlHelper.java ================================================ package cn.ponfee.commons.mybatis; /* * The MIT License (MIT) * * Copyright (c) 2014-2016 abel533@gmail.com * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ import org.apache.ibatis.annotations.Param; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.ParameterMapping; import org.apache.ibatis.mapping.ParameterMode; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.SystemMetaObject; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.type.JdbcType; import org.apache.ibatis.type.TypeHandlerRegistry; import java.lang.reflect.Method; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Mybatis - 获取Mybatis查询sql工具 * * https://gitee.com/free/Mybatis_Utils/tree/master/SqlHelper * * * SqlHelper.getNamespaceSql( * sqlSession, * "com.github.pagehelper.mapper.CountryMapper.listAll" * ) * || * \/ * SELECT * FROM t_sched_job * * * * * sqlHelper.getMapperSql( * sqlSession, * "cn.ponfee.dao.mapper.SchedJobMapper.query4page", * Arrays.asList(1, 2), * Arrays.asList(3, 4), * "id" * ) * || * \/ * SELECT * FROM t_sched_log * WHERE status IN (1,2) AND id NOT IN (3,4) * ORDER BY id * * * @author liuzh/abel533/isea533 */ public class SqlHelper { /** * 通过接口获取sql * * @param mapper * @param methodName * @param args * @return */ public static String getMapperSql(Object mapper, String methodName, Object... args) { MetaObject metaObject = SystemMetaObject.forObject(mapper); SqlSession session = (SqlSession) metaObject.getValue("h.sqlSession"); Class mapperInterface = (Class) metaObject.getValue("h.mapperInterface"); String fullMethodName = mapperInterface.getCanonicalName() + "." + methodName; if (args == null || args.length == 0) { return getNamespaceSql(session, fullMethodName, null); } else { return getMapperSql(session, mapperInterface, methodName, args); } } /** * 通过Mapper方法名获取sql * * @param session * @param fullMapperMethodName * @param args * @return */ public static String getMapperSql(SqlSession session, String fullMapperMethodName, Object... args) { if (args == null || args.length == 0) { return getNamespaceSql(session, fullMapperMethodName, null); } String methodName = fullMapperMethodName.substring(fullMapperMethodName.lastIndexOf('.') + 1); Class mapperInterface; try { mapperInterface = Class.forName(fullMapperMethodName.substring(0, fullMapperMethodName.lastIndexOf('.'))); return getMapperSql(session, mapperInterface, methodName, args); } catch (ClassNotFoundException e) { throw new IllegalArgumentException("参数" + fullMapperMethodName + "无效!"); } } /** * 通过Mapper接口和方法名 * * @param session * @param mapperInterface * @param methodName * @param args * @return */ @SuppressWarnings("unchecked") public static String getMapperSql(SqlSession session, Class mapperInterface, String methodName, Object... args) { String fullMapperMethodName = mapperInterface.getCanonicalName() + "." + methodName; if (args == null || args.length == 0) { return getNamespaceSql(session, fullMapperMethodName, null); } Method method = getDeclaredMethods(mapperInterface, methodName); Map params = new HashMap<>(); Class[] argTypes = method.getParameterTypes(); for (int i = 0; i < argTypes.length; i++) { if ( !RowBounds.class.isAssignableFrom(argTypes[i]) && !ResultHandler.class.isAssignableFrom(argTypes[i])) { String paramName = "param" + (params.size() + 1); paramName = getParamNameFromAnnotation(method, i, paramName); params.put(paramName, i >= args.length ? null : args[i]); } } if (args != null && args.length == 1) { Object params0 = wrapCollection(args[0]); if (params0 instanceof Map) { params.putAll((Map) params0); } } return getNamespaceSql(session, fullMapperMethodName, params); } /** * 通过命名空间方式获取sql * * @param session * @param namespace * @return */ public static String getNamespaceSql(SqlSession session, String namespace) { return getNamespaceSql(session, namespace, null); } /** * 通过命名空间方式获取sql * * @param session * @param namespace * @param params * @return */ public static String getNamespaceSql(SqlSession session, String namespace, Object params) { params = wrapCollection(params); Configuration configuration = session.getConfiguration(); MappedStatement mappedStatement = configuration.getMappedStatement(namespace); TypeHandlerRegistry typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry(); BoundSql boundSql = mappedStatement.getBoundSql(params); List parameterMappings = boundSql.getParameterMappings(); String sql = boundSql.getSql(); if (parameterMappings != null) { for (ParameterMapping parameterMapping : parameterMappings) { if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { value = boundSql.getAdditionalParameter(propertyName); } else if (params == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(params.getClass())) { value = params; } else { MetaObject metaObject = configuration.newMetaObject(params); value = metaObject.getValue(propertyName); } JdbcType jdbcType = parameterMapping.getJdbcType(); if (value == null && jdbcType == null) { jdbcType = configuration.getJdbcTypeForNull(); } sql = replaceParameter(sql, value, jdbcType, parameterMapping.getJavaType()); } } } return sql; } /** * 根据类型替换参数 * 仅作为数字和字符串两种类型进行处理,需要特殊处理的可以继续完善这里 * * @param sql * @param value * @param jdbcType * @param javaType * @return */ private static String replaceParameter(String sql, Object value, JdbcType jdbcType, Class javaType) { String strValue = String.valueOf(value); if (jdbcType != null) { switch (jdbcType) { //数字 case BIT: case TINYINT: case SMALLINT: case INTEGER: case BIGINT: case FLOAT: case REAL: case DOUBLE: case NUMERIC: case DECIMAL: break; //日期 case DATE: case TIME: case TIMESTAMP: //其他,包含字符串和其他特殊类型 default: strValue = "'" + strValue + "'"; } } else if (Number.class.isAssignableFrom(javaType)) { //不加单引号 } else { strValue = "'" + strValue + "'"; } return sql.replaceFirst("\\?", strValue); } /** * 获取指定的方法 * * @param clazz * @param methodName * @return */ private static Method getDeclaredMethods(Class clazz, String methodName) { Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { if (method.getName().equals(methodName)) { return method; } } throw new IllegalArgumentException("方法" + methodName + "不存在!"); } /** * 获取参数注解名 * * @param method * @param i * @param paramName * @return */ private static String getParamNameFromAnnotation(Method method, int i, String paramName) { final Object[] paramAnnos = method.getParameterAnnotations()[i]; for (Object paramAnno : paramAnnos) { if (paramAnno instanceof Param) { paramName = ((Param) paramAnno).value(); } } return paramName; } /** * 简单包装参数 * * @param object * @return */ private static Object wrapCollection(final Object object) { if (object instanceof List) { Map map = new HashMap<>(); map.put("list", object); return map; } else if (object != null && object.getClass().isArray()) { Map map = new HashMap<>(); map.put("array", object); return map; } return object; } } ================================================ FILE: src/main/java/cn/ponfee/commons/mybatis/SqlMapper.java ================================================ package cn.ponfee.commons.mybatis; import org.apache.commons.collections4.CollectionUtils; import org.apache.ibatis.builder.StaticSqlSource; import org.apache.ibatis.exceptions.TooManyResultsException; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.ResultMap; import org.apache.ibatis.mapping.SqlCommandType; import org.apache.ibatis.mapping.SqlSource; import org.apache.ibatis.scripting.LanguageDriver; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.SqlSession; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.function.BiFunction; import static cn.ponfee.commons.util.ObjectUtils.typeOf; /** *
     * MyBatis执行sql工具,在写SQL的时候建议使用参数形式的可以是${}或#{}
     * 不建议将参数直接拼到字符串中,当大量这么使用的时候由于缓存MappedStatement而占用更多的内存
     * https://gitee.com/free/Mybatis_Utils/tree/master/SqlMapper
     *
     * Mybatis-generator、通用Mapper、Mybatis-Plus对比:
     *   https://www.jianshu.com/p/7be6da536f8f
     *   https://blog.csdn.net/m0_37524586/article/details/88351833
     *
     * 
    * * @author liuzh * @since 2015-03-10 */ public class SqlMapper { private final SqlSession sqlSession; private final MSUtils msUtils; /** * 构造方法,默认缓存MappedStatement * * SqlSessionTemplate implements SqlSession * * @param sqlSession */ public SqlMapper(SqlSession sqlSession) { this.sqlSession = sqlSession; this.msUtils = new MSUtils(sqlSession.getConfiguration()); } /** * 查询返回一个结果,多个结果时抛出异常 * * @param sql 执行的sql * @return */ public Map selectOne(String sql) { return asSingleItem(selectList(sql)); } /** * 查询返回一个结果,多个结果时抛出异常 * * @param sql 执行的sql * @param param 参数 * @return */ public Map selectOne(String sql, Object param) { return asSingleItem(selectList(sql, param)); } /** * 查询返回一个结果,多个结果时抛出异常 * * @param sql 执行的sql * @param resultType 返回的结果类型 * @return */ public T selectOne(String sql, Class resultType) { return asSingleItem(selectList(sql, resultType)); } /** * 查询返回一个结果,多个结果时抛出异常 * * @param sql 执行的sql * @param param 参数 * @param resultType 返回的结果类型 * @return */ public T selectOne(String sql, Object param, Class resultType) { return asSingleItem(selectList(sql, param, resultType)); } /** * 查询返回List> * * @param sql 执行的sql * @return */ public List> selectList(String sql) { return sqlSession.selectList(msUtils.select(sql)); } /** * 查询返回List> * * @param sql 执行的sql * @param param 参数 * @return */ public List> selectList(String sql, Object param) { return sqlSession.selectList( msUtils.selectDynamic(sql, typeOf(param)), param ); } /** * 查询返回指定的结果类型 * * @param sql 执行的sql * @param resultType 返回的结果类型 * @return */ public List selectList(String sql, Class resultType) { String msId = resultType == null ? msUtils.select(sql) : msUtils.select(sql, resultType); return sqlSession.selectList(msId); } /** * 查询返回指定的结果类型 * * @param sql 执行的sql * @param param 参数 * @param resultType 返回的结果类型 * @return */ public List selectList(String sql, Object param, Class resultType) { String msId = resultType == null ? msUtils.selectDynamic(sql, typeOf(param)) : msUtils.selectDynamic(sql, typeOf(param), resultType); return sqlSession.selectList(msId, param); } /** *
         *  Query full scroll data
         *  
         *  <script>
         *    SELECT id, name FROM t_test 
         *    WHERE name IS NOT NULL <if test='id!=null'>AND id>#{id}</if>
         *    ORDER BY id ASC 
         *    LIMIT 5000
         *  </script>
         * 
    * * @param sql the mybatis sql script * @param param the param * @param resultType the result type * @param action the action */ public void selectScroll(String sql, P param, Class resultType, BiFunction, P> action) { List list; boolean hasNext; do { list = selectList(sql, param, resultType); hasNext = CollectionUtils.isNotEmpty(list); if (hasNext) { param = action.apply(param, list); } } while (hasNext); } /** * 插入数据 * * @param sql 执行的sql * @return */ public int insert(String sql) { return sqlSession.insert(msUtils.insert(sql)); } /** * 插入数据 * * @param sql 执行的sql * @param param 参数 * @return */ public int insert(String sql, Object param) { return sqlSession.insert( msUtils.insertDynamic(sql, typeOf(param)), param ); } /** * 更新数据 * * @param sql 执行的sql * @return */ public int update(String sql) { return sqlSession.update(msUtils.update(sql)); } /** * 更新数据 * * @param sql 执行的sql * @param param 参数 * @return */ public int update(String sql, Object param) { return sqlSession.update( msUtils.updateDynamic(sql, typeOf(param)), param ); } /** * 删除数据 * * @param sql 执行的sql * @return */ public int delete(String sql) { return sqlSession.delete(msUtils.delete(sql)); } /** * 删除数据 * * @param sql 执行的sql * @param param 参数 * @return */ public int delete(String sql, Object param) { return sqlSession.delete( msUtils.deleteDynamic(sql, typeOf(param)), param ); } /** * 获取List中最多只有一个的数据 * * @param list List结果 * @param 泛型类型 * @return */ private T asSingleItem(List list) { int rowSize = list == null ? 0 : list.size(); if (rowSize > 1) { throw new TooManyResultsException("Expected one row (or null) to be returned by selectOne(), but found: " + list.size()); } return rowSize == 1 ? list.get(0) : null; } // ---------------------------------------------------------------------private class private static class MSUtils { private final Configuration configuration; private final LanguageDriver languageDriver; private MSUtils(Configuration configuration) { this.configuration = configuration; this.languageDriver = configuration.getDefaultScriptingLanguageInstance(); } /** * 创建MSID * * @param sql 执行的sql * @param sql 执行的sqlCommandType * @return */ private String newMsId(String sql, SqlCommandType sqlCommandType) { return new StringBuilder(sqlCommandType.toString()) .append(".").append(sql.hashCode()).toString(); } /** * 是否已经存在该ID * * @param msId * @return */ private boolean hasMappedStatement(String msId) { return configuration.hasStatement(msId, false); } /** * 创建一个查询的MS * * @param msId * @param sqlSource 执行的sqlSource * @param resultType 返回的结果类型 */ private void newSelectMappedStatement(String msId, SqlSource sqlSource, final Class resultType) { List list = Collections.singletonList( new ResultMap.Builder( configuration, "defaultResultMap", resultType, new ArrayList<>(0) ).build() ); MappedStatement ms = new MappedStatement.Builder( configuration, msId, sqlSource, SqlCommandType.SELECT ).resultMaps(list).build(); //缓存 configuration.addMappedStatement(ms); } /** * 创建一个简单的MS * * @param msId * @param sqlSource 执行的sqlSource * @param sqlCommandType 执行的sqlCommandType */ private void newUpdateMappedStatement(String msId, SqlSource sqlSource, SqlCommandType sqlCommandType) { List list = Collections.singletonList( new ResultMap.Builder(configuration, "defaultResultMap", int.class, new ArrayList<>(0)).build() ); MappedStatement ms = new MappedStatement.Builder( configuration, msId, sqlSource, sqlCommandType ).resultMaps(list).build(); configuration.addMappedStatement(ms); } private String select(String sql) { String msId = newMsId(sql, SqlCommandType.SELECT); if (hasMappedStatement(msId)) { return msId; } StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql); newSelectMappedStatement(msId, sqlSource, Map.class); return msId; } private String selectDynamic(String sql, Class parameterType) { String msId = newMsId(sql + parameterType, SqlCommandType.SELECT); if (hasMappedStatement(msId)) { return msId; } SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType); newSelectMappedStatement(msId, sqlSource, Map.class); return msId; } private String select(String sql, Class resultType) { String msId = newMsId(resultType + sql, SqlCommandType.SELECT); if (hasMappedStatement(msId)) { return msId; } StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql); newSelectMappedStatement(msId, sqlSource, resultType); return msId; } private String selectDynamic(String sql, Class parameterType, Class resultType) { String msId = newMsId(resultType + sql + parameterType, SqlCommandType.SELECT); if (hasMappedStatement(msId)) { return msId; } SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType); newSelectMappedStatement(msId, sqlSource, resultType); return msId; } private String insert(String sql) { String msId = newMsId(sql, SqlCommandType.INSERT); if (hasMappedStatement(msId)) { return msId; } StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql); newUpdateMappedStatement(msId, sqlSource, SqlCommandType.INSERT); return msId; } private String insertDynamic(String sql, Class parameterType) { String msId = newMsId(sql + parameterType, SqlCommandType.INSERT); if (hasMappedStatement(msId)) { return msId; } SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType); newUpdateMappedStatement(msId, sqlSource, SqlCommandType.INSERT); return msId; } private String update(String sql) { String msId = newMsId(sql, SqlCommandType.UPDATE); if (hasMappedStatement(msId)) { return msId; } StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql); newUpdateMappedStatement(msId, sqlSource, SqlCommandType.UPDATE); return msId; } private String updateDynamic(String sql, Class parameterType) { String msId = newMsId(sql + parameterType, SqlCommandType.UPDATE); if (hasMappedStatement(msId)) { return msId; } SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType); newUpdateMappedStatement(msId, sqlSource, SqlCommandType.UPDATE); return msId; } private String delete(String sql) { String msId = newMsId(sql, SqlCommandType.DELETE); if (hasMappedStatement(msId)) { return msId; } StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql); newUpdateMappedStatement(msId, sqlSource, SqlCommandType.DELETE); return msId; } private String deleteDynamic(String sql, Class parameterType) { String msId = newMsId(sql + parameterType, SqlCommandType.DELETE); if (hasMappedStatement(msId)) { return msId; } SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType); newUpdateMappedStatement(msId, sqlSource, SqlCommandType.DELETE); return msId; } } } ================================================ FILE: src/main/java/cn/ponfee/commons/parser/DateUDF.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.parser; import cn.ponfee.commons.date.Dates; import org.apache.commons.lang3.StringUtils; import java.util.Date; import java.util.regex.Pattern; /** * https://www.cnblogs.com/qcfeng/p/7553500.html * * Date user defined functions * * @author Ponfee */ public final class DateUDF { private static final Pattern PATTERN = Pattern.compile("[\\-+]?\\d+[YyMDdHhmWw]"); // -------------------------------------------------now public static String now(String format) { return format(now(), format); } public static String now(String format, String offset) { return format(compute(now(), offset), format); } // -------------------------------------------------day public static String startDay(String format) { return format(Dates.startOfDay(now()), format); } public static String startDay(String format, String offset) { return format(Dates.startOfDay(compute(now(), offset)), format); } public static String endDay(String format) { return format(Dates.endOfDay(now()), format); } public static String endDay(String format, String offset) { return format(Dates.endOfDay(compute(now(), offset)), format); } // -------------------------------------------------week public static String startWeek(String format) { return format(Dates.startOfWeek(now()), format); } public static String startWeek(String format, String offset) { return format(Dates.startOfWeek(compute(now(), offset)), format); } public static String endWeek(String format) { return format(Dates.endOfWeek(now()), format); } public static String endWeek(String format, String offset) { return format(Dates.endOfWeek(compute(now(), offset)), format); } // -------------------------------------------------month public static String startMonth(String format) { return format(Dates.startOfMonth(now()), format); } public static String startMonth(String format, String offset) { return format(Dates.startOfMonth(compute(now(), offset)), format); } public static String endMonth(String format) { return format(Dates.endOfMonth(now()), format); } public static String endMonth(String format, String offset) { return format(Dates.endOfMonth(compute(now(), offset)), format); } // -------------------------------------------------year public static String startYear(String format) { return format(Dates.startOfYear(now()), format); } public static String startYear(String format, String offset) { return format(Dates.startOfYear(compute(now(), offset)), format); } public static String endYear(String format) { return format(Dates.endOfYear(now()), format); } public static String endYear(String format, String offset) { return format(Dates.endOfYear(compute(now(), offset)), format); } // -------------------------------------------------private methods private static Date now() { return new Date(); } private static String format(Date date, String format) { if (date == null) { return null; } else if ("timestamp".equalsIgnoreCase(format)) { return String.valueOf(date.getTime()); } else { return Dates.format(date, format); } } private static Date compute(Date dateTime, String offset) { if (!PATTERN.matcher(offset).matches()) { throw new IllegalArgumentException("Invalid offset: " + offset); } int amount = Integer.parseInt(StringUtils.substring(offset, 0, -1)); switch (offset.charAt(offset.length() - 1)) { case 'y': case 'Y': return Dates.plusYears(dateTime, amount); case 'M': return Dates.plusMonths(dateTime, amount); case 'w': case 'W': return Dates.plusWeeks(dateTime, amount); case 'd': case 'D': return Dates.plusDays(dateTime, amount); case 'H': case 'h': return Dates.plusHours(dateTime, amount); case 'm': return Dates.plusMinutes(dateTime, amount); default: throw new IllegalArgumentException("Invalid offset: " + offset); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/parser/ELParser.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.parser; import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Map; import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; import static com.google.common.base.CaseFormat.LOWER_CAMEL; import static com.google.common.base.CaseFormat.LOWER_UNDERSCORE; /** * EL parser utility. * * ELParser.parse("test |${abc}| a |${12}| |$[ yyyyMM \n]| xx |$[start_year( yyyyMMddHHmmss, -1y )]| aa |$[now( timestamp\n, -1y)]|", ImmutableMap.of("abc", 123, "test.1", "xxx")) * * @author Ponfee */ public class ELParser { private static final Pattern PARAMS_PATTERN = Pattern.compile("\\$\\{\\s*([a-zA-Z0-9_\\-.]+)\\s*\\}"); private static final Pattern DATETM_PATTERN = Pattern.compile("\\$\\[\\s*([a-zA-Z0-9_,\\-+.()\\s]+)\\s*\\]"); private static final Pattern SPEL_PATTERN = Pattern.compile("\\{\\{\\s*([^{}]+)\\s*\\}\\}"); public static String parse(String text) { return parseDateUDF(text); } public static String parse(String text, Map params) { text = parseParams(text, params); text = parseSpel(text, params); text = parseDateUDF(text); return text; } // ----------------------------------------------------------------------------- private methods private static String parseParams(String text, Map params) { if (MapUtils.isEmpty(params)) { return text; } String result = text; Matcher matcher = PARAMS_PATTERN.matcher(text); while (matcher.find()) { String name = matcher.group(1); if (params.containsKey(name)) { // matcher.group() -> matcher.group(0) result = StringUtils.replace(result, matcher.group(), Objects.toString(params.get(name), "")); } } return result; } private static String parseDateUDF(String text) { Matcher matcher = DATETM_PATTERN.matcher(text); String result = text; while (matcher.find()) { String val = translate(matcher.group(1)); if (val != null) { result = StringUtils.replace(result, matcher.group(), val); } } return result; } /** * SPEL Java: parser.parseExpression("T(java.lang.Math).PI").getValue(double.class); * parser.parseExpression("T(java.lang.Math).random()").getValue(double.class) * parser.parseExpression("'Hi,everybody'").getValue(String.class) * * SPEL Xml : parser.parseExpression("#{T(java.lang.Math).PI}").getValue(double.class); * parser.parseExpression("#{T(java.lang.Math).random()}").getValue(double.class) * parser.parseExpression("#{'Hi,everybody'}").getValue(String.class) * @param text * @param params * @return */ @SuppressWarnings("unchecked") private static String parseSpel(String text, Map params) { if (MapUtils.isEmpty(params)) { return text; } StandardEvaluationContext context = new StandardEvaluationContext(); context.setVariables((Map) params); // #key //context.setVariables("name", (Map) params); // #name[key] //context.setRootObject(params); // #root.field ExpressionParser parser = new SpelExpressionParser(); String result = text; Matcher matcher = SPEL_PATTERN.matcher(text); while (matcher.find()) { result = StringUtils.replace( result, matcher.group(), Objects.toString(parser.parseExpression(matcher.group(1)).getValue(context), null) ); } return result; } private static String translate(String text) { try { text = text.trim(); int argsStart = text.indexOf('('); if (argsStart < 1) { // $[yyyyMMdd] return DateUDF.now(text); } if (!text.endsWith(")")) { return null; } String methodName = LOWER_UNDERSCORE.to(LOWER_CAMEL, text.substring(0, argsStart).trim().toLowerCase()); String[] params = text.substring(argsStart + 1, text.lastIndexOf(')')).split(","); if (params.length == 1) { // e.g. $[now(timestamp)] or $[start_day(yyyyMMddHHmmss)] return (String) getPublicStaticMethod(DateUDF.class, methodName, String.class) .invoke(null, params[0].trim()); } else if (params.length == 2) { // e.g. $[start_year(yyyyMMddHHmmss, -1y)] return (String) getPublicStaticMethod(DateUDF.class, methodName, String.class, String.class) .invoke(null, params[0].trim(), params[1].trim()); } else { return null; } } catch (Exception ignored) { return null; } } private static Method getPublicStaticMethod(Class clazz, String methodName, Class... parameterTypes) throws Exception { Method method = ArrayUtils.isEmpty(parameterTypes) ? clazz.getDeclaredMethod(methodName) : clazz.getDeclaredMethod(methodName, parameterTypes); int modifiers = method.getModifiers(); return (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers)) ? method : null; } } ================================================ FILE: src/main/java/cn/ponfee/commons/pdf/PdfWaterMark.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.pdf; import com.itextpdf.text.*; import com.itextpdf.text.pdf.*; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * pdf加水印 * * @author Ponfee */ public class PdfWaterMark { /** * 添加水印图片 * @param pdf * @param img * @param out */ public static void waterImgMark(InputStream pdf, byte[] img, OutputStream out) { PdfStamper stamper = null; PdfReader reader = null; try { reader = new PdfReader(pdf); stamper = new PdfStamper(reader, out); Image image = Image.getInstance(img);// 水印图片 Rectangle pageRect; for (int n = stamper.getReader().getNumberOfPages() + 1, i = 1; i < n; i++) { pageRect = stamper.getReader().getPageSizeWithRotation(i); PdfContentByte content = stamper.getUnderContent(i); content.saveState(); image.setAbsolutePosition(pageRect.getWidth() - image.getWidth(), 0); // 水印添加到右下角 content.addImage(image); } out.flush(); } catch (DocumentException | IOException e) { throw new RuntimeException(e); } finally { if (stamper != null) { try { stamper.close(); } catch (DocumentException | IOException ignored) { ignored.printStackTrace(); } } if (reader != null) { reader.close(); } } } /** * 添加水印文字 * @param pdf * @param words * @param out */ public static void waterWordMark(InputStream pdf, String words, OutputStream out) { PdfStamper stamper = null; PdfReader reader = null; try { reader = new PdfReader(pdf); stamper = new PdfStamper(reader, out); //stamper.setEncryption("user".getBytes(), "owner".getBytes(), PdfWriter.ALLOW_COPY | PdfWriter.ALLOW_PRINTING, PdfWriter.STANDARD_ENCRYPTION_40); // 1、使用iTextAsian.jar中的字体:BaseFont.createFont("STSong-Light", "UniGB-UCS2-H",BaseFont.NOT_EMBEDDED); // 2、使用Windows系统字体(TrueType):BaseFont.createFont("C:/WINDOWS/Fonts/SIMYOU.TTF", BaseFont.IDENTITY_H,BaseFont.NOT_EMBEDDED); // 3、使用资源字体(ClassPath):BaseFont.createFont("/SIMYOU.TTF", BaseFont.IDENTITY_H,BaseFont.NOT_EMBEDDED); BaseFont base = BaseFont.createFont("code/ponfee/commons/pdf/aspose/simfang.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED); PdfGState pdfGState = new PdfGState(); pdfGState.setStrokeOpacity(0.2f); // 设置线条透明度为0.2 pdfGState.setFillOpacity(0.2f); // 设置填充透明度为0.2 for (int totalPage = stamper.getReader().getNumberOfPages(), i = 1; i <= totalPage; i++) { Rectangle pageRect = stamper.getReader().getPageSizeWithRotation(i); float x = pageRect.getWidth() / 2; // 计算水印X坐标 float y = pageRect.getHeight() / 2; // 计算水印Y坐标 PdfContentByte content = stamper.getUnderContent(i); // content = pdfStamper.getOverContent(i); content.saveState(); content.setGState(pdfGState); content.beginText(); content.setColorFill(BaseColor.LIGHT_GRAY); content.setFontAndSize(base, 60); content.showTextAligned(Element.ALIGN_CENTER, words, x, y, 45); // 水印文字成45度角倾斜 content.endText(); } out.flush(); } catch (DocumentException | IOException e) { throw new RuntimeException(e); } finally { if (stamper != null) { try { stamper.close(); } catch (DocumentException | IOException ignored) { ignored.printStackTrace(); } } if (reader != null) { reader.close(); } } } } ================================================ FILE: src/main/java/cn/ponfee/commons/pdf/sign/PdfSignature.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.pdf.sign; import cn.ponfee.commons.io.Files; import cn.ponfee.commons.jce.Providers; import com.itextpdf.text.Image; import com.itextpdf.text.Rectangle; import com.itextpdf.text.pdf.PdfReader; import com.itextpdf.text.pdf.PdfSignatureAppearance; import com.itextpdf.text.pdf.PdfSignatureAppearance.RenderingMode; import com.itextpdf.text.pdf.PdfStamper; import com.itextpdf.text.pdf.PdfStream; import com.itextpdf.text.pdf.security.BouncyCastleDigest; import com.itextpdf.text.pdf.security.ExternalSignature; import com.itextpdf.text.pdf.security.MakeSignature; import com.itextpdf.text.pdf.security.MakeSignature.CryptoStandard; import com.itextpdf.text.pdf.security.PrivateKeySignature; import sun.security.x509.AlgorithmId; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.security.cert.X509Certificate; import java.util.Calendar; /** * pdf签章 * * @author Ponfee */ @SuppressWarnings("restriction") public class PdfSignature { public static final float SIGN_AREA_RATE = 0.8f; /** * Sign pdf * * @param pdfInput pdf输入 * @param pdfOutput pdf输出 * @param stamp 签章图片信息 {@link Stamp} * @param signer 签名人信息 {@link Signer} */ public static void sign(InputStream pdfInput, OutputStream pdfOutput, Stamp stamp, Signer signer) { PdfReader reader = null; PdfStamper stamper = null; try { reader = new PdfReader(pdfInput); stamper = PdfStamper.createSignature(reader, pdfOutput, '\0', null, true); // true:允许对同一文档多次签名 stamper.getWriter().setCompressionLevel(PdfStream.DEFAULT_COMPRESSION); Calendar calendar = Calendar.getInstance(); PdfSignatureAppearance appearance = stamper.getSignatureAppearance(); //appearance.setReason("");appearance.setLocation("");appearance.setContact(""); appearance.setVisibleSignature( calcRectangle(signer.getImage(), stamp), stamp.getPageNo(), String.valueOf(calendar.getTimeInMillis()) ); appearance.setSignDate(calendar); // 设置签名时间为当前日期 appearance.setSignatureGraphic(signer.getImage()); appearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED); appearance.setRenderingMode(RenderingMode.GRAPHIC); ExternalSignature signature = new PrivateKeySignature( signer.getPriKey(), AlgorithmId.getDigAlgFromSigAlg(((X509Certificate) signer.getCertChain()[0]).getSigAlgName()), // 获取摘要算法 Providers.BC.getName() ); MakeSignature.signDetached( appearance, new BouncyCastleDigest(), signature, signer.getCertChain(), null, null, null, 0, CryptoStandard.CMS ); } catch (Exception e) { throw new RuntimeException(e); } finally { if (reader != null) { try { reader.close(); } catch (Exception ignored) { ignored.printStackTrace(); } } if (stamper != null) { try { stamper.close(); } catch (Exception ignored) { ignored.printStackTrace(); } } } } /** * 多次签章 * * @param pdf the pdf byte array data * @param stamps the stamp array * @param signer the signer * @return */ public static byte[] sign(byte[] pdf, Stamp[] stamps, Signer signer) { for (Stamp stamp : stamps) { ByteArrayOutputStream baos = new ByteArrayOutputStream(Files.BUFF_SIZE); sign(new ByteArrayInputStream(pdf), baos, stamp, signer); pdf = baos.toByteArray(); } return pdf; } /** * 计算签名位置 * * @param image the image * @param stamp the stamp * @return a Rectangle */ private static Rectangle calcRectangle(Image image, Stamp stamp) { return new Rectangle( stamp.getLeft(), stamp.getBottom(), stamp.getLeft() + image.getWidth() * SIGN_AREA_RATE, stamp.getBottom() + image.getTop() * SIGN_AREA_RATE ); } } ================================================ FILE: src/main/java/cn/ponfee/commons/pdf/sign/Signer.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.pdf.sign; import cn.ponfee.commons.util.ImageUtils; import com.itextpdf.text.BadElementException; import com.itextpdf.text.Image; import java.io.ByteArrayInputStream; import java.io.IOException; import java.security.PrivateKey; import java.security.cert.Certificate; /** * 签名人信息 * * @author Ponfee */ public class Signer { private final PrivateKey priKey; private final Certificate[] certChain; private final Image image; public Signer(PrivateKey priKey, Certificate[] certChain, byte[] img, boolean transparent) { this.priKey = priKey; this.certChain = certChain; if (transparent) { // 图片透明化处理 img = ImageUtils.transparent(new ByteArrayInputStream(img), 250, 235); } try { this.image = Image.getInstance(img); } catch (BadElementException | IOException e) { throw new IllegalArgumentException(e); } } public PrivateKey getPriKey() { return priKey; } public Certificate[] getCertChain() { return certChain; } public Image getImage() { return image; } } ================================================ FILE: src/main/java/cn/ponfee/commons/pdf/sign/Stamp.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.pdf.sign; /** * 印章信息 * * @author Ponfee */ public class Stamp implements java.io.Serializable { private static final long serialVersionUID = -6348664154098224106L; private final int pageNo; private final float left; private final float bottom; public Stamp(int pageNo, float left, float bottom) { this.pageNo = pageNo; this.left = left; this.bottom = bottom; } public int getPageNo() { return pageNo; } public float getLeft() { return left; } public float getBottom() { return bottom; } } ================================================ FILE: src/main/java/cn/ponfee/commons/reflect/BeanConverts.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.reflect; import com.google.common.collect.ImmutableMap; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; /** * Extract bean to array, list and map * * @author Ponfee */ public final class BeanConverts { /** * 将对象指定字段转为map * * @param bean * @param fields * @param * @return */ public static Object[] toArray(E bean, String... fields) { if (bean == null || fields == null) { return null; } return Stream.of(fields).map(f -> Fields.get(bean, f)).toArray(); } /** * 获取对象指定字段的值 * * @param beans * @param field * @param * @param * @return */ @SuppressWarnings({ "unchecked" }) public static List toList(List beans, String field) { if (beans == null) { return null; } return beans.stream().map(e -> (T) Fields.get(e, field)).collect(Collectors.toList()); } public static List toList(List beans, String... fields) { if (beans == null || fields == null || fields.length == 0) { return null; } return beans.stream() .map(e -> Stream.of(fields).map(f -> Fields.get(e, f)).toArray()) .collect(Collectors.toList()); } /** * 指定对象字段keyField的值作为key,字段valueField的值作为value * * @param bean * @param keyField * @param valueField * @return */ @SuppressWarnings("unchecked") public static Map toMap(E bean, String keyField, String valueField) { if (bean == null) { return null; } return ImmutableMap.of( (K) Fields.get(bean, keyField), (V) Fields.get(bean, valueField) ); } @SuppressWarnings("unchecked") public static Map toMap(List beans, String keyField, String valueField) { if (beans == null) { return null; } return beans.stream() .collect(Collectors.toMap(e -> (K) Fields.get(e, keyField), e -> (V) Fields.get(e, valueField))); } @SuppressWarnings("unchecked") public static Map toMap(List beans, String keyField) { if (beans == null) { return null; } return beans.stream() .collect(Collectors.toMap(e -> (K) Fields.get(e, keyField), Function.identity())); } } ================================================ FILE: src/main/java/cn/ponfee/commons/reflect/BeanCopiers.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.reflect; import cn.ponfee.commons.util.SynchronizedCaches; import org.apache.commons.lang3.tuple.Pair; import org.springframework.cglib.beans.BeanCopier; import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; /** * The bean copier utility based cglib * @see mapstruct官方文档 * * @author Ponfee */ public class BeanCopiers { private static final Map, Class>, BeanCopier> COPIER_CACHES = new HashMap<>(); public static BeanCopier get(Class sourceType, Class targetType) { Pair, Class> key = Pair.of(sourceType, targetType); return SynchronizedCaches.get(key, COPIER_CACHES, () -> BeanCopier.create(sourceType, targetType, false)); } /** * Copy properties from source to target * *
         * 1、名称相同而类型不同的属性不会被拷贝
         * 2、注意:即使源类型是基本类型(int、short、char等),目标类型是其包装类型(Integer、Short、Character等),或反之,都不会被拷贝
         * 3、源类和目标类有相同的属性(两者的getter都存在),但目标类的setter不存在:创建BeanCopier的时候抛异常
         * 4、当类型不一致是需要convert进行类型转换:org.springframework.cglib.core.Converter
         * 
         * BeanUtils/PropertyUtils -> commons-beanutils:commons-beanutils
         * 
    * * @param source source object * @param target target object * * @see org.apache.commons.beanutils.BeanUtils#copyProperties(Object, Object); * @see org.apache.commons.beanutils.PropertyUtils#copyProperties(Object, Object); * @see org.springframework.beans.BeanUtils#copyProperties(Object, Object) * @see org.springframework.cglib.beans.BeanCopier#create(Class, Class, boolean) * @see mapstruct */ public static void copy(Object source, Object target) { get(source.getClass(), target.getClass()).copy(source, target, null); } public static T copy(Object source, Supplier supplier) { T target = supplier.get(); copy(source, target); return target; } public static T copy(Object source, Class targetType) { T target = ClassUtils.newInstance(targetType); copy(source, target); return target; } } ================================================ FILE: src/main/java/cn/ponfee/commons/reflect/BeanMaps.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.reflect; import cn.ponfee.commons.util.ObjectUtils; import cn.ponfee.commons.util.SynchronizedCaches; import com.google.common.collect.ImmutableList; import org.apache.commons.collections4.CollectionUtils; import org.springframework.cglib.beans.BeanMap; import java.beans.BeanInfo; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import static com.google.common.base.CaseFormat.*; /** * Utility of Java Bean and Map mutual conversion * * @author Ponfee */ public enum BeanMaps { /** * Based Cglib */ CGLIB() { @Override @SuppressWarnings("unchecked") public Map toMap(Object bean) { if (bean == null) { return null; } return BeanMap.create(bean); } @Override public void copyFromMap(Map sourceMap, Object targetBean) { BeanMap.create(targetBean).putAll(sourceMap); } }, /** * Based Unsafe class */ FIELDS() { private final Map, List> cachedFields = new HashMap<>(); @Override public Map toMap(Object bean) { if (bean == null) { return null; } List fields = getFields(bean.getClass()); Map map = new HashMap<>(fields.size()); fields.forEach(f -> map.put(f.getName(), Fields.get(bean, f))); return map; } @Override public void copyFromMap(Map sourceMap, Object targetBean) { Class clazz = targetBean.getClass(); for (Field field : getFields(clazz)) { String name = field.getName(); if (sourceMap.containsKey(name)) { Class type = GenericUtils.getFieldActualType(clazz, field); Fields.put(targetBean, field, ObjectUtils.cast(sourceMap.get(name), type)); } } } private List getFields(Class beanType) { return SynchronizedCaches.get(beanType, cachedFields, type -> { List list = ClassUtils.listFields(type); return CollectionUtils.isEmpty(list) ? Collections.emptyList() : ImmutableList.copyOf(list); }); } }, /** * Based java.beans.Introspector */ PROPS() { @Override public Map toMap(Object bean) { try { BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass()); Map map = new HashMap<>(); String name; for (PropertyDescriptor prop : beanInfo.getPropertyDescriptors()) { if (!"class".equals((name = prop.getName()))) { // getClass() map.put(name, prop.getReadMethod().invoke(bean)); } } return map; } catch (Exception e) { throw new IllegalStateException(e); } } @Override public void copyFromMap(Map sourceMap, Object targetBean) { String name; Object value; Class type; try { BeanInfo beanInfo = Introspector.getBeanInfo(targetBean.getClass()); for (PropertyDescriptor prop : beanInfo.getPropertyDescriptors()) { if ("class".equals(name = prop.getName())) { // exclude getClass() continue; } String name0 = name; if ( !sourceMap.containsKey(name) && !sourceMap.containsKey(name = LOWER_CAMEL.to(LOWER_UNDERSCORE, name0)) && !sourceMap.containsKey(name = LOWER_CAMEL.to(LOWER_HYPHEN, name0)) ) { continue; } value = sourceMap.get(name); if ((type = prop.getPropertyType()).isPrimitive() && ObjectUtils.isEmpty(value)) { continue; // 基本类型时:value为null或为空字符串时跳过 } // set value into bean field prop.getWriteMethod().invoke(targetBean, ObjectUtils.cast(value, type)); } } catch (Exception e) { throw new IllegalStateException(e); } } }; /** * Returns a map from bean field-value * * @param bean the bean object * @return a map */ public abstract Map toMap(Object bean); /** * Copies map key-value to bean object field-value * * @param sourceMap the source map * @param targetBean the target bean */ public abstract void copyFromMap(Map sourceMap, Object targetBean); /** * Returns a bean object of specified Class instance, * and copy map key-value to bean object field-value * * @param map the map * @param beanType the type, must be has no args default constructor * @return a bean object of specified Class instance */ public final T toBean(Map map, Class beanType) { T bean = ObjectUtils.newInstance(beanType); this.copyFromMap(map, bean); return bean; } /** * Copies bean object field-value to map key-value * * @param sourceBean the source bean * @param targetMap the target map */ public final void copyFromBean(Object sourceBean, Map targetMap) { targetMap.putAll(this.toMap(sourceBean)); } } ================================================ FILE: src/main/java/cn/ponfee/commons/reflect/ClassUtils.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.reflect; import cn.ponfee.commons.base.PrimitiveTypes; import cn.ponfee.commons.base.tuple.Tuple2; import cn.ponfee.commons.base.tuple.Tuple3; import cn.ponfee.commons.collect.ArrayHashKey; import cn.ponfee.commons.collect.Collects; import cn.ponfee.commons.io.Files; import cn.ponfee.commons.model.Null; import cn.ponfee.commons.base.Predicates; import cn.ponfee.commons.util.Asserts; import cn.ponfee.commons.util.Strings; import cn.ponfee.commons.util.SynchronizedCaches; import cn.ponfee.commons.util.URLCodes; import org.apache.commons.lang3.ArrayUtils; import org.springframework.asm.*; import org.springframework.objenesis.ObjenesisHelper; import java.io.File; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.URL; import java.util.*; /** * 基于asm的Class工具类 * * @author Ponfee */ public final class ClassUtils { private static final Map> CONSTRUCTOR_CACHE = new HashMap<>(); private static final Map METHOD_CACHE = new HashMap<>(); /* public static final Pattern QUALIFIED_CLASS_NAME_PATTERN = Pattern.compile("^([a-zA-Z_$][a-zA-Z\\d_$]*\\.)*[a-zA-Z_$][a-zA-Z\\d_$]*$"); private static final GroovyClassLoader GROOVY_CLASS_LOADER = new GroovyClassLoader(); public static Class getClass(String text) { String key = DigestUtils.md5Hex(text); Class clazz = SynchronizedCaches.get(key, CLASS_CACHE, () -> { if (QUALIFIED_CLASS_NAME_PATTERN.matcher(text).matches()) { try { return Class.forName(text); } catch (Exception ignored) { ignored.printStackTrace(); } } try { return GROOVY_CLASS_LOADER.parseClass(text); } catch (Exception ignored) { ignored.printStackTrace(); return Null.class; } }); return clazz == Null.class ? null : (Class) clazz; } */ /** * 获取方法的参数名(编译未清除)

    * ClassUtils.getMethodParamNames(ClassUtils.class.getMethod("newInstance", Class.class, Class.class, Object.class)) -> [type, parameterType, arg] * * @param method the method * @return method args name * @see org.springframework.core.LocalVariableTableParameterNameDiscoverer#getParameterNames(Method) */ public static String[] getMethodParamNames(Method method) { // 获取ClassReader ClassReader classReader; try { // 第一种方式(cannot use in jar file) /*String name = getClassFilePath(method.getDeclaringClass()); classReader = new ClassReader(new FileInputStream(name));*/ // 第二种方式(sometimes was wrong) //classReader = new ClassReader(getClassName(method.getDeclaringClass())); // 第三种方式 Class clazz = method.getDeclaringClass(); classReader = new ClassReader(clazz.getResourceAsStream(clazz.getSimpleName() + ".class")); } catch (IOException e) { throw new RuntimeException(e); } String[] paramNames = new String[method.getParameterTypes().length]; classReader.accept(new ClassVisitor(Opcodes.ASM5, new ClassWriter(ClassWriter.COMPUTE_MAXS)) { @Override public MethodVisitor visitMethod(int access, String name, String desc, String sign, String[] ex) { if (!name.equals(method.getName()) || !isSameType(Type.getArgumentTypes(desc), method.getParameterTypes())) { return super.visitMethod(access, name, desc, sign, ex); // 方法名相同并且参数个数相同 } return new MethodVisitor(Opcodes.ASM5, cv.visitMethod(access, name, desc, sign, ex)) { @Override public void visitLocalVariable(String name, String desc, String sign, Label start, Label end, int index) { int i = index; if (!Modifier.isStatic(method.getModifiers())) { i -= 1; // 非静态方法第一个参数是“this” } if (i >= 0 && i < paramNames.length) { paramNames[i] = name; } super.visitLocalVariable(name, desc, sign, start, end, index); } }; } }, 0); return paramNames; } /** * 获取方法签名

    * ClassUtils.getMethodSignature(ClassUtils.class.getMethod("newInstance", Class.class, Class.class, Object.class)) -> public static java.lang.Object cn.ponfee.commons.reflect.ClassUtils.newInstance(java.lang.Class type, java.lang.Class parameterType, java.lang.Object arg) * * @param method the method * @return the method string * @see java.lang.reflect.Method#toString() * @see java.lang.reflect.Method#toGenericString() */ public static String getMethodSignature(Method method) { String[] names = getMethodParamNames(method); Class[] types = method.getParameterTypes(); List params = new ArrayList<>(); for (int i = 0; i < types.length; i++) { params.add(getClassName(types[i]) + " " + names[i]); } return new StringBuilder(Modifier.toString(method.getModifiers() & Modifier.methodModifiers())) .append(' ').append(getClassName(method.getReturnType())) .append(' ').append(getClassName(method.getDeclaringClass())) .append('.').append(method.getName()) .append('(').append(Strings.join(params, ",")).append(')') .toString(); } /** * Returns the member field(include super class) * * @param clazz the type * @param fieldName the field name * @return member field object */ public static Field getField(Class clazz, String fieldName) { if (clazz.isInterface() || clazz == Object.class) { return null; } Exception firstOccurException = null; do { try { Field field = clazz.getDeclaredField(fieldName); if (!Modifier.isStatic(field.getModifiers())) { return field; } } catch (Exception e) { if (firstOccurException == null) { firstOccurException = e; } } clazz = clazz.getSuperclass(); } while (clazz != null && clazz != Object.class); // not found throw new RuntimeException(firstOccurException); } /** * Returns member field list include super class(exclude transient field) * * @param clazz the class * @return a list filled fields */ public static List listFields(Class clazz) { if (clazz.isInterface() || clazz == Object.class) { return null; // error class args } List list = new ArrayList<>(); do { try { for (Field field : clazz.getDeclaredFields()) { int mdf = field.getModifiers(); if (!Modifier.isStatic(mdf) && !Modifier.isTransient(mdf)) { list.add(field); } } } catch (Exception ignored) { // ignored } clazz = clazz.getSuperclass(); } while (clazz != null && clazz != Object.class); return list; } /** * Returns the static field, find in class pointer chain * * @param clazz the clazz * @param staticFieldName the static field name * @return static field object */ public static Tuple2, Field> getStaticFieldInClassChain(Class clazz, String staticFieldName) { if (clazz == Object.class) { return null; } Exception firstOccurException = null; Queue> queue = Collects.newLinkedList(clazz); while (!queue.isEmpty()) { for (int i = queue.size(); i > 0; i--) { Class type = queue.poll(); try { Field field = type.getDeclaredField(staticFieldName); if (Modifier.isStatic(field.getModifiers())) { return Tuple2.of(type, field); } } catch (Exception e) { if (firstOccurException == null) { firstOccurException = e; } } // 可能是父类/父接口定义的属性(如:Tuple1.HASH_FACTOR,非继承,而是查找Class的指针链) if (type.getSuperclass() != Object.class) { queue.offer(type.getSuperclass()); } Arrays.stream(type.getInterfaces()).forEach(queue::offer); } } // not found throw new RuntimeException(firstOccurException); } /** * Returns the static field * * @param clazz the clazz * @param staticFieldName the static field name * @return static field object */ public static Field getStaticField(Class clazz, String staticFieldName) { if (clazz == Object.class) { return null; } try { Field field = clazz.getDeclaredField(staticFieldName); if (Modifier.isStatic(field.getModifiers())) { return field; } else { throw new RuntimeException("Non-static field " + getClassName(clazz) + "#" + staticFieldName); } } catch (Exception e) { throw new RuntimeException(e); } } /** * 获取类名称

    * ClassUtils.getClassName(ClassUtils.class) -> cn.ponfee.commons.reflect.ClassUtils * * @param clazz the class * @return class full name */ public static String getClassName(Class clazz) { String name = clazz.getCanonicalName(); if (name == null) { name = clazz.getName(); } return name; } /** * 包名称转目录路径名

    * getPackagePath("cn.ponfee.commons.reflect") -> code/ponfee/commons/reflect * * @param packageName the package name * @return * @see org.springframework.util.ClassUtils#convertClassNameToResourcePath */ public static String getPackagePath(String packageName) { return packageName.replace('.', '/') + "/"; } /** * 包名称转目录路径名

    * ClassUtils.getPackagePath(ClassUtils.class) -> code/ponfee/commons/reflect * * @param clazz the class * @return spec class file path */ public static String getPackagePath(Class clazz) { String className = getClassName(clazz); if (className.indexOf('.') < 0) { return ""; // none package name } return getPackagePath(className.substring(0, className.lastIndexOf('.'))); } /** * 获取类文件的路径(文件)

    * ClassUtils.getClassFilePath(ClassUtils.class) -> /Users/ponfee/scm/github/commons-core/target/classes/code/ponfee/commons/reflect/ClassUtils.class

    * ClassUtils.getClassFilePath(org.apache.commons.lang3.StringUtils.class) -> /Users/ponfee/.m2/repository/org/apache/commons/commons-lang3/3.12.0/commons-lang3-3.12.0.jar!/org/apache/commons/lang3/StringUtils.class * * @param clazz the class * @return spec class file path */ public static String getClassFilePath(Class clazz) { URL url = clazz.getProtectionDomain().getCodeSource().getLocation(); String path = new File(URLCodes.decodeURI(url.getPath(), Files.UTF_8)).getAbsolutePath(); if (path.toLowerCase().endsWith(".jar")) { path += "!"; } return path + File.separator + getClassName(clazz).replace('.', File.separatorChar) + ".class"; } /** * 获取指定类的类路径(目录)

    * ClassUtils.getClasspath(ClassUtils.class) -> /Users/ponfee/scm/github/commons-core/target/classes/

    * ClassUtils.getClasspath(org.apache.commons.lang3.StringUtils.class) -> /Users/ponfee/.m2/repository/org/apache/commons/commons-lang3/3.12.0/ * * @param clazz the class * @return spec classpath */ public static String getClasspath(Class clazz) { URL url = clazz.getProtectionDomain().getCodeSource().getLocation(); String path = URLCodes.decodeURI(url.getPath(), Files.UTF_8); if (path.toLowerCase().endsWith(".jar")) { path = path.substring(0, path.lastIndexOf("/") + 1); } return new File(path).getAbsolutePath() + File.separator; } /** * 获取当前的类路径(目录)

    * ClassUtils.getClasspath() -> /Users/ponfee/scm/github/commons-core/target/test-classes/ * * @return current main classpath */ public static String getClasspath() { String path = Thread.currentThread().getContextClassLoader().getResource("").getPath(); path = URLCodes.decodeURI(new File(path).getAbsolutePath(), Files.UTF_8); return path + File.separator; } // -----------------------------------------------------------------------------constructor & instance @SuppressWarnings("unchecked") public static Constructor getConstructor(Class type, Class... parameterTypes) { boolean noArgs = ArrayUtils.isEmpty(parameterTypes); Object key = noArgs ? type : Tuple2.of(type, ArrayHashKey.of((Object[]) parameterTypes)); Constructor constructor = (Constructor) SynchronizedCaches.get(key, CONSTRUCTOR_CACHE, () -> { try { return getConstructor0(type, parameterTypes); } catch (Exception ignored) { // No such constructor, use placeholder return Null.BROKEN_CONSTRUCTOR; } }); return constructor == Null.BROKEN_CONSTRUCTOR ? null : constructor; } public static T newInstance(Constructor constructor) { return newInstance(constructor, null); } public static T newInstance(Constructor constructor, Object[] args) { checkObjectArray(args); if (!constructor.isAccessible()) { constructor.setAccessible(true); } try { return ArrayUtils.isEmpty(args) ? constructor.newInstance() : constructor.newInstance(args); } catch (Exception e) { throw new RuntimeException(e); } } public static T newInstance(Class type, Class[] parameterTypes, Object[] args) { checkObjectArray(args); checkSameLength(parameterTypes, args); if (ArrayUtils.isEmpty(parameterTypes)) { // no args constructor return newInstance(type, null); } Constructor constructor = getConstructor(type, parameterTypes); if (constructor == null) { throw new RuntimeException("No such constructor: " + getClassName(type) + toString(parameterTypes)); } return newInstance(constructor, args); } public static T newInstance(Class type) { return newInstance(type, null); } /** * 泛型参数的构造函数需要使用 {{@link #newInstance(Class, Class[], Object[])}}

    * ClassUtils.newInstance(Tuple3.class, new Object[]{1, 2, 3})

    * ClassUtils.newInstance(Tuple2.class, new Object[]{new String[]{"a", "b"}, new Integer[]{1, 2}})

    * * @param type the type * @param args the args * @param * @return */ public static T newInstance(Class type, Object[] args) { checkObjectArray(args); if (ArrayUtils.isEmpty(args)) { Constructor constructor = getConstructor(type); return constructor != null ? newInstance(constructor, null) : ObjenesisHelper.newInstance(type); } Class[] parameterTypes = parseParameterTypes(args); Constructor constructor = obtainConstructor(type, parameterTypes); if (constructor == null) { throw new RuntimeException("Not found constructor: " + getClassName(type) + toString(parameterTypes)); } return newInstance(constructor, args); } // -------------------------------------------------------------------------------------------method & invoke public static Method getMethod(Object caller, String methodName, Class... parameterTypes) { Tuple2, Predicates> tuple = obtainClass(caller); Class type = tuple.a; boolean noArgs = ArrayUtils.isEmpty(parameterTypes); Object key = noArgs ? Tuple2.of(type, methodName) : Tuple3.of(type, methodName, ArrayHashKey.of((Object[]) parameterTypes)); Method method = SynchronizedCaches.get(key, METHOD_CACHE, () -> { try { Method m = getMethod0(type, methodName, parameterTypes); return (tuple.b.equals(Modifier.isStatic(m.getModifiers())) && !m.isSynthetic()) ? m : null; } catch (Exception ignored) { // No such method, use placeholder return Null.BROKEN_METHOD; } }); return method == Null.BROKEN_METHOD ? null : method; } public static T invoke(Object caller, Method method) { return invoke(caller, method, null); } public static T invoke(Object caller, Method method, Object[] args) { checkObjectArray(args); if (!method.isAccessible()) { method.setAccessible(true); } try { return (T) (ArrayUtils.isEmpty(args) ? method.invoke(caller) : method.invoke(caller, args)); } catch (Exception e) { throw new RuntimeException(e); } } public static T invoke(Object caller, String methodName) { return invoke(caller, methodName, null, null); } public static T invoke(Object caller, String methodName, Class[] parameterTypes, Object[] args) { checkObjectArray(args); checkSameLength(parameterTypes, args); Method method = getMethod(caller, methodName, parameterTypes); if (method == null) { throw new RuntimeException( "No such method: " + getClassName(caller.getClass()) + "#" + methodName + toString(parameterTypes) ); } return invoke(caller, method, args); } public static T invoke(Object caller, String methodName, Object[] args) { checkObjectArray(args); if (ArrayUtils.isEmpty(args)) { return invoke(caller, methodName, null, null); } Class[] parameterTypes = parseParameterTypes(args); Method method = obtainMethod(caller, methodName, parameterTypes); if (method == null) { Class clazz = (caller instanceof Class) ? (Class) caller : caller.getClass(); throw new RuntimeException("Not found method: " + getClassName(clazz) + "#" + methodName + toString(parameterTypes)); } return invoke(caller, method, args); } public static Tuple2, Predicates> obtainClass(Object obj) { if (obj instanceof Class && obj != Class.class) { // 静态方法 // 普通Class类实例(如String.class):只处理其所表示类的静态方法,如“String.valueOf(1)”。不支持Class类中的实例方法,如“String.class.getName()” return Tuple2.of((Class) obj, Predicates.Y); } else { // 实例方法 // 对于Class.class对象:只处理Class类中的实例方法,如“Class.class.getName()”。不支持Class类中的静态方法,如“Class.forName("cn.ponfee.commons.base.tuple.Tuple0")” return Tuple2.of(obj.getClass(), Predicates.N); } } // -------------------------------------------------------------------------------------------private methods private static void checkSameLength(Object[] a, Object[] b) { if (ArrayUtils.isEmpty(a) && ArrayUtils.isEmpty(b)) { return; } if (a.length != b.length) { throw new RuntimeException("Two array are different length: " + a.length + ", " + b.length); } } private static void checkObjectArray(Object[] array) { if (array != null && array.getClass() != Object[].class) { throw new RuntimeException("Args must Object[] type, but actual is " + array.getClass().getSimpleName()); } } private static Class[] parseParameterTypes(Object[] args) { Asserts.isTrue(ArrayUtils.isNotEmpty(args), "Should be always non empty."); Class[] parameterTypes = new Class[args.length]; for (int i = 0, n = args.length; i < n; i++) { parameterTypes[i] = (args[i] == null) ? null : args[i].getClass(); } return parameterTypes; } private static Method getMethod0(Class type, String methodName, Class[] parameterTypes) throws Exception { try { return type.getMethod(methodName, parameterTypes); } catch (Exception e) { try { return type.getDeclaredMethod(methodName, parameterTypes); } catch (Exception ignored) { // ignored } throw e; } } private static Constructor getConstructor0(Class type, Class[] parameterTypes) throws Exception { try { return type.getConstructor(parameterTypes); } catch (Exception e) { try { return type.getDeclaredConstructor(parameterTypes); } catch (Exception ignored) { // ignored } throw e; } } // -----------------------------------------------obtain constructor & method private static Constructor obtainConstructor(Class type, Class[] actualTypes) { Asserts.isTrue(ArrayUtils.isNotEmpty(actualTypes), "Should be always non empty."); Constructor constructor = obtainConstructor((Constructor[]) type.getConstructors(), actualTypes); if (constructor != null) { return constructor; } return obtainConstructor((Constructor[]) type.getDeclaredConstructors(), actualTypes); } private static Constructor obtainConstructor(Constructor[] constructors, Class[] actualTypes) { if (ArrayUtils.isEmpty(constructors)) { return null; } for (Constructor constructor : constructors) { if (matches(constructor.getParameterTypes(), actualTypes)) { return constructor; } } return null; } private static Method obtainMethod(Object caller, String methodName, Class[] actualTypes) { Asserts.isTrue(ArrayUtils.isNotEmpty(actualTypes), "Should be always non empty."); Tuple2, Predicates> tuple = obtainClass(caller); // getMethod:获取类的所有public方法,包括自身的和从父类、接口继承的 Method method = obtainMethod(tuple.a.getMethods(), methodName, tuple.b, actualTypes); if (method != null) { return method; } // getDeclaredMethods:获取类自身声明的方法,包含public、protected和private return obtainMethod(tuple.a.getDeclaredMethods(), methodName, tuple.b, actualTypes); } private static Method obtainMethod(Method[] methods, String methodName, Predicates flag, Class[] actualTypes) { if (ArrayUtils.isEmpty(methods)) { return null; } for (Method method : methods) { boolean matches = method.getName().equals(methodName) && !method.isSynthetic() && flag.equals(Modifier.isStatic(method.getModifiers())) && matches(method.getParameterTypes(), actualTypes); if (matches) { return method; } } return null; } /** * 方法匹配 * * @param definedTypes 方法体中定义的参数类型 * @param actualTypes 调用方法实际传入的参数类型 * @return */ private static boolean matches(Class[] definedTypes, Class[] actualTypes) { if (definedTypes.length != actualTypes.length) { return false; } for (int i = 0, n = definedTypes.length; i < n; i++) { Class definedType = definedTypes[i], actualType = actualTypes[i]; if (definedType.isPrimitive()) { // 方法参数为基本数据类型 PrimitiveTypes ept = PrimitiveTypes.ofPrimitive(definedType); PrimitiveTypes apt = PrimitiveTypes.ofPrimitiveOrWrapper(actualType); if (apt == null || !apt.isCastable(ept)) { return false; } } else if (actualType != null && !definedType.isAssignableFrom(actualType)) { // actualType为空则可转任何对象类型(非基本数据类型) return false; } } return true; } /** * 比较参数类型是否一致

    * * @param types asm的类型({@link Type}) * @param classes java 类型({@link Class}) * @return {@code true} if the Type array each of equals the Class array */ private static boolean isSameType(Type[] types, Class[] classes) { if (types.length != classes.length) { return false; } for (int i = 0; i < types.length; i++) { if (!Type.getType(classes[i]).equals(types[i])) { return false; } } return true; } private static String toString(Class[] parameterTypes) { return ArrayUtils.isEmpty(parameterTypes) ? "()" : "(" + Strings.join(Arrays.asList(parameterTypes), ", ") + ")"; } } ================================================ FILE: src/main/java/cn/ponfee/commons/reflect/Fields.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.reflect; import cn.ponfee.commons.base.tuple.Tuple2; import cn.ponfee.commons.base.Predicates; import sun.misc.Unsafe; import java.lang.reflect.Field; import java.lang.reflect.Modifier; /** * 高效的反射工具类(基于sun.misc.Unsafe) * * @author Ponfee */ @SuppressWarnings("restriction") public final class Fields { // sun.misc.Unsafe.getUnsafe() will be throws "java.lang.SecurityException: Unsafe" // caller code must use in BootstrapClassLoader to load (JAVA_HOME/jre/lib) // but application code load by sun.misc.Launcher.AppClassLoader private static final Unsafe UNSAFE; static { try { Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); UNSAFE = (Unsafe) f.get(null); // If the underlying field is a static field, // the {@code obj} argument is ignored; it may be null. // Set static field's value {@code f.set(null, value)} // restore the accessible value f.setAccessible(false); } catch (NoSuchFieldException | IllegalAccessException e) { throw new RuntimeException("failed to get unsafe instance", e); } } private static final long BASE_OFFSET = UNSAFE.arrayBaseOffset(Object[].class); // 16 private static final int INDEX_SCALE = UNSAFE.arrayIndexScale(Object[].class); // 4 private static final int ADDRESS_SIZE = UNSAFE.addressSize(); // 8(64 bit cpu) public static long addressOf(Object obj) { return addressOf(new Object[]{obj}, 0); } /** * Returns the object reference pointer address of jvm * * @param array the obj array * @param index the array position * @return reference pointer address */ public static long addressOf(Object[] array, int index) { switch (INDEX_SCALE) { case 4: return (UNSAFE.getInt(array, BASE_OFFSET + (long) index * INDEX_SCALE) & 0xFFFFFFFFL) * ADDRESS_SIZE; case 8: return UNSAFE.getLong(array, BASE_OFFSET + (long) index * INDEX_SCALE) * ADDRESS_SIZE; default: throw new Error("Unsupported address size: " + INDEX_SCALE); } } /** * put field to target object * @param target 目标对象 * @param name 字段名 * @param value 字段值 */ public static void put(Object target, String name, Object value) { try { Tuple2 tuple = getField(target, name); put(tuple.a, tuple.b, value); } catch (Exception e) { throw new RuntimeException(e); } } /** * put field to target object if value is null * @param target 目标对象 * @param name 字段名 * @param value 字段值 */ public static void putIfNull(Object target, String name, Object value) { try { Tuple2 tuple = getField(target, name); putIfNull(tuple.a, tuple.b, value); } catch (Exception e) { throw new RuntimeException(e); } } /** * put field to target object if value is null * @param target * @param field * @param value */ public static void putIfNull(Object target, Field field, Object value) { if (get(target, field) == null) { put(target, field, value); } } /** * put field to target object * @param target target object * @param field object field * @param value field value */ public static void put(Object target, Field field, Object value) { //field.setAccessible(true); unnecessary set accessible to true long fieldOffset = getFieldOffset(field); Class type = GenericUtils.getFieldActualType(target.getClass(), field); if (Boolean.TYPE.equals(type)) { UNSAFE.putBoolean(target, fieldOffset, (boolean) value); } else if (Byte.TYPE.equals(type)) { UNSAFE.putByte(target, fieldOffset, (byte) value); } else if (Character.TYPE.equals(type)) { UNSAFE.putChar(target, fieldOffset, (char) value); } else if (Short.TYPE.equals(type)) { UNSAFE.putShort(target, fieldOffset, (short) value); } else if (Integer.TYPE.equals(type)) { UNSAFE.putInt(target, fieldOffset, (int) value); } else if (Long.TYPE.equals(type)) { UNSAFE.putLong(target, fieldOffset, (long) value); } else if (Double.TYPE.equals(type)) { UNSAFE.putDouble(target, fieldOffset, (double) value); } else if (Float.TYPE.equals(type)) { UNSAFE.putFloat(target, fieldOffset, (float) value); } else { UNSAFE.putObject(target, fieldOffset, value); } } /** * get field of target object * @param target 目标对象 * @param name field name * @return the field value */ public static Object get(Object target, String name) { try { Tuple2 tuple = getField(target, name); return get(tuple.a, tuple.b); } catch (Exception e) { throw new RuntimeException(e); } } /** * get field of target object * @param target 目标对象 * @param field 字段 * @return */ public static Object get(Object target, Field field) { long fieldOffset = getFieldOffset(field); Class type = GenericUtils.getFieldActualType(target.getClass(), field); if (Boolean.TYPE.equals(type)) { return UNSAFE.getBoolean(target, fieldOffset); } else if (Byte.TYPE.equals(type)) { return UNSAFE.getByte(target, fieldOffset); } else if (Character.TYPE.equals(type)) { return UNSAFE.getChar(target, fieldOffset); } else if (Short.TYPE.equals(type)) { return UNSAFE.getShort(target, fieldOffset); } else if (Integer.TYPE.equals(type)) { return UNSAFE.getInt(target, fieldOffset); } else if (Long.TYPE.equals(type)) { return UNSAFE.getLong(target, fieldOffset); } else if (Double.TYPE.equals(type)) { return UNSAFE.getDouble(target, fieldOffset); } else if (Float.TYPE.equals(type)) { return UNSAFE.getFloat(target, fieldOffset); } else { return UNSAFE.getObject(target, fieldOffset); } } /** * put of volatile * @param target * @param field * @param value */ public static void putVolatile(Object target, Field field, Object value) { //field.setAccessible(true); unnecessary set accessible to true long fieldOffset = getFieldOffset(field); Class type = GenericUtils.getFieldActualType(target.getClass(), field); if (Boolean.TYPE.equals(type)) { UNSAFE.putBooleanVolatile(target, fieldOffset, (boolean) value); } else if (Byte.TYPE.equals(type)) { UNSAFE.putByteVolatile(target, fieldOffset, (byte) value); } else if (Character.TYPE.equals(type)) { UNSAFE.putCharVolatile(target, fieldOffset, (char) value); } else if (Short.TYPE.equals(type)) { UNSAFE.putShortVolatile(target, fieldOffset, (short) value); } else if (Integer.TYPE.equals(type)) { UNSAFE.putIntVolatile(target, fieldOffset, (int) value); } else if (Long.TYPE.equals(type)) { UNSAFE.putLongVolatile(target, fieldOffset, (long) value); } else if (Double.TYPE.equals(type)) { UNSAFE.putDoubleVolatile(target, fieldOffset, (double) value); } else if (Float.TYPE.equals(type)) { UNSAFE.putFloatVolatile(target, fieldOffset, (float) value); } else { UNSAFE.putObjectVolatile(target, fieldOffset, value); } } /** * 支持volatile语义 * @param target * @param name * @return */ public static Object getVolatile(Object target, String name) { try { Tuple2 tuple = getField(target, name); return getVolatile(tuple.a, tuple.b); } catch (Exception e) { throw new RuntimeException(e); } } /** * 支持volatile语义 * @param target * @param field * @return */ public static Object getVolatile(Object target, Field field) { long fieldOffset = getFieldOffset(field); Class type = GenericUtils.getFieldActualType(target.getClass(), field); if (Boolean.TYPE.equals(type)) { return UNSAFE.getBooleanVolatile(target, fieldOffset); } else if (Byte.TYPE.equals(type)) { return UNSAFE.getByteVolatile(target, fieldOffset); } else if (Character.TYPE.equals(type)) { return UNSAFE.getCharVolatile(target, fieldOffset); } else if (Short.TYPE.equals(type)) { return UNSAFE.getShortVolatile(target, fieldOffset); } else if (Integer.TYPE.equals(type)) { return UNSAFE.getIntVolatile(target, fieldOffset); } else if (Long.TYPE.equals(type)) { return UNSAFE.getLongVolatile(target, fieldOffset); } else if (Double.TYPE.equals(type)) { return UNSAFE.getDoubleVolatile(target, fieldOffset); } else if (Float.TYPE.equals(type)) { return UNSAFE.getFloatVolatile(target, fieldOffset); } else { return UNSAFE.getObjectVolatile(target, fieldOffset); } } // ----------------------------------------------------------------private methods private static Tuple2 getField(Object obj, String name) { Tuple2, Predicates> tuple = ClassUtils.obtainClass(obj); if (tuple.b.state()) { // static // field.get(null); // field.set(null, value); 使用field设置final属性会报错,只能使用Unsafe return ClassUtils.getStaticFieldInClassChain(tuple.a, name); //return Tuple2.of(obj, ClassUtils.getStaticField(tuple.a, name)); } else { // member return Tuple2.of(obj, ClassUtils.getField(tuple.a, name)); } } private static long getFieldOffset(Field field) { return Modifier.isStatic(field.getModifiers()) ? UNSAFE.staticFieldOffset(field) : UNSAFE.objectFieldOffset(field); } } ================================================ FILE: src/main/java/cn/ponfee/commons/reflect/GenericUtils.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.reflect; import cn.ponfee.commons.util.SynchronizedCaches; import com.google.common.base.Preconditions; import org.apache.commons.lang3.ArrayUtils; import java.lang.reflect.*; import java.util.*; import java.util.Map.Entry; /** * 泛型工具类 * * https://segmentfault.com/a/1190000018319217 * * @author Ponfee */ public final class GenericUtils { private static final Map, Map>> VARIABLE_TYPE_MAPPING = new HashMap<>(); /** * map泛型协变 * @param origin * @return */ public static Map covariant(Map origin) { if (origin == null) { return null; } Map target = new HashMap<>(origin.size()); for (Entry entry : origin.entrySet()) { target.put(entry.getKey(), Objects.toString(entry.getValue(), null)); } return target; } public static Map covariant(Properties properties) { return (Map) properties; } // ----------------------------------------------------------------------------class actual type argument /** * 获取泛型的实际类型参数 * * @param clazz * @return */ public static Class getActualTypeArgument(Class clazz) { return getActualTypeArgument(clazz, 0); } /** * public class GenericClass extends GenericSuperClass implements GenericInterface {} * * @param clazz * @param genericArgsIndex * @return */ @SuppressWarnings("unchecked") public static Class getActualTypeArgument(Class clazz, int genericArgsIndex) { int index = 0; for (Type type : getGenericTypes(clazz)) { if (type instanceof ParameterizedType) { Type[] acts = ((ParameterizedType) type).getActualTypeArguments(); if (acts.length + index < genericArgsIndex) { index += acts.length; } else { return getActualType(null, acts[genericArgsIndex - index]); } } } return (Class) Object.class; } // ----------------------------------------------------------------------------method actual arg type argument public static Class getActualArgTypeArgument(Method method, int methodArgsIndex) { return getActualArgTypeArgument(method, methodArgsIndex, 0); } /** * public void genericMethod(List list, Map map){} * * @param method 方法对象 * @param methodArgsIndex 方法参数索引号 * @param genericArgsIndex 泛型参数索引号 * @return */ public static Class getActualArgTypeArgument(Method method, int methodArgsIndex, int genericArgsIndex) { return getActualTypeArgument(method.getGenericParameterTypes()[methodArgsIndex], genericArgsIndex); } // ----------------------------------------------------------------------------method actual return type argument public static Class getActualReturnTypeArgument(Method method) { return getActualReturnTypeArgument(method, 0); } /** * public List genericMethod(){} * * @param method the method * @param genericArgsIndex the generic argument index * @return */ public static Class getActualReturnTypeArgument(Method method, int genericArgsIndex) { return getActualTypeArgument(method.getGenericReturnType(), genericArgsIndex); } // ---------------------------------------------------------------------------- public static Class getActualTypeArgument(Field field) { return getActualTypeArgument(field, 0); } /** * private List list; -> Long * * @param field the class field * @param genericArgsIndex the genericArgsIndex * @return */ public static Class getActualTypeArgument(Field field, int genericArgsIndex) { return getActualTypeArgument(field.getGenericType(), genericArgsIndex); } // -------------------------------------------------------------------get actual variable type public static Class getFieldActualType(Class clazz, String fieldName) { Field field = ClassUtils.getField(clazz, fieldName); if (field == null) { throw new IllegalArgumentException("Type " + ClassUtils.getClassName(clazz) + " not exists field '" + fieldName + "'"); } return getFieldActualType(clazz, field); } /** *

    {@code
         * public abstract class BaseEntity {
         *   private I id;
         * }
         * }
    * *
    {@code
         * public class BeanClass extends BaseEntity {}
         * }
    * * @param clazz the sub class * @param field the super class defined field * @return a Class of field actual type */ public static Class getFieldActualType(Class clazz, Field field) { return Modifier.isStatic(field.getModifiers()) ? (Class) field.getType() : getActualType(clazz, field.getGenericType()); } /** * Returns method arg actual type *
    {@code
         * public abstract class ClassA {
         *   public void method(T arg) {}
         * }
         * }
    *
    {@code
         * public class ClassB extends classA{}
         * }
    * * @param clazz the sub class * @param method the super class defined method * @param methodArgsIndex the method arg index * @return a Class of method arg actual type */ public static Class getMethodArgActualType(Class clazz, Method method, int methodArgsIndex) { return getActualType(clazz, method.getGenericParameterTypes()[methodArgsIndex]); } /** * Returns method return actual type *
    {@code
         * public abstract class ClassA {
         *   public T method() {}
         * }
         * }
    *
    {@code
         * public class ClassB extends classA{}
         * }
    * * @param clazz the sub class * @param method the super class defined method * @return a Class of method return actual type */ public static Class getMethodReturnActualType(Class clazz, Method method) { return getActualType(clazz, method.getGenericReturnType()); } // public class ClassA extends ClassB> implements interfaceC>, interfaceD {} public static List getGenericTypes(Class clazz) { if (clazz == null || clazz == Object.class) { return Collections.emptyList(); } List types = new ArrayList<>(); if (!clazz.isInterface()) { types.add(clazz.getGenericSuperclass()); // Map } Collections.addAll(types, clazz.getGenericInterfaces()); // List, Y return types; } public static Map> getActualTypeVariableMapping(Class clazz) { Map> result = new HashMap<>(); for (Type type : getGenericTypes(clazz)) { resolveMapping(result, type); } return result.isEmpty() ? Collections.emptyMap() : result; } public static Class getRawType(Type type) { if (type instanceof Class) { return (Class) type; } else if (type instanceof ParameterizedType) { // cn.ponfee.commons.tree.NodePath return (Class) ((ParameterizedType) type).getRawType(); } else { throw new UnsupportedOperationException("Unsupported type: " + type); } } @SuppressWarnings("unchecked") public static Class getActualTypeArgument(Type type, int genericArgsIndex) { Preconditions.checkArgument(genericArgsIndex >= 0, "Generic args index cannot be negative."); if (!(type instanceof ParameterizedType)) { return (Class) Object.class; } Type[] types = ((ParameterizedType) type).getActualTypeArguments(); return genericArgsIndex >= types.length ? (Class) Object.class : getActualType(null, types[genericArgsIndex]); } // -------------------------------------------------------------------private methods @SuppressWarnings("unchecked") private static Class getActualType(Class clazz, Type type) { if (type instanceof Class) { // private String name; return (Class) type; } else if (type instanceof ParameterizedType) { // public class Sup { // private List list1; -> java.util.List // private List list2; -> java.util.List // } return getActualType(clazz, ((ParameterizedType) type).getRawType()); } else if (type instanceof GenericArrayType) { // private E[] array; Type etype = ((GenericArrayType) type).getGenericComponentType(); // E: element type return (Class) Array.newInstance(getActualType(clazz, etype), 0).getClass(); } else if (type instanceof TypeVariable) { // public class Sup { // private E id; // } // public class Sub extends Sup {} return getVariableActualType(clazz, (TypeVariable) type); } else if (type instanceof WildcardType) { WildcardType wtype = (WildcardType) type; if (ArrayUtils.isNotEmpty(wtype.getLowerBounds())) { // 下限List return getActualType(clazz, wtype.getLowerBounds()[0]); } else if (ArrayUtils.isNotEmpty(wtype.getUpperBounds())) { // 上限List return getActualType(clazz, wtype.getUpperBounds()[0]); } else { // List return (Class) Object.class; } } else { return (Class) Object.class; } } @SuppressWarnings("unchecked") private static Class getVariableActualType(Class clazz, TypeVariable var) { if (clazz == null) { return (Class) Object.class; } return (Class) SynchronizedCaches.get(clazz, VARIABLE_TYPE_MAPPING, GenericUtils::getActualTypeVariableMapping) .getOrDefault(getTypeVariableName(null, var).get(0), Object.class); } private static void resolveMapping(Map> result, Type type) { if (!(type instanceof ParameterizedType)) { return; } ParameterizedType ptype = (ParameterizedType) type; Class rawType = (Class) ptype.getRawType(); // (Map).getRawType() -> Map TypeVariable[] vars = rawType.getTypeParameters(); Type[] acts = ptype.getActualTypeArguments(); for (int i = 0; i < acts.length; i++) { Class varType = getActualType(null, acts[i]); getTypeVariableName(rawType, vars[i]).forEach(e -> result.put(e, varType)); resolveMapping(result, acts[i]); } } private static List getTypeVariableName(Class clazz, TypeVariable var) { List names = new ArrayList<>(); getTypeVariableName(names, clazz, var); return names; } // Class, Method, Constructor private static void getTypeVariableName(List names, Class clazz, TypeVariable var) { names.add(var.getGenericDeclaration().toString() + "[" + var.getName() + "]"); if (clazz == null || clazz == Object.class) { return; } for (Type type : getGenericTypes(clazz)) { if (!(type instanceof ParameterizedType)) { continue; } ParameterizedType ptype = (ParameterizedType) type; Type[] types = ptype.getActualTypeArguments(); if (ArrayUtils.isEmpty(types)) { continue; } for (int i = 0; i < types.length; i++) { if (!(types[i] instanceof TypeVariable)) { continue; } // find the type variable origin difined class if (((TypeVariable) types[i]).getName().equals(var.getTypeName())) { clazz = (Class) ptype.getRawType(); getTypeVariableName(names, clazz, clazz.getTypeParameters()[i]); break; } } } } } ================================================ FILE: src/main/java/cn/ponfee/commons/resource/ClassPathResourceLoader.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.resource; import cn.ponfee.commons.io.Closeables; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.*; import java.net.JarURLConnection; import java.net.URL; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import static org.apache.commons.lang3.ArrayUtils.isEmpty; import static org.apache.commons.lang3.StringUtils.join; /** * 类资源加载器 * * @author Ponfee */ final class ClassPathResourceLoader { private static final String URL_PROTOCOL_FILE = "file"; private static final String URL_PROTOCOL_JAR = "jar"; private static final String URL_PROTOCOL_ZIP = "zip"; private static final String JAR_URL_SEPARATOR = "!/"; private static final Logger LOG = LoggerFactory.getLogger(ClassPathResourceLoader.class); /** * 加载资源文件 * @param filePath * @param contextClass * @param encoding * @return */ Resource getResource(String filePath, Class contextClass, String encoding) { Enumeration urls; JarFile jar = null; ZipFile zip = null; try { if (contextClass != null) { urls = contextClass.getClassLoader().getResources(filePath); } else { urls = Thread.currentThread().getContextClassLoader().getResources(filePath); } while (urls.hasMoreElements()) { URL url = urls.nextElement(); switch (url.getProtocol()) { case URL_PROTOCOL_FILE: String path = URLDecoder.decode(url.getFile(), encoding); // 判断是否是指定类所在Jar包中的文件:path.length()-filePath.length() == path.lastIndexOf(filePath) if (checkWithoutClass(contextClass, path.substring(0, path.length() - filePath.length()), encoding)) { continue; } return new Resource(path, new File(path).getName(), new FileInputStream(path)); case URL_PROTOCOL_JAR: jar = ((JarURLConnection) url.openConnection()).getJarFile(); // 判断是否是指定类所在Jar包中的文件 if (checkWithoutClass(contextClass, jar.getName(), encoding)) { continue; } Enumeration entries = jar.entries(); while (entries.hasMoreElements()) { // 获取jar里的一个实体:可以是目录或其他如META-INF等文件 JarEntry entry = entries.nextElement(); if (!filePath.equals(entry.getName())) { continue; } String fileName = entry.getName(); fileName = fileName.replace("\\", "/"); if (fileName.contains("/")) { fileName = fileName.substring(fileName.lastIndexOf("/") + 1); } return new Resource(URLDecoder.decode(url.getFile(), encoding), fileName, transform(jar.getInputStream(entry))); } jar.close(); jar = null; break; case URL_PROTOCOL_ZIP: // as a zip file in weblogic environment String zipPath = URLDecoder.decode(url.getFile(), encoding); if (zipPath.startsWith(ResourceLoaderFacade.FS_PREFIX)) { zipPath = zipPath.substring(ResourceLoaderFacade.FS_PREFIX.length()); } if (!zipPath.contains(JAR_URL_SEPARATOR)) { continue; } zipPath = zipPath.substring(0, zipPath.lastIndexOf(JAR_URL_SEPARATOR)); // 判断是否是指定类所在Jar包中的文件 if (checkWithoutClass(contextClass, zipPath, encoding)) { continue; } zip = new ZipFile(zipPath); // org.apache.tools.zip.ZipEntry; // org.apache.tools.zip.ZipFile; //Enumeration entries = zip.getEntries(); Enumeration entries0 = zip.entries(); while (entries0.hasMoreElements()) { ZipEntry entry = entries0.nextElement(); if (!filePath.equals(entry.getName())) { continue; } String fileName = entry.getName(); fileName = fileName.replace("\\", "/"); if (fileName.contains("/")) { fileName = fileName.substring(fileName.lastIndexOf("/") + 1); } return new Resource( URLDecoder.decode(url.getFile(), encoding), fileName, transform(zip.getInputStream(entry)) ); } zip.close(); zip = null; break; default: throw new UnsupportedOperationException("Unsupported protocol: " + url.getProtocol()); } } return null; } catch (IOException e) { LOG.error("load resource from jar file occur error", e); return null; } finally { Closeables.console(jar); Closeables.console(zip); } } List listResources(String directory, String[] extensions, boolean recursive, Class contextClass, String encoding) { List list = new ArrayList<>(); JarFile jar = null; ZipFile zip = null; Enumeration dirs; try { if (contextClass != null) { dirs = contextClass.getClassLoader().getResources(directory); } else { dirs = Thread.currentThread().getContextClassLoader().getResources(directory); } while (dirs.hasMoreElements()) { URL url = dirs.nextElement(); switch (url.getProtocol()) { case URL_PROTOCOL_FILE: String path = URLDecoder.decode(url.getFile(), encoding); // 判断是否是指定类所在Jar包中的文件:path.length()-directory.length() == path.lastIndexOf(directory) if (checkWithoutClass(contextClass, path.substring(0, path.length() - directory.length()), encoding)) { continue; } Collection files = FileUtils.listFiles(new File(path), extensions, recursive); if (files != null && !files.isEmpty()) { for (File file : files) { list.add(new Resource(file.getAbsolutePath(), file.getName(), new FileInputStream(file))); } } break; case URL_PROTOCOL_JAR: jar = ((JarURLConnection) url.openConnection()).getJarFile(); // 判断是否是指定类所在Jar包中的文件 if (checkWithoutClass(contextClass, jar.getName(), encoding)) { continue; } Enumeration entries = jar.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if (entry.isDirectory()) { continue; } String name = entry.getName(); // 1、全目录匹配 或 当可递归时子目录 // 2、匹配后缀 int idx = name.lastIndexOf('/'); boolean isDir = (idx != -1 && name.substring(0, idx).equals(directory)) || (recursive && name.startsWith(directory)); boolean isSufx = isEmpty(extensions) || name.toLowerCase().matches("^(.+\\.)(" + join(extensions, "|") + ")$"); if (isDir && isSufx) { list.add(new Resource(URLDecoder.decode(url.getFile(), encoding), entry.getName(), transform(jar.getInputStream(entry)))); } } jar.close(); jar = null; break; case URL_PROTOCOL_ZIP: // weblogic is zip file String zipPath = URLDecoder.decode(url.getFile(), encoding); if (zipPath.startsWith(ResourceLoaderFacade.FS_PREFIX)) { zipPath = zipPath.substring(ResourceLoaderFacade.FS_PREFIX.length()); } if (!zipPath.contains(JAR_URL_SEPARATOR)) { continue; } zipPath = zipPath.substring(0, zipPath.lastIndexOf(JAR_URL_SEPARATOR)); // 判断是否是指定类所在Jar包中的文件 if (checkWithoutClass(contextClass, zipPath, encoding)) { continue; } zip = new ZipFile(zipPath); //Enumeration entries = zip.getEntries(); Enumeration entries0 = zip.entries(); while (entries0.hasMoreElements()) { ZipEntry entry = entries0.nextElement(); String name = entry.getName(); int idx = name.lastIndexOf('/'); // 1、全目录匹配 或 当可递归时子目录 // 2、匹配后缀 boolean isDir = (idx != -1 && name.substring(0, idx).equals(directory)) || (recursive && name.startsWith(directory)); boolean isSuffix = isEmpty(extensions) || name.toLowerCase().matches("^(.+\\.)(" + join(extensions, "|") + ")$"); if (isDir && isSuffix) { list.add(new Resource( URLDecoder.decode(url.getFile(), encoding), entry.getName(), transform(zip.getInputStream(entry)) )); } } zip.close(); zip = null; break; default: throw new UnsupportedOperationException("un supported process " + url.getProtocol()); } } return list; } catch (IOException e) { LOG.error("load resource from jar file occur error", e); return list; } finally { Closeables.console(jar); Closeables.console(zip); } } /** * 判断资源文件是否在contextClass的classpath中(jar包或class目录) * @param contextClass * @param filepath * @param encoding * @return * @throws IOException */ private static boolean checkWithoutClass(Class contextClass, String filepath, String encoding) throws IOException { if (contextClass == null) { return false; } String destPath = contextClass.getProtectionDomain().getCodeSource().getLocation().getFile(); destPath = URLDecoder.decode(destPath, encoding); return !new File(destPath).getCanonicalFile().equals(new File(filepath).getCanonicalFile()); } /** * 流转换 * @param input * @return * @throws IOException */ private static ByteArrayInputStream transform(InputStream input) throws IOException { if (input instanceof ByteArrayInputStream) { return (ByteArrayInputStream) input; } try { return new ByteArrayInputStream(IOUtils.toByteArray(input)); } finally { Closeables.console(input); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/resource/FileSystemResourceLoader.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.resource; import org.apache.commons.io.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** * 文件资源加载器 * * @author Ponfee */ final class FileSystemResourceLoader { private static final Logger LOG = LoggerFactory.getLogger(FileSystemResourceLoader.class); Resource getResource(String filePath, String encoding) { try { File f = new File(filePath); return new Resource(f.getAbsolutePath(), f.getName(), new FileInputStream(f)); } catch (FileNotFoundException e) { LOG.error("file not found: " + filePath, e); return null; } } List listResources(String directory, String[] extensions, boolean recursive) { List list = new ArrayList<>(); try { File fileDir = new File(directory); Collection files = FileUtils.listFiles(fileDir, extensions, recursive); if (files != null && !files.isEmpty()) { for (File f : files) { list.add(new Resource(f.getAbsolutePath(), f.getName(), new FileInputStream(f))); } } } catch (FileNotFoundException e) { LOG.error("file not found: " + directory, e); } return list; } } ================================================ FILE: src/main/java/cn/ponfee/commons/resource/Resource.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.resource; import cn.ponfee.commons.io.Closeables; import java.io.Closeable; import java.io.InputStream; /** * 资源类 * * @author Ponfee */ public class Resource implements Closeable { private final String filePath; private final String fileName; private InputStream stream; public Resource(String filePath, String fileName, InputStream stream) { this.filePath = filePath; this.fileName = fileName; this.stream = stream; } public String getFilePath() { return filePath; } public String getFileName() { return fileName; } public InputStream getStream() { return stream; } @Override public String toString() { return "Resource [filePath=" + filePath + ", fileName=" + fileName + ", stream=" + stream + "]"; } @Override public void close() { Closeables.console(stream); stream = null; } } ================================================ FILE: src/main/java/cn/ponfee/commons/resource/ResourceLoaderFacade.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.resource; import cn.ponfee.commons.io.Files; import cn.ponfee.commons.reflect.ClassUtils; import cn.ponfee.commons.util.Strings; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import javax.annotation.Nonnull; import javax.servlet.ServletContext; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 资源文件加载门面类 *
     *  {@link Class#getResourceAsStream(String)           } :path不以“/”开头则从此类所在的jar包下获取,以“/”开头则从classpath根下获取(内部还是由ClassLoader获取资源)
     *  {@link ClassLoader#getResourceAsStream(String)     } :从classpath根下获取(path不能以“/”开头,否则报错)
     *  {@link ServletContext#getResourceAsStream(String)  } :从WebAPP根目录下取资源,'/'开头和不以'/'开头情况一样
     * 
    * *
      *
    • classpath:

      以'/'开头表示在jar包中的绝对路径(内部还是由ClassLoader获取),不以'/'开头表示在jar包中与指定类的相对路径 *

    • *
    • webapp:
    • *
    • file:
    • *
    *

    default classpath: * *

     *  ResourceLoaderFacade.getResource("StringUtils.class", StringUtils.class);
     *  ResourceLoaderFacade.getResource("/mybatis-conf.xml", ResourceLoaderFacade.class); // 类所在jar包中的绝对路径
     *  ResourceLoaderFacade.getResource("mybatis-conf.xml", ResourceLoaderFacade.class); // 类所在jar包中且相对该类的路径
     *  ResourceLoaderFacade.getResource("/log4j2.xml");
     *  ResourceLoaderFacade.getResource("log4j2.xml");
     *  ResourceLoaderFacade.getResource("file:d:/import.txt");
     * 
    * * @author Ponfee */ public final class ResourceLoaderFacade { //private static final String CP_ALL_PREFIX = "classpath*:"; private static final String WEB_PREFIX = "webapp:"; static final String FS_PREFIX = "file:"; private static final Pattern PATTERN = Pattern.compile("^(\\s*(?i)(classpath|webapp|file):\\s*)?(.+)$"); private static final ClassPathResourceLoader CP_LOADER = new ClassPathResourceLoader(); private static final FileSystemResourceLoader FS_LOADER = new FileSystemResourceLoader(); private static final WebappResourceLoader WEB_LOADER = new WebappResourceLoader(); public static void setServletContext(@Nonnull ServletContext servletContext) { if (servletContext != null) { WEB_LOADER.setServletContext(servletContext); } } public static Resource getResource(String filePath, Class contextClass) { return getResource(filePath, contextClass, null); } public static Resource getResource(String filePath, String encoding) { return getResource(filePath, null, encoding); } public static Resource getResource(String filePath) { return getResource(filePath, null, null); } /** * 文件资源加载 * @param filePath "/"表示根路径开始,其它为相对路径 * @param contextClass * @param encoding * @return */ public static Resource getResource(String filePath, Class contextClass, String encoding) { Matcher matcher = PATTERN.matcher(filePath); if (!matcher.matches()) { throw new IllegalArgumentException("Invalid file path: " + filePath); } if (StringUtils.isEmpty(encoding)) { encoding = Files.UTF_8; } filePath = Strings.cleanPath(matcher.group(3).trim()); switch (ObjectUtils.defaultIfNull(matcher.group(1), "").toLowerCase()) { case FS_PREFIX: return FS_LOADER.getResource(filePath, encoding); case WEB_PREFIX: return WEB_LOADER.getResource(resolveWebapp(filePath), encoding); default: // 内部用的classLoader加载,不能以“/”开头,XX.class.getResourceAsStream("/com/x/file/myfile.xml")才能以“/”开头 // "/"开头表示取根路径,非"/"开头则加上contextClass的包路径(如果contextClass不为空) filePath = resolveClasspath(filePath, contextClass); return CP_LOADER.getResource(filePath, contextClass, encoding); // 默认为classpath } } /** * 路径默认为空串 * * @param extensions * @param contextClass * @return */ public static List listResources(String[] extensions, Class contextClass) { return listResources("", extensions, false, contextClass, Files.UTF_8); } public static List listResources(String dir, String[] extensions, boolean recursive) { return listResources(dir, extensions, recursive, null, Files.UTF_8); } public static List listResources(String dir, String[] extensions, boolean recursive, String encoding) { return listResources(dir, extensions, recursive, null, encoding); } /** * 路径匹配过滤加载 * @param dir "/"表示根路径开始,其它为相对路径 * @param extensions * @param recursive * @param contextClass * @param encoding * @return */ public static List listResources(String dir, String[] extensions, boolean recursive, Class contextClass, String encoding) { if (StringUtils.isBlank(dir)) { dir = "."; } Matcher matcher = PATTERN.matcher(dir); if (!matcher.matches()) { throw new IllegalArgumentException("Invalid directory: " + dir); } if (StringUtils.isEmpty(encoding)) { encoding = Files.UTF_8; } dir = Strings.cleanPath(matcher.group(3).trim()); switch (ObjectUtils.defaultIfNull(matcher.group(1), "").toLowerCase()) { case FS_PREFIX: return FS_LOADER.listResources(dir, extensions, recursive); case WEB_PREFIX: return WEB_LOADER.listResources(resolveWebapp(dir), extensions, recursive, encoding); default: // 内部用的classLoader加载,不能以“/”开头,XX.class.getResourceAsStream("/com/x/file/myfile.xml")才能以“/”开头 // "/"开头表示取根路径,非"/"开头则加上contextClass的包路径(如果contextClass不为空) dir = resolveClasspath(dir, contextClass); return CP_LOADER.listResources(dir, extensions, recursive, contextClass, encoding); // default classpath } } private static String resolveWebapp(String path) { if (!path.startsWith("/")) { path = "/" + path; } return path; } private static String resolveClasspath(String path, Class contextClass) { if (path.startsWith("/")) { path = path.substring(1); } else if (contextClass != null) { path = ClassUtils.getPackagePath(contextClass) + "/" + path; } return path; } } ================================================ FILE: src/main/java/cn/ponfee/commons/resource/ResourceScanner.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.resource; import cn.ponfee.commons.exception.Throwables.ThrowingFunction; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.core.type.filter.AssignableTypeFilter; import org.springframework.core.type.filter.TypeFilter; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Annotation; import java.nio.charset.Charset; import java.util.*; import static org.springframework.core.io.support.ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX; /** *
     *  资源扫描文件,用法:
     *   new ResourceScanner("/*.template").scan4text()
     *   new ResourceScanner("/**∕tika*.xml").scan4text()
     *
     *   // findAllClassPathResources:“/*” 等同 “*”,“/”开头会被截取path.substring(1)
     *   new ResourceScanner("*.xml").scan4bytes()
     *   new ResourceScanner("/*.xml").scan4bytes()
     *   new ResourceScanner("**∕*.xml").scan4bytes()
     *   new ResourceScanner("/**∕*.xml").scan4bytes()
     *   new ResourceScanner("/log4j2.xml.template").scan4bytes()
     *   new ResourceScanner("log4j2.xml.template").scan4bytes()
     *
     *   new ResourceScanner("/cn/ponfee/commons/jce/*.class").scan4bytes()
     *   new ResourceScanner("/cn/ponfee/commons/jce/**∕*.class").scan4bytes()
     *
     *   new ResourceScanner("/cn/ponfee/commons/base/**∕*.class").scan4class()
     *   new ResourceScanner("/cn/ponfee/commons/**∕*.class").scan4class(null, new Class[] {Service.class})
     *   new ResourceScanner("/cn/ponfee/commons/**∕*.class").scan4class(null, new Class[] {Component.class})
     *   new ResourceScanner("/cn/ponfee/commons/**∕*.class").scan4class(new Class[]{Tuple.class}, null)
     * 
    * * @author Ponfee * @see org.springframework.context.annotation.ClassPathBeanDefinitionScanner */ public class ResourceScanner { private static final Logger LOG = LoggerFactory.getLogger(ResourceScanner.class); /** * Prefix of resource schema. * * @see org.springframework.core.io.support.ResourcePatternResolver#CLASSPATH_ALL_URL_PREFIX * @see org.springframework.util.ResourceUtils#CLASSPATH_URL_PREFIX * @see org.springframework.util.ResourceUtils#FILE_URL_PREFIX * @see org.springframework.util.ResourceUtils#JAR_URL_PREFIX * @see org.springframework.util.ResourceUtils#WAR_URL_PREFIX */ private final String urlPrefix; private final List locationPatterns; public ResourceScanner(String... locationPatterns) { this(CLASSPATH_ALL_URL_PREFIX, locationPatterns); } public ResourceScanner(String urlPrefix, String[] locationPatterns) { if (ArrayUtils.isEmpty(locationPatterns)) { locationPatterns = new String[]{"*"}; } this.urlPrefix = urlPrefix; this.locationPatterns = Arrays.asList(locationPatterns); } /** * 类扫描 * * @return result of class set */ public Set> scan4class() { return scan4class(null, null); } /** * 类扫描 * * @param assignableTypes 扫描指定的子类 * @param annotationTypes 扫描包含指定注解的类 * @return result of class set */ public Set> scan4class(Class[] assignableTypes, Class[] annotationTypes) { List typeFilters = new LinkedList<>(); if (ArrayUtils.isNotEmpty(assignableTypes)) { Arrays.stream(assignableTypes).map(AssignableTypeFilter::new).forEach(typeFilters::add); } if (ArrayUtils.isNotEmpty(annotationTypes)) { // considerMetaAnnotations=true: @Service -> @Component Arrays.stream(annotationTypes).map(AnnotationTypeFilter::new).forEach(typeFilters::add); } Set> result = new HashSet<>(); PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); MetadataReaderFactory factory = new CachingMetadataReaderFactory(resolver); try { for (String locationPattern : this.locationPatterns) { for (Resource resource : resolver.getResources(urlPrefix + locationPattern)) { if (!resource.isReadable()) { continue; } MetadataReader reader = factory.getMetadataReader(resource); if (!matches(typeFilters, reader, factory)) { continue; } try { result.add(Class.forName(reader.getClassMetadata().getClassName())); } catch (Throwable e) { LOG.error("Load class occur error.", e); } } } return result; } catch (IOException e) { return ExceptionUtils.rethrow(e); } } /** * Scan as byte array * * @return type of Map result */ public Map scan4bytes() { return scan(IOUtils::toByteArray); } /** * Scan as string * * @return type of Map result */ public Map scan4text() { return scan4text(Charset.defaultCharset()); } /** * Scan as string * * @param charset the charset * @return type of Map result */ public Map scan4text(Charset charset) { return scan(e -> IOUtils.toString(e, charset)); } // --------------------------------------------------------------------------private methods private Map scan(ThrowingFunction mapper) { Map result = new HashMap<>(16); PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); try { for (String locationPattern : locationPatterns) { for (Resource resource : resolver.getResources(urlPrefix + locationPattern)) { if (!resource.isReadable()) { continue; } try (InputStream input = resource.getInputStream()) { result.put(resource.getFilename(), mapper.apply(input)); } catch (Throwable e) { LOG.error("Resource scan location pattern failed: " + locationPattern, e); } } } } catch (IOException e) { return ExceptionUtils.rethrow(e); } return result; } private static boolean matches(List filters, MetadataReader reader, MetadataReaderFactory factory) throws IOException { if (filters.isEmpty()) { return true; } for (TypeFilter filter : filters) { if (filter.match(reader, factory)) { return true; } } return false; } } ================================================ FILE: src/main/java/cn/ponfee/commons/resource/WebappResourceLoader.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.resource; import org.apache.commons.io.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.ServletContext; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** * webapp资源加载器 * * @author Ponfee */ final class WebappResourceLoader { private static final Logger LOG = LoggerFactory.getLogger(WebappResourceLoader.class); private ServletContext servletContext; WebappResourceLoader() {} void setServletContext(ServletContext servletContext) { this.servletContext = servletContext; } ServletContext getServletContext() { return this.servletContext; } Resource getResource(String filePath, String encoding) { try { File f = new File(servletContext.getRealPath(filePath)); return new Resource(f.getAbsolutePath(), f.getName(), new FileInputStream(f)); } catch (FileNotFoundException e) { LOG.error("file not found [" + filePath + "]", e); return null; } } List listResources(String directory, String[] extensions, boolean recursive, String encoding) { List list = new ArrayList<>(); try { File fileDir = new File(servletContext.getRealPath(directory)); Collection files = FileUtils.listFiles(fileDir, extensions, recursive); if (files != null && !files.isEmpty()) { for (File f : files) { list.add(new Resource(f.getAbsolutePath(), f.getName(), new FileInputStream(f))); } } } catch (FileNotFoundException e) { LOG.error("file not found [" + directory + "]", e); } return list; } } ================================================ FILE: src/main/java/cn/ponfee/commons/schema/DataColumn.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.schema; import java.io.Serializable; /** * Column for table meta config * * @author Ponfee */ public class DataColumn implements Serializable { private static final long serialVersionUID = 7044462319527084588L; private String name; // 列名(如Mysql表的列名) private DataType type; // 类型 private String alias; // 别名(表头标题) public DataColumn() {} public DataColumn(String name, DataType type, String alias) { this.name = name; this.type = type; this.alias = alias; } public String getName() { return name; } public void setName(String name) { this.name = name; } public DataType getType() { return type; } public void setType(DataType type) { this.type = type; } public String getAlias() { return alias; } public void setAlias(String alias) { this.alias = alias; } public static DataColumn of(String name, DataType type, String alias) { return new DataColumn(name, type, alias); } public static DataColumn of(String name, DataType type) { return new DataColumn(name, type, null); } } ================================================ FILE: src/main/java/cn/ponfee/commons/schema/DataStructure.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.schema; /** * 数据格式标记类:结构化的数据 * * @author Ponfee */ public interface DataStructure extends java.io.Serializable { default String structure() { return DataStructures.ofType(this.getClass()).name(); } NormalStructure toNormal(); TableStructure toTable(); PlainStructure toPlain(); } ================================================ FILE: src/main/java/cn/ponfee/commons/schema/DataStructures.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.schema; import cn.ponfee.commons.exception.ServerException; import cn.ponfee.commons.json.Jsons; import com.google.common.base.CaseFormat; import org.apache.commons.lang3.StringUtils; import java.lang.reflect.Method; import java.text.ParseException; import java.util.Collections; import java.util.Optional; /** * 数据格式处理 * * @author Ponfee */ public enum DataStructures { /** 标准 */ NORMAL(NormalStructure.class) { @Override public NormalStructure empty() { return new NormalStructure(0); } }, // /** 表格 */ TABLE(TableStructure.class) { private final DataColumn[] empty = new DataColumn[0]; @Override public DataStructure empty() { return new TableStructure(empty, Collections.emptyList()); } @Override public DataStructure parse(String text) { TableStructure table = (TableStructure) Jsons.fromJson(text, this.type()); if (table.getColumns() == null && table.getDataset() == null) { throw new IllegalArgumentException("Invalid table structure: " + text); } return table; } }, // /** 原文 */ PLAIN(PlainStructure.class) { private final PlainStructure empty = new PlainStructure(""); @Override public DataStructure empty() { return empty; } }, // ; private static final DataStructures DEFAULT_STRUCTURE = NORMAL; private final Class type; DataStructures(Class type) { this.type = type; } public DataStructure parse(String text) { return Jsons.fromJson(text, this.type()); } public abstract DataStructure empty(); public Class type() { return this.type; } public static DataStructures ofType(Class type) { if (type == null) { return DEFAULT_STRUCTURE; } for (DataStructures ds : DataStructures.values()) { if (ds.type == type) { return ds; } } throw new UnsupportedOperationException("Unknown structure type: " + type); } public static DataStructures ofName(String name) { if (StringUtils.isBlank(name)) { return DEFAULT_STRUCTURE; } for (DataStructures ds : DataStructures.values()) { if (ds.name().equalsIgnoreCase(name)) { return ds; } } throw new UnsupportedOperationException("Unknown structure type: " + name); } public static DataStructure empty(String name) { return ofName(name).empty(); } // ---------------------------------------------------------------------------detect text to data structure public static DataStructure detect(String text, boolean strict) throws ParseException { for (DataStructures ds : DataStructures.values()) { try { return ds.parse(text); } catch (Exception ignored) { ignored.printStackTrace(); } } if (!strict) { return new PlainStructure(text); } throw new ParseException("Unresolvable text data: " + text, 0); } // ---------------------------------------------------------------------------convert source structure to target structure public static T convert(S source, Class targetType) { return convert(source, ofType(targetType).name()); } public static T convert(S source, DataStructures targetType) { return convert(source, (targetType == null ? DEFAULT_STRUCTURE : targetType).name()); } @SuppressWarnings("unchecked") public static T convert(S source, String structure) { if (source == null) { return null; } DataStructures sourceType = ofType(source.getClass()); String structure0 = Optional.ofNullable(structure).filter(StringUtils::isNotBlank) .map(String::toUpperCase).orElse(DEFAULT_STRUCTURE.name()); if (structure0.equals(sourceType.name())) { return (T) source; } // toNormal(), toTable(), toPlain() String methodName = "to" + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, structure0); Method method; try { method = source.getClass().getDeclaredMethod(methodName); } catch (Exception e) { throw new UnsupportedOperationException("Unknown structure type: " + structure, e); } try { return (T) method.invoke(source); } catch (Exception e) { if (StringUtils.isBlank(structure)) { return (T) source.toPlain(); } throw new ServerException("Structure type convert failed, expect: " + structure + ", actual: " + sourceType.name(), e); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/schema/DataTable.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.schema; import java.io.Serializable; import java.util.List; /** * Data table structure: a DataColumn array of columns * and two-dimensional array dataset

    * * @author Ponfee */ public class DataTable implements Serializable { private static final long serialVersionUID = 3710299712677057559L; private String name; // 表名 private String alias; // 表别名 private DataColumn[] columns; // 数据列元数据信息 private List dataset; // 数据集 public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAlias() { return alias; } public void setAlias(String alias) { this.alias = alias; } public DataColumn[] getColumns() { return columns; } public void setColumns(DataColumn[] columns) { this.columns = columns; } public List getDataset() { return dataset; } public void setDataset(List dataset) { this.dataset = dataset; } } ================================================ FILE: src/main/java/cn/ponfee/commons/schema/DataType.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.schema; import cn.ponfee.commons.date.JavaUtilDateFormat; import cn.ponfee.commons.math.Numbers; import com.google.common.collect.ImmutableMap; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import java.sql.Types; import java.text.DateFormat; import java.text.ParseException; import java.util.Date; import java.util.Map; import java.util.Objects; import java.util.regex.Pattern; /** * Data type enum * * If the data value cannot convert the target type then fix is null * * @author Ponfee */ @SuppressWarnings("unchecked") public enum DataType { BOOLEAN("布尔") { @Override protected T parseObject0(Object value) { if (value instanceof Boolean) { return (T) value; } return (T) BOOLEAN_MAPPING.get(value.toString()); } }, DECIMAL("小数") { @Override protected T parseObject0(Object value) { return (T) Numbers.toWrapDouble(value); } }, INTEGER("整数") { @Override protected T parseObject0(Object value) { return (T) Numbers.toWrapLong(value); } }, STRING("字符串") { @Override protected T parseObject0(Object value) { return (T) value.toString(); } @Override protected String toString0(Object value) { return value.toString(); } @Override public boolean test0(Object value) { return true; } }, DATE("日期") { private final JavaUtilDateFormat format = new JavaUtilDateFormat("yyyy-MM-dd"); @Override protected T parseObject0(Object value) { return (T) parseToDate(this.format, value); } @Override protected String convert0(Object value) { return toString0(value); } @Override protected String toString0(Object value) { return dateToString(this.format, value); } }, DATE_TIME("日期时间") { @Override protected T parseObject0(Object value) { return (T) parseToDate(JavaUtilDateFormat.DEFAULT, value); } @Override protected String convert0(Object value) { return toString0(value); } @Override protected String toString0(Object value) { return dateToString(JavaUtilDateFormat.DEFAULT, value); } }, TIMESTAMP("时间戳(毫秒)") { @Override protected T parseObject0(Object value) { if (value instanceof Date) { return (T) (Long) ((Date) value).getTime(); } return (T) Numbers.toWrapLong(value); } }, ; private final String description; DataType(String description) { this.description = description; } protected abstract T parseObject0(Object value); protected String toString0(Object value) { return Objects.toString(parseObject0(value), null); } /** * Default is call inner method {@link #parseObject0(Object)},

    * but {@link #DATE} and {@link #DATE_TIME} override this * method for convert a string with date format

    * * @param value the vlaue * @return an object of convert result */ protected T convert0(Object value) { return parseObject0(value); } protected boolean test0(Object value) { return parseObject0(value) != null; } // -----------------------------------------------------------public methods public final T parseObject(Object value) { return value == null ? null : parseObject0(value); } public final String toString(Object value) { return value == null ? null : toString0(value); } /** * Like parseObject, except {@link #DATE} or {@link #DATE_TIME} convert to date format string * * @param value the value * @return an target object, except {@link #DATE} or {@link #DATE_TIME} is a string of date format */ public final Object convert(Object value) { return value == null ? null : ObjectUtils.defaultIfNull(convert0(value), value); } public final boolean test(Object value) { // allow null(empty) value if (value == null) { return true; } if ((value instanceof String) && StringUtils.isEmpty((String) value)) { return true; } return test0(value); } public String description() { return this.description; } public static DataType of(String name) { for (DataType dt : DataType.values()) { if (dt.name().equalsIgnoreCase(name)) { return dt; } } return STRING; } // -------------------------------------------------------------------------detect data type private static final Map BOOLEAN_MAPPING = ImmutableMap. builder() .put("TRUE", Boolean.TRUE) .put("True", Boolean.TRUE) .put("true", Boolean.TRUE) .put("FALSE", Boolean.FALSE) .put("False", Boolean.FALSE) .put("false", Boolean.FALSE) .build(); private static final Pattern PATTERN_INTEGER = Pattern.compile("^[-+]?(([1-9]\\d*)|0)$"); private static final Pattern PATTERN_DECIMAL = Pattern.compile("^[-+]?(([1-9]\\d*)|0)\\.\\d+$"); public static DataType detect(String value) { if (StringUtils.isBlank(value)) { return STRING; } if (BOOLEAN_MAPPING.containsKey(value)) { return BOOLEAN; } if (PATTERN_INTEGER.matcher(value).matches() && INTEGER.parseObject0(value) != null) { return INTEGER; } if (PATTERN_DECIMAL.matcher(value).matches() && DECIMAL.parseObject0(value) != null) { return DECIMAL; } if (value.length() == 10 && DATE.parseObject0(value) != null) { return DATE; } if (value.length() == 19 && DATE_TIME.parseObject0(value) != null) { return DATE_TIME; } return STRING; } public static DataType ofDatabaseType(int type) { switch (type) { case Types.DATE: return DATE_TIME; case Types.TIMESTAMP: return TIMESTAMP; case Types.BIT: case Types.BOOLEAN: return BOOLEAN; case Types.REAL: case Types.TINYINT: case Types.SMALLINT: case Types.INTEGER: case Types.BIGINT: return INTEGER; case Types.FLOAT: case Types.DOUBLE: case Types.DECIMAL: return DECIMAL; default: // VARCHAR, CHAR, NVARCHAR, NCHAR, LONGVARCHAR, LONGNVARCHAR return STRING; } } // ---------------------------------------------------------------------private methods private static String dateToString(DateFormat format, Object value) { if (value instanceof Date) { return format.format((Date) value); } String text = value.toString(); if (StringUtils.isBlank(text)) { return null; } try { return format.format(format.parse(text)); } catch (ParseException ignored) { return null; } } private static Date parseToDate(DateFormat format, Object value) { if (value instanceof Date) { return (Date) value; } if (value instanceof Number) { return new Date(((Number) value).longValue()); } String text = value.toString(); if (StringUtils.isBlank(text)) { return null; } try { return format.parse(text); } catch (ParseException ignored) { return null; } } } ================================================ FILE: src/main/java/cn/ponfee/commons/schema/GridTable.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.schema; import org.apache.commons.lang3.StringUtils; import java.io.Serializable; import java.util.Arrays; /** * Grid table for front view * * @author Ponfee */ public class GridTable implements Serializable { private static final long serialVersionUID = 4900630719709337101L; private Columns[] columns; // 表头 private NormalStructure dataset; // 表体 public static GridTable of(TableStructure ts) { if (ts == null) { return null; } GridTable table = new GridTable(); table.setColumns(Columns.convert(ts.getColumns())); table.setDataset(ts.toNormal()); return table; } public static class Columns implements Serializable { private static final long serialVersionUID = 1L; private String title; private String dataIndex; private String key; public Columns() {} public Columns(String title, String name) { this.title = StringUtils.isBlank(title) ? name : title; this.dataIndex = name; this.key = name; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getDataIndex() { return dataIndex; } public void setDataIndex(String dataIndex) { this.dataIndex = dataIndex; } public String getKey() { return key; } public void setKey(String key) { this.key = key; } public static Columns convert(DataColumn column) { return new Columns(column.getAlias(), column.getName()); } public static Columns[] convert(DataColumn[] columns) { if (columns == null) { return null; } return Arrays.stream(columns).map(Columns::convert).toArray(Columns[]::new); } } // ------------------------------------------------------------------------getter/setter public Columns[] getColumns() { return columns; } public void setColumns(Columns[] columns) { this.columns = columns; } public NormalStructure getDataset() { return dataset; } public void setDataset(NormalStructure dataset) { this.dataset = dataset; } } ================================================ FILE: src/main/java/cn/ponfee/commons/schema/NormalStructure.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.schema; import cn.ponfee.commons.json.Jsons; import java.util.*; import java.util.Map.Entry; /** * [ * {"name":"alice", "age":10}, * {"name":"bob", "age":18}, * {"name":"tom", "age":31} * ] * * @author Ponfee */ public final class NormalStructure extends ArrayList> implements DataStructure { private static final long serialVersionUID = 9067243551591375987L; public NormalStructure() {} public NormalStructure(int minCapacity) { super(minCapacity); } @Override public NormalStructure toNormal() { return this; } @Override public TableStructure toTable() { List dataset = new ArrayList<>(this.size()); int r = 0, c = 0; // r: row index, c: column index // first row LinkedHashMap map = this.get(r++); DataColumn[] columns = new DataColumn[map.size()]; Object[] row = new Object[map.size()]; for (Iterator> iter = map.entrySet().iterator(); iter.hasNext(); c++) { Entry entry = iter.next(); DataType type = DataType.detect(Objects.toString(entry.getValue(), null)); columns[c] = new DataColumn(entry.getKey(), type, null); row[c] = columns[c].getType().convert(entry.getValue()); } dataset.add(row); for (int n = this.size(); r < n; r++) { map = this.get(r); row = new Object[map.size()]; for (c = 0; c < columns.length; c++) { row[c] = map.get(columns[c].getName()); // columns[c].getType().convert(map.get(columns[c].getName())); } dataset.add(row); } return new TableStructure(columns, dataset); } @Override public PlainStructure toPlain() { return new PlainStructure(Jsons.toJson(this)); } } ================================================ FILE: src/main/java/cn/ponfee/commons/schema/PlainStructure.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.schema; import cn.ponfee.commons.exception.ServerException; import com.alibaba.fastjson.annotation.JSONType; import com.alibaba.fastjson.parser.DefaultJSONParser; import com.alibaba.fastjson.parser.JSONToken; import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer; import com.alibaba.fastjson.serializer.JSONSerializer; import com.alibaba.fastjson.serializer.ObjectSerializer; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import java.io.IOException; import java.lang.reflect.Type; import java.util.Objects; /** * 原文格式:As a string * * @author Ponfee */ @JSONType(serializer = PlainStructure.FastjsonSerializer.class, deserializer = PlainStructure.FastjsonSerializer.class) // fastjson @JsonSerialize(using = PlainStructure.JacksonSerializer.class) // jackson @JsonDeserialize(using = PlainStructure.JacksonDeserializer.class) // jackson public final class PlainStructure implements DataStructure, CharSequence { private static final long serialVersionUID = 1L; private final String plain; public PlainStructure(String plain) { // if plain is null, shoudle make the PlainStructure object is null; this.plain = Objects.requireNonNull(plain); } @Override public NormalStructure toNormal() { try { return (NormalStructure) DataStructures.NORMAL.parse(this.plain); } catch (Exception e) { throw new ServerException("Convert to normal structure fail: " + this.plain, e); } } @Override public TableStructure toTable() { try { return (TableStructure) DataStructures.TABLE.parse(this.plain); } catch (Exception e) { throw new ServerException("Convert to table structure fail: " + this.plain, e); } } @Override public PlainStructure toPlain() { return this; } @Override public String toString() { return this.plain; } @Override public int length() { return this.plain.length(); } @Override public char charAt(int index) { return this.plain.charAt(index); } @Override public CharSequence subSequence(int start, int end) { return this.plain.subSequence(start, end); } // -----------------------------------------------------custom fastjson serialize/deserialize public static class FastjsonSerializer implements ObjectSerializer, ObjectDeserializer { @Override public void write(JSONSerializer serializer, Object value, Object fieldName,Type fieldType, int features) { if (value == null) { serializer.writeNull(); } else { serializer.write(value.toString()); } } @Override @SuppressWarnings("unchecked") public PlainStructure deserialze(DefaultJSONParser parser, Type type, Object fieldName) { if (type != PlainStructure.class) { throw new UnsupportedOperationException( "Only supported deserialize PlainStructure, cannot supported: " + type ); } String value = parser.getLexer().stringVal(); // 解决报错问题:not close json text, token : string parser.getLexer().nextToken(JSONToken.LITERAL_STRING); return value == null ? null : new PlainStructure(value); } @Override public int getFastMatchToken() { return 0; } } // -----------------------------------------------------custom jackson serialize/deserialize public static class JacksonSerializer extends JsonSerializer { @Override public void serialize(PlainStructure value, JsonGenerator jsonGenerator, SerializerProvider provider) throws IOException { if (value == null) { jsonGenerator.writeNull(); } else { jsonGenerator.writeString(value.toString()); } } } public static class JacksonDeserializer extends JsonDeserializer { @Override public PlainStructure deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { String value = p.getText(); return value == null ? null : new PlainStructure(value); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/schema/TableStructure.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.schema; import cn.ponfee.commons.json.Jsons; import java.util.LinkedHashMap; import java.util.List; /** * { * "columns":[ * {"name":"name","type":"STRING", "alias":"姓名"}, * {"name":"age", "type":"INTEGER","alias":"年龄"} * ], * "dataset":[ * ["alice",10], * ["bob", 18], * ["tom", 31] * ], * } * * @author Ponfee */ public final class TableStructure implements DataStructure { private static final long serialVersionUID = 1L; private DataColumn[] columns; // 数据列元数据信息 private List dataset; // 数据集二维表数据 public TableStructure() {} public TableStructure(DataColumn[] columns, List dataset) { this.columns = columns; this.dataset = dataset; } @Override public NormalStructure toNormal() { NormalStructure list = new NormalStructure(); for (Object[] row : dataset) { LinkedHashMap map = new LinkedHashMap<>(row.length); for (int i = 0; i < row.length; i++) { map.put(columns[i].getName(), row[i]); // columns[i].getType().convert(row[i]) } list.add(map); } return list; } @Override public TableStructure toTable() { return this; } @Override public PlainStructure toPlain() { return new PlainStructure(Jsons.toJson(this)); } public static TableStructure of(DataTable table) { return new TableStructure(table.getColumns(), table.getDataset()); } // ---------------------------------------------------------------------getter/setter public DataColumn[] getColumns() { return columns; } public void setColumns(DataColumn[] columns) { this.columns = columns; } public List getDataset() { return dataset; } public void setDataset(List dataset) { this.dataset = dataset; } } ================================================ FILE: src/main/java/cn/ponfee/commons/schema/json/JsonExtractUtils.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.schema.json; import cn.ponfee.commons.collect.Collects; import cn.ponfee.commons.collect.Maps; import cn.ponfee.commons.json.Jsons; import cn.ponfee.commons.model.Null; import cn.ponfee.commons.schema.*; import cn.ponfee.commons.tree.NodePath; import cn.ponfee.commons.tree.PlainNode; import cn.ponfee.commons.tree.TreeNode; import cn.ponfee.commons.util.ObjectUtils; import com.alibaba.fastjson.JSON; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.tuple.Pair; import javax.annotation.Nonnull; import java.math.BigDecimal; import java.math.BigInteger; import java.text.ParseException; import java.util.*; import java.util.Map.Entry; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.stream.Collectors; /** * The utility class for extract json schema and data * * @author Ponfee */ public final class JsonExtractUtils { static final String ARRAY_EMPTY = "[--]"; static final String ARRAY_ARRAY = "[[]]"; static final String ARRAY_OBJECT = "[{}]"; static final String ARRAY_BASIC = "[()]"; static final String ARRAY_INDEX = "[%02d]"; static final Map NULL_VALUE_MAPPING = Collections.unmodifiableMap( Maps.toMap( ARRAY_EMPTY, null, ARRAY_ARRAY, Collections.singletonList(Collections.emptyList()), ARRAY_OBJECT, Collections.singletonList(Collections.emptyMap()), ARRAY_BASIC, Collections.emptyList() ) ); private static final String ROOT = "Root"; public static TreeNode extractSchema(String text) throws ParseException { // com.fasterxml.jackson.databind.ObjectMapper#readTree(String) return extractSchema(JSON.parse(text)); } /** * Returns a tree data of extracted the json data structure schema * * @param obj the object of use {@link JSON#parse(String)} parsed * @return a tree node of json data schema */ public static TreeNode extractSchema(Object obj) throws ParseException { if (!ObjectUtils.isComplexType(obj)) { throw new ParseException("The basic type data cannot extract schema: " + obj, 0); } List ids = new LinkedList<>(); extractSchema(ids, null, obj, new AtomicInteger(1)); return ids.isEmpty() ? null : buildTree(ids); } public static DataStructure extractData(String original, @Nonnull JsonTree tree) { Object obj; try { obj = JSON.parse(original); } catch (Exception ignored) { ignored.printStackTrace(); return new PlainStructure(original); } if (!ObjectUtils.isComplexType(obj)) { return new PlainStructure(original); } return extractData(obj, tree); } /** * Returns a DataStructure object by user specified json columns in tree * * @param object the object of use {@link JSON#parse(String)} parsed * @param tree the json tree * @return a DataStructure object */ public static TableStructure extractData(@Nonnull Object object, @Nonnull JsonTree tree) { List> dataset = new LinkedList<>(); LinkedHashSet> extracted = new LinkedHashSet<>(); Map, JsonTree> config = tree.toFlatMap(); extractData(dataset, tree.getPath(), object, config, extracted); Set duplicate = extracted .stream() .map(Collects::getLast) .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())) .entrySet() .stream() .filter(e -> e.getValue() > 1) .map(Entry::getKey) .collect(Collectors.toSet()); DataColumn[] columns = new DataColumn[extracted.size()]; int i = 0; for (NodePath path : extracted) { JsonTree node = config.get(path); String name = node.getName(); if (duplicate.contains(name)) { // prevent repeat name name += "-" + String.format("%02d", node.getOrders()); } columns[i++] = new DataColumn(name, node.getType(), null); } return new TableStructure( columns, dataset.stream().map(List::toArray).collect(Collectors.toList()) ); } // -----------------------------------------------------------------------------------private methods @SuppressWarnings("unchecked") private static void extractSchema(List ids, JsonId parent, Object object, AtomicInteger count) { if (object instanceof Map) { // JSONObject for (Entry entry : ((Map) object).entrySet()) { DataType dataType = detectDataType(entry.getValue()); JsonId id = new JsonId(parent, entry.getKey(), dataType, count.getAndIncrement()); ids.add(id); if (dataType == null) { // complex json data type extractSchema(ids, id, entry.getValue(), count); } } } else if ((object instanceof List) || object.getClass().isArray()) { // JSONArray List list = Collects.toList(object); switch (detectArrayType(list)) { case EMPTY: // 空的数组 ids.add(new JsonId(parent, ARRAY_EMPTY, null, count.getAndIncrement())); break; case ARRAY: // 二维数组 ids.add(parent = new JsonId(parent, ARRAY_ARRAY, null, count.getAndIncrement())); buildArrayColumns(findFirstArray((List>) list), ids, parent, count); break; case OBJECT: // 数组对象 ids.add(parent = new JsonId(parent, ARRAY_OBJECT, null, count.getAndIncrement())); extractSchema(ids, parent, findFirstObject((List>) list), count); // 继续下钻解析 break; case BASIC: // 基本类型(一行) ids.add(parent = new JsonId(parent, ARRAY_BASIC, null, count.getAndIncrement())); buildArrayColumns(list, ids, parent, count); break; default: throw new RuntimeException("Unknown data type: " + Jsons.toJson(list)); // cannot happened } } else { // If others json type then to skip } } private static TreeNode buildTree(List ids) { if (CollectionUtils.isEmpty(ids)) { return null; } TreeNode root = TreeNode. builder( new JsonId(null, ROOT, null, 0) ).build(); try { root.mount(ids.stream().map(JsonExtractUtils::toNode).collect(Collectors.toList())); return root; } catch (Exception e) { throw new IllegalStateException("Parsed json schema occur error: " + e.getMessage(), e); } } private static PlainNode toNode(JsonId id) { return new PlainNode<>(id, id.getParent(), null); } @SuppressWarnings("unchecked") private static void extractData(List> dataset, NodePath parent, Object object, Map, JsonTree> config, LinkedHashSet> extracted) { JsonTree tree = config.get(parent); if (CollectionUtils.isEmpty(tree.getChildren())) { throw new IllegalStateException("Parent \"" + parent + "\" have not children."); } if (!tree.isChecked()) { return; // parent not check } if (object == null) { if (tree.getChildren().size() == 1) { String childName = tree.getChildren().get(0).getName(); // if ARRAY_EMPTY then null, else if single map key then Collections.emptyMap() object = NULL_VALUE_MAPPING.getOrDefault(childName, Collections.emptyMap()); } else { object = Collections.emptyMap(); } } if (object instanceof Map) { // JSONObject Map map = (Map) object; List checkedNodes = new LinkedList<>(); for (String name : map.keySet()) { JsonTree node = config.get(new NodePath<>(parent, name)); if (node != null && node.isChecked()) { checkedNodes.add(node); } } // adjust orders checkedNodes.sort(Comparator.comparing(JsonTree::getOrders)); List> subset = new LinkedList<>(); for (JsonTree node : checkedNodes) { Object value = map.get(node.getName()); if (node.getType() != null) { // or CollectionUtils.isEmpty(node.getChildren()) // leaf node, extract this value data extracted.add(node.getPath()); concat(subset, getValue(value, node.getType())); } else { List> dataset0 = new LinkedList<>(); extractData(dataset0, node.getPath(), value, config, extracted); concat(subset, dataset0); } } append(dataset, subset); } else if ((object instanceof List) || object.getClass().isArray()) { // JSONArray List list = Collects.toList(object); JsonTree node; switch (detectArrayType(list)) { case EMPTY: // 空的数组 // Nothing to do break; case ARRAY: // 二维数组 node = config.get(new NodePath<>(parent, ARRAY_ARRAY)); if (node != null && node.isChecked()) { List> checkedNodes = getCheckedChildren(node, config, extracted); List> subset = new LinkedList<>(); for (List array : ((List>) list)) { List row = new LinkedList<>(); int size = array.size(); for (Pair pair : checkedNodes) { int idx = pair.getLeft(); row.add(idx >= size ? null : getValue(array.get(idx), pair.getRight().getType())); } subset.add(row); } concat(dataset, subset); } break; case OBJECT: // 数组对象 node = config.get(new NodePath<>(parent, ARRAY_OBJECT)); if (node != null && node.isChecked()) { for (Map map : (List>) list) { if (map == null) { map = Collections.emptyMap(); } extractData(dataset, node.getPath(), map, config, extracted); } } break; case BASIC: // 基本类型(一行多列) node = config.get(new NodePath<>(parent, ARRAY_BASIC)); if (node != null && node.isChecked()) { List> checkedNodes = getCheckedChildren(node, config, extracted); int size = list.size(); List row = new LinkedList<>(); for (Pair pair : checkedNodes) { int idx = pair.getLeft(); // 如果之后数据的长度不够就横向重复 //row.add(getValue(list.get(idx >= size ? size - 1 : idx), pair.getRight().getType())); // 如果之后数据的长度不够就为null row.add(idx >= size ? null : getValue(list.get(idx), pair.getRight().getType())); } concat(dataset, Collections.singletonList(row)); } break; default: throw new RuntimeException("Unknown data type: " + Jsons.toJson(list)); // cannot happened } } else { // If others json type then to skip } } /** * Detect the array inner emelent type(the first non null value) * * @param list the array * @return a type */ private static ArrayType detectArrayType(List list) { if (list.isEmpty()) { return ArrayType.EMPTY; } for (Object obj : list) { if (obj == null) { continue; } if (obj instanceof Map) { return ArrayType.OBJECT; } else if (obj instanceof List) { return ArrayType.ARRAY; } else { return ArrayType.BASIC; } } // if all elements is null, then determine is basic array type return ArrayType.BASIC; } private enum ArrayType { EMPTY, OBJECT, ARRAY, BASIC } private static Map findFirstObject(List> list) { for (Map map : list) { if (map != null) { return map; } } throw new RuntimeException("Empty [OBJECT]"); // cannot happened } private static List findFirstArray(List> list) { for (List array : list) { if (array != null) { return array; } } throw new RuntimeException("Empty [ARRAY]"); // cannot happened } private static DataType detectDataType(Object value, DataType defaultType) { DataType dataType = detectDataType(value); return dataType == null ? defaultType : dataType; } // 两种特殊类型:数组(array)、对象(object) // 四种基础类型:字符串(string)、数字(number)、布尔型(boolean)、NULL值 private static DataType detectDataType(Object value) { if (value == null || value instanceof CharSequence) { // cannot detect if value is null, then determine it's a string type return DataType.STRING; } if (value instanceof Boolean) { return DataType.BOOLEAN; } if (value instanceof Integer || value instanceof Long || value instanceof BigInteger) { return DataType.INTEGER; } if (value instanceof BigDecimal || value instanceof Float || value instanceof Double) { return DataType.DECIMAL; } // if not complex type then determine it's a string type return ObjectUtils.isComplexType(value) ? null : DataType.STRING; } private static Object getValue(Object value, DataType dataType) { return dataType == DataType.STRING ? Objects.toString(value, null) : value; } private static void buildArrayColumns(List list, List ids, JsonId parent, AtomicInteger count) { for (Object element : list) { int index = count.getAndIncrement(); // 二维数组和基本数组不再下钻解析,如果为复合类型(二维数组),则直接判决其为STRING DataType dataType = detectDataType(element, DataType.STRING); ids.add(new JsonId(parent, String.format(ARRAY_INDEX, index), dataType, index)); } } private static List> getCheckedChildren( JsonTree node, Map, JsonTree> config, LinkedHashSet> columns) { int startIndex = node.getOrders() + 1; List> checkedNodes = new LinkedList<>(); for (int i = 0, n = node.getChildren().size(); i < n; i++) { String name = String.format(ARRAY_INDEX, startIndex + i); JsonTree child = config.get(new NodePath<>(node.getPath(), name)); if (child != null && child.isChecked()) { checkedNodes.add(Pair.of(i, child)); columns.add(child.getPath()); } } if (checkedNodes.isEmpty()) { throw new IllegalStateException( "Parent is checked but not checked children: " + node.getPath().toString() ); } return checkedNodes; } // ------------------------------------------------------------append table /** * Appends the sub dataset after dataset last row, as new row * * [[1,2], [3,4]] append [["a","b"], ["c","d"]] => [[1,2], [3,4], ["a","b"], ["c","d"]] * * @param dataset the dataset * @param subset the sub dataset */ public static void append(List> dataset, List> subset) { if (subset.isEmpty()) { return; } if (dataset.isEmpty()) { for (List row : subset) { dataset.add(new LinkedList<>(row)); } return; } int maxCol = Math.max(dataset.get(0).size(), subset.get(0).size()); completeCol(dataset, maxCol); completeCol(subset, maxCol); dataset.addAll(subset); } private static void completeCol(List> listArray, int maxCol) { if (listArray.isEmpty() || listArray.get(0).size() >= maxCol) { return; } for (List array : listArray) { Object lastCol = array.get(array.size() - 1); for (int i = maxCol - array.size(); i > 0; i--) { array.add(lastCol); } } } // ------------------------------------------------------------join table /** * Concats the sub dataset after dataset last column, as new column * * [[1,2], [3,4]] concat "a" => [[1,2,"a"], [3,4,"a"]] * * @param dataset the dataset * @param value the new column vlaue */ public static void concat(List> dataset, Object value) { if (dataset.isEmpty()) { List row = new LinkedList<>(); row.add(value); dataset.add(row); } else { for (List array : dataset) { array.add(value); } } } /** * Concats the sub dataset after dataset last column, as new column * * [[1,2], [3,4]] concat [["a","b"], ["c","d"]] => [[1,2,"a","b"], [3,4,"c","d"]] * * @param dataset the dataset * @param subset the sub dataset */ public static void concat(List> dataset, List> subset) { if (subset.isEmpty()) { return; } if (dataset.isEmpty()) { dataset.addAll(subset); return; } int maxRow = Math.max(dataset.size(), subset.size()); completeRow(dataset, maxRow); completeRow(subset, maxRow); for (int i = 0; i < maxRow; i++) { dataset.get(i).addAll(subset.get(i)); } } private static void completeRow(List> listArray, int maxRow) { if (listArray.size() >= maxRow) { return; } List lastRow = listArray.get(listArray.size() - 1); for (int i = maxRow - listArray.size(); i > 0; i--) { listArray.add(new LinkedList<>(lastRow)); // repeat last row } } } ================================================ FILE: src/main/java/cn/ponfee/commons/schema/json/JsonId.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.schema.json; import cn.ponfee.commons.schema.DataType; import cn.ponfee.commons.tree.NodeId; import javax.annotation.Nonnull; import java.util.Objects; /** * The element id of json data structure * * @author Ponfee */ public class JsonId extends NodeId { private static final long serialVersionUID = -6344204521700761391L; private final String name; // 节点名称 private final DataType type; // 数据类型 private final int orders; // 次序 public JsonId(JsonId parent, @Nonnull String name, DataType type, int orders) { super(parent); this.name = Objects.requireNonNull(name); this.type = type; this.orders = orders; } @Override protected boolean equals(JsonId another) { return this.name.equals(another.name); } @Override protected int compare(JsonId another) { int a = this.orders - another.orders; return a != 0 ? a : this.name.compareTo(another.name); } @Override protected int hash() { return this.name == null ? 0 : this.name.hashCode(); } @Override public JsonId clone() { return new JsonId( this.parent == null ? null : this.parent.clone(), this.name, this.type, this.orders ); } // -----------------------------------------------------------------setter public String getName() { return name; } public DataType getType() { return type; } public int getOrders() { return orders; } } ================================================ FILE: src/main/java/cn/ponfee/commons/schema/json/JsonTree.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.schema.json; import cn.ponfee.commons.collect.Collects; import cn.ponfee.commons.json.Jsons; import cn.ponfee.commons.model.Null; import cn.ponfee.commons.model.ToJsonString; import cn.ponfee.commons.schema.DataType; import cn.ponfee.commons.tree.NodePath; import cn.ponfee.commons.tree.TreeNode; import cn.ponfee.commons.tree.TreeTrait; import com.alibaba.fastjson.annotation.JSONField; import org.apache.commons.collections4.CollectionUtils; import java.io.Serializable; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * Json data tree structure * * @author Ponfee */ public class JsonTree extends ToJsonString implements Serializable, Comparable, TreeTrait { private static final long serialVersionUID = 2185766536906561848L; // 解决NodePath泛型参数为具体类型时,FastJson反序列化的报错问题 @JSONField(deserializeUsing = NodePath.FastjsonDeserializer.class) private NodePath path; // 路径 private String name; // 节点 private int orders; // 次序 private boolean checked; // 是否选中 private DataType type; // 数据类型 private List children; // 子节点列表 @Override public int compareTo(JsonTree o) { return this.path.compareTo(o.path); } @Override public int hashCode() { return this.path.hashCode(); } @Override public boolean equals(Object obj) { if (!(obj instanceof JsonTree)) { return false; } JsonTree other = (JsonTree) obj; if (!this.path.equals(other.path)) { return false; } if ( CollectionUtils.isEmpty(this.children) && CollectionUtils.isEmpty(other.children) ) { return true; } if ( CollectionUtils.isEmpty(this.children) || CollectionUtils.isEmpty(other.children) || this.children.size() != other.children.size() ) { return false; } try { this.sortByName(); other.sortByName(); for (int i = 0, n = this.children.size(); i < n; i++) { if (!this.children.get(i).equals(other.children.get(i))) { return false; } } return true; } finally { this.sortByOrders(); other.sortByOrders(); } } @Override public String toString() { return Jsons.toJson(this); //return JSON.toJSONString(this, FastjsonPropertyFilter.exclude("children")); } @Override public void setChildren(List children) { if (CollectionUtils.isNotEmpty(children)) { List duplicated = Collects.duplicate(children, JsonTree::getName); if (CollectionUtils.isNotEmpty(duplicated)) { throw new IllegalStateException("Duplicated child name " + duplicated); } } this.children = children; } // --------------------------------------------------------------------------sort public void sortByOrders() { this.sortChildren(Comparator.comparing(JsonTree::getOrders)); } public void sortByName() { this.sortChildren(Comparator.comparing(JsonTree::getName)); } public void sortChildren(Comparator comparator) { if (CollectionUtils.isNotEmpty(this.children)) { this.children.sort(comparator); for (JsonTree node : this.children) { node.sortChildren(comparator); } } } public Map, JsonTree> toFlatMap() { Map, JsonTree> map = new HashMap<>(); this.toFlatMap(map); return map; } // --------------------------------------------------------------------------static methods public static JsonTree convert(TreeNode tree) { JsonId id = tree.getNid(); JsonTree jt = new JsonTree(); jt.setName(id.getName()); jt.setOrders(id.getOrders()); jt.setChecked(false); jt.setType(id.getType()); jt.setPath(new NodePath<>(tree.getPath().stream().map(JsonId::getName).collect(Collectors.toList()))); return jt; } public static boolean hasChoose(JsonTree root) { if (root == null) { return false; } checkChoose(root); return root.checked; } // --------------------------------------------------------------------------getter/setter public NodePath getPath() { return path; } public void setPath(NodePath path) { this.path = path; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getOrders() { return orders; } public void setOrders(int orders) { this.orders = orders; } public boolean isChecked() { return checked; } public void setChecked(boolean checked) { this.checked = checked; } @Override public List getChildren() { return children; } public DataType getType() { return type; } public void setType(DataType type) { this.type = type; } // --------------------------------------------------------------------------private methods private static boolean checkChoose(JsonTree node) { if (CollectionUtils.isEmpty(node.children)) { return node.checked; } boolean hasLeafChildChoose = false; for (JsonTree child : node.children) { if (child.checked && !node.checked) { throw new IllegalStateException( "Child is checked but parent is unchecked: " + child.path.toString() ); } if (CollectionUtils.isEmpty(child.children)) { // leaf node if (child.checked && JsonExtractUtils.ARRAY_EMPTY.equals(child.name)) { throw new IllegalStateException( "Empty array cannot be checked: " + child.path.toString() ); } hasLeafChildChoose |= child.checked; } else { hasLeafChildChoose |= checkChoose(child); } } if (node.checked && !hasLeafChildChoose) { throw new IllegalStateException( "Parent is checked but not checked children: " + node.path.toString() ); } return hasLeafChildChoose; } private void toFlatMap(Map, JsonTree> map) { map.put(this.getPath(), this); if (CollectionUtils.isNotEmpty(this.children)) { for (JsonTree node : this.children) { node.toFlatMap(map); } } } } ================================================ FILE: src/main/java/cn/ponfee/commons/serial/ByteArraySerializer.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.serial; import cn.ponfee.commons.io.GzipProcessor; import cn.ponfee.commons.reflect.ClassUtils; /** * 字段串序例化 * * @author Ponfee */ public class ByteArraySerializer extends Serializer { @Override protected byte[] serialize0(Object obj, boolean compress) { if (!(obj instanceof byte[])) { throw new SerializationException( "Object must be byte[].class type, but it's " + ClassUtils.getClassName(obj.getClass()) + " type." ); } byte[] bytes = (byte[]) obj; return compress ? GzipProcessor.compress(bytes) : bytes; } @Override protected T deserialize0(byte[] bytes, Class clazz, boolean compress) { if (clazz != byte[].class) { throw new SerializationException( "clazz must be byte[].class, but it's " + ClassUtils.getClassName(clazz) + ".class" ); } return (T) (compress ? GzipProcessor.decompress(bytes) : bytes); } // ------------------------------------------------------------------- public byte[] serialize(byte[] bytes, boolean compress) { if (bytes == null || bytes.length == 0) { return bytes; } return compress ? GzipProcessor.compress(bytes) : bytes; } public byte[] deserialize(byte[] bytes, boolean compress) { if (bytes == null || bytes.length == 0) { return bytes; } return compress ? GzipProcessor.decompress(bytes) : bytes; } } ================================================ FILE: src/main/java/cn/ponfee/commons/serial/ByteArrayTraitSerializer.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.serial; import cn.ponfee.commons.collect.ByteArrayTrait; import cn.ponfee.commons.io.GzipProcessor; import cn.ponfee.commons.reflect.ClassUtils; /** * * Byte array trait Serializer * * @author Ponfee */ public class ByteArrayTraitSerializer extends Serializer { @Override protected byte[] serialize0(Object obj, boolean compress) { if (!(obj instanceof ByteArrayTrait)) { throw new SerializationException( "object must be ByteArrayTrait type, but it's " + ClassUtils.getClassName(obj.getClass()) + " type" ); } return serialize((ByteArrayTrait) obj, compress); } @SuppressWarnings("unchecked") @Override protected T deserialize0(byte[] bytes, Class clazz, boolean compress) { if (!ByteArrayTrait.class.isAssignableFrom(clazz)) { throw new SerializationException( "clazz must be ByteArrayTrait.class, but it's " + ClassUtils.getClassName(clazz) + ".class" ); } if (compress) { bytes = GzipProcessor.decompress(bytes); } return (T) ofBytes(bytes, (Class) clazz); } public static T ofBytes(byte[] bytes, Class type) { //return clazz.getDeclaredMethod("fromByteArray", byte[].class).invoke(null, bytes); return ClassUtils.newInstance(type, new Class[]{byte[].class}, new Object[]{bytes}); } // ---------------------------------------------------------------------- /** * serialize the byte array of ByteArrayTrait * * @param trait * @param compress * @return */ public byte[] serialize(ByteArrayTrait trait, boolean compress) { if (trait == null) { return null; } byte[] bytes = trait.toByteArray(); return compress ? GzipProcessor.compress(bytes) : bytes; } } ================================================ FILE: src/main/java/cn/ponfee/commons/serial/FstSerializer.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.serial; import cn.ponfee.commons.io.GzipProcessor; import org.apache.commons.lang3.ClassUtils; import org.nustaq.serialization.FSTConfiguration; /** * Fst Serializer * * @author Ponfee */ public class FstSerializer extends Serializer { // createDefaultConfiguration private static final ThreadLocal FST_CFG = ThreadLocal.withInitial(FSTConfiguration::createStructConfiguration); @Override protected byte[] serialize0(Object obj, boolean compress) { byte[] bytes = FST_CFG.get().asByteArray(obj); return compress ? GzipProcessor.compress(bytes) : bytes; } @SuppressWarnings("unchecked") @Override protected T deserialize0(byte[] bytes, Class clazz, boolean compress) { if (compress) { bytes = GzipProcessor.decompress(bytes); } T obj = (T) FST_CFG.get().asObject(bytes); if (obj != null && !ClassUtils.isAssignable(obj.getClass(), clazz)) { throw new ClassCastException( ClassUtils.getName(obj.getClass()) + " can't be cast to " + ClassUtils.getName(clazz) ); } return obj; } } ================================================ FILE: src/main/java/cn/ponfee/commons/serial/HessianSerializer.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.serial; import cn.ponfee.commons.io.Closeables; import cn.ponfee.commons.io.ExtendedGZIPOutputStream; import com.caucho.hessian.io.HessianSerializerInput; import com.caucho.hessian.io.HessianSerializerOutput; import org.apache.commons.lang3.ClassUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; /** * hessian序例化 * * @author Ponfee */ public class HessianSerializer extends Serializer { private static final Logger LOG = LoggerFactory.getLogger(HessianSerializer.class); @Override protected byte[] serialize0(Object obj, boolean compress) { GZIPOutputStream gzout = null; HessianSerializerOutput hessian = null; try { ByteArrayOutputStream baos = new ByteArrayOutputStream(BYTE_SIZE); if (compress) { gzout = new ExtendedGZIPOutputStream(baos); hessian = new HessianSerializerOutput(gzout); } else { hessian = new HessianSerializerOutput(baos); } hessian.writeObject(obj); hessian.close(); hessian = null; if (gzout != null) { gzout.close(); gzout = null; } return baos.toByteArray(); } catch (IOException e) { throw new SerializationException(e); } finally { if (hessian != null) { try { hessian.close(); } catch (IOException e) { LOG.error("close hessian exception", e); } } Closeables.log(gzout, "close GZIPOutputStream exception"); } } @SuppressWarnings("unchecked") @Override protected T deserialize0(byte[] bytes, Class clazz, boolean compress) { GZIPInputStream gzin = null; HessianSerializerInput hessian = null; try { ByteArrayInputStream bais = new ByteArrayInputStream(bytes); if (compress) { gzin = new GZIPInputStream(bais); hessian = new HessianSerializerInput(gzin); } else { hessian = new HessianSerializerInput(bais); } T t = (T) hessian.readObject(); if (t != null && !ClassUtils.isAssignable(t.getClass(), clazz)) { throw new ClassCastException( ClassUtils.getName(t.getClass()) + " can't be cast to " + ClassUtils.getName(clazz) ); } return t; } catch (IOException e) { throw new SerializationException(e); } finally { if (hessian != null) { try { hessian.close(); } catch (Exception e) { LOG.error("close hessian exception", e); } } Closeables.log(gzin, "close GZIPInputStream exception"); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/serial/JdkSerializer.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.serial; import cn.ponfee.commons.io.Closeables; import cn.ponfee.commons.io.ExtendedGZIPOutputStream; import org.apache.commons.lang3.ClassUtils; import java.io.*; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; /** *
     * JDK序例化
     *
     * 1、Default serialization process:仅实现了Serializable接口,则JDK会使用默认的序列化进程序列化和反序列化对象
     * Note:针对所有non-transient和non-static成员变量
     * {@code java.io.ObjectOutputStream#defaultWriteObject() }
     * {@code java.io.ObjectInputStream#defaultReadObject() }
     *
     * 2、Customizing the serialization process:不仅实现了Serializable接口还定义了两个方法,则JDK会使用这两个方法定制化的进行序列化和反序列化对象
     * Note: must private access modifier, Subclasses will be inherit this method
     * {@code private void readObject(ObjectInputStream input) }
     * {@code private void writeObject(ObjectOutputSteam out) }
     *
     * 3、java.io.Externalizable:该接口是继承于Serializable,也是自定义实现序列化和反序列化方式的一种方式
     * {@code void writeExternal(ObjectOutput out) }
     * {@code void readExternal(ObjectInput in) }
     * 
    * * @author Ponfee */ public class JdkSerializer extends Serializer { @Override protected byte[] serialize0(Object obj, boolean compress) { GZIPOutputStream gzout = null; ObjectOutputStream oos = null; try { ByteArrayOutputStream baos = new ByteArrayOutputStream(BYTE_SIZE); if (compress) { gzout = new ExtendedGZIPOutputStream(baos); oos = new ObjectOutputStream(gzout); } else { oos = new ObjectOutputStream(baos); } oos.writeObject(obj); oos.close(); oos = null; if (gzout != null) { gzout.close(); gzout = null; } return baos.toByteArray(); } catch (IOException e) { throw new SerializationException(e); } finally { // 先打开的后关闭,后打开的先关闭 // 看依赖关系,如果流a依赖流b,应该先关闭流a,再关闭流b // 处理流a依赖节点流b,应该先关闭处理流a,再关闭节点流b Closeables.log(oos, "close ObjectOutputStream exception"); Closeables.log(gzout, "close GZIPOutputStream exception"); } } @SuppressWarnings("unchecked") @Override protected T deserialize0(byte[] bytes, Class clazz, boolean compress) { GZIPInputStream gzin = null; ObjectInputStream ois = null; try { ByteArrayInputStream bais = new ByteArrayInputStream(bytes); if (compress) { gzin = new GZIPInputStream(bais); ois = new ObjectInputStream(gzin); } else { ois = new ObjectInputStream(bais); } T t = (T) ois.readObject(); if (t != null && !ClassUtils.isAssignable(t.getClass(), clazz)) { throw new ClassCastException( ClassUtils.getName(t.getClass()) + " can't be cast to " + ClassUtils.getName(clazz) ); } return t; } catch (IOException | ClassNotFoundException e) { throw new SerializationException(e); } finally { Closeables.log(ois, "close ObjectInputStream exception"); Closeables.log(gzin, "close GZIPInputStream exception"); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/serial/JsonSerializer.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.serial; import cn.ponfee.commons.io.Closeables; import cn.ponfee.commons.io.ExtendedGZIPOutputStream; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.io.IOUtils; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; /** * json序例化 * * @author Ponfee */ public class JsonSerializer extends Serializer { /** json object mapper */ private static final ObjectMapper MAPPER = new ObjectMapper(); static { MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); MAPPER.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); } @Override protected byte[] serialize0(Object obj, boolean compress) { GZIPOutputStream gzout = null; try { byte[] data = MAPPER.writeValueAsBytes(obj); if (compress) { ByteArrayOutputStream baos = new ByteArrayOutputStream(BYTE_SIZE); gzout = new ExtendedGZIPOutputStream(baos); gzout.write(data, 0, data.length); gzout.close(); gzout = null; data = baos.toByteArray(); } return data; } catch (IOException e) { throw new SerializationException(e); } finally { Closeables.log(gzout, "close GZIPOutputStream exception"); } } @Override protected T deserialize0(byte[] bytes, Class clazz, boolean compress) { GZIPInputStream gzin = null; try { if (compress) { gzin = new GZIPInputStream(new ByteArrayInputStream(bytes)); bytes = IOUtils.toByteArray(gzin); } return MAPPER.readValue(bytes, clazz); } catch (IOException e) { throw new SerializationException(e); } finally { Closeables.log(gzin, "close GZIPInputStream exception"); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/serial/KryoSerializer.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.serial; import cn.ponfee.commons.io.Closeables; import cn.ponfee.commons.io.ExtendedGZIPOutputStream; import cn.ponfee.commons.io.Files; import com.esotericsoftware.kryo.Kryo; import com.esotericsoftware.kryo.io.ByteBufferInput; import com.esotericsoftware.kryo.io.ByteBufferOutput; import com.esotericsoftware.kryo.io.Input; import com.esotericsoftware.kryo.io.Output; import com.esotericsoftware.kryo.util.Pool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; /** * kryo序例化 * * the bean class must include default no-arg constructor * * @author Ponfee */ public class KryoSerializer extends Serializer { private static final Logger LOG = LoggerFactory.getLogger(KryoSerializer.class); public static final KryoSerializer INSTANCE = new KryoSerializer(); //private static final ThreadLocal KRYO_HOLDER = ThreadLocal.withInitial(Kryo::new); // Pool constructor arguments: thread safe, soft references, maximum capacity private static final Pool KRYO_POOL = new Pool(true, false, 32) { @Override protected Kryo create () { Kryo kryo = new Kryo(); // Configure the Kryo instance. kryo.setRegistrationRequired(false); //kryo.register(A.class, B.class); //kryo.register(B.class, new com.esotericsoftware.kryo.serializers.JavaSerializer()); //kryo.addDefaultSerializer(A.class, ASerializer.class); return kryo; } }; @Override protected byte[] serialize0(Object obj, boolean compress) { GZIPOutputStream gzout = null; Output output = null; Kryo kryo = null; try { ByteArrayOutputStream baos = new ByteArrayOutputStream(BYTE_SIZE); if (compress) { gzout = new ExtendedGZIPOutputStream(baos); output = new ByteBufferOutput(gzout, Files.BUFF_SIZE); } else { output = new ByteBufferOutput(baos, Files.BUFF_SIZE); } (kryo = obtain()).writeObject(output, obj); output.close(); output = null; if (gzout != null) { gzout.close(); gzout = null; } return baos.toByteArray(); } catch (IOException e) { throw new SerializationException(e); } finally { free(kryo); Closeables.log(output, "close Output exception"); Closeables.log(gzout, "close GZIPOutputStream exception"); } } @Override protected T deserialize0(byte[] bytes, Class clazz, boolean compress) { GZIPInputStream gzin = null; Input input = null; Kryo kryo = null; try { if (compress) { gzin = new GZIPInputStream(new ByteArrayInputStream(bytes)); input = new ByteBufferInput(gzin); } else { input = new ByteBufferInput(bytes); } return (kryo = obtain()).readObject(input, clazz); } catch (IOException e) { throw new SerializationException(e); } finally { free(kryo); Closeables.log(input, "close Input exception"); Closeables.log(gzin, "close GZIPInputStream exception"); } } private Kryo obtain() { return KRYO_POOL.obtain(); } private void free(Kryo kryo) { if (kryo == null) { return; } try { KRYO_POOL.free(kryo); } catch (Throwable t) { LOG.error("release kryo occur error", t); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/serial/NullSerializer.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.serial; /** * The {@code NullSerializer} class is representing unable serializer, * it will be throws NullPointerException * * @author Ponfee */ public final class NullSerializer extends Serializer { public static final NullSerializer SINGLETON = new NullSerializer(); private NullSerializer() {} @Override protected byte[] serialize0(T obj, boolean compress) { throw new NullPointerException(); } @Override protected T deserialize0(byte[] bytes, Class clazz, boolean compress) { throw new NullPointerException(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/serial/ProtostuffSerializer.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.serial; import cn.ponfee.commons.io.GzipProcessor; import cn.ponfee.commons.util.ObjectUtils; import cn.ponfee.commons.util.SynchronizedCaches; import io.protostuff.LinkedBuffer; import io.protostuff.ProtostuffIOUtil; import io.protostuff.Schema; import io.protostuff.runtime.RuntimeSchema; import java.util.HashMap; import java.util.Map; /** * Protostuff Serializer * * @author Ponfee */ public class ProtostuffSerializer extends Serializer { private static final Map, Schema> SCHEMA_CACHE = new HashMap<>(); @SuppressWarnings("unchecked") @Override protected byte[] serialize0(T obj, boolean compress) { LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE); try { byte[] bytes = ProtostuffIOUtil.toByteArray( obj, getSchema((Class) obj.getClass()), buffer ); return compress ? GzipProcessor.compress(bytes) : bytes; } finally { buffer.clear(); } } @Override protected T deserialize0(byte[] bytes, Class type, boolean compress) { if (compress) { bytes = GzipProcessor.decompress(bytes); } T message = ObjectUtils.newInstance(type); ProtostuffIOUtil.mergeFrom(bytes, message, getSchema(type)); return message; } // ------------------------------------------------------------------------private methods @SuppressWarnings("unchecked") private static Schema getSchema(Class type) { return (Schema) SynchronizedCaches.get(type, SCHEMA_CACHE, RuntimeSchema::createFrom); } } ================================================ FILE: src/main/java/cn/ponfee/commons/serial/SerializationException.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.serial; /** * 序例化异常类 * * @author Ponfee */ public class SerializationException extends RuntimeException { private static final long serialVersionUID = -5285807406910063551L; public SerializationException(String msg, Throwable cause) { super(msg, cause); } public SerializationException(String msg) { super(msg); } public SerializationException(Throwable cause) { super(cause); } } ================================================ FILE: src/main/java/cn/ponfee/commons/serial/Serializer.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.serial; /** * 序例化抽象类 * * Method template pattern * * @author Ponfee */ public abstract class Serializer { static final int BYTE_SIZE = 512; /** * 对象序例化为流数据 * * @param obj 对象 * @param compress 是否要压缩:true是;false否; * @return 序例化后的流数据 */ protected abstract byte[] serialize0(T obj, boolean compress); /** * 流数据反序例化为对象 * * @param bytes 流数据 * @param compress 是否被压缩:true是;false否; * @return 反序例化后的对象 */ protected abstract T deserialize0(byte[] bytes, Class clazz, boolean compress); // ---------------------------------------------------------------------------------- public final byte[] serialize(Object obj, boolean compress) { if (obj == null) { return null; } return Serializer.this.serialize0(obj, compress); } public final byte[] serialize(Object obj) { return serialize(obj, false); } public final T deserialize(byte[] bytes, Class clazz, boolean compress) { if (bytes == null) { return null; } return Serializer.this.deserialize0(bytes, clazz, compress); } public final T deserialize(byte[] bytes, Class clazz) { return this.deserialize(bytes, clazz, false); } } ================================================ FILE: src/main/java/cn/ponfee/commons/serial/StringSerializer.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.serial; import cn.ponfee.commons.io.GzipProcessor; import cn.ponfee.commons.reflect.ClassUtils; import java.nio.charset.Charset; /** * 字段串序例化 * * @author Ponfee */ public class StringSerializer extends Serializer { private final Charset charset; public StringSerializer() { this(Charset.defaultCharset()); } public StringSerializer(String charset) { this(Charset.forName(charset)); } public StringSerializer(Charset charset) { if (charset == null) { throw new IllegalArgumentException("charset cannot be null"); } this.charset = charset; } @Override protected byte[] serialize0(Object obj, boolean compress) { if (!(obj instanceof String)) { throw new SerializationException( "object must be java.lang.String type, but it's " + ClassUtils.getClassName(obj.getClass()) + " type" ); } return serialize((String) obj, compress); } @Override @SuppressWarnings("unchecked") protected T deserialize0(byte[] bytes, Class clazz, boolean compress) { if (clazz != String.class) { throw new SerializationException( "clazz must be java.lang.String.class, but it's " + ClassUtils.getClassName(clazz) + ".class" ); } return (T) deserialize(bytes, compress); } // ---------------------------------------------------------------------- /** * serialize the byte array of string * * @param str * @param compress * @return */ public byte[] serialize(String str, boolean compress) { if (str == null) { return null; } byte[] bytes = str.getBytes(charset); return compress ? GzipProcessor.compress(bytes) : bytes; } /** * deserialize the byte array to string * * @param bytes * @param compress * @return */ public String deserialize(byte[] bytes, boolean compress) { if (bytes == null) { return null; } if (compress) { bytes = GzipProcessor.decompress(bytes); } return new String(bytes, charset); } } ================================================ FILE: src/main/java/cn/ponfee/commons/serial/ToStringSerializer.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.serial; import cn.ponfee.commons.io.GzipProcessor; import cn.ponfee.commons.reflect.ClassUtils; import cn.ponfee.commons.util.ObjectUtils; import javax.annotation.Nonnull; import java.lang.reflect.Constructor; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Objects; /** * Object toString Serializer * * @author Ponfee */ public class ToStringSerializer extends Serializer { private final Charset charset; public ToStringSerializer() { this(StandardCharsets.UTF_8); } public ToStringSerializer(@Nonnull Charset charset) { this.charset = Objects.requireNonNull(charset); } @Override protected byte[] serialize0(Object obj, boolean compress) { byte[] bytes = obj.toString().getBytes(charset); return compress ? GzipProcessor.compress(bytes) : bytes; } @Override protected T deserialize0(byte[] bytes, Class type, boolean compress) { if (compress) { bytes = GzipProcessor.decompress(bytes); } Constructor constructor = ClassUtils.getConstructor(type, byte[].class); if (constructor != null) { try { return constructor.newInstance(bytes); } catch (Exception e) { throw new IllegalStateException(e); } } else { return ObjectUtils.cast(new String(bytes, charset), type); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/serial/WrappedSerializer.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.serial; import cn.ponfee.commons.base.Symbol; import cn.ponfee.commons.collect.ByteArrayTrait; import cn.ponfee.commons.collect.ByteArrayWrapper; import cn.ponfee.commons.io.GzipProcessor; import cn.ponfee.commons.math.Numbers; import cn.ponfee.commons.reflect.ClassUtils; import cn.ponfee.commons.util.Bytes; import com.google.common.collect.ImmutableMap.Builder; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.EnumUtils; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.util.Date; import java.util.HashMap; import java.util.Map; import static java.nio.charset.StandardCharsets.UTF_8; /** * Wrapped other Serializer * * @author Ponfee */ public class WrappedSerializer extends Serializer { public static final WrappedSerializer WRAPPED_KRYO_SERIALIZER = new WrappedSerializer(KryoSerializer.INSTANCE); public static final WrappedSerializer WRAPPED_TOSTRING_SERIALIZER = new WrappedSerializer(new ToStringSerializer()); public static final byte BOOL_TRUE_BYTE = (byte) 0xFF; public static final byte BOOL_FALSE_BYTE = 0x00; private static final Map, Object> PRIMITIVES = new Builder, Object>() .put(boolean.class, Boolean.FALSE) .put(byte.class, Numbers.ZERO_BYTE) .put(short.class, (short) 0) .put(char.class, Symbol.Char.ZERO) .put(int.class, Numbers.ZERO_INT) .put(long.class, 0L) .put(float.class, 0.0F) .put(double.class, 0.0D) .build(); private final Serializer wrapper; public WrappedSerializer(Serializer wrapped) { this.wrapper = wrapped; } @Override protected byte[] serialize0(Object obj, boolean compress) { byte[] bytes = serialize0(obj); return compress ? GzipProcessor.compress(bytes) : bytes; } @Override protected T deserialize0(byte[] bytes, Class type, boolean compress) { if (compress) { bytes = GzipProcessor.decompress(bytes); } return deserialize0(bytes, type); } // ---------------------------------------------------------------------------primitive&wrapper type public byte[] serialize(boolean value) { return new byte[]{value ? BOOL_TRUE_BYTE : BOOL_FALSE_BYTE}; } public byte[] serialize(byte value) { return new byte[]{value}; } public byte[] serialize(short value) { return Bytes.toBytes(value); } public byte[] serialize(char value) { return Bytes.toBytes(value); } public byte[] serialize(int value) { return Bytes.toBytes(value); } public byte[] serialize(long value) { return Bytes.toBytes(value); } public byte[] serialize(float value) { return Bytes.toBytes(value); } public byte[] serialize(double value) { return Bytes.toBytes(value); } public byte[] serialize(Boolean value) { return value == null ? null : serialize((boolean) value); } public byte[] serialize(Byte value) { return value == null ? null : serialize((byte) value); } public byte[] serialize(Short value) { return value == null ? null : serialize((short) value); } public byte[] serialize(Character value) { return value == null ? null : serialize((char) value); } public byte[] serialize(Integer value) { return value == null ? null : serialize((int) value); } public byte[] serialize(Long value) { return value == null ? null : serialize((long) value); } public byte[] serialize(Float value) { return value == null ? null : serialize((float) value); } public byte[] serialize(Double value) { return value == null ? null : serialize((double) value); } // ---------------------------------------------------------------------------other type public byte[] serialize(byte[] value) { return value; } public byte[] serialize(Byte[] value) { return value == null ? null : ArrayUtils.toPrimitive(value); } public byte[] serialize(Date value) { return value == null ? null : Bytes.toBytes(value.getTime()); } public byte[] serialize(ByteArrayWrapper value) { return value == null ? null : value.getArray(); } public byte[] serialize(ByteArrayTrait value) { return value == null ? null : value.toByteArray(); } public byte[] serialize(CharSequence value) { return value == null ? null : Serializers.STRING.toBytes(value.toString()); } public byte[] serialize(InputStream value) { if (value == null) { return null; } try (InputStream input = value) { return IOUtils.toByteArray(input); } catch (IOException e) { throw new RuntimeException(e); } } public byte[] serialize(ByteBuffer value) { return value == null ? null : value.array(); } public byte[] serialize(Enum value) { // Bytes.toBytes(value.ordinal()) return value == null ? null : Serializers.STRING.toBytes(value.name()); } // ---------------------------------------------------------------------------private methods /** * Returns the serialize byte array data for value * * @param value the value * @return a byte array */ private byte[] serialize0(Object value) { if (value == null) { return null; } Serializers serializer = Serializers.of(value.getClass()); if (serializer != null) { return serializer.toBytes(value); } if (value instanceof CharSequence) { return Serializers.STRING.toBytes(value.toString()); } else if (value instanceof InputStream) { return serialize((InputStream) value); } else if (value instanceof ByteArrayTrait) { return ((ByteArrayTrait) value).toByteArray(); } else if (value instanceof ByteBuffer) { return ((ByteBuffer) value).array(); } else if (value instanceof Enum) { return serialize((Enum) value); } else { return wrapper.serialize(value); } } /** * Returns a object or primitive value from * deserialize the byte array * * @param value the byte array * @param type the target type * @return a spec type object */ @SuppressWarnings({"unchecked", "rawtypes"}) private T deserialize0(byte[] value, Class type) { if (ArrayUtils.isEmpty(value) && type.isPrimitive()) { return (T) PRIMITIVES.get(type); // primitive type use default value } if (value == null) { return null; } Serializers serializer = Serializers.of(type); if (serializer != null) { return serializer.fromBytes(value); } if (CharSequence.class.isAssignableFrom(type)) { return ClassUtils.newInstance(type, new Class[]{String.class}, new Object[]{Serializers.STRING.fromBytes(value)}); } else if (InputStream.class.isAssignableFrom(type)) { return (T) new ByteArrayInputStream(value); } else if (ByteArrayTrait.class.isAssignableFrom(type)) { return (T) ByteArrayTraitSerializer.ofBytes(value, (Class) type); } else if (ByteBuffer.class.isAssignableFrom(type)) { return (T) ByteBuffer.wrap(value); } else if (type.isEnum()) { //return type.getEnumConstants()[Bytes.toInt(value)]; return (T) EnumUtils.getEnumIgnoreCase((Class) type, Serializers.STRING.fromBytes(value)); } else { return wrapper.deserialize(value, type); } } // ---------------------------------------------------------------------------private class @SuppressWarnings("unchecked") private enum Serializers { BOOLEAN(boolean.class, Boolean.class) { @Override byte[] to(Object value) { return new byte[]{(boolean) value ? BOOL_TRUE_BYTE : BOOL_FALSE_BYTE}; } @Override Boolean from(byte[] value) { return value[0] != BOOL_FALSE_BYTE; } }, BYTE(byte.class, Byte.class) { @Override byte[] to(Object value) { return new byte[]{(byte) value}; } @Override Byte from(byte[] value) { return value[0]; } }, SHORT(short.class, Short.class) { @Override byte[] to(Object value) { return Bytes.toBytes((short) value); } @Override Short from(byte[] value) { return Bytes.toShort(value); } }, CHAR(char.class, Character.class) { @Override byte[] to(Object value) { return Bytes.toBytes((char) value); } @Override Character from(byte[] value) { return Bytes.toChar(value); } }, INT(int.class, Integer.class) { @Override byte[] to(Object value) { return Bytes.toBytes((int) value); } @Override Integer from(byte[] value) { return Bytes.toInt(value); } }, LONG(long.class, Long.class) { @Override byte[] to(Object value) { return Bytes.toBytes((long) value); } @Override Long from(byte[] value) { return Bytes.toLong(value); } }, FLOAT(float.class, Float.class) { @Override byte[] to(Object value) { return Bytes.toBytes((float) value); } @Override Float from(byte[] value) { return Bytes.toFloat(value); } }, DOUBLE(double.class, Double.class) { @Override byte[] to(Object value) { return Bytes.toBytes((double) value); } @Override Double from(byte[] value) { return Bytes.toDouble(value); } }, PRIMITIVE_BYTES(byte[].class) { @Override byte[] to(Object value) { return (byte[]) value; } @Override byte[] from(byte[] value) { return value; } }, WRAP_BYTES(Byte[].class) { @Override byte[] to(Object value) { return ArrayUtils.toPrimitive((Byte[]) value); } @Override Byte[] from(byte[] value) { return ArrayUtils.toObject(value); } }, STRING(String.class) { @Override byte[] to(Object value) { return ((String) value).getBytes(UTF_8); } @Override String from(byte[] value) { return new String(value, UTF_8); } }, DATE(Date.class) { @Override byte[] to(Object value) { return Bytes.toBytes(((Date) value).getTime()); } @Override Date from(byte[] value) { return new Date(Bytes.toLong(value)); } }, BYTE_ARRAY_WRAP(ByteArrayWrapper.class) { @Override byte[] to(Object value) { return ((ByteArrayWrapper) value).getArray(); } @Override ByteArrayWrapper from(byte[] value) { return ByteArrayWrapper.of(value); } }; Serializers(Class... types) { for (Class type : types) { Hide.MAPPING.put(type, this); } } final byte[] toBytes(Object value) { return value == null ? null : to(value); } final T fromBytes(byte[] value) { return value == null ? null : from(value); } abstract byte[] to(Object value); abstract T from(byte[] value); static Serializers of(Class targetType) { return Hide.MAPPING.get(targetType); } } private static class Hide { private static final Map, Serializers> MAPPING = new HashMap<>(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/spring/BaseController.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.spring; import cn.ponfee.commons.date.JavaUtilDateFormat; import cn.ponfee.commons.date.LocalDateTimeFormat; import cn.ponfee.commons.model.TypedKeyValue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.beans.PropertyEditorSupport; import java.text.ParseException; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.util.Date; /** * Spring web base controller * * @author Ponfee */ public abstract class BaseController implements TypedKeyValue { protected final Logger log = LoggerFactory.getLogger(getClass()); @InitBinder public void initBinder(WebDataBinder binder) { binder.registerCustomEditor(Date.class, new PropertyEditorSupport() { @Override public void setAsText(String text) { try { super.setValue(JavaUtilDateFormat.DEFAULT.parse(text)); } catch (ParseException e) { throw new IllegalArgumentException("Invalid date format: " + text); } } }); binder.registerCustomEditor(LocalDateTime.class, new PropertyEditorSupport() { @Override public void setAsText(String text) { super.setValue(LocalDateTimeFormat.DEFAULT.parse(text)); } }); binder.registerCustomEditor(LocalDate.class, new PropertyEditorSupport() { @Override public void setAsText(String text) { LocalDateTime dateTime = LocalDateTimeFormat.DEFAULT.parse(text); super.setValue(dateTime == null ? null : dateTime.toLocalDate()); } }); binder.registerCustomEditor(LocalTime.class, new PropertyEditorSupport() { private final DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss"); @Override public void setAsText(String text) { super.setValue(LocalTime.parse(text, timeFormatter)); } }); } public static ServletRequestAttributes getRequestAttributes() { return (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); } public static HttpServletRequest getRequest() { return getRequestAttributes().getRequest(); } public static HttpServletResponse getResponse() { return getRequestAttributes().getResponse(); } public static HttpSession getSession() { return getRequest().getSession(); } @Override public String getValue(String key) { return getRequest().getParameter(key); } } ================================================ FILE: src/main/java/cn/ponfee/commons/spring/JdbcTemplateWrapper.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.spring; import org.springframework.jdbc.core.ConnectionCallback; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.util.Assert; import javax.annotation.Nullable; import java.util.List; import java.util.stream.Collectors; /** * Wrapped jdbc template. * * @author Ponfee */ public class JdbcTemplateWrapper { private final JdbcTemplate jdbcTemplate; private JdbcTemplateWrapper(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } public static JdbcTemplateWrapper of(JdbcTemplate jdbcTemplate) { return new JdbcTemplateWrapper(jdbcTemplate); } public void execute(String sql) { jdbcTemplate.execute(sql); } public T execute(ConnectionCallback action) { return jdbcTemplate.execute(action); } public int insert(String sql, @Nullable Object... args) { Assert.isTrue(sql.startsWith("INSERT "), () -> "Invalid DELETE sql: " + sql); return jdbcTemplate.update(sql, args); } public int update(String sql, @Nullable Object... args) { Assert.isTrue(sql.startsWith("UPDATE "), () -> "Invalid DELETE sql: " + sql); return jdbcTemplate.update(sql, args); } public int delete(String sql, @Nullable Object... args) { Assert.isTrue(sql.startsWith("DELETE "), () -> "Invalid DELETE sql: " + sql); return jdbcTemplate.update(sql, args); } public List queryForList(String sql, RowMapper rowMapper, @Nullable Object... args) { Assert.isTrue(sql.startsWith("SELECT "), () -> "Invalid SELECT sql: " + sql); return jdbcTemplate.queryForStream(sql, rowMapper, args).collect(Collectors.toList()); } } ================================================ FILE: src/main/java/cn/ponfee/commons/spring/LocalizedMethodArgumentResolver.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.spring; import cn.ponfee.commons.collect.Collects; import cn.ponfee.commons.json.Jsons; import cn.ponfee.commons.util.ObjectUtils; import com.google.common.collect.ImmutableSet; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.core.MethodParameter; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.Assert; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.Objects; import java.util.Set; import static org.springframework.web.bind.annotation.RequestMethod.*; /** * Localized method parameter for spring web {@code org.springframework.stereotype.Controller} methods. *

    Can defined multiple object arguments for {@code org.springframework.web.bind.annotation.RequestMapping} method. * * @author Ponfee */ public class LocalizedMethodArgumentResolver implements HandlerMethodArgumentResolver { //private final WeakHashMap> resolvedCache = new WeakHashMap<>(); private static final Set QUERY_PARAM_METHODS = ImmutableSet.of( GET.name(), DELETE.name(), HEAD.name(), OPTIONS.name() ); private static final String CACHE_ATTRIBUTE_KEY = "LOCALIZED_METHOD_ARGUMENTS"; private static final Class MARKED_ANNOTATION_TYPE = LocalizedMethodArguments.class; @Override public boolean supportsParameter(MethodParameter parameter) { if (!(parameter.getExecutable() instanceof Method)) { return false; } return isAnnotationPresent(parameter.getMethod(), MARKED_ANNOTATION_TYPE) || isAnnotationPresent(parameter.getDeclaringClass(), MARKED_ANNOTATION_TYPE); } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws IOException { Method method = Objects.requireNonNull(parameter.getMethod()); HttpServletRequest httpServletRequest = Objects.requireNonNull(webRequest.getNativeRequest(HttpServletRequest.class)); int parameterIndex = parameter.getParameterIndex(); Object[] arguments; if (parameterIndex == 0) { arguments = parseMethodParameters(method, httpServletRequest); if (method.getParameterCount() > 1) { // CACHE_KEY_PREFIX + method.toString() httpServletRequest.setAttribute(CACHE_ATTRIBUTE_KEY, arguments); } } else { arguments = (Object[]) httpServletRequest.getAttribute(CACHE_ATTRIBUTE_KEY); } return Collects.get(arguments, parameterIndex); } private Object[] parseMethodParameters(Method method, HttpServletRequest request) throws IOException { if (QUERY_PARAM_METHODS.contains(request.getMethod())) { return parseQueryString(method, request.getParameterMap()); } else { try (ServletInputStream inputStream = request.getInputStream()) { String body = IOUtils.toString(inputStream, StandardCharsets.UTF_8); if (StringUtils.isEmpty(body)) { return parseQueryString(method, request.getParameterMap()); } else { return Jsons.parseMethodArgs(body, method); } } } } private Object[] parseQueryString(Method method, Map parameterMap) { int parameterCount = method.getParameterCount(); Object[] arguments = new Object[parameterCount]; for (int i = 0; i < parameterCount; i++) { String argName = "args[" + i + "]"; String[] array = parameterMap.get(argName); Assert.isTrue( array == null || array.length <= 1, () -> "Argument cannot be multiple value, name: " + argName + ", value: " + Jsons.toJson(array) ); String argValue = Collects.get(array, 0); Type argType = method.getGenericParameterTypes()[i]; if (argValue == null) { // if basic type then set default value arguments[i] = (argType instanceof Class) ? ObjectUtils.cast(null, (Class) argType) : null; } else { arguments[i] = Jsons.fromJson(argValue, argType); } } return arguments; } private static boolean isAnnotationPresent(Method method, Class annotationType) { return AnnotationUtils.findAnnotation(method, annotationType) != null; } private static boolean isAnnotationPresent(Class clazz, Class annotationType) { return AnnotationUtils.findAnnotation(clazz, annotationType) != null; } } ================================================ FILE: src/main/java/cn/ponfee/commons/spring/LocalizedMethodArguments.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.spring; import java.lang.annotation.*; /** * Localization method arguments annotation definition. * * @author Ponfee */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface LocalizedMethodArguments { } ================================================ FILE: src/main/java/cn/ponfee/commons/spring/MarkRpcController.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.spring; import org.springframework.web.bind.annotation.RestController; /** * Mark this subclass is a spring web controller and with rpc({@code LocalizedMethodArguments}) trait * * @author Ponfee */ @RestController @LocalizedMethodArguments public interface MarkRpcController { } ================================================ FILE: src/main/java/cn/ponfee/commons/spring/PageMethodArgumentResolver.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.spring; import cn.ponfee.commons.math.Numbers; import cn.ponfee.commons.model.PageHandler; import cn.ponfee.commons.model.PageParameter; import cn.ponfee.commons.reflect.Fields; import com.google.common.collect.ImmutableList; import org.apache.commons.lang3.StringUtils; import org.springframework.core.MethodParameter; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; import java.util.List; import java.util.Map; import static cn.ponfee.commons.model.PageHandler.DEFAULT_LIMIT; import static cn.ponfee.commons.model.PageHandler.DEFAULT_PAGE_SIZE; /** * 分页查询方法参数解析,在spring-mvc配置文件中做如下配置 * * * * * * * 配置完之后PageMethodArgumentResolver这个spring bean会被注入到RequestMappingHandlerAdapter.argumentResolvers中 * * https://blog.csdn.net/lqzkcx3/article/details/78794636 * * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter * @see org.springframework.web.method.support.HandlerMethodArgumentResolverComposite * @see org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver * @see org.springframework.web.method.annotation.RequestParamMethodArgumentResolver * * @author Ponfee */ public class PageMethodArgumentResolver implements HandlerMethodArgumentResolver { // pageSize(or limit) has not spec or spec less 1 then use this default value private static final int DEFAULT_SIZE = 20; private static final List SIZE_PARAMS = ImmutableList.of( DEFAULT_PAGE_SIZE, DEFAULT_LIMIT ); @Override public boolean supportsParameter(MethodParameter parameter) { //return parameter.hasParameterAnnotation(PageRequestParam.class); return PageParameter.class == parameter.getParameterType(); } @Override public PageParameter resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { Map params = webRequest.getParameterMap(); PageParameter pp = new PageParameter(params.size(), 1); params.forEach((key, value) -> { if (PageParameter.PAGE_PARAMS.contains(key)) { int value0 = Numbers.toInt(value[0], 0); if (value0 < 1 && SIZE_PARAMS.contains(key)) { value0 = DEFAULT_SIZE; } Fields.put(pp, key, value0); pp.put(key, value0); } else if (PageParameter.SORT_PARAM.equalsIgnoreCase(key)) { // value:“name ASC, age DESC” String value0 = StringUtils.join(value, ',').trim(); Fields.put(pp, PageParameter.SORT_PARAM, value0); pp.put(PageParameter.SORT_PARAM, value0); } else { pp.put(key, value.length == 1 ? value[0].trim() : value); } }); if (pp.getLimit() > 0) { // use limit query if (pp.getLimit() > PageHandler.MAX_SIZE) { pp.setLimit(PageHandler.MAX_SIZE); } if (pp.getOffset() < 0) { pp.setOffset(0); // start with 0 } } else { if (pp.getPageSize() < 1) { pp.setPageSize(DEFAULT_SIZE); } else if (pp.getPageSize() > PageHandler.MAX_SIZE) { pp.setPageSize(PageHandler.MAX_SIZE); } if (pp.getPageNum() < 1) { pp.setPageNum(1); // start with 1 } } return pp; } } ================================================ FILE: src/main/java/cn/ponfee/commons/spring/ProxyUtils.java ================================================ package cn.ponfee.commons.spring; import cn.ponfee.commons.reflect.Fields; import org.springframework.aop.framework.Advised; import org.springframework.aop.framework.AdvisedSupport; import org.springframework.aop.support.AopUtils; /** * Spring proxy utils * * @author Ponfee */ public class ProxyUtils { /** * Returns the proxy target object * * @param object the object * @return target object * @throws Exception */ public static Object getTargetObject(Object object) throws Exception { if (!AopUtils.isAopProxy(object)) { return object; } if (object instanceof Advised) { return ((Advised) object).getTargetSource().getTarget(); } if (AopUtils.isJdkDynamicProxy(object)) { return getProxyTargetObject(Fields.get(object, "h")); } if (AopUtils.isCglibProxy(object)) { return getProxyTargetObject(Fields.get(object, "CGLIB$CALLBACK_0")); } return object; } private static Object getProxyTargetObject(Object proxy) throws Exception { AdvisedSupport advisedSupport = (AdvisedSupport) Fields.get(proxy, "advised"); return advisedSupport.getTargetSource().getTarget(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/spring/RpcController.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.spring; import org.springframework.web.bind.annotation.RestController; /** * Mark this subclass is a spring web controller and with rpc({@code LocalizedMethodArguments}) trait * * @author Ponfee */ @RestController @LocalizedMethodArguments public interface RpcController { } ================================================ FILE: src/main/java/cn/ponfee/commons/spring/SpringContextHolder.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.spring; import cn.ponfee.commons.reflect.ClassUtils; import cn.ponfee.commons.reflect.Fields; import cn.ponfee.commons.reflect.GenericUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.BeansException; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.util.Assert; import javax.annotation.Resource; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; /** *

     * ContextLoaderListener的bean factory是DispatcherServlet的parent
     * spring上下文无法访问spring mvc上下文,但spring mvc上下文却能访问spring上下文,使用List解决
     * 
    * * spring上下文持有类 * * @author Ponfee */ public class SpringContextHolder implements ApplicationContextAware, DisposableBean { private static final List HOLDER = new ArrayList<>(); private static final AtomicBoolean INITIALIZED = new AtomicBoolean(false); @Override public void setApplicationContext(ApplicationContext cxt) throws BeansException { synchronized (SpringContextHolder.class) { if (!HOLDER.contains(cxt)) { HOLDER.add(cxt); } INITIALIZED.set(true); } } public static boolean isInitialized() { return INITIALIZED.get(); } /** * 通过名称获取bean * * @param name * @return */ public static Object getBean(String name) { return get(c -> c.getBean(name)); } /** * 通过类获取bean * * @param clazz * @return */ public static T getBean(Class clazz) { return get(c -> c.getBean(clazz)); } /** * @param name * @param clazz * @return */ public static T getBean(String name, Class clazz) { return get(c -> c.getBean(name, clazz)); } /** * 判断是否含有该名称的Bean * * @param name * @return */ public static boolean containsBean(String name) { for (ApplicationContext c : HOLDER) { if (c.containsBean(name)) { return true; } } return false; } /** * 判断Bean是否单例 * * @param name * @return */ public static boolean isSingleton(String name) { BeansException ex = null; for (ApplicationContext c : HOLDER) { try { if (c.isSingleton(name)) { return true; } } catch (BeansException e) { if (ex == null) { ex = e; } } } if (ex == null) { return false; } else { throw ex; } } /** * 获取Bean的类型 * * @param name * @return */ public static Class getType(String name) { return get(c -> c.getType(name)); } /** * 获取bean的别名 * * @param name * @return */ public static String[] getAliases(String name) { return get(c -> c.getAliases(name)); // 不抛异常 } /** * Returns a map that conatain spec annotation beans * * @param annotationType the Annotation type * @return a map */ public static Map getBeansWithAnnotation(Class annotationType) { return get(c -> c.getBeansWithAnnotation(annotationType)); } // ----------------------------------------------------------------------- /** * Injects the field from spring container for object * * @param object the object * * @see #autowire(Object) */ public static void inject(Object object) { Assert.state(HOLDER.size() > 0, "Must be defined SpringContextHolder within spring config file."); for (Field field : ClassUtils.listFields(object.getClass())) { Object fieldValue = null; Class fieldType = GenericUtils.getFieldActualType(object.getClass(), field); Resource resource = field.getAnnotation(Resource.class); if (resource != null) { fieldValue = getBean(StringUtils.isNotBlank(resource.name()) ? resource.name() : field.getName(), fieldType); if (fieldValue == null) { fieldValue = getBean(fieldType); } } else if (field.isAnnotationPresent(Autowired.class)) { Qualifier qualifier = field.getAnnotation(Qualifier.class); if (qualifier != null && StringUtils.isNotBlank(qualifier.value())) { fieldValue = getBean(qualifier.value(), fieldType); } else { fieldValue = getBean(fieldType); } } if (fieldType.isInstance(fieldValue)) { Fields.put(object, field, fieldValue); } } } /** * Autowire annotated from spring container for object * * @param object the object * * @see #inject(Object) */ public static void autowire(Object object) { Assert.state(HOLDER.size() > 0, "Must be defined SpringContextHolder within spring config file."); for (ApplicationContext context : HOLDER) { context.getAutowireCapableBeanFactory().autowireBean(object); } } @Override public void destroy() { /* synchronized (SpringContextHolder.class) { HOLDER.clear(); } */ } private static T get(Function finder) throws BeansException { //Assert.state(HOLDER.size() > 0, "must be defined SpringContextHolder within spring config file."); BeansException ex = null; T result; for (ApplicationContext c : HOLDER) { try { if ((result = finder.apply(c)) != null) { return result; } } catch (BeansException e) { if (ex == null) { ex = e; } } } if (ex == null) { return null; } else { throw ex; } } } ================================================ FILE: src/main/java/cn/ponfee/commons/spring/SpringUtils.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.spring; import org.apache.commons.io.IOUtils; import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.Resource; import org.springframework.util.ResourceUtils; import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.URL; /** * Spring utils * * @author Ponfee */ public final class SpringUtils { public static Resource getResource(String resourceLocation) throws IOException { // return new DefaultResourceLoader().getResource(resourceLocation); URL url = ResourceUtils.getURL(resourceLocation); byte[] bytes = IOUtils.toByteArray(url); return new InputStreamResource(new ByteArrayInputStream(bytes)); } } ================================================ FILE: src/main/java/cn/ponfee/commons/spring/TransactionUtils.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.spring; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.DefaultTransactionDefinition; import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.Assert; import java.util.function.Consumer; import static cn.ponfee.commons.exception.Throwables.ThrowingSupplier; /** * Spring transaction utility. * * @author Ponfee */ public class TransactionUtils { /** * 在事务提交后再执行 * * @param action the action code */ public static void doAfterTransactionCommit(final Runnable action) { if (TransactionSynchronizationManager.isActualTransactionActive()) { TransactionSynchronization ts = new TransactionSynchronization() { @Override public void afterCommit() { action.run(); } }; TransactionSynchronizationManager.registerSynchronization(ts); } else { action.run(); } } /** * 创建一个新事务,如果当前存在事务,则将这个事务挂起。 *

    内部事务与外部事务相互独立,互不依赖。 * * @param txManager the txManager * @param action the action code * @param log the exception log * @param return type * @return do action result */ public static R doInRequiresNewTransaction(PlatformTransactionManager txManager, ThrowingSupplier action, Consumer log) { return doInPropagationTransaction(txManager, action, log, TransactionDefinition.PROPAGATION_REQUIRES_NEW); } /** * 如果当前存在事务则开启一个嵌套事务,如果当前不存在事务则新建一个事务并运行。 *

    内部事务为外部事务的一个子事务。 *

    内部事务的提交/回滚不影响外部事务的提交/回滚 *

    内部事务的提交/回滚最终依赖外部事务的提交/回滚。 * * @param txManager the txManager * @param action the action code * @param log the exception log * @param return type * @return do action result */ public static R doInNestedTransaction(PlatformTransactionManager txManager, ThrowingSupplier action, Consumer log) { Assert.isTrue( TransactionSynchronizationManager.isActualTransactionActive(), "Do nested transaction must be in parent transaction." ); return doInPropagationTransaction(txManager, action, log, TransactionDefinition.PROPAGATION_NESTED); } // ----------------------------------------------------------------------private methods private static R doInPropagationTransaction(PlatformTransactionManager txManager, ThrowingSupplier action, Consumer log, int transactionPropagation) { DefaultTransactionDefinition txDefinition = new DefaultTransactionDefinition(); txDefinition.setPropagationBehavior(transactionPropagation); TransactionStatus status = txManager.getTransaction(txDefinition); try { R result = action.get(); txManager.commit(status); return result; } catch (Throwable t) { txManager.rollback(status); log.accept(t); return null; } } } ================================================ FILE: src/main/java/cn/ponfee/commons/spring/TypedMapMethodArgumentResolver.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.spring; import cn.ponfee.commons.model.TypedParameter; import org.springframework.core.MethodParameter; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; import java.util.LinkedHashMap; import java.util.Map; /** * The common request parameter resolver for map model * * @author Ponfee */ public class TypedMapMethodArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { return TypedParameter.class == parameter.getParameterType(); } @Override public TypedParameter resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { Map params = webRequest.getParameterMap(); Map map = new LinkedHashMap<>(params.size(), 1); params.forEach((key, value) -> map.put(key, value.length == 1 ? value[0] : value)); return new TypedParameter(map); } } ================================================ FILE: src/main/java/cn/ponfee/commons/spring/YamlProperties.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.spring; import cn.ponfee.commons.model.TypedMap; import cn.ponfee.commons.base.Symbol.Char; import cn.ponfee.commons.reflect.ClassUtils; import cn.ponfee.commons.reflect.Fields; import cn.ponfee.commons.util.ObjectUtils; import cn.ponfee.commons.util.Strings; import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.Resource; import java.io.*; import java.lang.reflect.Field; import java.util.Properties; /** * Yaml properties * * @author Ponfee */ public class YamlProperties extends Properties implements TypedMap { private static final long serialVersionUID = -1599483902442715272L; public YamlProperties(File file) throws IOException { try (InputStream inputStream = new FileInputStream(file)) { loadYaml(inputStream); } } public YamlProperties(InputStream inputStream) { loadYaml(inputStream); } public YamlProperties(byte[] content) { loadYaml(new ByteArrayInputStream(content)); } public T extract(Class beanType, String prefix) { T bean = ClassUtils.newInstance(beanType); char[] separators = {Char.HYPHEN, Char.DOT}; for (Field field : ClassUtils.listFields(beanType)) { for (char separator : separators) { String name = prefix + Strings.toSeparatedName(field.getName(), separator); if (super.containsKey(name)) { Fields.put(bean, field, ObjectUtils.cast(get(name), field.getType())); break; } } } return bean; } private void loadYaml(InputStream inputStream) { Resource resource = new InputStreamResource(inputStream); super.putAll(YamlPropertySourceFactory.loadYml(resource)); } } ================================================ FILE: src/main/java/cn/ponfee/commons/spring/YamlPropertySourceFactory.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.spring; import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; import org.springframework.core.env.PropertiesPropertySource; import org.springframework.core.env.PropertySource; import org.springframework.core.io.Resource; import org.springframework.core.io.support.DefaultPropertySourceFactory; import org.springframework.core.io.support.EncodedResource; import java.io.IOException; import java.util.Properties; /** * Spring yaml properties source factory, *

    for help use annotation {@link org.springframework.context.annotation.PropertySource} * *

    {@code
     * @PropertySource(value = "classpath:xxx.yml", factory = YamlPropertySourceFactory.class)
     * public class DruidConfig {
     *   @Value("${datasource.jdbc-url}")
     *   private String jdbcUrl;
     *
     *   @Value("${datasource.username}")
     *   private String username;
     *
     *   @Value("${datasource.password}")
     *   private String password;
     * }}
    * * @author Ponfee */ public class YamlPropertySourceFactory extends DefaultPropertySourceFactory { @Override public PropertySource createPropertySource(String name, EncodedResource resource) throws IOException { String sourceName = name != null ? name : resource.getResource().getFilename(); if (!resource.getResource().exists()) { return new PropertiesPropertySource(sourceName, new Properties()); } else if (sourceName.endsWith(".yml") || sourceName.endsWith(".yaml")) { //return new YamlPropertySourceLoader().load(sourceName, resource.getResource()).get(0); return new PropertiesPropertySource(sourceName, loadYml(resource.getResource())); } else { return super.createPropertySource(name, resource); } } public static Properties loadYml(Resource resource) { YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); factory.setResources(resource); factory.afterPropertiesSet(); return factory.getObject(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/tree/BaseNode.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.tree; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.SerializationUtils; import org.springframework.util.Assert; import java.io.Serializable; import java.util.List; /** * 基于树形结构节点的基类 * * @param the node id type * @param the attachment biz object type * @author Ponfee */ public abstract class BaseNode, A> implements Serializable, Cloneable { private static final long serialVersionUID = -4116799955526185765L; // -------------------------------------------------------------------基础信息 protected final T nid; // node id protected final T pid; // parent node id protected final boolean enabled; // 状态(业务相关):false无效;true有效; protected final boolean available; // 是否可用(parent.available & this.enabled) protected final A attach; // 附加信息(与业务相关) // -------------------------------------------------------------------以ROOT为根的整棵树 protected int level; // 节点层级(以根节点为1开始,往下逐级加1) protected List path; // 节点路径list(父节点在前,末尾元素是节点本身的nid) protected int degree; // 节点的度数(子节点数量,叶子节点的度为0) protected int leftLeafCount; // 左叶子节点数量(在其左边的所有叶子节点数量:相邻左兄弟节点的左叶子节点个数+该兄弟节点的子节点个数) // -------------------------------------------------------------------以当前节点为根的子树 protected int treeDepth; // 树的深度(叶子节点的树深度为1) protected int treeNodeCount; // 树的节点数量 protected int treeMaxDegree; // 树中最大的度数(树中所有节点数目=所有节点度数之和+1) protected int treeLeafCount; // 树的叶子节点数量(叶子节点为1) protected int childrenCount; // 子节点个数 protected int siblingOrdinal; // 兄弟节点按顺序排行(从1开始) public BaseNode(T nid, T pid, A attach) { this(nid, pid, true, attach); } public BaseNode(T nid, T pid, boolean enabled, A attach) { this(nid, pid, enabled, enabled, attach); } public BaseNode(T nid, T pid, boolean enabled, boolean available, A attach) { Assert.isTrue(ObjectUtils.isNotEmpty(nid), "Node id cannot be empty."); this.nid = nid; this.pid = pid; this.enabled = enabled; this.available = available; this.attach = attach; } /** * Deep copy * * @return a copies of node */ @Override public BaseNode clone() { return SerializationUtils.clone(this); } // -----------------------------------------------final field getter public T getNid() { return nid; } public T getPid() { return pid; } public boolean isEnabled() { return enabled; } public boolean isAvailable() { return available; } public A getAttach() { return attach; } // ----------------------------------------------------others field getter public int getLevel() { return level; } public List getPath() { return path; } public int getDegree() { return degree; } public int getLeftLeafCount() { return leftLeafCount; } public int getTreeDepth() { return treeDepth; } public int getTreeNodeCount() { return treeNodeCount; } public int getTreeMaxDegree() { return treeMaxDegree; } public int getTreeLeafCount() { return treeLeafCount; } public int getChildrenCount() { return childrenCount; } public int getSiblingOrdinal() { return siblingOrdinal; } } ================================================ FILE: src/main/java/cn/ponfee/commons/tree/FlatNode.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.tree; import org.apache.commons.collections4.CollectionUtils; import java.io.Serializable; import java.util.function.Function; /** * 节点扁平结构 * * @param the node id type * @param the attachment biz object type * @author Ponfee */ public final class FlatNode, A> extends BaseNode { private static final long serialVersionUID = 5191371614061952661L; private final boolean leaf; // 是否叶子节点 FlatNode(TreeNode n) { super(n.nid, n.pid, n.enabled, n.available, n.attach); super.level = n.level; super.degree = n.degree; super.path = n.path; super.leftLeafCount = n.leftLeafCount; super.treeDepth = n.treeDepth; super.treeNodeCount = n.treeNodeCount; super.treeMaxDegree = n.treeMaxDegree; super.treeLeafCount = n.treeLeafCount; super.childrenCount = n.childrenCount; super.siblingOrdinal = n.siblingOrdinal; this.leaf = CollectionUtils.isEmpty(n.getChildren()); } public R convert(Function, R> convertor) { return convertor.apply(this); } // ----------------------------------------------getter/setter public boolean isLeaf() { return leaf; } } ================================================ FILE: src/main/java/cn/ponfee/commons/tree/MapTreeTrait.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.tree; import java.io.Serializable; import java.util.LinkedHashMap; import java.util.List; /** * The map for Tree node * * @param the node id type * @param the attachment biz object type * @author Ponfee */ public class MapTreeTrait, A> extends LinkedHashMap implements TreeTrait> { private static final long serialVersionUID = -5799393887664198242L; public static final String DEFAULT_CHILDREN_KEY = "children"; private final String childrenKey; public MapTreeTrait() { this(DEFAULT_CHILDREN_KEY); } public MapTreeTrait(String childrenKey) { this.childrenKey = childrenKey; } @Override public void setChildren(List> children) { super.put(childrenKey, children); } @Override public List> getChildren() { return (List>) super.get(childrenKey); } } ================================================ FILE: src/main/java/cn/ponfee/commons/tree/NodeId.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.tree; import cn.ponfee.commons.model.ToJsonString; import org.apache.commons.lang3.builder.HashCodeBuilder; import java.io.Serializable; import java.util.Objects; /** * Base node id * * @author Ponfee * @param the NodeId implementation sub class */ public abstract class NodeId> extends ToJsonString implements Comparable, Serializable, Cloneable { private static final long serialVersionUID = -9004940918491918780L; protected final T parent; public NodeId(T parent) { this.parent = parent; } @Override @SuppressWarnings("unchecked") public final boolean equals(Object obj) { if (obj == null || this.getClass() != obj.getClass()) { return false; } T o = (T) obj; return Objects.equals(this.parent, o.parent) && this.equals(o); } @Override public final int compareTo(T another) { if (this.parent == null) { return another.parent == null ? this.compare(another) : -1; } if (another.parent == null) { return 1; } int a = this.parent.compareTo(another.parent); return a != 0 ? a : this.compare(another); } @Override public final int hashCode() { return new HashCodeBuilder() .append(this.parent) .append(this.hash()) .build(); } protected abstract boolean equals(T another); protected abstract int compare(T another); protected abstract int hash(); @Override public abstract T clone(); public final T getParent() { return parent; } } ================================================ FILE: src/main/java/cn/ponfee/commons/tree/NodePath.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.tree; import cn.ponfee.commons.collect.ImmutableArrayList; import cn.ponfee.commons.reflect.GenericUtils; import com.alibaba.fastjson.annotation.JSONType; import com.alibaba.fastjson.parser.DefaultJSONParser; import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.ArrayUtils; import java.io.IOException; import java.io.Serializable; import java.lang.reflect.Type; import java.util.Iterator; import java.util.List; import java.util.Objects; /** * Representing immutable node path array * * @param the node id type * @author Ponfee */ // NodePath is extends ArrayList, so must be use mappingTo in fastjson // if not do it then deserialized json as a collection type(java.util.ArrayList) // hashCode()/equals() extends ImmutableArrayList @JSONType(mappingTo = NodePath.FastjsonDeserializeMarker.class) // fastjson @JsonDeserialize(using = NodePath.JacksonDeserializer.class) // jackson public final class NodePath> extends ImmutableArrayList implements Comparable> { private static final long serialVersionUID = 9090552044337950223L; public NodePath() { // Note: For help deserialization(jackson) } @SafeVarargs public NodePath(T... path) { super(path); } public NodePath(T[] parent, T child) { super(ArrayUtils.addAll(Objects.requireNonNull(parent), child)); } public NodePath(List path) { super(path.toArray()); } public NodePath(List path, T child) { super(ArrayUtils.addAll(path.toArray(), child)); } public NodePath(NodePath parent) { super(parent.toArray()); } public NodePath(NodePath parent, T child) { super(parent.join(child)); } @Override public int compareTo(NodePath o) { int c; for (Iterator a = this.iterator(), b = o.iterator(); a.hasNext() && b.hasNext(); ) { if ((c = a.next().compareTo(b.next())) != 0) { return c; } } return super.size() - o.size(); } @Override public boolean equals(Object obj) { return (obj instanceof NodePath) && super.equals(obj); } @Override public NodePath clone() { return new NodePath<>(this); } // -----------------------------------------------------custom fastjson deserialize @JSONType(deserializer = FastjsonDeserializer.class) public static class FastjsonDeserializeMarker { } /** *
     {@code
         *   public static class IntegerNodePath {
         *     // 当定义的NodePath字段其泛型参数为具体类型时,必须用JSONField注解,否则报错
         *     @JSONField(deserializeUsing = FastjsonDeserializer.class)
         *     private NodePath path; // ** NodePath **
         *   }
         * }
    * * @param */ public static class FastjsonDeserializer> implements ObjectDeserializer { @Override @SuppressWarnings("unchecked") public NodePath deserialze(DefaultJSONParser parser, Type type, Object fieldName) { if (GenericUtils.getRawType(type) != NodePath.class) { throw new UnsupportedOperationException("Only supported deserialize NodePath, cannot supported: " + type); } List list = parser.parseArray(GenericUtils.getActualTypeArgument(type, 0)); return list.isEmpty() ? null : new NodePath<>(list); } @Override public int getFastMatchToken() { return 0 /*JSONToken.RBRACKET*/; } } // -----------------------------------------------------custom jackson deserialize public static class JacksonDeserializer> extends JsonDeserializer> { @Override @SuppressWarnings("unchecked") public NodePath deserialize(JsonParser p, DeserializationContext ctx) throws IOException { List list = p.readValueAs(List.class); return CollectionUtils.isEmpty(list) ? null : new NodePath<>(list); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/tree/PlainNode.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.tree; import java.io.Serializable; /** * Representing plain node * * @param the node id type * @param
    the attachment biz object type * @author Ponfee */ public final class PlainNode, A> extends BaseNode { private static final long serialVersionUID = -2189191471047483877L; public PlainNode(T nid, T pid, A attach) { super(nid, pid, attach); } public PlainNode(T nid, T pid, boolean enabled, A attach) { super(nid, pid, enabled, attach); } public PlainNode(T nid, T pid, boolean enabled, boolean available, A attach) { super(nid, pid, enabled, available, attach); } } ================================================ FILE: src/main/java/cn/ponfee/commons/tree/SiblingNodesComparator.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.tree; import cn.ponfee.commons.collect.Comparators; import org.apache.commons.collections4.CollectionUtils; import java.io.Serializable; import java.util.Comparator; import java.util.List; import java.util.function.Function; /** * Sibling nodes comparator * * @author Ponfee */ public class SiblingNodesComparator, A> { private final Comparator> comparator; private SiblingNodesComparator(Comparator> comparator) { this.comparator = comparator; } public static , A, U extends Comparable> SiblingNodesComparator comparing(Function, U> first) { // default nullsLast and ASC return comparing(first, false, true); } public static , A, U extends Comparable> SiblingNodesComparator comparing(Function, U> first, boolean nullsFirst, boolean asc) { return new SiblingNodesComparator<>(Comparator.comparing(first, comparator(nullsFirst, asc))); } public > SiblingNodesComparator thenComparing(Function, U> then) { // default nullsLast and ASC return thenComparing(then, false, true); } public > SiblingNodesComparator thenComparing(Function, U> then, boolean nullsFirst, boolean asc) { return new SiblingNodesComparator<>(comparator.thenComparing(then, comparator(nullsFirst, asc))); } public void sort(TreeNode root) { sort(root.getChildren()); } public void sort(List> children) { if (CollectionUtils.isNotEmpty(children)) { children.sort(comparator); } } public Comparator get() { return comparator; } private static > Comparator comparator(boolean nullsFirst, boolean asc) { Comparator comparator = Comparators.order(asc); return nullsFirst ? Comparator.nullsFirst(comparator) : Comparator.nullsLast(comparator); } } ================================================ FILE: src/main/java/cn/ponfee/commons/tree/TreeNode.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.tree; import cn.ponfee.commons.collect.Collects; import cn.ponfee.commons.reflect.Fields; import cn.ponfee.commons.tree.print.MultiwayTreePrinter; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import java.io.IOException; import java.io.Serializable; import java.util.*; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; /** *
     * Tree node structure
     *
     * ┌───────────────────────────┐
     * │              0            │
     * │        ┌─────┴─────┐      │
     * │        1           2      │
     * │    ┌───┴───┐   ┌───┴───┐  │
     * │    3       4   5       6  │
     * │  ┌─┴─┐   ┌─┘              │
     * │  7   8   9                │
     * └───────────────────────────┘
     *
     * 上面这棵二叉树中的遍历方式:
     *   DFS前序遍历:0137849256
     *   DFS中序遍历:7381940526
     *   DFS后序遍历:7839415620
     *   BFS广度优先:0123456789
     *   CFS孩子优先:0123478956         (备注:教科书上没有CFS一说,是我为方便说明描述而取名的)
     * 
    * * @param the node id type * @param
    the attachment biz object type * @author Ponfee */ public final class TreeNode, A> extends BaseNode { private static final long serialVersionUID = -9081626363752680404L; public static final String DEFAULT_ROOT_ID = "__ROOT__"; // 用于比较兄弟节点次序 private final Comparator> siblingNodesComparator; // 子节点列表(空列表则表示为叶子节点) private final LinkedList> children = new LinkedList<>(); // 是否构建path private final boolean buildPath; /** * Constructs a tree node * * @param nid the node id * @param pid the parent node id(withhold this pid field value,when use if the other root node mount this node as child) * @param enabled the node is enabled * @param available the current node is available(parent.available & this.enabled) * @param attach the attachment for biz object * @param siblingNodesComparator the comparator for sort sibling nodes(has the same parent node) * @param buildPath the if whether build path * @param doMount the if whether do mount, if is inner new TreeNode then false else true */ TreeNode(T nid, T pid, boolean enabled, boolean available, A attach, Comparator> siblingNodesComparator, boolean buildPath, boolean doMount) { super(nid, pid, enabled, available, attach); this.siblingNodesComparator = Objects.requireNonNull(siblingNodesComparator); this.buildPath = buildPath; if (doMount) { // as root node if new instance at external(TreeNodeBuilder) or of(TreeNode) mount(null); } } public static , A> TreeNodeBuilder builder(T nid) { return new TreeNodeBuilder<>(nid); } // ------------------------------------------------------mount children nodes public > TreeNode mount(List nodes) { mount(nodes, false); return this; } /** * Mount a tree * * @param list 子节点列表 * @param ignoreOrphan {@code true}忽略孤儿节点,{@code false}如果有孤儿节点则会抛异常 */ public > TreeNode mount(List list, boolean ignoreOrphan) { if (list == null) { list = Collections.emptyList(); } // 1、预处理 List> nodes = prepare(list); // 2、检查是否存在重复节点 List checkDuplicateList = Collects.newLinkedList(super.nid); nodes.forEach(n -> checkDuplicateList.add(n.nid)); List duplicated = Collects.duplicate(checkDuplicateList); if (CollectionUtils.isNotEmpty(duplicated)) { throw new IllegalStateException("Duplicated nodes: " + duplicated); } // 3、以此节点为根构建节点树 super.level = 1; // root node level is 1 super.path = null; // reset with null super.leftLeafCount = 0; // root node left leaf count is 1 super.siblingOrdinal = 1; mount0(null, nodes, ignoreOrphan, super.nid); // 4、检查是否存在孤儿节点 if (!ignoreOrphan && CollectionUtils.isNotEmpty(nodes)) { String nids = nodes.stream().map(e -> String.valueOf(e.getNid())).collect(Collectors.joining(",")); throw new IllegalStateException("Invalid orphan nodes: [" + nids + "]"); } // 5、统计 count(); return this; } // -------------------------------------------------------------DFS /** * 深度优先搜索DFS(Depth-First Search):使用前序遍历 *

    Should be invoking after {@link #mount(List)} * * @return a list nodes for DFS tree node */ public List> flatDFS() { List> collect = Lists.newLinkedList(); Deque> stack = Collects.newLinkedList(this); while (!stack.isEmpty()) { TreeNode node = stack.pop(); collect.add(new FlatNode<>(node)); node.ifChildrenPresent(cs -> { // 反向遍历子节点 for (Iterator> iter = cs.descendingIterator(); iter.hasNext(); ) { stack.push(iter.next()); } }); } return collect; } /*// 递归方式DFS public List> flatDFS() { List> collect = Lists.newLinkedList(); dfs(collect); return collect; } private void dfs(List> collect) { collect.add(new FlatNode<>(this)); forEachChild(child -> child.dfs(collect)); } */ // -------------------------------------------------------------CFS /** * 按层级方式展开节点:兄弟节点相邻 *

    子节点优先搜索CFS(Children-First Search) *

    Should be invoking after {@link #mount(List)} * * Note:为了构建复杂表头,保证左侧的叶子节点必须排在右侧叶子节点前面,此处不能用广度优先搜索策略 * * @return a list nodes for CFS tree node */ public List> flatCFS() { List> collect = Collects.newLinkedList(new FlatNode<>(this)); Deque> stack = Collects.newLinkedList(this); while (!stack.isEmpty()) { TreeNode node = stack.pop(); node.ifChildrenPresent(cs -> { cs.forEach(child -> collect.add(new FlatNode<>(child))); // 反向遍历子节点 for (Iterator> iter = cs.descendingIterator(); iter.hasNext(); ) { stack.push(iter.next()); } }); } return collect; } /*// 递归方式CFS public List> flatCFS() { List> collect = Collects.newLinkedList(new FlatNode<>(this)); cfs(collect); return collect; } private void cfs(List> collect) { forEachChild(child -> collect.add(new FlatNode<>(child))); forEachChild(child -> child.cfs(collect)); } */ // -------------------------------------------------------------BFS /** * 广度优先遍历BFS(Breath-First Search) * * @return a list nodes for BFS tree node */ public List> flatBFS() { List> collect = new LinkedList<>(); Queue> queue = Collects.newLinkedList(this); while (!queue.isEmpty()) { for (int i = queue.size(); i > 0; i--) { TreeNode node = queue.poll(); collect.add(new FlatNode<>(node)); node.forEachChild(queue::offer); } } return collect; } /*// 递归方式BFS public List> flatBFS() { List> collect = new LinkedList<>(); Queue> queue = Collects.newLinkedList(this); bfs(queue, collect); return collect; } private void bfs(Queue> queue, List> collect) { int size = queue.size(); if (size == 0) { return; } while (size-- > 0) { TreeNode node = queue.poll(); collect.add(new FlatNode<>(node)); node.forEachChild(queue::offer); } bfs(queue, collect); } */ // -----------------------------------------------------------children public void ifChildrenPresent(Consumer>> childrenProcessor) { if (!children.isEmpty()) { childrenProcessor.accept(children); } } public void forEachChild(Consumer> childProcessor) { if (!children.isEmpty()) { children.forEach(childProcessor); } } // -----------------------------------------------------------tree traverse /** * Traverses the tree * * @param action the action function */ public void traverse(Consumer> action) { Deque> stack = Collects.newLinkedList(this); while (!stack.isEmpty()) { TreeNode node = stack.pop(); action.accept(node); node.forEachChild(stack::push); } } // -----------------------------------------------------------convert to TreeTrait public > E convert(Function, E> convert) { return convert(convert, true); } public > E convert(Function, E> convert, boolean containsUnavailable) { if (!available && !containsUnavailable) { // not contains unavailable node return null; } E root = convert.apply(this); convert(convert, root, containsUnavailable); return root; } public String print(Function, CharSequence> nodeLabel) throws IOException { StringBuilder builder = new StringBuilder(); new MultiwayTreePrinter<>(builder, nodeLabel, TreeNode::getChildren).print(this); return builder.toString(); } @Override public String toString() { try { return print(e -> String.valueOf(e.getNid())); } catch (IOException e) { return ExceptionUtils.rethrow(e); } } public LinkedList> getChildren() { return children; } // -----------------------------------------------------------private methods private > List> prepare(List nodes) { List> list = Lists.newLinkedList(); // nodes list for (BaseNode node : nodes) { if (node instanceof TreeNode) { // if tree node, then add all the tree nodes that includes the node's children(recursive) ((TreeNode) node).traverse(list::add); } else { list.add(node); // node.clone() } } // the root node children ifChildrenPresent(cs -> { cs.forEach(child -> child.traverse(list::add)); cs.clear(); // clear the root node children before mount }); return list; } private > void mount0(List parentPath, List nodes, boolean ignoreOrphan, T mountPidIfNull) { // current "this" is parent: AbstractNode parent = this; // 当前节点路径=父节点路径+当前节点 // the "super" means defined in super class BaseNode's field, is not parent node super.path = buildPath(parentPath, super.nid); // find child nodes for the current node for (Iterator iter = nodes.iterator(); iter.hasNext();) { BaseNode node = iter.next(); if (!ignoreOrphan && ObjectUtils.isEmpty(node.pid)) { // effect condition that pid is null // 不忽略孤儿节点且节点的父节点为空,则其父节点视为根节点(将其挂载到根节点下) Fields.put(node, "pid", mountPidIfNull); // pid is final modify } if (super.nid.equals(node.pid)) { // found a child node TreeNode child = new TreeNode<>( node.nid, node.pid, node.enabled, super.available && node.enabled, // recompute the child node is available node.attach, this.siblingNodesComparator, this.buildPath, false ); child.level = super.level + 1; children.add(child); // 挂载子节点 iter.remove(); // remove the found child node } } ifChildrenPresent(cs -> { // recursion to mount child tree cs.forEach(child -> child.mount0(path, nodes, ignoreOrphan, mountPidIfNull)); // sort the children list(sibling nodes sort) cs.sort(siblingNodesComparator); }); super.degree = children.size(); } private void count() { if (children.isEmpty()) { // 叶子节点 super.treeDepth = 1; super.treeMaxDegree = 0; super.treeLeafCount = 1; super.treeNodeCount = 1; super.childrenCount = 0; return; } // 非叶子节点 int maxChildTreeDepth = 0, maxChildTreeMaxDegree = 0, sumChildrenTreeLeafCount = 0, sumChildrenTreeNodeCount = 0; TreeNode child; for (int i = 0; i < children.size(); i++) { child = children.get(i); child.siblingOrdinal = i + 1; // 1、统计左叶子节点数量 if (i == 0) { // 是最左子节点:左叶子节点个数=父节点的左叶子节点个数 child.leftLeafCount = super.leftLeafCount; } else { // 非最左子节点:左叶子节点个数=相邻左兄弟节点的左叶子节点个数+该兄弟节点的子节点个数 TreeNode prevSibling = children.get(i - 1); child.leftLeafCount = prevSibling.leftLeafCount + prevSibling.treeLeafCount; } // 2、递归 child.count(); // 3、统计子叶子节点数量及整棵树节点的数量 maxChildTreeDepth = Math.max(maxChildTreeDepth, child.treeDepth); maxChildTreeMaxDegree = Math.max(maxChildTreeMaxDegree, child.degree); sumChildrenTreeLeafCount += child.treeLeafCount; sumChildrenTreeNodeCount += child.treeNodeCount; } super.treeDepth = maxChildTreeDepth + 1; // 加上自身节点的层级 super.treeMaxDegree = Math.max(maxChildTreeMaxDegree, super.degree); // 树中的最大度数 super.treeLeafCount = sumChildrenTreeLeafCount; // 子节点的叶子节点之和 super.treeNodeCount = sumChildrenTreeNodeCount + 1; // 要包含节点本身 super.childrenCount = children.size(); // 子节点数量 } /** * Returns a immutable list for current node path * * @param parentPath the parent node path * @param nid the current node id * @return a immutable list appended current node id */ private List buildPath(List parentPath, T nid) { if (!buildPath) { return null; } // already check duplicated, so cannot happen has circular dependencies state /* if (IterableUtils.matchesAny(parentPath, nid::equals)) { // 节点路径中已经包含了此节点,则视为环状 throw new IllegalStateException("Node circular dependencies: " + parentPath + " -> " + nid); } */ int size = parentPath == null ? 1 : parentPath.size() + 1; ImmutableList.Builder builder = ImmutableList.builderWithExpectedSize(size); // root node un-contains null parent if (parentPath != null) { builder.addAll(parentPath); } return builder.add(nid).build(); } private > void convert(Function, E> convert, E parent, boolean containsUnavailable) { if (children.isEmpty()) { parent.setChildren(null); return; } List list = new LinkedList<>(); for (TreeNode child : children) { if (child.available || containsUnavailable) { // filter unavailable E node = convert.apply(child); child.convert(convert, node, containsUnavailable); list.add(node); } } parent.setChildren(list); } } ================================================ FILE: src/main/java/cn/ponfee/commons/tree/TreeNodeBuilder.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.tree; import java.io.Serializable; import java.util.Comparator; import java.util.Objects; /** * Builds tree node as root node * * @param the node id type * @param the attachment biz object type * @author Ponfee */ public final class TreeNodeBuilder, A> { private final T nid; private Comparator> siblingNodesComparator = Comparator.comparing(TreeNode::getNid); private T pid = null; private boolean enabled = true; private boolean available = true; private A attach = null; private boolean buildPath = true; TreeNodeBuilder(T nid) { this.nid = Objects.requireNonNull(nid); } public TreeNodeBuilder pid(T pid) { this.pid = pid; return this; } public TreeNodeBuilder siblingNodesComparator(Comparator> comparator) { this.siblingNodesComparator = Objects.requireNonNull(comparator, "Sibling nodes comparator cannot be null."); return this; } public TreeNodeBuilder enabled(boolean enabled) { this.enabled = enabled; return this; } public TreeNodeBuilder available(boolean available) { this.available = available; return this; } public TreeNodeBuilder attach(A attach) { this.attach = attach; return this; } public TreeNodeBuilder buildPath(boolean buildPath) { this.buildPath = buildPath; return this; } public TreeNode build() { return new TreeNode<>(nid, pid, enabled, available, attach, siblingNodesComparator, buildPath, true); } } ================================================ FILE: src/main/java/cn/ponfee/commons/tree/TreeTrait.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.tree; import java.io.Serializable; import java.util.List; /** * The trait for Tree node * * @param the node id type * @param the attachment biz object type * @param the TreeTrait type * @author Ponfee */ public interface TreeTrait, A, E extends TreeTrait> { /** * Sets node list as children * * @param children the children node list */ void setChildren(List children); /** * Gets children node list * * @return children node list */ List getChildren(); } ================================================ FILE: src/main/java/cn/ponfee/commons/tree/print/BinaryTreePrinter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.tree.print; import cn.ponfee.commons.io.Files; import com.google.common.base.Strings; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.function.Function; /** * Print binary tree * * @author Ponfee */ public class BinaryTreePrinter { public enum Branch { RECTANGLE, TRIANGLE } private final Appendable output; private final Function nodeLabel; private final Function leftChild; private final Function rightChild; /** * 分支是方形还是三角形 */ private final Branch branch; /** * 只有一个子节点时,是否区分左右方向 */ private final boolean directed; /** * 单棵树节点间的空隙 */ private final int nodeSpace; /** * 多棵树时,树间的空隙 */ private final int treeSpace; BinaryTreePrinter(Appendable output, Function nodeLabel, Function leftChild, Function rightChild, Branch branch, boolean directed, int nodeSpace, int treeSpace) { this.output = output; this.nodeLabel = nodeLabel; this.leftChild = leftChild; this.rightChild = rightChild; this.branch = branch; this.directed = directed; this.nodeSpace = nodeSpace; this.treeSpace = Math.max((treeSpace / 2) * 2 + 1, 3); } /** * Prints ascii representation of binary tree. * Parameter nodeSpace is minimum number of spaces between adjacent node labels. * Parameter squareBranches, when set to true, results in branches being printed with ASCII box * drawing characters. */ public void print(T root) throws IOException { printTreeLines(buildTreeLines(root)); } /** * Prints ascii representations of multiple trees across page. * Parameter nodeSpace is minimum number of spaces between adjacent node labels in a tree. * Parameter treeSpace is horizontal distance between trees, as well as number of blank lines * between rows of trees. * Parameter lineWidth is maximum width of output * Parameter squareBranches, when set to true, results in branches being printed with ASCII box * drawing characters. * * @param trees the multiple tree * @param lineWidth 行的宽度:小于该宽度则多棵树水平排列,否则换行后再来打印下一棵树 */ public void print(List trees, int lineWidth) throws IOException { List> allTreeLines = new ArrayList<>(trees.size()); int[] treeWidths = new int[trees.size()]; int[] minLeftOffsets = new int[trees.size()]; int[] maxRightOffsets = new int[trees.size()]; for (int i = 0; i < trees.size(); i++) { List treeLines = buildTreeLines(trees.get(i)); allTreeLines.add(treeLines); minLeftOffsets[i] = minLeftOffset(treeLines); maxRightOffsets[i] = maxRightOffset(treeLines); treeWidths[i] = maxRightOffsets[i] - minLeftOffsets[i] + 1; } String halfTreeSpaceStr = spaces(treeSpace/2); int nextTreeIndex = 0; while (nextTreeIndex < trees.size()) { // print a row of trees starting at nextTreeIndex // first figure range of trees we can print for next row int sumOfWidths = treeWidths[nextTreeIndex]; int endTreeIndex = nextTreeIndex + 1; while (endTreeIndex < trees.size() && sumOfWidths + treeSpace + treeWidths[endTreeIndex] < lineWidth) { sumOfWidths += (treeSpace + treeWidths[endTreeIndex]); endTreeIndex++; } endTreeIndex--; // find max number of lines for tallest tree int maxLines = allTreeLines.stream().mapToInt(List::size).max().orElse(0); // print trees line by line for (int i = 0; i < maxLines; i++) { for (int j = nextTreeIndex; j <= endTreeIndex; j++) { List treeLines = allTreeLines.get(j); if (i >= treeLines.size()) { output.append(spaces(treeWidths[j])); } else { int leftSpaces = -(minLeftOffsets[j] - treeLines.get(i).leftOffset); int rightSpaces = maxRightOffsets[j] - treeLines.get(i).rightOffset; output.append(spaces(leftSpaces)).append(treeLines.get(i).line).append(spaces(rightSpaces)); } if (j < endTreeIndex) { output.append(halfTreeSpaceStr).append('|').append(halfTreeSpaceStr); } } output.append(Files.UNIX_LINE_SEPARATOR); } nextTreeIndex = endTreeIndex + 1; } } private void printTreeLines(List treeLines) throws IOException { if (treeLines.size() <= 0) { return; } int minLeftOffset = minLeftOffset(treeLines); int maxRightOffset = maxRightOffset(treeLines); for (TreeLine treeLine : treeLines) { output.append(spaces(-(minLeftOffset - treeLine.leftOffset))) .append(treeLine.line) .append(spaces(maxRightOffset - treeLine.rightOffset)) .append(Files.UNIX_LINE_SEPARATOR); } } private List buildTreeLines(T root) { if (root == null) { return Collections.emptyList(); } String rootLabel = nodeLabel.apply(root); List leftTreeLines = buildTreeLines(leftChild.apply(root)); List rightTreeLines = buildTreeLines(rightChild.apply(root)); int leftCount = leftTreeLines.size(); int rightCount = rightTreeLines.size(); int minCount = Math.min(leftCount, rightCount); int maxCount = Math.max(leftCount, rightCount); // The left and right subtree print representations have jagged edges, and we essentially we have to // figure out how close together we can bring the left and right roots so that the edges just meet on // some line. Then we add hspace, and round up to next odd number. int maxRootSpacing = 0; for (int i = 0; i < minCount; i++) { maxRootSpacing = Math.max(maxRootSpacing, leftTreeLines.get(i).rightOffset - rightTreeLines.get(i).leftOffset); } int rootSpacing = maxRootSpacing + nodeSpace; if ((rootSpacing & 0x01) == 0) { rootSpacing++; } // rootSpacing is now the number of spaces between the roots of the two subtrees List allTreeLines = new ArrayList<>(); // strip ANSI escape codes to get length of rendered string. Fixes wrong padding when labels use ANSI escapes for colored nodes. String renderedRootLabel = rootLabel.replaceAll("\\e\\[[\\d;]*[^\\d;]", ""); // add the root and the two branches leading to the subtrees allTreeLines.add(new TreeLine(rootLabel, -(renderedRootLabel.length() - 1) / 2, renderedRootLabel.length() / 2)); // also calculate offset adjustments for left and right subtrees int leftTreeAdjust = 0; int rightTreeAdjust = 0; boolean hasLeftTreeLines = !leftTreeLines.isEmpty(); boolean hasRightTreeLines = !rightTreeLines.isEmpty(); if (hasLeftTreeLines && hasRightTreeLines) { // there's a left and right subtree if (branch == Branch.RECTANGLE) { int adjust = (rootSpacing / 2) + 1; String horizontal = String.join("", Collections.nCopies(rootSpacing / 2, "─")); String branch = "┌" + horizontal + "┴" + horizontal + "┐"; allTreeLines.add(new TreeLine(branch, -adjust, adjust)); rightTreeAdjust = adjust; leftTreeAdjust = -adjust; } else { if (rootSpacing == 1) { allTreeLines.add(new TreeLine("/ \\", -1, 1)); rightTreeAdjust = 2; leftTreeAdjust = -2; } else { for (int i = 1; i < rootSpacing; i += 2) { String branches = "/" + spaces(i) + "\\"; allTreeLines.add(new TreeLine(branches, -((i + 1) / 2), (i + 1) / 2)); } rightTreeAdjust = (rootSpacing / 2) + 1; leftTreeAdjust = -((rootSpacing / 2) + 1); } } } else if (hasLeftTreeLines) { // there's a left subtree only if (branch == Branch.RECTANGLE) { if (directed) { allTreeLines.add(new TreeLine("│", 0, 0)); } else { allTreeLines.add(new TreeLine("┌┘", -1, 0)); leftTreeAdjust = -1; } } else { allTreeLines.add(new TreeLine("/", -1, -1)); leftTreeAdjust = -2; } } else if (hasRightTreeLines) { // there's a right subtree only if (branch == Branch.RECTANGLE) { if (directed) { allTreeLines.add(new TreeLine("│", 0, 0)); } else { allTreeLines.add(new TreeLine("└┐", 0, 1)); rightTreeAdjust = 1; } } else { allTreeLines.add(new TreeLine("\\", 1, 1)); rightTreeAdjust = 2; } } // now add joined lines of subtrees, with appropriate number of separating spaces, and adjusting offsets for (int i = 0; i < maxCount; i++) { TreeLine left, right; if (i >= leftTreeLines.size()) { // nothing remaining on left subtree right = rightTreeLines.get(i); right.leftOffset += rightTreeAdjust; right.rightOffset += rightTreeAdjust; allTreeLines.add(right); } else if (i >= rightTreeLines.size()) { // nothing remaining on right subtree left = leftTreeLines.get(i); left.leftOffset += leftTreeAdjust; left.rightOffset += leftTreeAdjust; allTreeLines.add(left); } else { left = leftTreeLines.get(i); right = rightTreeLines.get(i); int adjustedRootSpacing = (rootSpacing == 1 ? (branch == Branch.RECTANGLE ? 1 : 3) : rootSpacing); TreeLine combined = new TreeLine( left.line + spaces(adjustedRootSpacing - left.rightOffset + right.leftOffset) + right.line, left.leftOffset + leftTreeAdjust, right.rightOffset + rightTreeAdjust ); allTreeLines.add(combined); } } return allTreeLines; } private static int minLeftOffset(List treeLines) { return treeLines.stream().mapToInt(e -> e.leftOffset).min().orElse(0); } private static int maxRightOffset(List treeLines) { return treeLines.stream().mapToInt(e -> e.rightOffset).max().orElse(0); } private static String spaces(int n) { return Strings.repeat(" ", n); } private static class TreeLine { final String line; int leftOffset; int rightOffset; TreeLine(String line, int leftOffset, int rightOffset) { this.line = line; this.leftOffset = leftOffset; this.rightOffset = rightOffset; } } } ================================================ FILE: src/main/java/cn/ponfee/commons/tree/print/BinaryTreePrinterBuilder.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.tree.print; import java.util.function.Function; /** * Binary tree printer builder * * @author Ponfee */ public class BinaryTreePrinterBuilder { private final Appendable output; private final Function nodeLabel; private final Function leftChild; private final Function rightChild; private BinaryTreePrinter.Branch branch = BinaryTreePrinter.Branch.RECTANGLE; private boolean directed = true; private int nodeSpace = 2; private int treeSpace = 5; public BinaryTreePrinterBuilder(Function nodeLabel, Function leftChild, Function rightChild) { this(System.out, nodeLabel, leftChild, rightChild); } public BinaryTreePrinterBuilder(Appendable output, Function nodeLabel, Function leftChild, Function rightChild) { this.output = output; this.nodeLabel = nodeLabel; this.leftChild = leftChild; this.rightChild = rightChild; } public BinaryTreePrinterBuilder branch(BinaryTreePrinter.Branch branch) { this.branch = branch; return this; } public BinaryTreePrinterBuilder directed(boolean directed) { this.directed = directed; return this; } public BinaryTreePrinterBuilder nodeSpace(int nodeSpace) { this.nodeSpace = nodeSpace; return this; } public BinaryTreePrinterBuilder treeSpace(int treeSpace) { this.treeSpace = treeSpace; return this; } public BinaryTreePrinter build() { return new BinaryTreePrinter<>( output, nodeLabel, leftChild, rightChild, branch, directed, nodeSpace, treeSpace ); } } ================================================ FILE: src/main/java/cn/ponfee/commons/tree/print/MultiwayTreePrinter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.tree.print; import cn.ponfee.commons.base.tuple.Tuple4; import cn.ponfee.commons.collect.Collects; import com.google.common.collect.Lists; import java.io.IOException; import java.util.Deque; import java.util.List; import java.util.function.Function; /** * Print multiway tree * * @author Ponfee */ public final class MultiwayTreePrinter { private final Appendable output; private final Function nodeLabel; private final Function> nodeChildren; public MultiwayTreePrinter(Appendable output, Function nodeLabel, Function> nodeChildren) { this.output = output; this.nodeLabel = nodeLabel; this.nodeChildren = nodeChildren; } /*// DFS递归方式 public void print(T root) throws IOException { print("", "", "", root); } private void print(String prefix, String middle, String suffix, T node) throws IOException { output.append(prefix).append(suffix).append(nodeLabel.apply(node)).append('\n'); // print children List children = nodeChildren.apply(node); if (children == null || children.isEmpty()) { return; } if (middle.length() > 0) { prefix += middle; } int index = children.size(); for (T child : children) { if (--index > 0) { print(prefix, "│ ", "├── ", child); } else { // last child of parent, space: (char) 0xa0 print(prefix, " ", "└── ", child); } } } */ public void print(T root) throws IOException { Deque> stack = Collects.newLinkedList(Tuple4.of("", "", "", root)); while (!stack.isEmpty()) { Tuple4 tuple = stack.pop(); output.append(tuple.a).append(tuple.c).append(nodeLabel.apply(tuple.d)).append('\n'); List children = nodeChildren.apply(tuple.d); if (children != null && !children.isEmpty()) { String a = tuple.b.isEmpty() ? tuple.a : tuple.a + tuple.b; int index = 0; for (T child : Lists.reverse(children)) { if (index++ == 0) { // last child of parent, space: (char) 0xa0 stack.push(Tuple4.of(a, " ", "└── ", child)); } else { stack.push(Tuple4.of(a, "│ ", "├── ", child)); } } } } } } ================================================ FILE: src/main/java/cn/ponfee/commons/util/Asserts.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import org.apache.commons.lang3.StringUtils; /** * Extended org.springframework.util.Assert * * @author Ponfee */ public class Asserts extends org.springframework.util.Assert { public static void notEmpty(String text, String msg) { if (StringUtils.isEmpty(text)) { throw new IllegalArgumentException(msg); } } public static void notBlank(String text, String msg) { if (StringUtils.isBlank(text)) { throw new IllegalArgumentException(msg); } } public static void minLen(String text, int min, String msg) { if (text != null && text.length() < min) { throw new IllegalArgumentException(msg); } } public static void maxLen(String text, int max, String msg) { if (text != null && text.length() > max) { throw new IllegalArgumentException(msg); } } public static void range(int value, int min, int max, String msg) { if (value < min || value > max) { throw new IllegalArgumentException(msg); } } public static void rangeLen(String text, int min, int max, String msg) { if (text == null) { return; } range(text.length(), min, max, msg); } } ================================================ FILE: src/main/java/cn/ponfee/commons/util/Base58.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import cn.ponfee.commons.jce.digest.DigestUtils; import org.apache.commons.lang3.ArrayUtils; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.util.Arrays; /** *

     * https://www.jianshu.com/p/ffc97c4d2306
     * 在计算机系统中数值使用补码来表示和存储
     *  原码(True form)               :正数的原码为其二进制;负数的原码为对应的正数值在高位补1;
     *  反码(1's complement):正数的反码与原码相同;负数的反码为其原码除符号位以外各位取反;
     *  补码(2's complement):正数的补码与原码相同;负数的补码为其反码(最低位)加1;
     * 
     * 补码系统的最大优点是可以在加法或减法处理中,不需因为数字的正负而使用不同的计算方式
     * 计算机中只有加法,用两数补码相加,结果仍是补码表示
     * 补码转原码:减1再取反
     * 
     * byte b = a byte number;
     * int i = b & 0xff; // 使得i与b的二进制补码一致
     * 
     * 0xff is the bit length mask, calc bit length mask can use: 
     *      (1<<bits)-1 or -1L^(-1L<<bits)
     * 
     * Base58 code:except number 0, uppercase letter I and O, lowercase latter l
     * Reference from internet
     * 
    * * @author Ponfee */ public class Base58 { private static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); private static final int LENGTH = ALPHABET.length; private static final int[] INDEXES = new int[128]; static { Arrays.fill(INDEXES, -1); for (int i = 0; i < LENGTH; i++) { INDEXES[ALPHABET[i]] = i; } } /** * Encodes the given bytes as a base58 string (no checksum is appended). * @param data the bytes to encode * @return the base58-encoded string */ public static String encode(byte[] data) { if (data.length == 0) { return ""; } // Duplicate data data = Arrays.copyOfRange(data, 0, data.length); // Count leading zeroes. int zeroCount = 0; while (zeroCount < data.length && data[zeroCount] == 0) { ++zeroCount; } // The actual encoding. byte[] temp = new byte[data.length << 1]; int j = temp.length; for (int startAt = zeroCount; startAt < data.length;) { int mod = divmod58(data, startAt); if (data[startAt] == 0) { ++startAt; } temp[--j] = (byte) ALPHABET[mod]; } // Strip extra '1' if there are some after decoding. while (j < temp.length && temp[j] == ALPHABET[0]) { ++j; } // Add as many leading '1' as there were leading zeros. while (--zeroCount >= 0) { temp[--j] = (byte) ALPHABET[0]; } return new String(temp, j, temp.length - j, StandardCharsets.US_ASCII); } /** * Decodes the given base58 string into the original data bytes. * @param data the base58-encoded string to decode * @return the decoded data bytes */ public static byte[] decode(String data) { if (data.length() == 0) { return new byte[0]; } byte[] input58 = new byte[data.length()]; // Transform the String to a base58 byte sequence for (int i = 0; i < data.length(); ++i) { char c = data.charAt(i); int digit58 = -1; if (c >= 0 && c < 128) { digit58 = INDEXES[c]; } if (digit58 < 0) { throw new IllegalArgumentException("Illegal character '" + c + "' at [" + i + "]"); } input58[i] = (byte) digit58; } // Count leading zeroes int zeroCount = 0; while (zeroCount < input58.length && input58[zeroCount] == 0) { ++zeroCount; } // The encoding byte[] temp = new byte[data.length()]; int j = temp.length; int startAt = zeroCount; while (startAt < input58.length) { byte mod = divmod256(input58, startAt); if (input58[startAt] == 0) { ++startAt; } temp[--j] = mod; } // Do no add extra leading zeroes, move j to first non null byte. while (j < temp.length && temp[j] == 0) { ++j; } return Arrays.copyOfRange(temp, j - zeroCount, temp.length); } /** * base58 decode and construct BigInteger * @param data the base58-encoded string to decode * @return the BigInteger of base58-decoded */ public static BigInteger decodeToBigInteger(String data) { return Bytes.toBigInteger(decode(data)); } /** * Encodes the given bytes as a base58 string (with appended checksum). * @param data the bytes to encode * @return the base58-encoded string is appended checksum */ public static String encodeChecked(byte[] data) { return encode(ArrayUtils.addAll(data, checksum(data))); } /** * Decodes the given base58 string into the original data bytes, using the checksum in the * last 4 bytes of the decoded data to verify that the rest are correct. The checksum is * removed from the returned data. * * if the data is not base 58 or the checksum is invalid then throw IllegalArgumentException * * @param data the base58-encoded string to decode (which should include the checksum) */ public static byte[] decodeChecked(String data) { byte[] decoded = decode(data); if (decoded.length < 4) { throw new IllegalArgumentException("Data too short."); } int bounds = decoded.length - 4; byte[] origin = Arrays.copyOfRange(decoded, 0, bounds); byte[] checksum = Arrays.copyOfRange(decoded, bounds, decoded.length); if (!Arrays.equals(checksum, checksum(origin))) { throw new IllegalArgumentException("Invalid checksum."); } return origin; } // --------------------------------------------------------------private methods // number -> number / LENGTH, returns number % LENGTH private static int divmod58(byte[] number, int startAt) { int remainder = 0; for (int i = startAt; i < number.length; i++) { // b & 0xFF再转int是为了保持二进制补码的一致性 // byte b = -127; 补码:10000001 // int i = -127; 补码:111111111111111111111111 10000001 // Integer.toBinaryString(b & 0xFF) --> 10000001 // new BigInteger(1, new byte[] { b }).toString(2); // 10000001 int digit256 = number[i] & 0xFF; int temp = (remainder << 8) + digit256; number[i] = (byte) (temp / LENGTH); remainder = temp % LENGTH; } return remainder; } // number -> number / 256, returns number % 256 private static byte divmod256(byte[] number58, int startAt) { int remainder = 0; for (int i = startAt; i < number58.length; i++) { int digit58 = number58[i] & 0xFF; int temp = remainder * LENGTH + digit58; number58[i] = (byte) (temp / 256); remainder = temp & 0xFF; // % 256 } return (byte) remainder; } private static byte[] checksum(byte[] data) { // twice sha256 byte[] twiceSha256 = DigestUtils.sha256(DigestUtils.sha256(data)); return Arrays.copyOfRange(twiceSha256, 0, 4); // take previous 4 bytes } } ================================================ FILE: src/main/java/cn/ponfee/commons/util/Base64UrlSafe.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import java.util.Base64; /** * Base64 Url Safe * * @author Ponfee */ public class Base64UrlSafe { public static String encode(byte[] data) { return Base64.getUrlEncoder().withoutPadding().encodeToString(data); } public static byte[] decode(String b64) { return Base64.getUrlDecoder().decode(b64); } } ================================================ FILE: src/main/java/cn/ponfee/commons/util/Bytes.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import cn.ponfee.commons.math.Numbers; import org.apache.commons.lang3.StringUtils; import java.math.BigDecimal; import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Formatter; import java.util.Objects; import java.util.zip.CRC32; /** * byte[] *
     * 转hex:new BigInteger(1, bytes).toString(16);
     * Padding4位:(4 - (length & 0x03)) & 0x03
     *
     * 左移<<:      该数对应的二进制码整体左移,左边超出的部分舍弃,右边补0
     * 右移>>:      该数对应的二进制码整体右移,左边部分以原有标志位填充,右边超出的部分舍弃
     * 无符号右移>>>: 该数对应的二进制码整体右移,左边部分以0填充,右边超出的部分舍弃
     * 
    * * @author Ponfee */ public final class Bytes { private static final char SPACE_CHAR = ' '; private static final char[] HEX_LOWER_CODES = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; private static final char[] HEX_UPPER_CODES = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; /** * Dump byte array, like as these * {@link org.apache.commons.io.HexDump#dump(byte[], long, java.io.OutputStream, int)}, * {@link sun.misc.HexDumpEncoder#encode(byte[], java.io.OutputStream);} * * @param data 字节数组 * @param chunk 每行块数 * @param block 每块大小 * @return Dump the byte array as hex string */ public static String dumpHex(byte[] data, int chunk, int block) { Formatter fmt = new Formatter(), text; String lineNumberFormat = "%0" + (Integer.toHexString((data.length + 15) / 16).length() + 1) + "x: "; for (int i, j = 0, wid = block * chunk; j * wid < data.length; j++) { fmt.format(lineNumberFormat, j * wid); // 输出行号:“00000: ” text = new Formatter(); // 右边文本 for (i = 0; i < wid && (i + j * wid) < data.length; i++) { byte b = data[i + j * wid]; fmt.format("%02X ", b); // 输出hex:“B1 ” if ((i + 1) % block == 0 || i + 1 == wid) { fmt.format("%s", SPACE_CHAR); // block与block间加一个空格 } if (b > 0x1F && b < 0x7F) { text.format("%c", b); } else { text.format("%c", '.'); // 非ascii码则输出“.” } } if (i < wid) { // 最后一行空格补全:i为该行的byte数 fmt.format("%s", StringUtils.repeat(SPACE_CHAR, (wid - i) * 3)); // 补全byte位 for (int k = i + 1; k <= wid; k += block) { fmt.format("%s", SPACE_CHAR); // 补全block与block间的空格 } } fmt.format("%s", SPACE_CHAR); // block与text间加一个空格 fmt.format("%s", text); // 输出text:“..@.s.UwH...b{.U” fmt.format("%s", "\n"); // 输出换行 text.close(); } fmt.flush(); try { return fmt.toString(); } finally { fmt.close(); } } public static String dumpHex(byte[] data) { return dumpHex(data, 2, 8); } /** * convert the byte array to binary string * byte: * -1: 11111111 * 0: 00000000 * 127: 01111111 * -128: 10000000 * * @param array * @return */ public static String toBinary(byte... array) { if (array == null || array.length == 0) { return null; } StringBuilder builder = new StringBuilder(array.length << 3); String binary; for (byte b : array) { // byte & 0xFF :byte转int保留bit位 // byte | 0x100:对于正数保留八位,保证未尾8位为原byte的bit位,即1xxxxxxxx // 正数会有前缀0,如果不加,转binary string时前面的0会被舍去 // 也可以用 “byte + 0x100”或者“leftPad(binaryString, 8, '0')” binary = Integer.toBinaryString((b & 0xFF) | 0x100); builder.append(binary, 1, binary.length()); } return builder.toString(); } // -----------------------------------------------------------------hexEncode/hexDecode public static void encodeHex(char[] charArray, int i, byte b) { charArray[ i] = HEX_LOWER_CODES[(0xF0 & b) >>> 4]; charArray[++i] = HEX_LOWER_CODES[ 0x0F & b ]; } public static String encodeHex(byte b, boolean lowercase) { char[] codes = lowercase ? HEX_LOWER_CODES : HEX_UPPER_CODES; return new String(new char[] { codes[(0xF0 & b) >>> 4], codes[0x0F & b] }); } public static String encodeHex(byte[] bytes) { return encodeHex(bytes, true); } /** * encode the byte array the hex string * @param bytes * @param lowercase * @return */ public static String encodeHex(byte[] bytes, boolean lowercase) { //new BigInteger(1, bytes).toString(16); int len = bytes.length; char[] out = new char[len << 1]; char[] codes = lowercase ? HEX_LOWER_CODES : HEX_UPPER_CODES; // one byte -> two char for (int i = 0, j = 0; i < len; i++) { out[j++] = codes[(0xF0 & bytes[i]) >>> 4]; out[j++] = codes[ 0x0F & bytes[i] ]; } return new String(out); } /** * Decode hex string to byte array * * @param hex the hex string * @return byte array */ public static byte[] decodeHex(String hex) { int len = hex.length(); if ((len & 0x01) == 1) { throw new IllegalArgumentException("Hex string must be twice length."); } byte[] out = new byte[len >> 1]; // two char -> one byte for (int i = 0, j = 0; j < len; i++, j += 2) { char c1 = hex.charAt(j), c2 = hex.charAt(j + 1); out[i] = (byte) (Character.digit(c1, 16) << 4 | Character.digit(c2, 16)); } return out; } // -----------------------------------------------------------------String /** * Converts byte array to String * * @param bytes the byte array * @return a string */ public static String toString(byte[] bytes) { return bytes == null ? null : new String(bytes); } /** * Converts byte array to String * * @param bytes the byte array * @param charset the Charset * @return a string */ public static String toString(byte[] bytes, Charset charset) { return bytes == null ? null : new String(bytes, charset); } /** * Converts String to byte array * * @param value the string value * @return a byte array */ public static byte[] toBytes(String value) { return value == null ? null : value.getBytes(); } /** * Converts string to byte array * * @param value the string value * @param charset the charset * @return a byte array */ public static byte[] toBytes(String value, Charset charset) { return value == null ? null : value.getBytes(charset); } // -----------------------------------------------------------------char array /** * Converts byte array to char array * * @param bytes the byte array * @return a char array */ public static char[] toCharArray(byte[] bytes) { return toCharArray(bytes, StandardCharsets.US_ASCII); } /** * Converts byte array to char array * * @param bytes the byte array * @param charset the charset * @return a char array */ public static char[] toCharArray(byte[] bytes, Charset charset) { //return new String(bytes, charset).toCharArray(); ByteBuffer buffer = ByteBuffer.allocate(bytes.length); buffer.put(bytes); buffer.flip(); return charset.decode(buffer).array(); } /** * Converts char array to byte array * * @param chars the char array * @return a byte array */ public static byte[] toBytes(char[] chars) { return toBytes(chars, StandardCharsets.US_ASCII); } /** * Converts char array to byte array * * @param chars the char array * @param charset the charset * @return a byte array */ public static byte[] toBytes(char[] chars, Charset charset) { //return new String(chars).getBytes(charset); CharBuffer buffer = CharBuffer.allocate(chars.length); buffer.put(chars); buffer.flip(); return charset.encode(buffer).array(); } // -----------------------------------------------------------------short public static byte[] toBytes(short value) { return new byte[] { (byte) (value >>> 8), (byte) value }; //return ByteBuffer.allocate(Short.BYTES).putShort(value).array(); } public static short toShort(byte[] bytes) { return toShort(bytes, 0); } public static short toShort(byte[] bytes, int fromIdx) { return (short) ( (bytes[ fromIdx] ) << 8 | (bytes[++fromIdx] & 0xFF) ); //return ByteBuffer.wrap(bytes, fromIdx, Short.BYTES).getShort(); //ByteBuffer buffer = ByteBuffer.allocate(Short.BYTES); //buffer.put(bytes, fromIdx, Short.BYTES).flip(); //return buffer.getShort(); } // -----------------------------------------------------------------char public static byte[] toBytes(char value) { return new byte[] { (byte) (value >>> 8), (byte) value }; } public static char toChar(byte[] bytes) { return toChar(bytes, 0); } public static char toChar(byte[] bytes, int fromIdx) { return (char) ( (bytes[ fromIdx] ) << 8 | (bytes[++fromIdx] & 0xFF) ); } // -----------------------------------------------------------------int public static byte[] toBytes(int value) { return new byte[] { (byte) (value >>> 24), (byte) (value >>> 16), (byte) (value >>> 8), (byte) (value ) }; } public static int toInt(byte[] bytes) { return toInt(bytes, 0); } public static int toInt(byte[] bytes, int fromIdx) { return (bytes[ fromIdx] ) << 24 // 高8位转int后左移24位,刚好剩下原来的8位,故不用&0xFF | (bytes[++fromIdx] & 0xFF) << 16 // 其它转int:若为负数,则是其补码表示,故要&0xFF | (bytes[++fromIdx] & 0xFF) << 8 | (bytes[++fromIdx] & 0xFF); } // -----------------------------------------------------------------long /** * convert long value to byte array * @param value the long number * @return byte array */ public static byte[] toBytes(long value) { return new byte[] { (byte) (value >>> 56), (byte) (value >>> 48), (byte) (value >>> 40), (byte) (value >>> 32), (byte) (value >>> 24), (byte) (value >>> 16), (byte) (value >>> 8), (byte) (value ) }; } /** * convert byte array to long number * @param bytes the byte array * @param fromIdx the byte array offset * @return long number */ public static long toLong(byte[] bytes, int fromIdx) { return ((long) bytes[ fromIdx] ) << 56 | ((long) bytes[++fromIdx] & 0xFF) << 48 | ((long) bytes[++fromIdx] & 0xFF) << 40 | ((long) bytes[++fromIdx] & 0xFF) << 32 | ((long) bytes[++fromIdx] & 0xFF) << 24 | ((long) bytes[++fromIdx] & 0xFF) << 16 | ((long) bytes[++fromIdx] & 0xFF) << 8 | ((long) bytes[++fromIdx] & 0xFF); } /** * convert byte array to long number * @param bytes the byte array * @return */ public static long toLong(byte[] bytes) { return toLong(bytes, 0); } /** * Long value to hex string * * @param value the long value * @return String of long value hex string */ public static String toHex(long value) { return toHex(value, true); } /** * Long value to hex string * * @param value the long value * @param lowercase {@code true} if lowercase hex string, else uppercase * @return String of long value hex string */ public static String toHex(long value, boolean lowercase) { char[] a = lowercase ? HEX_LOWER_CODES : HEX_UPPER_CODES; int mask = 0x0F; return new String(new char[]{ a[ (int) (value >>> 60)], a[mask & (int) (value >>> 56)], a[mask & (int) (value >>> 52)], a[mask & (int) (value >>> 48)], a[mask & (int) (value >>> 44)], a[mask & (int) (value >>> 40)], a[mask & (int) (value >>> 36)], a[mask & (int) (value >>> 32)], a[mask & (int) (value >>> 28)], a[mask & (int) (value >>> 24)], a[mask & (int) (value >>> 20)], a[mask & (int) (value >>> 16)], a[mask & (int) (value >>> 12)], a[mask & (int) (value >>> 8)], a[mask & (int) (value >>> 4)], a[mask & (int) (value )] }); } // -----------------------------------------------------------------float public static byte[] toBytes(float value) { return toBytes(Float.floatToIntBits(value)); } public static float toFloat(byte[] bytes) { return toFloat(bytes, 0); } public static float toFloat(byte[] bytes, int fromIdx) { return Float.intBitsToFloat(toInt(bytes, fromIdx)); } // -----------------------------------------------------------------double public static byte[] toBytes(double value) { return toBytes(Double.doubleToLongBits(value)); } public static double toDouble(byte[] bytes) { return toDouble(bytes, 0); } public static double toDouble(byte[] bytes, int fromIdx) { return Double.longBitsToDouble(toLong(bytes, fromIdx)); } // ---------------------------------------------------------BigDecimal /** * Convert a BigDecimal value to a byte array * * @param val the BigDecimal value * @return the byte array */ public static byte[] toBytes(BigDecimal val) { byte[] valueBytes = val.unscaledValue().toByteArray(); byte[] result = new byte[valueBytes.length + Integer.BYTES]; put(val.scale(), result, 0); System.arraycopy(valueBytes, 0, result, 4, valueBytes.length); return result; } /** * Puts int number to byte array * * @param val the int value * @param bytes the byte array * @param offset the byte array start offset */ public static void put(int val, byte[] bytes, int offset) { for (int i = 3; i >= 0; i--, offset++) { bytes[offset] = (byte) (val >>> (i << 3)); } } public static void put(long val, byte[] bytes, int offset) { for (int i = 7; i >= 0; i--, offset++) { bytes[offset] = (byte) (val >>> (i << 3)); } } /** * Converts a byte array to a BigDecimal * * @param bytes * @return the char value */ public static BigDecimal toBigDecimal(byte[] bytes) { return toBigDecimal(bytes, 0, bytes.length); } /** * Converts a byte array to a BigDecimal value * * @param bytes * @param offset * @param length * @return the char value */ public static BigDecimal toBigDecimal(byte[] bytes, int offset, final int length) { if (bytes == null || length < Integer.BYTES + 1 || (offset + length > bytes.length)) { return null; } int scale = toInt(bytes, offset); byte[] tcBytes = new byte[length - Integer.BYTES]; System.arraycopy(bytes, offset + Integer.BYTES, tcBytes, 0, length - Integer.BYTES); return new BigDecimal(new BigInteger(tcBytes), scale); } // ---------------------------------------------------------BigInteger /** * Converts byte array to positive BigInteger * * @param bytes the byte array * @return a positive BigInteger number */ public static BigInteger toBigInteger(byte[] bytes) { if (bytes == null || bytes.length == 0) { return BigInteger.ZERO; } return new BigInteger(1, bytes); } // ----------------------------------------------------------others /** * merge byte arrays * @param first first byte array of args * @param rest others byte array * @return a new byte array of them */ public static byte[] concat(byte[] first, byte[]... rest) { Objects.requireNonNull(first, "the first array arg cannot be null"); if (rest == null || rest.length == 0) { return first; } int totalLength = first.length; for (byte[] array : rest) { if (array != null) { totalLength += array.length; } } byte[] result = Arrays.copyOf(first, totalLength); int offset = first.length; for (byte[] array : rest) { if (array != null) { System.arraycopy(array, 0, result, offset, array.length); offset += array.length; } } return result; /* ByteArrayOutputStream baos = new ByteArrayOutputStream(totalLength); baos.write(first, 0, first.length); for (byte[] array : rest) { if (array != null) { baos.write(array, 0, array.length); } } return baos.toByteArray(); */ } public static void tailCopy(byte[] src, int destLen) { tailCopy(src, new byte[destLen]); } public static void tailCopy(byte[] src, byte[] dest) { tailCopy(src, 0, src.length, dest, 0, dest.length); } /** * copy src to dest * 从尾部开始拷贝src到dest: * 若src数据不足则在dest前面补0 * 若src数据有多则舍去src前面的数据 * * @param src * @param srcFrom * @param srcLen * @param dest * @param destFrom * @param destLen */ public static void tailCopy(byte[] src, int srcFrom, int srcLen, byte[] dest, int destFrom, int destLen) { tailCopy(src, srcFrom, srcLen, dest, destFrom, destLen, Numbers.ZERO_BYTE); } /** * copy src to dest * 从尾部开始拷贝src到dest: * 若src数据不足则在dest前面补heading * 若src数据有多则舍去src前面的数据 * * @param src * @param srcFrom * @param srcLen * @param dest * @param destFrom * @param destLen * @param heading */ public static void tailCopy(byte[] src, int srcFrom, int srcLen, byte[] dest, int destFrom, int destLen, byte heading) { int srcTo = Math.min(src.length, srcFrom + srcLen), destTo = Math.min(dest.length, destFrom + destLen); for (int i = destTo - 1, j = srcTo - 1; i >= destFrom; i--, j--) { dest[i] = (j < srcFrom) ? heading : src[j]; } } /** * copy src to dest * 从首部开始拷贝src到dest: * 若src数据不足则在dest后面补tailing * 若src数据有多则舍去src后面的数据 * * @param src * @param dest */ public static void headCopy(byte[] src, byte[] dest) { headCopy(src, 0, src.length, dest, 0, dest.length, Numbers.ZERO_BYTE); } public static void headCopy(byte[] src, int srcFrom, int srcLen, byte[] dest, int destFrom, int destLen, byte tailing) { int srcTo = Math.min(src.length, srcFrom + srcLen), destTo = Math.min(dest.length, destFrom + destLen); for (int i = destFrom, j = srcFrom; i < destTo; i++, j++) { dest[i] = (j < srcTo) ? src[j] : tailing; } } public static long crc32(byte[] bytes) { CRC32 crc32 = new CRC32(); crc32.update(bytes); return crc32.getValue(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/util/CRC16.java ================================================ /** * Copyright (c) 2013-2019 Nikita Koksharov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cn.ponfee.commons.util; /** * @author
    Mark Paluch * * @see java.util.zip.CRC32#CRC32() */ public final class CRC16 { private static final int[] LOOKUP_TABLE = { 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 }; private CRC16() {} public static int digest(byte[] bytes) { int crc = 0x0000; for (byte b : bytes) { crc = (crc << 8) ^ LOOKUP_TABLE[((crc >>> 8) ^ (b & 0xFF)) & 0xFF]; } return crc & 0xFFFF; } } ================================================ FILE: src/main/java/cn/ponfee/commons/util/Captchas.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import javax.imageio.ImageIO; import java.awt.*; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.OutputStream; import static java.util.concurrent.ThreadLocalRandom.current; /** * 图片验证码生成类 * * @author Ponfee */ public class Captchas { // 使用到Algerian字体,系统里没有的话需要安装字体 // 去掉了1,0,i,o几个容易混淆的字符 private static final char[] CODES = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ".toCharArray(); private static final Color[] COLOR_SPACES = { Color.RED, Color.GRAY, Color.YELLOW, Color.WHITE, Color.GREEN, Color.CYAN, Color.PINK, Color.BLUE, Color.MAGENTA, Color.ORANGE, Color.LIGHT_GRAY }; /** * 使用系统默认字符源生成验证码 * @param size 验证码长度 * @return */ public static String random(int size) { return random(size, CODES); } /** * 使用指定源生成验证码 * @param size 验证码长度 * @param sources 验证码字符源 * @return */ public static String random(int size, char[] sources) { if (sources == null || sources.length == 0) { sources = CODES; } StringBuilder codes = new StringBuilder(size); for (int i = 0, length = sources.length; i < size; i++) { codes.append(sources[SecureRandoms.nextInt(length)]); } return codes.toString(); } public static void generate(int width, OutputStream out, String code) { generate(width, (int) Math.ceil(width * 0.618D), out, code); } /** * 输出指定验证码图片流 * @param width * @param height * @param out * @param code */ public static void generate(int width, int height, OutputStream out, String code) { BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics2D g2 = image.createGraphics(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); Color[] colors = new Color[5]; for (int i = 0; i < colors.length; i++) { colors[i] = COLOR_SPACES[current().nextInt(COLOR_SPACES.length)]; } g2.setColor(Color.GRAY); // 设置边框色 g2.fillRect(0, 0, width, height); Color c = getRandColor(200, 250); g2.setColor(c); // 设置背景色 g2.fillRect(0, 2, width, height - 4); // 绘制干扰线 g2.setColor(getRandColor(160, 200)); // 设置线条的颜色 for (int i = 0; i < 15; i++) { int x = current().nextInt(width - 1); int y = current().nextInt(height - 1); int xl = current().nextInt(6) + 1; int yl = current().nextInt(12) + 1; g2.drawLine(x, y, x + xl + 40, y + yl + 20); } // 添加噪点 float yawpRate = 0.03f; // 噪声率 int area = (int) (yawpRate * width * height); for (int i = 0; i < area; i++) { int x = current().nextInt(width); int y = current().nextInt(height); int rgb = getRandomIntColor(); image.setRGB(x, y, rgb); } shear(g2, width, height, c); // 使图片扭曲 g2.setColor(getRandColor(100, 160)); int fontSize = height - 14; g2.setFont(new Font("Algerian", Font.ITALIC, fontSize)); char[] chars = code.toCharArray(); int size = code.length(); for (int i = 0; i < size; i++) { AffineTransform affine = new AffineTransform(); int signum = (current().nextBoolean() ? 1 : -1); affine.setToRotation( Math.PI / 4.0D * current().nextDouble() * signum, width / size * i + fontSize / 2, height / 2 ); g2.setTransform(affine); int x = 1, y = 4; g2.drawChars( chars, i, 1, (width - 7) / size * i + x, height / 2 + fontSize / 2 - y ); } g2.dispose(); try { ImageIO.write(image, "JPEG", out); //JPEGCodec.createJPEGEncoder(out).encode(image); } catch (IOException e) { throw new RuntimeException(e); } } //-------------------------private methods private static Color getRandColor(int fc, int bc) { fc = Math.min(fc, 255); bc = Math.min(bc, 255); int r = fc + current().nextInt(bc - fc); int g = fc + current().nextInt(bc - fc); int b = fc + current().nextInt(bc - fc); return new Color(r, g, b); } private static int getRandomIntColor() { int color = 0; for (int c : getRandomRgb()) { color = (color << 8) | c; } return color; } private static int[] getRandomRgb() { return new int[] { current().nextInt(256), current().nextInt(256), current().nextInt(256) }; } private static void shear(Graphics g, int w1, int h1, Color color) { shearX(g, w1, h1, color); shearY(g, w1, h1, color); } private static void shearX(Graphics g, int w, int h, Color color) { int period = current().nextInt(2), phase = current().nextInt(2); double frames = 2 * Math.PI * phase; for (int d, i = 0; i < h; i++) { d = (int) ((period >> 1) * Math.sin((double) i / period + frames)); g.copyArea(0, i, w, 1, d, 0); //if (current().nextBoolean()) { g.setColor(color); g.drawLine(d, i, 0, i); g.drawLine(d + w, i, w, i); //} } } private static void shearY(Graphics g, int w, int h, Color color) { int period = current().nextInt(40) + 10, // 50 phase = 7; double frames = (2 * Math.PI * phase) / 20.0D; for (int d, i = 0; i < w; i++) { d = (int) ((period >> 1) * Math.sin((double) i / period + frames)); g.copyArea(i, 0, 1, h, 0, d); //if (current().nextBoolean()) { g.setColor(color); g.drawLine(i, d, i, 0); g.drawLine(i, d + h, i, h); //} } } } ================================================ FILE: src/main/java/cn/ponfee/commons/util/Colors.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import java.awt.Color; /** * color rgb and hex transform * * @author Ponfee */ public final class Colors { public static Color fromHex(String hex) { if (hex == null || hex.isEmpty()) { return null; } return new Color(Integer.parseInt(hex.substring(1), 16)); } public static String toHex(Color c) { return '#' + hex(c.getRed()) + hex(c.getGreen()) + hex(c.getBlue()); } private static String hex(int i) { // StringUtils.leftPad(Integer.toHexString(i), 2, "0") return String.format("%02x", i); } } ================================================ FILE: src/main/java/cn/ponfee/commons/util/ConsistentHash.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import org.apache.commons.codec.digest.DigestUtils; import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.Iterator; import java.util.SortedMap; import java.util.TreeMap; import java.util.function.Function; /** * Consistent hashing algorithm. * * @param the ring node type * @author Ponfee */ public class ConsistentHash { /** * Hash String to long value */ @FunctionalInterface public interface HashFunction { /** * Returns key's int hash value * * @param key string key * @return int hash value */ int hash(String key); HashFunction MD5 = key -> { byte[] digest = DigestUtils.md5(key); assert digest.length == 16; int hash = 0; for (int i = 15; i >= 3; ) { int h = ((digest[i--] & 0xFF) << 24) | ((digest[i--] & 0xFF) << 16) | ((digest[i--] & 0xFF) << 8) | ((digest[i--] & 0xFF) ); hash = (i == 11) ? h : (hash ^ h); } return hash; }; HashFunction FNV = key -> { int p = 16777619; int h = (int) 2166136261L; for (int i = 0; i < key.length(); i++) { h = (h ^ key.charAt(i)) * p; } h += h << 13; h ^= h >> 7; h += h << 3; h ^= h >> 17; h += h << 5; return h; }; HashFunction CRC16 = key -> cn.ponfee.commons.util.CRC16.digest(key.getBytes(StandardCharsets.UTF_8)); } /** * Virtual node of the consistent hash ring. */ private class VirtualNode { private final T physicalNode; private final String physicalKey; private final String virtualKey; private VirtualNode(T physicalNode, int replicaIndex) { this.physicalNode = physicalNode; this.physicalKey = keyMapper.apply(physicalNode); this.virtualKey = "SHARD-" + physicalKey + "-NODE-" + replicaIndex; } private boolean isVirtualNodeOf(T pNode) { return physicalKey.equals(keyMapper.apply(pNode)); } } private final TreeMap ring = new TreeMap<>(); private final Function keyMapper; private final HashFunction hashFunction; public ConsistentHash(Collection pNodes, int vNodeCount) { this(pNodes, vNodeCount, String::valueOf, HashFunction.MD5); } public ConsistentHash(Collection pNodes, int vNodeCount, Function keyMapper) { this(pNodes, vNodeCount, keyMapper, HashFunction.MD5); } /** * @param pNodes collections of physical nodes * @param vNodeCount number of virtual nodes * @param keyMapper physical node mapping to string key function * @param hashFunction hash function to hash node instances */ public ConsistentHash(Collection pNodes, int vNodeCount, Function keyMapper, HashFunction hashFunction) { if (keyMapper == null) { throw new NullPointerException("Key mapper is null."); } if (hashFunction == null) { throw new NullPointerException("Hash function is null."); } this.keyMapper = keyMapper; this.hashFunction = hashFunction; if (pNodes != null) { for (T pNode : pNodes) { addNode(pNode, vNodeCount); } } } /** * Add physic node to the hash ring with some virtual nodes * * @param pNode physical node * @param vNodeCount the number virtual node of the physical node. */ public void addNode(T pNode, int vNodeCount) { if (vNodeCount < 0) { throw new IllegalArgumentException("Invalid virtual node counts :" + vNodeCount); } int existingReplicas = getExistingReplicas(pNode); for (int i = 0; i < vNodeCount; i++) { VirtualNode vNode = new VirtualNode(pNode, i + existingReplicas); ring.put(hashFunction.hash(vNode.virtualKey), vNode); } } /** * Remove the physical node from the hash ring * * @param pNode the physical node */ public void removeNode(T pNode) { Iterator it = ring.keySet().iterator(); while (it.hasNext()) { Integer key = it.next(); VirtualNode virtualNode = ring.get(key); if (virtualNode.isVirtualNodeOf(pNode)) { it.remove(); } } } /** * Returns the physical node of counted specified key * * @param key the key to find the nearest physical node * @return routed physical node */ public T routeNode(String key) { if (ring.isEmpty()) { return null; } SortedMap tailMap = ring.tailMap(hashFunction.hash(key)); VirtualNode virtualNode = tailMap.isEmpty() ? ring.firstEntry().getValue() : ring.get(tailMap.firstKey()); return virtualNode.physicalNode; } public int getExistingReplicas(T pNode) { return (int) ring.entrySet() .stream() .filter(e -> e.getValue().isVirtualNodeOf(pNode)) .count(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/util/CurrencyEnum.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import java.util.Currency; import java.util.Map; /** * Currency enum definition. * * * * @author Ponfee * @see java.util.Currency */ public enum CurrencyEnum { /** 人民币 [¥], previous: {0xffe5}, Same symbol as JPY Japan Yen. */ CNY(new char[]{0xa5}), /** 美元 [US$] */ USD(new char[]{0x55, 0x53, 0x24}), /** 港元 [HK$] */ HKD(new char[]{0x48, 0x4b, 0x24}), /** 台币 [NT$] */ TWD(new char[]{0x4e, 0x54, 0x24}), /** 欧元 [€] */ EUR(new char[]{0x20ac}), /** 英镑 [£], previous: {0xffe1} */ GBP(new char[]{0xa3}), /** 日元 [¥], Same symbol as CNY China Yuan Renminbi. */ JPY(new char[]{0xa5}), /** 巴西雷亚尔 [R$] */ BRL(new char[]{0x52, 0x24}), /** 俄罗斯卢布 [₽] */ RUB(new char[]{0x20bd}), /** 澳元 [AU$] */ AUD(new char[]{0x41, 0x55, 0x24}), /** 加元 [CA$] */ CAD(new char[]{0x43, 0x41, 0x24}), /** 印度卢比 [₹], previous: {0x52, 0x73, 0x2e} */ INR(new char[]{0x20b9}), /** 乌克兰里夫纳 [₴], previous: {0x433, 0x440, 0x43d, 0x2e} */ UAH(new char[]{0x20b4}), /** 墨西哥比索 [MX$] */ MXN(new char[]{0x4d, 0x58, 0x24}), /** 瑞士法郎 [CHF] */ CHF(new char[]{0x43, 0x48, 0x46}), /** 新加坡元 [SG$] */ SGD(new char[]{0x53, 0x47, 0x24}), /** 波兰兹罗提 [zł] */ PLN(new char[]{0x7a, 0x142}), /** 瑞典克朗 [kr] */ SEK(new char[]{0x6b, 0x72}), /** 智利比索 [CL$] */ CLP(new char[]{0x43, 0x4c, 0x24}), /** 韩元 [₩] */ KRW(new char[]{0x20a9}), /** 肯尼亚先令 [KSh] */ KES(new char[]{0x4b, 0x53, 0x68}), /** 澳门元 [MOP] */ MOP(new char[]{0x4d, 0x4f, 0x50}), /** 印度尼西亚卢比 [Rp] */ IDR(new char[]{0x52, 0x70}), /** 沙特里亚尔 [﷼] */ SAR(new char[]{0xfdfc}), /** 保加利亚列弗 [лв] */ BGN(new char[]{0x43b, 0x432}), /** 罗马尼亚新列伊 [lei] */ RON(new char[]{0x6c, 0x65, 0x69}), /** 捷克克朗 [Kč] */ CZK(new char[]{0x4b, 0x10d}), /** 匈牙利福林 [Ft] */ HUF(new char[]{0x46, 0x74}), /** 越南盾 [₫] */ VND(new char[]{0x20ab}), /** 马来西亚林吉特 [RM] */ MYR(new char[]{0x52, 0x4d}), /** 菲律宾比索 [₱] */ PHP(new char[]{0x20b1}), /** 泰铢 [฿] */ THB(new char[]{0xe3f}), /** 巴基斯坦卢比 [₨] */ PKR(new char[]{0x20a8}), /** 挪威克朗 [kr] */ NOK(new char[]{0x6b, 0x72}), /** 丹麦克朗 [kr] */ DKK(new char[]{0x6b, 0x72}), /** 阿联酋迪拉姆 [AED] */ AED(new char[]{0x41, 0x45, 0x44}), /** 阿富汗尼 [؋] */ AFN(new char[]{0x60b}), /** 阿尔巴尼列克 [Lek] */ ALL(new char[]{0x4c, 0x65, 0x6b}), /** 亚美尼亚德拉姆 [AMD] */ AMD(new char[]{0x41, 0x4d, 0x44}), /** 荷兰盾 [ƒ] */ ANG(new char[]{0x192}), /** 安哥拉宽扎 [AOA] */ AOA(new char[]{0x41, 0x4f, 0x41}), /** 阿根廷比索 [$] */ ARS(new char[]{0x24}), /** 阿鲁巴或荷兰盾 [ƒ] */ AWG(new char[]{0x192}), /** 阿塞拜疆新马纳特 [₼], previous: {0x43c, 0x430, 0x43d} */ AZN(new char[]{0x20bc}), /** 波斯尼亚可兑换马尔卡 [KM] */ BAM(new char[]{0x4b, 0x4d}), /** 巴巴多斯元 [$] */ BBD(new char[]{0x24}), /** 孟加拉国塔卡 [BDT] */ BDT(new char[]{0x42, 0x44, 0x54}), /** 巴林第纳尔 [BHD] */ BHD(new char[]{0x42, 0x48, 0x44}), /** 布隆迪法郎 [BIF] */ BIF(new char[]{0x42, 0x49, 0x46}), /** 百慕大元 [$] */ BMD(new char[]{0x24}), /** 文莱元 [$] */ BND(new char[]{0x24}), /** 玻利维亚诺 [$b] */ BOB(new char[]{0x24, 0x62}), /** 巴哈马元 [$] */ BSD(new char[]{0x24}), /** 不丹努尔特鲁姆 [BTN] */ BTN(new char[]{0x42, 0x54, 0x4e}), /** 博茨瓦纳普拉 [P] */ BWP(new char[]{0x50}), /** 白俄罗斯卢布 [p.] */ BYR(new char[]{0x70, 0x2e}), /** 伯利兹元 [BZ$] */ BZD(new char[]{0x42, 0x5a, 0x24}), /** 刚果法郎 [CDF] */ CDF(new char[]{0x43, 0x44, 0x46}), /** 哥伦比亚比索 [$] */ COP(new char[]{0x24}), /** 哥斯达黎加科朗 [₡] */ CRC(new char[]{0x20a1}), /** 古巴可兑换比索 [CUC] */ CUC(new char[]{0x43, 0x55, 0x43}), /** 古巴比索 [₱] */ CUP(new char[]{0x20b1}), /** 佛得角埃斯库多 [CVE] */ CVE(new char[]{0x43, 0x56, 0x45}), /** 吉布提法郎 [DJF] */ DJF(new char[]{0x44, 0x4a, 0x46}), /** 多米尼加比索 [RD$] */ DOP(new char[]{0x52, 0x44, 0x24}), /** 阿尔及利亚第纳尔 [DZD] */ DZD(new char[]{0x44, 0x5a, 0x44}), /** 埃及镑 [£] */ EGP(new char[]{0xa3}), /** 厄立特里亚纳克法 [ERN] */ ERN(new char[]{0x45, 0x52, 0x4e}), /** 埃塞俄比亚比尔 [ETB] */ ETB(new char[]{0x45, 0x54, 0x42}), /** 斐济元 [$] */ FJD(new char[]{0x24}), /** 福克兰群岛镑 [£] */ FKP(new char[]{0xa3}), /** 格鲁吉亚拉里 [GEL] */ GEL(new char[]{0x47, 0x45, 0x4c}), /** 加纳塞地 [¢], previous: {0x47, 0x48, 0x53} */ GHS(new char[]{0xa2}), /** 直布罗陀镑 [£] */ GIP(new char[]{0xa3}), /** 冈比亚达拉西 [GMD] */ GMD(new char[]{0x47, 0x4d, 0x44}), /** 几内亚法郎 [GNF] */ GNF(new char[]{0x47, 0x4e, 0x46}), /** 危地马拉格查尔 [Q] */ GTQ(new char[]{0x51}), /** 圭亚那元 [$] */ GYD(new char[]{0x24}), /** 洪都拉斯伦皮拉 [L] */ HNL(new char[]{0x4c}), /** 克罗地亚库纳 [kn] */ HRK(new char[]{0x6b, 0x6e}), /** 海地古德 [HTG] */ HTG(new char[]{0x48, 0x54, 0x47}), /** 以色列谢克尔 [₪] */ ILS(new char[]{0x20aa}), /** 伊拉克第纳尔 [IQD] */ IQD(new char[]{0x49, 0x51, 0x44}), /** 伊朗里亚尔 [﷼] */ IRR(new char[]{0xfdfc}), /** 冰岛克朗 [kr] */ ISK(new char[]{0x6b, 0x72}), /** 牙买加元 [J$] */ JMD(new char[]{0x4a, 0x24}), /** 约旦第纳尔 [JOD] */ JOD(new char[]{0x4a, 0x4f, 0x44}), /** 吉尔吉斯斯坦索姆 [лв] */ KGS(new char[]{0x43b, 0x432}), /** 柬埔寨瑞尔 [៛] */ KHR(new char[]{0x17db}), /** 科摩罗法郎 [KMF] */ KMF(new char[]{0x4b, 0x4d, 0x46}), /** 朝鲜元 [₩] */ KPW(new char[]{0x20a9}), /** 科威特第纳尔 [KWD] */ KWD(new char[]{0x4b, 0x57, 0x44}), /** 开曼元 [$] */ KYD(new char[]{0x24}), /** 哈萨克斯坦坚戈 [лв] */ KZT(new char[]{0x43b, 0x432}), /** 老挝基普 [₭] */ LAK(new char[]{0x20ad}), /** 黎巴嫩镑 [£] */ LBP(new char[]{0xa3}), /** 斯里兰卡卢比 [₨] */ LKR(new char[]{0x20a8}), /** 利比里亚元 [$] */ LRD(new char[]{0x24}), /** 巴索托洛蒂 [LSL] */ LSL(new char[]{0x4c, 0x53, 0x4c}), /** 利比亚第纳尔 [LYD] */ LYD(new char[]{0x4c, 0x59, 0x44}), /** 摩洛哥迪拉姆 [MAD] */ MAD(new char[]{0x4d, 0x41, 0x44}), /** 摩尔多瓦列伊 [MDL] */ MDL(new char[]{0x4d, 0x44, 0x4c}), /** 马尔加什阿里亚 [MGA] */ MGA(new char[]{0x4d, 0x47, 0x41}), /** 马其顿第纳尔 [ден] */ MKD(new char[]{0x434, 0x435, 0x43d}), /** 缅元 [MMK] */ MMK(new char[]{0x4d, 0x4d, 0x4b}), /** 蒙古图格里克 [₮] */ MNT(new char[]{0x20ae}), /** 毛里塔尼亚乌吉亚 [MRO] */ MRO(new char[]{0x4d, 0x52, 0x4f}), /** 毛里塔尼亚卢比 [₨] */ MUR(new char[]{0x20a8}), /** 马尔代夫拉菲亚 [MVR] */ MVR(new char[]{0x4d, 0x56, 0x52}), /** 马拉维克瓦查 [MWK] */ MWK(new char[]{0x4d, 0x57, 0x4b}), /** 莫桑比克梅蒂卡尔 [MT] */ MZN(new char[]{0x4d, 0x54}), /** 纳米比亚元 [$] */ NAD(new char[]{0x24}), /** 尼日利亚奈拉 [₦] */ NGN(new char[]{0x20a6}), /** 尼加拉瓜科多巴 [C$] */ NIO(new char[]{0x43, 0x24}), /** 尼泊尔卢比 [₨] */ NPR(new char[]{0x20a8}), /** 新西兰元 [NZ$] */ NZD(new char[]{0x4e, 0x5a, 0x24}), /** 阿曼里亚尔 [﷼] */ OMR(new char[]{0xfdfc}), /** 巴拿马巴波亚 [B/.] */ PAB(new char[]{0x42, 0x2f, 0x2e}), /** 秘鲁新索尔 [S/.] */ PEN(new char[]{0x53, 0x2f, 0x2e}), /** 巴布亚新几内亚基那 [PGK] */ PGK(new char[]{0x50, 0x47, 0x4b}), /** 巴拉圭瓜拉尼 [Gs] */ PYG(new char[]{0x47, 0x73}), /** 卡塔尔里亚尔 [﷼] */ QAR(new char[]{0xfdfc}), /** 塞尔维亚第纳尔 [Дин.] */ RSD(new char[]{0x414, 0x438, 0x43d, 0x2e}), /** 卢旺达法郎 [RWF] */ RWF(new char[]{0x52, 0x57, 0x46}), /** 所罗门群岛元 [$] */ SBD(new char[]{0x24}), /** 塞舌尔卢比 [₨] */ SCR(new char[]{0x20a8}), /** 苏丹镑 [SDG] */ SDG(new char[]{0x53, 0x44, 0x47}), /** 圣赫勒拿镑 [£] */ SHP(new char[]{0xa3}), /** 塞拉利昂利昂 [SLL] */ SLL(new char[]{0x53, 0x4c, 0x4c}), /** 索马里先令 [S] */ SOS(new char[]{0x53}), /** 苏里南元 [$] */ SRD(new char[]{0x24}), /** 圣多美多布拉 [STD] */ STD(new char[]{0x53, 0x54, 0x44}), /** 叙利亚镑 [£] */ SYP(new char[]{0xa3}), /** 斯威士兰里兰吉尼 [SZL] */ SZL(new char[]{0x53, 0x5a, 0x4c}), /** 塔吉克斯坦索莫尼 [TJS] */ TJS(new char[]{0x54, 0x4a, 0x53}), /** 土库曼斯坦马纳特 [TMT] */ TMT(new char[]{0x54, 0x4d, 0x54}), /** 突尼斯第纳尔 [TND] */ TND(new char[]{0x54, 0x4e, 0x44}), /** 汤加潘加 [TOP] */ TOP(new char[]{0x54, 0x4f, 0x50}), /** 土耳其里拉 [₺], previous: {0x54, 0x52, 0x59} */ TRY(new char[]{0x20ba}), /** 特立尼达元 [TT$] */ TTD(new char[]{0x54, 0x54, 0x24}), /** 坦桑尼亚先令 [TZS] */ TZS(new char[]{0x54, 0x5a, 0x53}), /** 乌干达先令 [UGX] */ UGX(new char[]{0x55, 0x47, 0x58}), /** 乌拉圭比索 [$U] */ UYU(new char[]{0x24, 0x55}), /** 乌兹别克斯坦索姆 [лв] */ UZS(new char[]{0x43b, 0x432}), /** 委内瑞拉玻利瓦尔 [Bs] */ VEF(new char[]{0x42, 0x73}), /** 瓦努阿图瓦图 [VUV] */ VUV(new char[]{0x56, 0x55, 0x56}), /** 萨摩亚塔拉 [WST] */ WST(new char[]{0x57, 0x53, 0x54}), /** 中非金融合作法郎 [XAF] */ XAF(new char[]{0x58, 0x41, 0x46}), /** 银(盎司) [XAG] */ XAG(new char[]{0x58, 0x41, 0x47}), /** 金(盎司) [XAU] */ XAU(new char[]{0x58, 0x41, 0x55}), /** 东加勒比元 [$] */ XCD(new char[]{0x24}), /** 国际货币基金组织特别提款权 [XDR] */ XDR(new char[]{0x58, 0x44, 0x52}), /** CFA 法郎 [XOF] */ XOF(new char[]{0x58, 0x4f, 0x46}), /** 钯(盎司) [XPD] */ XPD(new char[]{0x58, 0x50, 0x44}), /** CFP 法郎 [XPF] */ XPF(new char[]{0x58, 0x50, 0x46}), /** 铂(盎司) [XPT] */ XPT(new char[]{0x58, 0x50, 0x54}), /** 也门里亚尔 [﷼] */ YER(new char[]{0xfdfc}), /** 南非兰特 [R] */ ZAR(new char[]{0x52}), /** Belarus Ruble [Br] */ BYN(new char[]{0x42, 0x72}), /** El Salvador Colon [$] */ SVC(new char[]{0x24}), /** Zimbabwe Dollar [Z$] */ ZWD(new char[]{0x5a, 0x24}), // ----------------------------------------------------------------------------------others /** 安道尔比塞塔 [ADP] */ ADP(new char[]{0x41, 0x44, 0x50}), /** 奥地利先令 [ATS] */ ATS(new char[]{0x41, 0x54, 0x53}), /** AYM [AYM] */ AYM(new char[]{0x41, 0x59, 0x4d}), /** 比利时法郎 [BEF] */ BEF(new char[]{0x42, 0x45, 0x46}), /** 保加利亚硬列弗 [BGL] */ BGL(new char[]{0x42, 0x47, 0x4c}), /** 玻利维亚 Mvdol(资金) [BOV] */ BOV(new char[]{0x42, 0x4f, 0x56}), /** CHE [CHE] */ CHE(new char[]{0x43, 0x48, 0x45}), /** CHW [CHW] */ CHW(new char[]{0x43, 0x48, 0x57}), /** 智利 Unidades de Fomento(资金) [CLF] */ CLF(new char[]{0x43, 0x4c, 0x46}), /** COU [COU] */ COU(new char[]{0x43, 0x4f, 0x55}), /** 塞浦路斯镑 [CYP] */ CYP(new char[]{0x43, 0x59, 0x50}), /** 德国马克 [DEM] */ DEM(new char[]{0x44, 0x45, 0x4d}), /** 爱沙尼亚克朗 [EEK] */ EEK(new char[]{0x45, 0x45, 0x4b}), /** 西班牙比塞塔 [ESP] */ ESP(new char[]{0x45, 0x53, 0x50}), /** 芬兰马克 [FIM] */ FIM(new char[]{0x46, 0x49, 0x4d}), /** 法国法郎 [FRF] */ FRF(new char[]{0x46, 0x52, 0x46}), /** 加纳塞第 [GHC] */ GHC(new char[]{0x47, 0x48, 0x43}), /** 希腊德拉克马 [GRD] */ GRD(new char[]{0x47, 0x52, 0x44}), /** 几内亚比绍比索 [GWP] */ GWP(new char[]{0x47, 0x57, 0x50}), /** 爱尔兰镑 [IEP] */ IEP(new char[]{0x49, 0x45, 0x50}), /** 意大利里拉 [ITL] */ ITL(new char[]{0x49, 0x54, 0x4c}), /** 立陶宛立特 [LTL] */ LTL(new char[]{0x4c, 0x54, 0x4c}), /** 卢森堡法郎 [LUF] */ LUF(new char[]{0x4c, 0x55, 0x46}), /** 拉脱维亚拉特 [LVL] */ LVL(new char[]{0x4c, 0x56, 0x4c}), /** 马达加斯加法郎 [MGF] */ MGF(new char[]{0x4d, 0x47, 0x46}), /** Mauritanian Ouguiya [MRU] */ MRU(new char[]{0x4d, 0x52, 0x55}), /** 马耳他里拉 [MTL] */ MTL(new char[]{0x4d, 0x54, 0x4c}), /** 墨西哥 Unidad de Inversion (UDI)(资金) [MXV] */ MXV(new char[]{0x4d, 0x58, 0x56}), /** 旧莫桑比克美提卡 [MZM] */ MZM(new char[]{0x4d, 0x5a, 0x4d}), /** 荷兰盾 [NLG] */ NLG(new char[]{0x4e, 0x4c, 0x47}), /** 葡萄牙埃斯库多 [PTE] */ PTE(new char[]{0x50, 0x54, 0x45}), /** 苏丹第纳尔 [SDD] */ SDD(new char[]{0x53, 0x44, 0x44}), /** 斯洛文尼亚托拉尔 [SIT] */ SIT(new char[]{0x53, 0x49, 0x54}), /** 斯洛伐克克朗 [SKK] */ SKK(new char[]{0x53, 0x4b, 0x4b}), /** 苏里南盾 [SRG] */ SRG(new char[]{0x53, 0x52, 0x47}), /** South Sudanese Pound [SSP] */ SSP(new char[]{0x53, 0x53, 0x50}), /** São Tomé and Príncipe Dobra [STN] */ STN(new char[]{0x53, 0x54, 0x4e}), /** 土库曼斯坦马纳特 [TMM] */ TMM(new char[]{0x54, 0x4d, 0x4d}), /** 帝汶埃斯库多 [TPE] */ TPE(new char[]{0x54, 0x50, 0x45}), /** 土耳其里拉 [TRL] */ TRL(new char[]{0x54, 0x52, 0x4c}), /** 美元(次日) [USN] */ USN(new char[]{0x55, 0x53, 0x4e}), /** 美元(当日) [USS] */ USS(new char[]{0x55, 0x53, 0x53}), /** UYI [UYI] */ UYI(new char[]{0x55, 0x59, 0x49}), /** 委内瑞拉博利瓦 [VEB] */ VEB(new char[]{0x56, 0x45, 0x42}), /** Venezuelan Bolívar Soberano [VED] */ //VED(new char[]{0x56, 0x45, 0x44}), // Already invalided /** Venezuelan Bolívar Soberano [VES] */ VES(new char[]{0x56, 0x45, 0x53}), /** 欧洲复合单位 [XBA] */ XBA(new char[]{0x58, 0x42, 0x41}), /** 欧洲货币联盟 [XBB] */ XBB(new char[]{0x58, 0x42, 0x42}), /** 欧洲计算单位 (XBC) [XBC] */ XBC(new char[]{0x58, 0x42, 0x43}), /** 欧洲计算单位 (XBD) [XBD] */ XBD(new char[]{0x58, 0x42, 0x44}), /** 法国 UIC 法郎 [XFU] */ XFU(new char[]{0x58, 0x46, 0x55}), /** Sucre [XSU] */ XSU(new char[]{0x58, 0x53, 0x55}), /** 为测试保留的代码 [XTS] */ XTS(new char[]{0x58, 0x54, 0x53}), /** ADB Unit of Account [XUA] */ XUA(new char[]{0x58, 0x55, 0x41}), /** 南斯拉夫偌威第纳尔 [YUM] */ YUM(new char[]{0x59, 0x55, 0x4d}), /** 赞比亚克瓦查 [ZMK] */ ZMK(new char[]{0x5a, 0x4d, 0x4b}), /** 货币未知或无效 [XXX] */ XXX(new char[]{0x58, 0x58, 0x58}), // -------------------------------Invalid currency /** Guernsey Pound [£] */ //GGP(new char[]{0xa3}), /** Isle of Man Pound [£] */ //IMP(new char[]{0xa3}), /** Jersey Pound [£] */ //JEP(new char[]{0xa3}), /** Tuvalu Dollar [$] */ //TVD(new char[]{0x24}), ; private static final Map CURRENCY_CODES = Enums.toMap(CurrencyEnum.class, CurrencyEnum::currencyCode); private static final Map NUMERIC_CODES = Enums.toMap(CurrencyEnum.class, CurrencyEnum::numericCode); /** * 币种代码 */ private final String currencyCode; /** * 币种符号 */ private final String currencySymbol; /** * 世界各国和地区名称数字 */ private final String numericCode; /** * 币种实例对象 */ private final Currency currency; /** * Constructor * * @param currencySymbol 币种符号 */ CurrencyEnum(char[] currencySymbol) { this.currencyCode = super.name(); this.currencySymbol = new String(currencySymbol); this.currency = Currency.getInstance(currencyCode); this.numericCode = String.format("%03d", currency.getNumericCode()); } /** * @return 币种代码(e.g. CNY) */ public String currencyCode() { return currencyCode; } /** * @return 世界各国和地区名称数字代码(e.g. 156) */ public String numericCode() { return numericCode; } /** * @return 币种符号(e.g. ¥) */ public String currencySymbol() { return currencySymbol; } /** *
         *  java.util.Currency.getDisplayName(Locale.CHINA)  -> 人民币
         *  java.util.Currency.getDisplayName(Locale.US)     -> Chinese Yuan
         *  java.util.Currency.getSymbol(Locale.CHINA)       -> ¥
         *  java.util.Currency.getNumericCode()              -> 156
         * 
    * * @return {@code java.util.Currency } object instance */ public Currency currency() { return currency; } // --------------------------------------------------------------of methods /** * Gets CurrencyEnum by currency code * * @param currencyCode the currency code * @return CurrencyEnum */ public static CurrencyEnum ofCurrencyCode(String currencyCode) { return CURRENCY_CODES.get(currencyCode); } /** * Gets CurrencyEnum by numeric code * * @param numericCode the numeric code * @return CurrencyEnum */ public static CurrencyEnum ofNumericCode(String numericCode) { return NUMERIC_CODES.get(numericCode); } /** * Gets CurrencyEnum by currency * * @param currency the currency * @return CurrencyEnum */ public static CurrencyEnum ofCurrency(Currency currency) { return CURRENCY_CODES.get(currency.getCurrencyCode()); } } ================================================ FILE: src/main/java/cn/ponfee/commons/util/Enums.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import com.google.common.collect.ImmutableMap; import org.apache.commons.lang3.EnumUtils; import java.util.Map; import java.util.function.Function; /** * Enum utility * * @author Ponfee */ public class Enums { /** * Gets the {@code Map} of enums by name. * * @param enumType the enum type * @param map key mapper * @return the immutable map of enum to map enums, never null * @see EnumUtils#getEnumMap(Class) */ public static > Map toMap(Class enumType) { return toMap(enumType, Enum::name); } /** * Returns {@code Map} of enum * * @param enumType the enum type * @param keyMapper map key mapper * @param then map key type * @param the enum type * @return the immutable map of enum to map enums, never null */ public static > Map toMap(Class enumType, Function keyMapper) { E[] enumConstants = enumType.getEnumConstants(); ImmutableMap.Builder mapping = ImmutableMap.builderWithExpectedSize(enumConstants.length << 1); for (final E e: enumConstants) { mapping.put(keyMapper.apply(e), e); } return mapping.build(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/util/ExtendMethodHandles.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import cn.ponfee.commons.exception.Throwables.ThrowingSupplier; import cn.ponfee.commons.reflect.ClassUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles.Lookup; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.function.Function; /** *
     * jdk8中如果直接调用{@link MethodHandles#lookup()}获取到的{@link Lookup}
     * 在调用方法 {@link Lookup#findSpecial(Class, String, java.lang.invoke.MethodType, Class)}
     * 和{@link Lookup#unreflectSpecial(Method, Class)}
     * 获取父类方法句柄{@link MethodHandle}时
     * 可能出现权限不够, 抛出如下异常, 所以通过反射创建{@link Lookup}解决该问题.
     *
     *  java.lang.IllegalAccessException: no private access for invokespecial: interface com.example.demo.methodhandle.UserService, from com.example.demo.methodhandle.UserServiceInvoke
     *  at java.lang.invoke.MemberName.makeAccessException(MemberName.java:850)
     *  at java.lang.invoke.MethodHandles$Lookup.checkSpecialCaller(MethodHandles.java:1572)
     *
     * 而jdk11中直接调用{@link MethodHandles#lookup()}获取到的{@link Lookup},也只能对接口类型才会权限获取方法的方法句柄{@link MethodHandle}.
     * 如果是普通类型Class,需要使用jdk9开始提供的 MethodHandles#privateLookupIn(java.lang.Class, java.lang.invoke.MethodHandles.Lookup)方法.
     * 
    * * 参考文章 * * @author Ponfee */ public class ExtendMethodHandles { public static final Function, Lookup> METHOD_LOOKUP = getMethodLookup(); /** * Jdk9中的MethodHandles.lookup()方法返回的Lookup对象,有权限访问specialCaller != lookupClass()的类 * 但是只能适用于接口,java.lang.invoke.MethodHandles.Lookup#checkSpecialCaller(Class) */ public static MethodHandle getSpecialMethodHandle(Method parentMethod) { Class declaringClass = parentMethod.getDeclaringClass(); Lookup lookup = METHOD_LOOKUP.apply(declaringClass); try { return lookup.in(declaringClass).unreflectSpecial(parentMethod, declaringClass); } catch (IllegalAccessException e) { return ExceptionUtils.rethrow(e); } } private static Function, Lookup> getMethodLookup() { // 先查询jdk9开始提供的“java.lang.invoke.MethodHandles.privateLookupIn”方法 Method jdk9PrivateLookupInMethod = ClassUtils.getMethod(MethodHandles.class, "privateLookupIn", Class.class, Lookup.class); if (jdk9PrivateLookupInMethod != null) { return callerClass -> ThrowingSupplier.doChecked(() -> (Lookup) jdk9PrivateLookupInMethod.invoke(MethodHandles.class, callerClass, MethodHandles.lookup())); } // 查询jdk8版本:这种方式也适用于jdk9及以上的版本,但优先上面的可以避免jdk9反射警告 Constructor jdk8LookupConstructor = ClassUtils.getConstructor(Lookup.class, Class.class, int.class); if (jdk8LookupConstructor == null) { // 未找到则可能是jdk8以下版本 throw new UnsupportedOperationException("Not found method 'private java.lang.invoke.MethodHandles$Lookup(java.lang.Class,int)'."); } jdk8LookupConstructor.setAccessible(true); int modifiers = Lookup.PRIVATE | Lookup.PROTECTED | Lookup.PACKAGE | Lookup.PUBLIC; return callerClass -> ThrowingSupplier.doChecked(() -> jdk8LookupConstructor.newInstance(callerClass, modifiers)); } } ================================================ FILE: src/main/java/cn/ponfee/commons/util/FailRetryTemplate.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.function.Supplier; /** * Fail retry template(template method pattern) * * @author Ponfee */ public class FailRetryTemplate { private static final Logger LOG = LoggerFactory.getLogger(FailRetryTemplate.class); public static T execute(Supplier normal, Supplier message) throws Exception { return execute(normal, normal, 5, message); } public static T execute(Supplier normal, Supplier fallback, int failRetryCount, Supplier message) throws Exception { int i = 0; Exception ex; String logMsg = null; do { try { if (i == 0) { return normal.get(); } else { return fallback.get(); } } catch (Exception e) { ex = e; if (i < failRetryCount) { // not the last loop if (logMsg == null) { logMsg = UuidUtils.uuid32() + " - " + message.get(); } int count = i + 1; LOG.error("Execute failed, will retrying - " + count + " - " + logMsg, e); Thread.sleep(5000L * count); } } } while (++i <= failRetryCount); throw ex; } } ================================================ FILE: src/main/java/cn/ponfee/commons/util/Holder.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import java.util.Objects; import java.util.function.*; /** * 变量持有,用于lambda方法体内 *

    non-thread-safe * * @param the type T * @author Ponfee */ public final class Holder { private T value; private Holder(T value) { this.value = value; } public static Holder empty() { return new Holder<>(null); } public static Holder of(T value) { return new Holder<>(value); } /** * Returns the holder value whether null * * @return a boolean, if {@code true} then the value is null */ public boolean isEmpty() { return value == null; } /** * Sets a newly value and return former value * * @param value the newly value * @return then former value */ public T set(T value) { T former = this.value; this.value = value; return former; } /** * Sets a new value if former value is null * * @param value the new value */ public void setIfAbsent(T value) { if (this.value == null) { this.value = value; } } /** * Replaces value if former value is not null, * and return former value * * @param value the newly value * @return then former value */ public T setIfPresent(T value) { T former = this.value; if (this.value != null) { this.value = value; } return former; } public T setIfMatches(T value, Predicate predicate) { T former = this.value; if (predicate.test(this.value)) { this.value = value; } return former; } public T setIfMatches(T value, BiPredicate predicate) { T former = this.value; if (predicate.test(this.value, value)) { this.value = value; } return former; } public void ifPresent(Consumer consumer) { if (value != null) { consumer.accept(value); } } public Holder map(Function mapper) { Objects.requireNonNull(mapper); return isEmpty() ? empty() : of(mapper.apply(value)); } public Holder filter(Predicate predicate) { Objects.requireNonNull(predicate); return (isEmpty() || predicate.test(value)) ? this : empty(); } public T get() { return value; } public T orElse(T other) { return value != null ? value : other; } public T orElseGet(Supplier other) { return value != null ? value : other.get(); } public T orElseThrow(Supplier exceptionSupplier) throws E { if (value != null) { return value; } else { throw exceptionSupplier.get(); } } @Override public boolean equals(Object obj) { if (this == obj) { return true; } return (obj instanceof Holder) && Objects.equals(value, ((Holder) obj).value); } @Override public int hashCode() { return Objects.hashCode(value); } @Override public String toString() { return value != null ? String.format("Holder(%s)", value) : "Holder.empty"; } } ================================================ FILE: src/main/java/cn/ponfee/commons/util/IdcardResolver.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import cn.ponfee.commons.date.Dates; import com.google.common.collect.ImmutableMap; import java.util.*; import java.util.concurrent.ThreadLocalRandom; import java.util.regex.Pattern; import static java.util.Calendar.YEAR; /** * 身份证解析及生成 * * http://www.mca.gov.cn/article/sj/xzqh/2018/ * * @author Ponfee */ public class IdcardResolver { private static final long ORIGINAL = Dates.toDate("1950-01-01 00:00:00").getTime(); /** * 证件类型 */ public enum CertType { FIRST, SECOND, HONGKONG, MACAO, TAIWAN, PASSPORT } /** * 性别:M男;F女;N未知; */ public enum Sex { M, F, N } private String idcard; // idCard private boolean isValid = false; // 是否有效 private CertType type; // 类型 private String province; // 省份 private String district; // 市县 private Date birthday; // 生日 private Integer age; // 年龄 private Sex sex; // 性别 /** * 随机身份证号码生成 * @return */ public static String generate() { Random random = ThreadLocalRandom.current(); StringBuilder builder = new StringBuilder(18); builder.append(AREA_CODE_LIST.get(random.nextInt(AREA_CODE_LIST.size()))); // 行政区号:6位 builder.append(Dates.format(Dates.random(ORIGINAL, System.currentTimeMillis()), "yyyyMMdd")); // 生日:8位 // 当地派出所在该日期的出生顺序号:3位,其中17位(倒数第二位)男为单数,女为双数 builder.append(String.format("%03d", random.nextInt(1000))); builder.append(genPowerSum(builder.toString().toCharArray())); // 校验码:1位 return builder.toString(); } public IdcardResolver(String idcard) { if (idcard == null) { return; } this.idcard = idcard.trim().toUpperCase(); this.isValid = this.resolve(this.idcard); } /** * 解析 * @return */ private boolean resolve(String idcard) { return isSecond(idcard) || isFirst(idcard) || isPassport(idcard) || isOther(idcard); } /** * 验证15位身份编码是否合法 * @param idCard 身份编码 * @return 是否合法 */ private static final Pattern FIRST = Pattern.compile("^[0-9]{15}$"); private boolean isFirst(String idcard) { if (!FIRST.matcher(idcard).matches()) { return false; } // 转成18位 idcard = idcard.substring(0, 6) + "19" + idcard.substring(6); idcard += genPowerSum(idcard.toCharArray()); if (isSecond(idcard)) { this.type = CertType.FIRST; return true; } else { return false; } } /** * 验证18位身份编码是否合法 * @param idCard 身份编码 * @return 是否合法 */ private static final Pattern SECOND = Pattern.compile("^[0-9]{17}[0-9X]$"); private boolean isSecond(String idcard) { if (!SECOND.matcher(idcard).matches()) { return false; } // 城市验证 this.province = CITY_CODES.get(idcard.substring(0, 2)); if (this.province == null) { return false; } this.district = DISTRICT_CODE_MAPPING.get(Integer.parseInt(idcard.substring(0, 6))); //if (this.district == null) return false; // 有些县升为地级市,code改变了 // 校验码验证 String prefix17 = idcard.substring(0, 17); String checkCode = genPowerSum(prefix17.toCharArray()); if (!checkCode.equals(idcard.substring(17))) { return false; } // 生日验证 if (!verifyBirthday(idcard.substring(6, 14))) { return false; } // 性别提取 if ((Character.getNumericValue(idcard.charAt(16)) & 0x01) == 1) { this.sex = Sex.M; } else { this.sex = Sex.F; } this.type = CertType.SECOND; return true; } /** * 护照验证 * @param idcard * @return */ private static final Pattern PASSPORT_REGEX = Pattern.compile( "^1[45][0-9]{7}|G[0-9]{8}|P[0-9]{7}|S[0-9]{7,8}|D[0-9]+$" ); private boolean isPassport(String idcard) { if (PASSPORT_REGEX.matcher(idcard).matches()) { this.type = CertType.PASSPORT; return true; } return false; } /** * 验证10位身份编码是否合法 * @param idCard 身份编码 * @return 身份证信息数组 */ private static final Pattern HONGKONG = Pattern.compile("^[A-Z]{1,2}[0-9]{6}\\(?[0-9A]\\)?$"); private static final Pattern MACO = Pattern.compile("^([157])[0-9]{6}\\(?[0-9A-Z]\\)?$"); private static final Pattern TAIWAN = Pattern.compile("^[a-zA-Z][0-9]{9}$"); private boolean isOther(String idcard) { idcard = idcard.replaceAll("[(|)]", ""); if (HONGKONG.matcher(idcard).matches()) { // 香港 this.type = CertType.HONGKONG; this.sex = Sex.N; return validateHKCard(idcard); } else if (MACO.matcher(idcard).matches()) { // 澳门 this.type = CertType.MACAO; this.sex = Sex.N; return true; } else if (TAIWAN.matcher(idcard).matches()) { // 台湾 this.type = CertType.TAIWAN; String sex = idcard.substring(1, 2); if ("1".equals(sex)) { this.sex = Sex.M; } else if ("2".equals(sex)) { this.sex = Sex.F; } else { this.sex = Sex.N; } return validateTWCard(idcard); } else { return false; } } /** * 验证台湾身份证号码 * @param idCard 身份证号码 * @return 验证码是否符合 */ private boolean validateTWCard(String idCard) { String start = idCard.substring(0, 1); String mid = idCard.substring(1, 9); String end = idCard.substring(9, 10); int iStart = TW_FIRST_CODE.get(start); int sum = iStart / 10 + (iStart % 10) * 9; int iteration = 8; for (char c : mid.toCharArray()) { sum = sum + (int) c * iteration--; } return (10 - sum % 10) % 10 == Integer.parseInt(end); } /** *

         *   验证香港身份证号码(存在bug,部份特殊身份证无法校验)
         *   身份证前2位为英文字符,如果只出现一个英文字符则表示第一位是空格,对应数字58 
         *   前2位英文字符A-Z分别对应数字10-35 最后一位校验码为0-9的数字加上字符"A","A"代表10
         *   将身份证号码全部转换为数字,分别对应乘9-1相加的总和,整除11则证件号码有效
         * 
    * * @param idCard 身份证号码 * @return 验证码是否符合 */ private boolean validateHKCard(String idCard) { String card = idCard.replaceAll("[\\(|\\)]", ""); int sum; if (card.length() == 9) { sum = ((int) card.substring(0, 1).toUpperCase().toCharArray()[0] - 55) * 9 + ((int) card.substring(1, 2).toUpperCase().toCharArray()[0] - 55) * 8; card = card.substring(1, 9); } else { sum = 522 + ((int) card.substring(0, 1).toUpperCase().toCharArray()[0] - 55) * 8; } String mid = card.substring(1, 7); String end = card.substring(7, 8); int iteration = 7; for (char c : mid.toCharArray()) { sum = sum + (int) c * iteration--; } if ("A".equalsIgnoreCase(end)) { sum = sum + 10; } else { sum = sum + Integer.parseInt(end); } return sum % 11 == 0; } /** *
         * 18位身份证验证
         * 根据〖中华人民共和国国家标准 GB 11643-1999〗中有关公民身份号码的规定,公民身份号码是特征组合码,
         * 由十七位数字本体码和一位数字校验码组成。
         *
         * 排列顺序从左至右依次为:六位数字地址码,八位数字出生日期码,三位数字顺序码和一位数字校验码。
         * 第十八位数字(校验码)的计算方法为:
         * 1.将前面的身份证号码17位数分别乘以不同的系数。从第一位到第十七位的系数分别为:7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2
         * 2.将这17位数字和系数相乘的结果相加。
         * 3.用加出来和除以11,看余数是多少?
         * 4.余数只可能有0 1 2 3 4 5 6 7 8 9 10这11个数字。其分别对应的最后一位身份证的号码为1 0 X 9 8 7 6 5 4 3 2。
         * 5.通过上面得知如果余数是2,就会在身份证的第18位数字上出现罗马数字的Ⅹ。如果余数是10,身份证的最后一位号码就是2。
         * 
    */ private static String genPowerSum(char[] chars) { int[] n = new int[chars.length]; int result = 0; for (int i = 0; i < n.length; i++) { n[i] = Character.getNumericValue(chars[i]); } for (int i = 0; i < n.length; i++) { result += POWER[i] * n[i]; } return JUXTAPOSE[result % 11]; } /** * 验证生日是否有效 * @param date * @return */ private boolean verifyBirthday(String date) { try { this.birthday = Dates.toDate(date, "yyyyMMdd"); if (this.birthday.after(new Date())) { return false; } Calendar calendar = Calendar.getInstance(); calendar.setTime(this.birthday); this.age = Calendar.getInstance().get(YEAR) - calendar.get(YEAR); return true; } catch (Exception e) { return false; } } public String getIdcard() { return idcard; } public boolean isValid() { return isValid; } public CertType getType() { return type; } public String getProvince() { return province; } public String getDistrict() { return district; } public Date getBirthday() { return birthday; } public Sex getSex() { return sex; } public int getAge() { return age; } @Override public String toString() { return new StringBuilder("IdcardResolver {idcard=") .append(idcard) .append(", isValid=").append(isValid) .append(", type=").append(type) .append(", province=").append(province) .append(", district=").append(district) .append(", birthday=").append(birthday) .append(", age=").append(age) .append(", sex=").append(sex) .append("}").toString(); } /** 每位加权因子 */ private static final int[] POWER = { 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 }; /** 校验和对照表 */ private static final String[] JUXTAPOSE = { "1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2" }; /** 大陆城市 */ private static final Map CITY_CODES = new ImmutableMap.Builder() .put("11", "北京") .put("12", "天津") .put("13", "河北") .put("14", "山西") .put("15", "内蒙古") .put("21", "辽宁") .put("22", "吉林") .put("23", "黑龙江") .put("31", "上海") .put("32", "江苏") .put("33", "浙江") .put("34", "安徽") .put("35", "福建") .put("36", "江西") .put("37", "山东") .put("41", "河南") .put("42", "湖北") .put("43", "湖南") .put("44", "广东") .put("45", "广西") .put("46", "海南") .put("50", "重庆") .put("51", "四川") .put("52", "贵州") .put("53", "云南") .put("54", "西藏") .put("61", "陕西") .put("62", "甘肃") .put("63", "青海") .put("64", "宁夏") .put("65", "新疆") .put("71", "台湾") .put("81", "香港") .put("82", "澳门") .put("91", "国外") .build(); /** 台湾身份首字母对应数字 */ private static final Map TW_FIRST_CODE = new ImmutableMap.Builder() .put("A", 10) .put("B", 11) .put("C", 12) .put("D", 13) .put("E", 14) .put("F", 15) .put("G", 16) .put("H", 17) .put("J", 18) .put("K", 19) .put("L", 20) .put("M", 21) .put("N", 22) .put("P", 23) .put("Q", 24) .put("R", 25) .put("S", 26) .put("T", 27) .put("U", 28) .put("V", 29) .put("X", 30) .put("Y", 31) .put("W", 32) .put("Z", 33) .put("I", 34) .put("O", 35) .build(); public static final Map DISTRICT_CODE_MAPPING; private static final List AREA_CODE_LIST; static { ImmutableMap.Builder b = new ImmutableMap.Builder<>(); b.put(110000, "北京市"); b.put(110101, "东城区"); b.put(110102, "西城区"); b.put(110105, "朝阳区"); b.put(110106, "丰台区"); b.put(110107, "石景山区"); b.put(110108, "海淀区"); b.put(110109, "门头沟区"); b.put(110111, "房山区"); b.put(110112, "通州区"); b.put(110113, "顺义区"); b.put(110114, "昌平区"); b.put(110115, "大兴区"); b.put(110116, "怀柔区"); b.put(110117, "平谷区"); b.put(110118, "密云区"); b.put(110119, "延庆区"); b.put(120000, "天津市"); b.put(120101, "和平区"); b.put(120102, "河东区"); b.put(120103, "河西区"); b.put(120104, "南开区"); b.put(120105, "河北区"); b.put(120106, "红桥区"); b.put(120110, "东丽区"); b.put(120111, "西青区"); b.put(120112, "津南区"); b.put(120113, "北辰区"); b.put(120114, "武清区"); b.put(120115, "宝坻区"); b.put(120116, "滨海新区"); b.put(120117, "宁河区"); b.put(120118, "静海区"); b.put(120119, "蓟州区"); b.put(130000, "河北省"); b.put(130100, "石家庄市"); b.put(130102, "长安区"); b.put(130104, "桥西区"); b.put(130105, "新华区"); b.put(130107, "井陉矿区"); b.put(130108, "裕华区"); b.put(130109, "藁城区"); b.put(130110, "鹿泉区"); b.put(130111, "栾城区"); b.put(130121, "井陉县"); b.put(130123, "正定县"); b.put(130125, "行唐县"); b.put(130126, "灵寿县"); b.put(130127, "高邑县"); b.put(130128, "深泽县"); b.put(130129, "赞皇县"); b.put(130130, "无极县"); b.put(130131, "平山县"); b.put(130132, "元氏县"); b.put(130133, "赵县"); b.put(130181, "辛集市"); b.put(130183, "晋州市"); b.put(130184, "新乐市"); b.put(130200, "唐山市"); b.put(130202, "路南区"); b.put(130203, "路北区"); b.put(130204, "古冶区"); b.put(130205, "开平区"); b.put(130207, "丰南区"); b.put(130208, "丰润区"); b.put(130209, "曹妃甸区"); b.put(130223, "滦县"); b.put(130224, "滦南县"); b.put(130225, "乐亭县"); b.put(130227, "迁西县"); b.put(130229, "玉田县"); b.put(130281, "遵化市"); b.put(130283, "迁安市"); b.put(130300, "秦皇岛市"); b.put(130302, "海港区"); b.put(130303, "山海关区"); b.put(130304, "北戴河区"); b.put(130306, "抚宁区"); b.put(130321, "青龙满族自治县"); b.put(130322, "昌黎县"); b.put(130324, "卢龙县"); b.put(130400, "邯郸市"); b.put(130402, "邯山区"); b.put(130403, "丛台区"); b.put(130404, "复兴区"); b.put(130406, "峰峰矿区"); b.put(130407, "肥乡区"); b.put(130408, "永年区"); b.put(130423, "临漳县"); b.put(130424, "成安县"); b.put(130425, "大名县"); b.put(130426, "涉县"); b.put(130427, "磁县"); b.put(130430, "邱县"); b.put(130431, "鸡泽县"); b.put(130432, "广平县"); b.put(130433, "馆陶县"); b.put(130434, "魏县"); b.put(130435, "曲周县"); b.put(130481, "武安市"); b.put(130500, "邢台市"); b.put(130502, "桥东区"); b.put(130503, "桥西区"); b.put(130521, "邢台县"); b.put(130522, "临城县"); b.put(130523, "内丘县"); b.put(130524, "柏乡县"); b.put(130525, "隆尧县"); b.put(130526, "任县"); b.put(130527, "南和县"); b.put(130528, "宁晋县"); b.put(130529, "巨鹿县"); b.put(130530, "新河县"); b.put(130531, "广宗县"); b.put(130532, "平乡县"); b.put(130533, "威县"); b.put(130534, "清河县"); b.put(130535, "临西县"); b.put(130581, "南宫市"); b.put(130582, "沙河市"); b.put(130600, "保定市"); b.put(130602, "竞秀区"); b.put(130606, "莲池区"); b.put(130607, "满城区"); b.put(130608, "清苑区"); b.put(130609, "徐水区"); b.put(130623, "涞水县"); b.put(130624, "阜平县"); b.put(130626, "定兴县"); b.put(130627, "唐县"); b.put(130628, "高阳县"); b.put(130629, "容城县"); b.put(130630, "涞源县"); b.put(130631, "望都县"); b.put(130632, "安新县"); b.put(130633, "易县"); b.put(130634, "曲阳县"); b.put(130635, "蠡县"); b.put(130636, "顺平县"); b.put(130637, "博野县"); b.put(130638, "雄县"); b.put(130681, "涿州市"); b.put(130682, "定州市"); b.put(130683, "安国市"); b.put(130684, "高碑店市"); b.put(130700, "张家口市"); b.put(130702, "桥东区"); b.put(130703, "桥西区"); b.put(130705, "宣化区"); b.put(130706, "下花园区"); b.put(130708, "万全区"); b.put(130709, "崇礼区"); b.put(130722, "张北县"); b.put(130723, "康保县"); b.put(130724, "沽源县"); b.put(130725, "尚义县"); b.put(130726, "蔚县"); b.put(130727, "阳原县"); b.put(130728, "怀安县"); b.put(130730, "怀来县"); b.put(130731, "涿鹿县"); b.put(130732, "赤城县"); b.put(130800, "承德市"); b.put(130802, "双桥区"); b.put(130803, "双滦区"); b.put(130804, "鹰手营子矿区"); b.put(130821, "承德县"); b.put(130822, "兴隆县"); b.put(130824, "滦平县"); b.put(130825, "隆化县"); b.put(130826, "丰宁满族自治县"); b.put(130827, "宽城满族自治县"); b.put(130828, "围场满族蒙古族自治县"); b.put(130881, "平泉市"); b.put(130900, "沧州市"); b.put(130902, "新华区"); b.put(130903, "运河区"); b.put(130921, "沧县"); b.put(130922, "青县"); b.put(130923, "东光县"); b.put(130924, "海兴县"); b.put(130925, "盐山县"); b.put(130926, "肃宁县"); b.put(130927, "南皮县"); b.put(130928, "吴桥县"); b.put(130929, "献县"); b.put(130930, "孟村回族自治县"); b.put(130981, "泊头市"); b.put(130982, "任丘市"); b.put(130983, "黄骅市"); b.put(130984, "河间市"); b.put(131000, "廊坊市"); b.put(131002, "安次区"); b.put(131003, "广阳区"); b.put(131022, "固安县"); b.put(131023, "永清县"); b.put(131024, "香河县"); b.put(131025, "大城县"); b.put(131026, "文安县"); b.put(131028, "大厂回族自治县"); b.put(131081, "霸州市"); b.put(131082, "三河市"); b.put(131100, "衡水市"); b.put(131102, "桃城区"); b.put(131103, "冀州区"); b.put(131121, "枣强县"); b.put(131122, "武邑县"); b.put(131123, "武强县"); b.put(131124, "饶阳县"); b.put(131125, "安平县"); b.put(131126, "故城县"); b.put(131127, "景县"); b.put(131128, "阜城县"); b.put(131182, "深州市"); b.put(140000, "山西省"); b.put(140100, "太原市"); b.put(140105, "小店区"); b.put(140106, "迎泽区"); b.put(140107, "杏花岭区"); b.put(140108, "尖草坪区"); b.put(140109, "万柏林区"); b.put(140110, "晋源区"); b.put(140121, "清徐县"); b.put(140122, "阳曲县"); b.put(140123, "娄烦县"); b.put(140181, "古交市"); b.put(140200, "大同市"); b.put(140212, "新荣区"); b.put(140213, "平城区"); b.put(140214, "云冈区"); b.put(140215, "云州区"); b.put(140221, "阳高县"); b.put(140222, "天镇县"); b.put(140223, "广灵县"); b.put(140224, "灵丘县"); b.put(140225, "浑源县"); b.put(140226, "左云县"); b.put(140300, "阳泉市"); b.put(140302, "城区"); b.put(140303, "矿区"); b.put(140311, "郊区"); b.put(140321, "平定县"); b.put(140322, "盂县"); b.put(140400, "长治市"); b.put(140402, "城区"); b.put(140411, "郊区"); b.put(140421, "长治县"); b.put(140423, "襄垣县"); b.put(140424, "屯留县"); b.put(140425, "平顺县"); b.put(140426, "黎城县"); b.put(140427, "壶关县"); b.put(140428, "长子县"); b.put(140429, "武乡县"); b.put(140430, "沁县"); b.put(140431, "沁源县"); b.put(140481, "潞城市"); b.put(140500, "晋城市"); b.put(140502, "城区"); b.put(140521, "沁水县"); b.put(140522, "阳城县"); b.put(140524, "陵川县"); b.put(140525, "泽州县"); b.put(140581, "高平市"); b.put(140600, "朔州市"); b.put(140602, "朔城区"); b.put(140603, "平鲁区"); b.put(140621, "山阴县"); b.put(140622, "应县"); b.put(140623, "右玉县"); b.put(140681, "怀仁市"); b.put(140700, "晋中市"); b.put(140702, "榆次区"); b.put(140721, "榆社县"); b.put(140722, "左权县"); b.put(140723, "和顺县"); b.put(140724, "昔阳县"); b.put(140725, "寿阳县"); b.put(140726, "太谷县"); b.put(140727, "祁县"); b.put(140728, "平遥县"); b.put(140729, "灵石县"); b.put(140781, "介休市"); b.put(140800, "运城市"); b.put(140802, "盐湖区"); b.put(140821, "临猗县"); b.put(140822, "万荣县"); b.put(140823, "闻喜县"); b.put(140824, "稷山县"); b.put(140825, "新绛县"); b.put(140826, "绛县"); b.put(140827, "垣曲县"); b.put(140828, "夏县"); b.put(140829, "平陆县"); b.put(140830, "芮城县"); b.put(140881, "永济市"); b.put(140882, "河津市"); b.put(140900, "忻州市"); b.put(140902, "忻府区"); b.put(140921, "定襄县"); b.put(140922, "五台县"); b.put(140923, "代县"); b.put(140924, "繁峙县"); b.put(140925, "宁武县"); b.put(140926, "静乐县"); b.put(140927, "神池县"); b.put(140928, "五寨县"); b.put(140929, "岢岚县"); b.put(140930, "河曲县"); b.put(140931, "保德县"); b.put(140932, "偏关县"); b.put(140981, "原平市"); b.put(141000, "临汾市"); b.put(141002, "尧都区"); b.put(141021, "曲沃县"); b.put(141022, "翼城县"); b.put(141023, "襄汾县"); b.put(141024, "洪洞县"); b.put(141025, "古县"); b.put(141026, "安泽县"); b.put(141027, "浮山县"); b.put(141028, "吉县"); b.put(141029, "乡宁县"); b.put(141030, "大宁县"); b.put(141031, "隰县"); b.put(141032, "永和县"); b.put(141033, "蒲县"); b.put(141034, "汾西县"); b.put(141081, "侯马市"); b.put(141082, "霍州市"); b.put(141100, "吕梁市"); b.put(141102, "离石区"); b.put(141121, "文水县"); b.put(141122, "交城县"); b.put(141123, "兴县"); b.put(141124, "临县"); b.put(141125, "柳林县"); b.put(141126, "石楼县"); b.put(141127, "岚县"); b.put(141128, "方山县"); b.put(141129, "中阳县"); b.put(141130, "交口县"); b.put(141181, "孝义市"); b.put(141182, "汾阳市"); b.put(150000, "内蒙古自治区"); b.put(150100, "呼和浩特市"); b.put(150102, "新城区"); b.put(150103, "回民区"); b.put(150104, "玉泉区"); b.put(150105, "赛罕区"); b.put(150121, "土默特左旗"); b.put(150122, "托克托县"); b.put(150123, "和林格尔县"); b.put(150124, "清水河县"); b.put(150125, "武川县"); b.put(150200, "包头市"); b.put(150202, "东河区"); b.put(150203, "昆都仑区"); b.put(150204, "青山区"); b.put(150205, "石拐区"); b.put(150206, "白云鄂博矿区"); b.put(150207, "九原区"); b.put(150221, "土默特右旗"); b.put(150222, "固阳县"); b.put(150223, "达尔罕茂明安联合旗"); b.put(150300, "乌海市"); b.put(150302, "海勃湾区"); b.put(150303, "海南区"); b.put(150304, "乌达区"); b.put(150400, "赤峰市"); b.put(150402, "红山区"); b.put(150403, "元宝山区"); b.put(150404, "松山区"); b.put(150421, "阿鲁科尔沁旗"); b.put(150422, "巴林左旗"); b.put(150423, "巴林右旗"); b.put(150424, "林西县"); b.put(150425, "克什克腾旗"); b.put(150426, "翁牛特旗"); b.put(150428, "喀喇沁旗"); b.put(150429, "宁城县"); b.put(150430, "敖汉旗"); b.put(150500, "通辽市"); b.put(150502, "科尔沁区"); b.put(150521, "科尔沁左翼中旗"); b.put(150522, "科尔沁左翼后旗"); b.put(150523, "开鲁县"); b.put(150524, "库伦旗"); b.put(150525, "奈曼旗"); b.put(150526, "扎鲁特旗"); b.put(150581, "霍林郭勒市"); b.put(150600, "鄂尔多斯市"); b.put(150602, "东胜区"); b.put(150603, "康巴什区"); b.put(150621, "达拉特旗"); b.put(150622, "准格尔旗"); b.put(150623, "鄂托克前旗"); b.put(150624, "鄂托克旗"); b.put(150625, "杭锦旗"); b.put(150626, "乌审旗"); b.put(150627, "伊金霍洛旗"); b.put(150700, "呼伦贝尔市"); b.put(150702, "海拉尔区"); b.put(150703, "扎赉诺尔区"); b.put(150721, "阿荣旗"); b.put(150722, "莫力达瓦达斡尔族自治旗"); b.put(150723, "鄂伦春自治旗"); b.put(150724, "鄂温克族自治旗"); b.put(150725, "陈巴尔虎旗"); b.put(150726, "新巴尔虎左旗"); b.put(150727, "新巴尔虎右旗"); b.put(150781, "满洲里市"); b.put(150782, "牙克石市"); b.put(150783, "扎兰屯市"); b.put(150784, "额尔古纳市"); b.put(150785, "根河市"); b.put(150800, "巴彦淖尔市"); b.put(150802, "临河区"); b.put(150821, "五原县"); b.put(150822, "磴口县"); b.put(150823, "乌拉特前旗"); b.put(150824, "乌拉特中旗"); b.put(150825, "乌拉特后旗"); b.put(150826, "杭锦后旗"); b.put(150900, "乌兰察布市"); b.put(150902, "集宁区"); b.put(150921, "卓资县"); b.put(150922, "化德县"); b.put(150923, "商都县"); b.put(150924, "兴和县"); b.put(150925, "凉城县"); b.put(150926, "察哈尔右翼前旗"); b.put(150927, "察哈尔右翼中旗"); b.put(150928, "察哈尔右翼后旗"); b.put(150929, "四子王旗"); b.put(150981, "丰镇市"); b.put(152200, "兴安盟"); b.put(152201, "乌兰浩特市"); b.put(152202, "阿尔山市"); b.put(152221, "科尔沁右翼前旗"); b.put(152222, "科尔沁右翼中旗"); b.put(152223, "扎赉特旗"); b.put(152224, "突泉县"); b.put(152500, "锡林郭勒盟"); b.put(152501, "二连浩特市"); b.put(152502, "锡林浩特市"); b.put(152522, "阿巴嘎旗"); b.put(152523, "苏尼特左旗"); b.put(152524, "苏尼特右旗"); b.put(152525, "东乌珠穆沁旗"); b.put(152526, "西乌珠穆沁旗"); b.put(152527, "太仆寺旗"); b.put(152528, "镶黄旗"); b.put(152529, "正镶白旗"); b.put(152530, "正蓝旗"); b.put(152531, "多伦县"); b.put(152900, "阿拉善盟"); b.put(152921, "阿拉善左旗"); b.put(152922, "阿拉善右旗"); b.put(152923, "额济纳旗"); b.put(210000, "辽宁省"); b.put(210100, "沈阳市"); b.put(210102, "和平区"); b.put(210103, "沈河区"); b.put(210104, "大东区"); b.put(210105, "皇姑区"); b.put(210106, "铁西区"); b.put(210111, "苏家屯区"); b.put(210112, "浑南区"); b.put(210113, "沈北新区"); b.put(210114, "于洪区"); b.put(210115, "辽中区"); b.put(210123, "康平县"); b.put(210124, "法库县"); b.put(210181, "新民市"); b.put(210200, "大连市"); b.put(210202, "中山区"); b.put(210203, "西岗区"); b.put(210204, "沙河口区"); b.put(210211, "甘井子区"); b.put(210212, "旅顺口区"); b.put(210213, "金州区"); b.put(210214, "普兰店区"); b.put(210224, "长海县"); b.put(210281, "瓦房店市"); b.put(210283, "庄河市"); b.put(210300, "鞍山市"); b.put(210302, "铁东区"); b.put(210303, "铁西区"); b.put(210304, "立山区"); b.put(210311, "千山区"); b.put(210321, "台安县"); b.put(210323, "岫岩满族自治县"); b.put(210381, "海城市"); b.put(210400, "抚顺市"); b.put(210402, "新抚区"); b.put(210403, "东洲区"); b.put(210404, "望花区"); b.put(210411, "顺城区"); b.put(210421, "抚顺县"); b.put(210422, "新宾满族自治县"); b.put(210423, "清原满族自治县"); b.put(210500, "本溪市"); b.put(210502, "平山区"); b.put(210503, "溪湖区"); b.put(210504, "明山区"); b.put(210505, "南芬区"); b.put(210521, "本溪满族自治县"); b.put(210522, "桓仁满族自治县"); b.put(210600, "丹东市"); b.put(210602, "元宝区"); b.put(210603, "振兴区"); b.put(210604, "振安区"); b.put(210624, "宽甸满族自治县"); b.put(210681, "东港市"); b.put(210682, "凤城市"); b.put(210700, "锦州市"); b.put(210702, "古塔区"); b.put(210703, "凌河区"); b.put(210711, "太和区"); b.put(210726, "黑山县"); b.put(210727, "义县"); b.put(210781, "凌海市"); b.put(210782, "北镇市"); b.put(210800, "营口市"); b.put(210802, "站前区"); b.put(210803, "西市区"); b.put(210804, "鲅鱼圈区"); b.put(210811, "老边区"); b.put(210881, "盖州市"); b.put(210882, "大石桥市"); b.put(210900, "阜新市"); b.put(210902, "海州区"); b.put(210903, "新邱区"); b.put(210904, "太平区"); b.put(210905, "清河门区"); b.put(210911, "细河区"); b.put(210921, "阜新蒙古族自治县"); b.put(210922, "彰武县"); b.put(211000, "辽阳市"); b.put(211002, "白塔区"); b.put(211003, "文圣区"); b.put(211004, "宏伟区"); b.put(211005, "弓长岭区"); b.put(211011, "太子河区"); b.put(211021, "辽阳县"); b.put(211081, "灯塔市"); b.put(211100, "盘锦市"); b.put(211102, "双台子区"); b.put(211103, "兴隆台区"); b.put(211104, "大洼区"); b.put(211122, "盘山县"); b.put(211200, "铁岭市"); b.put(211202, "银州区"); b.put(211204, "清河区"); b.put(211221, "铁岭县"); b.put(211223, "西丰县"); b.put(211224, "昌图县"); b.put(211281, "调兵山市"); b.put(211282, "开原市"); b.put(211300, "朝阳市"); b.put(211302, "双塔区"); b.put(211303, "龙城区"); b.put(211321, "朝阳县"); b.put(211322, "建平县"); b.put(211324, "喀喇沁左翼蒙古族自治县"); b.put(211381, "北票市"); b.put(211382, "凌源市"); b.put(211400, "葫芦岛市"); b.put(211402, "连山区"); b.put(211403, "龙港区"); b.put(211404, "南票区"); b.put(211421, "绥中县"); b.put(211422, "建昌县"); b.put(211481, "兴城市"); b.put(220000, "吉林省"); b.put(220100, "长春市"); b.put(220102, "南关区"); b.put(220103, "宽城区"); b.put(220104, "朝阳区"); b.put(220105, "二道区"); b.put(220106, "绿园区"); b.put(220112, "双阳区"); b.put(220113, "九台区"); b.put(220122, "农安县"); b.put(220182, "榆树市"); b.put(220183, "德惠市"); b.put(220200, "吉林市"); b.put(220202, "昌邑区"); b.put(220203, "龙潭区"); b.put(220204, "船营区"); b.put(220211, "丰满区"); b.put(220221, "永吉县"); b.put(220281, "蛟河市"); b.put(220282, "桦甸市"); b.put(220283, "舒兰市"); b.put(220284, "磐石市"); b.put(220300, "四平市"); b.put(220302, "铁西区"); b.put(220303, "铁东区"); b.put(220322, "梨树县"); b.put(220323, "伊通满族自治县"); b.put(220381, "公主岭市"); b.put(220382, "双辽市"); b.put(220400, "辽源市"); b.put(220402, "龙山区"); b.put(220403, "西安区"); b.put(220421, "东丰县"); b.put(220422, "东辽县"); b.put(220500, "通化市"); b.put(220502, "东昌区"); b.put(220503, "二道江区"); b.put(220521, "通化县"); b.put(220523, "辉南县"); b.put(220524, "柳河县"); b.put(220581, "梅河口市"); b.put(220582, "集安市"); b.put(220600, "白山市"); b.put(220602, "浑江区"); b.put(220605, "江源区"); b.put(220621, "抚松县"); b.put(220622, "靖宇县"); b.put(220623, "长白朝鲜族自治县"); b.put(220681, "临江市"); b.put(220700, "松原市"); b.put(220702, "宁江区"); b.put(220721, "前郭尔罗斯蒙古族自治县"); b.put(220722, "长岭县"); b.put(220723, "乾安县"); b.put(220781, "扶余市"); b.put(220800, "白城市"); b.put(220802, "洮北区"); b.put(220821, "镇赉县"); b.put(220822, "通榆县"); b.put(220881, "洮南市"); b.put(220882, "大安市"); b.put(222400, "延边朝鲜族自治州"); b.put(222401, "延吉市"); b.put(222402, "图们市"); b.put(222403, "敦化市"); b.put(222404, "珲春市"); b.put(222405, "龙井市"); b.put(222406, "和龙市"); b.put(222424, "汪清县"); b.put(222426, "安图县"); b.put(230000, "黑龙江省"); b.put(230100, "哈尔滨市"); b.put(230102, "道里区"); b.put(230103, "南岗区"); b.put(230104, "道外区"); b.put(230108, "平房区"); b.put(230109, "松北区"); b.put(230110, "香坊区"); b.put(230111, "呼兰区"); b.put(230112, "阿城区"); b.put(230113, "双城区"); b.put(230123, "依兰县"); b.put(230124, "方正县"); b.put(230125, "宾县"); b.put(230126, "巴彦县"); b.put(230127, "木兰县"); b.put(230128, "通河县"); b.put(230129, "延寿县"); b.put(230183, "尚志市"); b.put(230184, "五常市"); b.put(230200, "齐齐哈尔市"); b.put(230202, "龙沙区"); b.put(230203, "建华区"); b.put(230204, "铁锋区"); b.put(230205, "昂昂溪区"); b.put(230206, "富拉尔基区"); b.put(230207, "碾子山区"); b.put(230208, "梅里斯达斡尔族区"); b.put(230221, "龙江县"); b.put(230223, "依安县"); b.put(230224, "泰来县"); b.put(230225, "甘南县"); b.put(230227, "富裕县"); b.put(230229, "克山县"); b.put(230230, "克东县"); b.put(230231, "拜泉县"); b.put(230281, "讷河市"); b.put(230300, "鸡西市"); b.put(230302, "鸡冠区"); b.put(230303, "恒山区"); b.put(230304, "滴道区"); b.put(230305, "梨树区"); b.put(230306, "城子河区"); b.put(230307, "麻山区"); b.put(230321, "鸡东县"); b.put(230381, "虎林市"); b.put(230382, "密山市"); b.put(230400, "鹤岗市"); b.put(230402, "向阳区"); b.put(230403, "工农区"); b.put(230404, "南山区"); b.put(230405, "兴安区"); b.put(230406, "东山区"); b.put(230407, "兴山区"); b.put(230421, "萝北县"); b.put(230422, "绥滨县"); b.put(230500, "双鸭山市"); b.put(230502, "尖山区"); b.put(230503, "岭东区"); b.put(230505, "四方台区"); b.put(230506, "宝山区"); b.put(230521, "集贤县"); b.put(230522, "友谊县"); b.put(230523, "宝清县"); b.put(230524, "饶河县"); b.put(230600, "大庆市"); b.put(230602, "萨尔图区"); b.put(230603, "龙凤区"); b.put(230604, "让胡路区"); b.put(230605, "红岗区"); b.put(230606, "大同区"); b.put(230621, "肇州县"); b.put(230622, "肇源县"); b.put(230623, "林甸县"); b.put(230624, "杜尔伯特蒙古族自治县"); b.put(230700, "伊春市"); b.put(230702, "伊春区"); b.put(230703, "南岔区"); b.put(230704, "友好区"); b.put(230705, "西林区"); b.put(230706, "翠峦区"); b.put(230707, "新青区"); b.put(230708, "美溪区"); b.put(230709, "金山屯区"); b.put(230710, "五营区"); b.put(230711, "乌马河区"); b.put(230712, "汤旺河区"); b.put(230713, "带岭区"); b.put(230714, "乌伊岭区"); b.put(230715, "红星区"); b.put(230716, "上甘岭区"); b.put(230722, "嘉荫县"); b.put(230781, "铁力市"); b.put(230800, "佳木斯市"); b.put(230803, "向阳区"); b.put(230804, "前进区"); b.put(230805, "东风区"); b.put(230811, "郊区"); b.put(230822, "桦南县"); b.put(230826, "桦川县"); b.put(230828, "汤原县"); b.put(230881, "同江市"); b.put(230882, "富锦市"); b.put(230883, "抚远市"); b.put(230900, "七台河市"); b.put(230902, "新兴区"); b.put(230903, "桃山区"); b.put(230904, "茄子河区"); b.put(230921, "勃利县"); b.put(231000, "牡丹江市"); b.put(231002, "东安区"); b.put(231003, "阳明区"); b.put(231004, "爱民区"); b.put(231005, "西安区"); b.put(231025, "林口县"); b.put(231081, "绥芬河市"); b.put(231083, "海林市"); b.put(231084, "宁安市"); b.put(231085, "穆棱市"); b.put(231086, "东宁市"); b.put(231100, "黑河市"); b.put(231102, "爱辉区"); b.put(231121, "嫩江县"); b.put(231123, "逊克县"); b.put(231124, "孙吴县"); b.put(231181, "北安市"); b.put(231182, "五大连池市"); b.put(231200, "绥化市"); b.put(231202, "北林区"); b.put(231221, "望奎县"); b.put(231222, "兰西县"); b.put(231223, "青冈县"); b.put(231224, "庆安县"); b.put(231225, "明水县"); b.put(231226, "绥棱县"); b.put(231281, "安达市"); b.put(231282, "肇东市"); b.put(231283, "海伦市"); b.put(232700, "大兴安岭地区"); b.put(232701, "漠河市"); b.put(232721, "呼玛县"); b.put(232722, "塔河县"); b.put(310000, "上海市"); b.put(310101, "黄浦区"); b.put(310104, "徐汇区"); b.put(310105, "长宁区"); b.put(310106, "静安区"); b.put(310107, "普陀区"); b.put(310109, "虹口区"); b.put(310110, "杨浦区"); b.put(310112, "闵行区"); b.put(310113, "宝山区"); b.put(310114, "嘉定区"); b.put(310115, "浦东新区"); b.put(310116, "金山区"); b.put(310117, "松江区"); b.put(310118, "青浦区"); b.put(310120, "奉贤区"); b.put(310151, "崇明区"); b.put(320000, "江苏省"); b.put(320100, "南京市"); b.put(320102, "玄武区"); b.put(320104, "秦淮区"); b.put(320105, "建邺区"); b.put(320106, "鼓楼区"); b.put(320111, "浦口区"); b.put(320113, "栖霞区"); b.put(320114, "雨花台区"); b.put(320115, "江宁区"); b.put(320116, "六合区"); b.put(320117, "溧水区"); b.put(320118, "高淳区"); b.put(320200, "无锡市"); b.put(320205, "锡山区"); b.put(320206, "惠山区"); b.put(320211, "滨湖区"); b.put(320213, "梁溪区"); b.put(320214, "新吴区"); b.put(320281, "江阴市"); b.put(320282, "宜兴市"); b.put(320300, "徐州市"); b.put(320302, "鼓楼区"); b.put(320303, "云龙区"); b.put(320305, "贾汪区"); b.put(320311, "泉山区"); b.put(320312, "铜山区"); b.put(320321, "丰县"); b.put(320322, "沛县"); b.put(320324, "睢宁县"); b.put(320381, "新沂市"); b.put(320382, "邳州市"); b.put(320400, "常州市"); b.put(320402, "天宁区"); b.put(320404, "钟楼区"); b.put(320411, "新北区"); b.put(320412, "武进区"); b.put(320413, "金坛区"); b.put(320481, "溧阳市"); b.put(320500, "苏州市"); b.put(320505, "虎丘区"); b.put(320506, "吴中区"); b.put(320507, "相城区"); b.put(320508, "姑苏区"); b.put(320509, "吴江区"); b.put(320581, "常熟市"); b.put(320582, "张家港市"); b.put(320583, "昆山市"); b.put(320585, "太仓市"); b.put(320600, "南通市"); b.put(320602, "崇川区"); b.put(320611, "港闸区"); b.put(320612, "通州区"); b.put(320623, "如东县"); b.put(320681, "启东市"); b.put(320682, "如皋市"); b.put(320684, "海门市"); b.put(320685, "海安市"); b.put(320700, "连云港市"); b.put(320703, "连云区"); b.put(320706, "海州区"); b.put(320707, "赣榆区"); b.put(320722, "东海县"); b.put(320723, "灌云县"); b.put(320724, "灌南县"); b.put(320800, "淮安市"); b.put(320803, "淮安区"); b.put(320804, "淮阴区"); b.put(320812, "清江浦区"); b.put(320813, "洪泽区"); b.put(320826, "涟水县"); b.put(320830, "盱眙县"); b.put(320831, "金湖县"); b.put(320900, "盐城市"); b.put(320902, "亭湖区"); b.put(320903, "盐都区"); b.put(320904, "大丰区"); b.put(320921, "响水县"); b.put(320922, "滨海县"); b.put(320923, "阜宁县"); b.put(320924, "射阳县"); b.put(320925, "建湖县"); b.put(320981, "东台市"); b.put(321000, "扬州市"); b.put(321002, "广陵区"); b.put(321003, "邗江区"); b.put(321012, "江都区"); b.put(321023, "宝应县"); b.put(321081, "仪征市"); b.put(321084, "高邮市"); b.put(321100, "镇江市"); b.put(321102, "京口区"); b.put(321111, "润州区"); b.put(321112, "丹徒区"); b.put(321181, "丹阳市"); b.put(321182, "扬中市"); b.put(321183, "句容市"); b.put(321200, "泰州市"); b.put(321202, "海陵区"); b.put(321203, "高港区"); b.put(321204, "姜堰区"); b.put(321281, "兴化市"); b.put(321282, "靖江市"); b.put(321283, "泰兴市"); b.put(321300, "宿迁市"); b.put(321302, "宿城区"); b.put(321311, "宿豫区"); b.put(321322, "沭阳县"); b.put(321323, "泗阳县"); b.put(321324, "泗洪县"); b.put(330000, "浙江省"); b.put(330100, "杭州市"); b.put(330102, "上城区"); b.put(330103, "下城区"); b.put(330104, "江干区"); b.put(330105, "拱墅区"); b.put(330106, "西湖区"); b.put(330108, "滨江区"); b.put(330109, "萧山区"); b.put(330110, "余杭区"); b.put(330111, "富阳区"); b.put(330112, "临安区"); b.put(330122, "桐庐县"); b.put(330127, "淳安县"); b.put(330182, "建德市"); b.put(330200, "宁波市"); b.put(330203, "海曙区"); b.put(330205, "江北区"); b.put(330206, "北仑区"); b.put(330211, "镇海区"); b.put(330212, "鄞州区"); b.put(330213, "奉化区"); b.put(330225, "象山县"); b.put(330226, "宁海县"); b.put(330281, "余姚市"); b.put(330282, "慈溪市"); b.put(330300, "温州市"); b.put(330302, "鹿城区"); b.put(330303, "龙湾区"); b.put(330304, "瓯海区"); b.put(330305, "洞头区"); b.put(330324, "永嘉县"); b.put(330326, "平阳县"); b.put(330327, "苍南县"); b.put(330328, "文成县"); b.put(330329, "泰顺县"); b.put(330381, "瑞安市"); b.put(330382, "乐清市"); b.put(330400, "嘉兴市"); b.put(330402, "南湖区"); b.put(330411, "秀洲区"); b.put(330421, "嘉善县"); b.put(330424, "海盐县"); b.put(330481, "海宁市"); b.put(330482, "平湖市"); b.put(330483, "桐乡市"); b.put(330500, "湖州市"); b.put(330502, "吴兴区"); b.put(330503, "南浔区"); b.put(330521, "德清县"); b.put(330522, "长兴县"); b.put(330523, "安吉县"); b.put(330600, "绍兴市"); b.put(330602, "越城区"); b.put(330603, "柯桥区"); b.put(330604, "上虞区"); b.put(330624, "新昌县"); b.put(330681, "诸暨市"); b.put(330683, "嵊州市"); b.put(330700, "金华市"); b.put(330702, "婺城区"); b.put(330703, "金东区"); b.put(330723, "武义县"); b.put(330726, "浦江县"); b.put(330727, "磐安县"); b.put(330781, "兰溪市"); b.put(330782, "义乌市"); b.put(330783, "东阳市"); b.put(330784, "永康市"); b.put(330800, "衢州市"); b.put(330802, "柯城区"); b.put(330803, "衢江区"); b.put(330822, "常山县"); b.put(330824, "开化县"); b.put(330825, "龙游县"); b.put(330881, "江山市"); b.put(330900, "舟山市"); b.put(330902, "定海区"); b.put(330903, "普陀区"); b.put(330921, "岱山县"); b.put(330922, "嵊泗县"); b.put(331000, "台州市"); b.put(331002, "椒江区"); b.put(331003, "黄岩区"); b.put(331004, "路桥区"); b.put(331022, "三门县"); b.put(331023, "天台县"); b.put(331024, "仙居县"); b.put(331081, "温岭市"); b.put(331082, "临海市"); b.put(331083, "玉环市"); b.put(331100, "丽水市"); b.put(331102, "莲都区"); b.put(331121, "青田县"); b.put(331122, "缙云县"); b.put(331123, "遂昌县"); b.put(331124, "松阳县"); b.put(331125, "云和县"); b.put(331126, "庆元县"); b.put(331127, "景宁畲族自治县"); b.put(331181, "龙泉市"); b.put(340000, "安徽省"); b.put(340100, "合肥市"); b.put(340102, "瑶海区"); b.put(340103, "庐阳区"); b.put(340104, "蜀山区"); b.put(340111, "包河区"); b.put(340121, "长丰县"); b.put(340122, "肥东县"); b.put(340123, "肥西县"); b.put(340124, "庐江县"); b.put(340181, "巢湖市"); b.put(340200, "芜湖市"); b.put(340202, "镜湖区"); b.put(340203, "弋江区"); b.put(340207, "鸠江区"); b.put(340208, "三山区"); b.put(340221, "芜湖县"); b.put(340222, "繁昌县"); b.put(340223, "南陵县"); b.put(340225, "无为县"); b.put(340300, "蚌埠市"); b.put(340302, "龙子湖区"); b.put(340303, "蚌山区"); b.put(340304, "禹会区"); b.put(340311, "淮上区"); b.put(340321, "怀远县"); b.put(340322, "五河县"); b.put(340323, "固镇县"); b.put(340400, "淮南市"); b.put(340402, "大通区"); b.put(340403, "田家庵区"); b.put(340404, "谢家集区"); b.put(340405, "八公山区"); b.put(340406, "潘集区"); b.put(340421, "凤台县"); b.put(340422, "寿县"); b.put(340500, "马鞍山市"); b.put(340503, "花山区"); b.put(340504, "雨山区"); b.put(340506, "博望区"); b.put(340521, "当涂县"); b.put(340522, "含山县"); b.put(340523, "和县"); b.put(340600, "淮北市"); b.put(340602, "杜集区"); b.put(340603, "相山区"); b.put(340604, "烈山区"); b.put(340621, "濉溪县"); b.put(340700, "铜陵市"); b.put(340705, "铜官区"); b.put(340706, "义安区"); b.put(340711, "郊区"); b.put(340722, "枞阳县"); b.put(340800, "安庆市"); b.put(340802, "迎江区"); b.put(340803, "大观区"); b.put(340811, "宜秀区"); b.put(340822, "怀宁县"); b.put(340824, "潜山县"); b.put(340825, "太湖县"); b.put(340826, "宿松县"); b.put(340827, "望江县"); b.put(340828, "岳西县"); b.put(340881, "桐城市"); b.put(341000, "黄山市"); b.put(341002, "屯溪区"); b.put(341003, "黄山区"); b.put(341004, "徽州区"); b.put(341021, "歙县"); b.put(341022, "休宁县"); b.put(341023, "黟县"); b.put(341024, "祁门县"); b.put(341100, "滁州市"); b.put(341102, "琅琊区"); b.put(341103, "南谯区"); b.put(341122, "来安县"); b.put(341124, "全椒县"); b.put(341125, "定远县"); b.put(341126, "凤阳县"); b.put(341181, "天长市"); b.put(341182, "明光市"); b.put(341200, "阜阳市"); b.put(341202, "颍州区"); b.put(341203, "颍东区"); b.put(341204, "颍泉区"); b.put(341221, "临泉县"); b.put(341222, "太和县"); b.put(341225, "阜南县"); b.put(341226, "颍上县"); b.put(341282, "界首市"); b.put(341300, "宿州市"); b.put(341302, "埇桥区"); b.put(341321, "砀山县"); b.put(341322, "萧县"); b.put(341323, "灵璧县"); b.put(341324, "泗县"); b.put(341500, "六安市"); b.put(341502, "金安区"); b.put(341503, "裕安区"); b.put(341504, "叶集区"); b.put(341522, "霍邱县"); b.put(341523, "舒城县"); b.put(341524, "金寨县"); b.put(341525, "霍山县"); b.put(341600, "亳州市"); b.put(341602, "谯城区"); b.put(341621, "涡阳县"); b.put(341622, "蒙城县"); b.put(341623, "利辛县"); b.put(341700, "池州市"); b.put(341702, "贵池区"); b.put(341721, "东至县"); b.put(341722, "石台县"); b.put(341723, "青阳县"); b.put(341800, "宣城市"); b.put(341802, "宣州区"); b.put(341821, "郎溪县"); b.put(341822, "广德县"); b.put(341823, "泾县"); b.put(341824, "绩溪县"); b.put(341825, "旌德县"); b.put(341881, "宁国市"); b.put(350000, "福建省"); b.put(350100, "福州市"); b.put(350102, "鼓楼区"); b.put(350103, "台江区"); b.put(350104, "仓山区"); b.put(350105, "马尾区"); b.put(350111, "晋安区"); b.put(350112, "长乐区"); b.put(350121, "闽侯县"); b.put(350122, "连江县"); b.put(350123, "罗源县"); b.put(350124, "闽清县"); b.put(350125, "永泰县"); b.put(350128, "平潭县"); b.put(350181, "福清市"); b.put(350200, "厦门市"); b.put(350203, "思明区"); b.put(350205, "海沧区"); b.put(350206, "湖里区"); b.put(350211, "集美区"); b.put(350212, "同安区"); b.put(350213, "翔安区"); b.put(350300, "莆田市"); b.put(350302, "城厢区"); b.put(350303, "涵江区"); b.put(350304, "荔城区"); b.put(350305, "秀屿区"); b.put(350322, "仙游县"); b.put(350400, "三明市"); b.put(350402, "梅列区"); b.put(350403, "三元区"); b.put(350421, "明溪县"); b.put(350423, "清流县"); b.put(350424, "宁化县"); b.put(350425, "大田县"); b.put(350426, "尤溪县"); b.put(350427, "沙县"); b.put(350428, "将乐县"); b.put(350429, "泰宁县"); b.put(350430, "建宁县"); b.put(350481, "永安市"); b.put(350500, "泉州市"); b.put(350502, "鲤城区"); b.put(350503, "丰泽区"); b.put(350504, "洛江区"); b.put(350505, "泉港区"); b.put(350521, "惠安县"); b.put(350524, "安溪县"); b.put(350525, "永春县"); b.put(350526, "德化县"); b.put(350527, "金门县"); b.put(350581, "石狮市"); b.put(350582, "晋江市"); b.put(350583, "南安市"); b.put(350600, "漳州市"); b.put(350602, "芗城区"); b.put(350603, "龙文区"); b.put(350622, "云霄县"); b.put(350623, "漳浦县"); b.put(350624, "诏安县"); b.put(350625, "长泰县"); b.put(350626, "东山县"); b.put(350627, "南靖县"); b.put(350628, "平和县"); b.put(350629, "华安县"); b.put(350681, "龙海市"); b.put(350700, "南平市"); b.put(350702, "延平区"); b.put(350703, "建阳区"); b.put(350721, "顺昌县"); b.put(350722, "浦城县"); b.put(350723, "光泽县"); b.put(350724, "松溪县"); b.put(350725, "政和县"); b.put(350781, "邵武市"); b.put(350782, "武夷山市"); b.put(350783, "建瓯市"); b.put(350800, "龙岩市"); b.put(350802, "新罗区"); b.put(350803, "永定区"); b.put(350821, "长汀县"); b.put(350823, "上杭县"); b.put(350824, "武平县"); b.put(350825, "连城县"); b.put(350881, "漳平市"); b.put(350900, "宁德市"); b.put(350902, "蕉城区"); b.put(350921, "霞浦县"); b.put(350922, "古田县"); b.put(350923, "屏南县"); b.put(350924, "寿宁县"); b.put(350925, "周宁县"); b.put(350926, "柘荣县"); b.put(350981, "福安市"); b.put(350982, "福鼎市"); b.put(360000, "江西省"); b.put(360100, "南昌市"); b.put(360102, "东湖区"); b.put(360103, "西湖区"); b.put(360104, "青云谱区"); b.put(360105, "湾里区"); b.put(360111, "青山湖区"); b.put(360112, "新建区"); b.put(360121, "南昌县"); b.put(360123, "安义县"); b.put(360124, "进贤县"); b.put(360200, "景德镇市"); b.put(360202, "昌江区"); b.put(360203, "珠山区"); b.put(360222, "浮梁县"); b.put(360281, "乐平市"); b.put(360300, "萍乡市"); b.put(360302, "安源区"); b.put(360313, "湘东区"); b.put(360321, "莲花县"); b.put(360322, "上栗县"); b.put(360323, "芦溪县"); b.put(360400, "九江市"); b.put(360402, "濂溪区"); b.put(360403, "浔阳区"); b.put(360404, "柴桑区"); b.put(360423, "武宁县"); b.put(360424, "修水县"); b.put(360425, "永修县"); b.put(360426, "德安县"); b.put(360428, "都昌县"); b.put(360429, "湖口县"); b.put(360430, "彭泽县"); b.put(360481, "瑞昌市"); b.put(360482, "共青城市"); b.put(360483, "庐山市"); b.put(360500, "新余市"); b.put(360502, "渝水区"); b.put(360521, "分宜县"); b.put(360600, "鹰潭市"); b.put(360602, "月湖区"); b.put(360603, "余江区"); b.put(360681, "贵溪市"); b.put(360700, "赣州市"); b.put(360702, "章贡区"); b.put(360703, "南康区"); b.put(360704, "赣县区"); b.put(360722, "信丰县"); b.put(360723, "大余县"); b.put(360724, "上犹县"); b.put(360725, "崇义县"); b.put(360726, "安远县"); b.put(360727, "龙南县"); b.put(360728, "定南县"); b.put(360729, "全南县"); b.put(360730, "宁都县"); b.put(360731, "于都县"); b.put(360732, "兴国县"); b.put(360733, "会昌县"); b.put(360734, "寻乌县"); b.put(360735, "石城县"); b.put(360781, "瑞金市"); b.put(360800, "吉安市"); b.put(360802, "吉州区"); b.put(360803, "青原区"); b.put(360821, "吉安县"); b.put(360822, "吉水县"); b.put(360823, "峡江县"); b.put(360824, "新干县"); b.put(360825, "永丰县"); b.put(360826, "泰和县"); b.put(360827, "遂川县"); b.put(360828, "万安县"); b.put(360829, "安福县"); b.put(360830, "永新县"); b.put(360881, "井冈山市"); b.put(360900, "宜春市"); b.put(360902, "袁州区"); b.put(360921, "奉新县"); b.put(360922, "万载县"); b.put(360923, "上高县"); b.put(360924, "宜丰县"); b.put(360925, "靖安县"); b.put(360926, "铜鼓县"); b.put(360981, "丰城市"); b.put(360982, "樟树市"); b.put(360983, "高安市"); b.put(361000, "抚州市"); b.put(361002, "临川区"); b.put(361003, "东乡区"); b.put(361021, "南城县"); b.put(361022, "黎川县"); b.put(361023, "南丰县"); b.put(361024, "崇仁县"); b.put(361025, "乐安县"); b.put(361026, "宜黄县"); b.put(361027, "金溪县"); b.put(361028, "资溪县"); b.put(361030, "广昌县"); b.put(361100, "上饶市"); b.put(361102, "信州区"); b.put(361103, "广丰区"); b.put(361121, "上饶县"); b.put(361123, "玉山县"); b.put(361124, "铅山县"); b.put(361125, "横峰县"); b.put(361126, "弋阳县"); b.put(361127, "余干县"); b.put(361128, "鄱阳县"); b.put(361129, "万年县"); b.put(361130, "婺源县"); b.put(361181, "德兴市"); b.put(370000, "山东省"); b.put(370100, "济南市"); b.put(370102, "历下区"); b.put(370103, "市中区"); b.put(370104, "槐荫区"); b.put(370105, "天桥区"); b.put(370112, "历城区"); b.put(370113, "长清区"); b.put(370114, "章丘区"); b.put(370124, "平阴县"); b.put(370125, "济阳县"); b.put(370126, "商河县"); b.put(370200, "青岛市"); b.put(370202, "市南区"); b.put(370203, "市北区"); b.put(370211, "黄岛区"); b.put(370212, "崂山区"); b.put(370213, "李沧区"); b.put(370214, "城阳区"); b.put(370215, "即墨区"); b.put(370281, "胶州市"); b.put(370283, "平度市"); b.put(370285, "莱西市"); b.put(370300, "淄博市"); b.put(370302, "淄川区"); b.put(370303, "张店区"); b.put(370304, "博山区"); b.put(370305, "临淄区"); b.put(370306, "周村区"); b.put(370321, "桓台县"); b.put(370322, "高青县"); b.put(370323, "沂源县"); b.put(370400, "枣庄市"); b.put(370402, "市中区"); b.put(370403, "薛城区"); b.put(370404, "峄城区"); b.put(370405, "台儿庄区"); b.put(370406, "山亭区"); b.put(370481, "滕州市"); b.put(370500, "东营市"); b.put(370502, "东营区"); b.put(370503, "河口区"); b.put(370505, "垦利区"); b.put(370522, "利津县"); b.put(370523, "广饶县"); b.put(370600, "烟台市"); b.put(370602, "芝罘区"); b.put(370611, "福山区"); b.put(370612, "牟平区"); b.put(370613, "莱山区"); b.put(370634, "长岛县"); b.put(370681, "龙口市"); b.put(370682, "莱阳市"); b.put(370683, "莱州市"); b.put(370684, "蓬莱市"); b.put(370685, "招远市"); b.put(370686, "栖霞市"); b.put(370687, "海阳市"); b.put(370700, "潍坊市"); b.put(370702, "潍城区"); b.put(370703, "寒亭区"); b.put(370704, "坊子区"); b.put(370705, "奎文区"); b.put(370724, "临朐县"); b.put(370725, "昌乐县"); b.put(370781, "青州市"); b.put(370782, "诸城市"); b.put(370783, "寿光市"); b.put(370784, "安丘市"); b.put(370785, "高密市"); b.put(370786, "昌邑市"); b.put(370800, "济宁市"); b.put(370811, "任城区"); b.put(370812, "兖州区"); b.put(370826, "微山县"); b.put(370827, "鱼台县"); b.put(370828, "金乡县"); b.put(370829, "嘉祥县"); b.put(370830, "汶上县"); b.put(370831, "泗水县"); b.put(370832, "梁山县"); b.put(370881, "曲阜市"); b.put(370883, "邹城市"); b.put(370900, "泰安市"); b.put(370902, "泰山区"); b.put(370911, "岱岳区"); b.put(370921, "宁阳县"); b.put(370923, "东平县"); b.put(370982, "新泰市"); b.put(370983, "肥城市"); b.put(371000, "威海市"); b.put(371002, "环翠区"); b.put(371003, "文登区"); b.put(371082, "荣成市"); b.put(371083, "乳山市"); b.put(371100, "日照市"); b.put(371102, "东港区"); b.put(371103, "岚山区"); b.put(371121, "五莲县"); b.put(371122, "莒县"); b.put(371200, "莱芜市"); b.put(371202, "莱城区"); b.put(371203, "钢城区"); b.put(371300, "临沂市"); b.put(371302, "兰山区"); b.put(371311, "罗庄区"); b.put(371312, "河东区"); b.put(371321, "沂南县"); b.put(371322, "郯城县"); b.put(371323, "沂水县"); b.put(371324, "兰陵县"); b.put(371325, "费县"); b.put(371326, "平邑县"); b.put(371327, "莒南县"); b.put(371328, "蒙阴县"); b.put(371329, "临沭县"); b.put(371400, "德州市"); b.put(371402, "德城区"); b.put(371403, "陵城区"); b.put(371422, "宁津县"); b.put(371423, "庆云县"); b.put(371424, "临邑县"); b.put(371425, "齐河县"); b.put(371426, "平原县"); b.put(371427, "夏津县"); b.put(371428, "武城县"); b.put(371481, "乐陵市"); b.put(371482, "禹城市"); b.put(371500, "聊城市"); b.put(371502, "东昌府区"); b.put(371521, "阳谷县"); b.put(371522, "莘县"); b.put(371523, "茌平县"); b.put(371524, "东阿县"); b.put(371525, "冠县"); b.put(371526, "高唐县"); b.put(371581, "临清市"); b.put(371600, "滨州市"); b.put(371602, "滨城区"); b.put(371603, "沾化区"); b.put(371621, "惠民县"); b.put(371622, "阳信县"); b.put(371623, "无棣县"); b.put(371625, "博兴县"); b.put(371626, "邹平县"); b.put(371700, "菏泽市"); b.put(371702, "牡丹区"); b.put(371703, "定陶区"); b.put(371721, "曹县"); b.put(371722, "单县"); b.put(371723, "成武县"); b.put(371724, "巨野县"); b.put(371725, "郓城县"); b.put(371726, "鄄城县"); b.put(371728, "东明县"); b.put(410000, "河南省"); b.put(410100, "郑州市"); b.put(410102, "中原区"); b.put(410103, "二七区"); b.put(410104, "管城回族区"); b.put(410105, "金水区"); b.put(410106, "上街区"); b.put(410108, "惠济区"); b.put(410122, "中牟县"); b.put(410181, "巩义市"); b.put(410182, "荥阳市"); b.put(410183, "新密市"); b.put(410184, "新郑市"); b.put(410185, "登封市"); b.put(410200, "开封市"); b.put(410202, "龙亭区"); b.put(410203, "顺河回族区"); b.put(410204, "鼓楼区"); b.put(410205, "禹王台区"); b.put(410212, "祥符区"); b.put(410221, "杞县"); b.put(410222, "通许县"); b.put(410223, "尉氏县"); b.put(410225, "兰考县"); b.put(410300, "洛阳市"); b.put(410302, "老城区"); b.put(410303, "西工区"); b.put(410304, "瀍河回族区"); b.put(410305, "涧西区"); b.put(410306, "吉利区"); b.put(410311, "洛龙区"); b.put(410322, "孟津县"); b.put(410323, "新安县"); b.put(410324, "栾川县"); b.put(410325, "嵩县"); b.put(410326, "汝阳县"); b.put(410327, "宜阳县"); b.put(410328, "洛宁县"); b.put(410329, "伊川县"); b.put(410381, "偃师市"); b.put(410400, "平顶山市"); b.put(410402, "新华区"); b.put(410403, "卫东区"); b.put(410404, "石龙区"); b.put(410411, "湛河区"); b.put(410421, "宝丰县"); b.put(410422, "叶县"); b.put(410423, "鲁山县"); b.put(410425, "郏县"); b.put(410481, "舞钢市"); b.put(410482, "汝州市"); b.put(410500, "安阳市"); b.put(410502, "文峰区"); b.put(410503, "北关区"); b.put(410505, "殷都区"); b.put(410506, "龙安区"); b.put(410522, "安阳县"); b.put(410523, "汤阴县"); b.put(410526, "滑县"); b.put(410527, "内黄县"); b.put(410581, "林州市"); b.put(410600, "鹤壁市"); b.put(410602, "鹤山区"); b.put(410603, "山城区"); b.put(410611, "淇滨区"); b.put(410621, "浚县"); b.put(410622, "淇县"); b.put(410700, "新乡市"); b.put(410702, "红旗区"); b.put(410703, "卫滨区"); b.put(410704, "凤泉区"); b.put(410711, "牧野区"); b.put(410721, "新乡县"); b.put(410724, "获嘉县"); b.put(410725, "原阳县"); b.put(410726, "延津县"); b.put(410727, "封丘县"); b.put(410728, "长垣县"); b.put(410781, "卫辉市"); b.put(410782, "辉县市"); b.put(410800, "焦作市"); b.put(410802, "解放区"); b.put(410803, "中站区"); b.put(410804, "马村区"); b.put(410811, "山阳区"); b.put(410821, "修武县"); b.put(410822, "博爱县"); b.put(410823, "武陟县"); b.put(410825, "温县"); b.put(410882, "沁阳市"); b.put(410883, "孟州市"); b.put(410900, "濮阳市"); b.put(410902, "华龙区"); b.put(410922, "清丰县"); b.put(410923, "南乐县"); b.put(410926, "范县"); b.put(410927, "台前县"); b.put(410928, "濮阳县"); b.put(411000, "许昌市"); b.put(411002, "魏都区"); b.put(411003, "建安区"); b.put(411024, "鄢陵县"); b.put(411025, "襄城县"); b.put(411081, "禹州市"); b.put(411082, "长葛市"); b.put(411100, "漯河市"); b.put(411102, "源汇区"); b.put(411103, "郾城区"); b.put(411104, "召陵区"); b.put(411121, "舞阳县"); b.put(411122, "临颍县"); b.put(411200, "三门峡市"); b.put(411202, "湖滨区"); b.put(411203, "陕州区"); b.put(411221, "渑池县"); b.put(411224, "卢氏县"); b.put(411281, "义马市"); b.put(411282, "灵宝市"); b.put(411300, "南阳市"); b.put(411302, "宛城区"); b.put(411303, "卧龙区"); b.put(411321, "南召县"); b.put(411322, "方城县"); b.put(411323, "西峡县"); b.put(411324, "镇平县"); b.put(411325, "内乡县"); b.put(411326, "淅川县"); b.put(411327, "社旗县"); b.put(411328, "唐河县"); b.put(411329, "新野县"); b.put(411330, "桐柏县"); b.put(411381, "邓州市"); b.put(411400, "商丘市"); b.put(411402, "梁园区"); b.put(411403, "睢阳区"); b.put(411421, "民权县"); b.put(411422, "睢县"); b.put(411423, "宁陵县"); b.put(411424, "柘城县"); b.put(411425, "虞城县"); b.put(411426, "夏邑县"); b.put(411481, "永城市"); b.put(411500, "信阳市"); b.put(411502, "浉河区"); b.put(411503, "平桥区"); b.put(411521, "罗山县"); b.put(411522, "光山县"); b.put(411523, "新县"); b.put(411524, "商城县"); b.put(411525, "固始县"); b.put(411526, "潢川县"); b.put(411527, "淮滨县"); b.put(411528, "息县"); b.put(411600, "周口市"); b.put(411602, "川汇区"); b.put(411621, "扶沟县"); b.put(411622, "西华县"); b.put(411623, "商水县"); b.put(411624, "沈丘县"); b.put(411625, "郸城县"); b.put(411626, "淮阳县"); b.put(411627, "太康县"); b.put(411628, "鹿邑县"); b.put(411681, "项城市"); b.put(411700, "驻马店市"); b.put(411702, "驿城区"); b.put(411721, "西平县"); b.put(411722, "上蔡县"); b.put(411723, "平舆县"); b.put(411724, "正阳县"); b.put(411725, "确山县"); b.put(411726, "泌阳县"); b.put(411727, "汝南县"); b.put(411728, "遂平县"); b.put(411729, "新蔡县"); b.put(419001, "济源市"); b.put(420000, "湖北省"); b.put(420100, "武汉市"); b.put(420102, "江岸区"); b.put(420103, "江汉区"); b.put(420104, "硚口区"); b.put(420105, "汉阳区"); b.put(420106, "武昌区"); b.put(420107, "青山区"); b.put(420111, "洪山区"); b.put(420112, "东西湖区"); b.put(420113, "汉南区"); b.put(420114, "蔡甸区"); b.put(420115, "江夏区"); b.put(420116, "黄陂区"); b.put(420117, "新洲区"); b.put(420200, "黄石市"); b.put(420202, "黄石港区"); b.put(420203, "西塞山区"); b.put(420204, "下陆区"); b.put(420205, "铁山区"); b.put(420222, "阳新县"); b.put(420281, "大冶市"); b.put(420300, "十堰市"); b.put(420302, "茅箭区"); b.put(420303, "张湾区"); b.put(420304, "郧阳区"); b.put(420322, "郧西县"); b.put(420323, "竹山县"); b.put(420324, "竹溪县"); b.put(420325, "房县"); b.put(420381, "丹江口市"); b.put(420500, "宜昌市"); b.put(420502, "西陵区"); b.put(420503, "伍家岗区"); b.put(420504, "点军区"); b.put(420505, "猇亭区"); b.put(420506, "夷陵区"); b.put(420525, "远安县"); b.put(420526, "兴山县"); b.put(420527, "秭归县"); b.put(420528, "长阳土家族自治县"); b.put(420529, "五峰土家族自治县"); b.put(420581, "宜都市"); b.put(420582, "当阳市"); b.put(420583, "枝江市"); b.put(420600, "襄阳市"); b.put(420602, "襄城区"); b.put(420606, "樊城区"); b.put(420607, "襄州区"); b.put(420624, "南漳县"); b.put(420625, "谷城县"); b.put(420626, "保康县"); b.put(420682, "老河口市"); b.put(420683, "枣阳市"); b.put(420684, "宜城市"); b.put(420700, "鄂州市"); b.put(420702, "梁子湖区"); b.put(420703, "华容区"); b.put(420704, "鄂城区"); b.put(420800, "荆门市"); b.put(420802, "东宝区"); b.put(420804, "掇刀区"); b.put(420822, "沙洋县"); b.put(420881, "钟祥市"); b.put(420882, "京山市"); b.put(420900, "孝感市"); b.put(420902, "孝南区"); b.put(420921, "孝昌县"); b.put(420922, "大悟县"); b.put(420923, "云梦县"); b.put(420981, "应城市"); b.put(420982, "安陆市"); b.put(420984, "汉川市"); b.put(421000, "荆州市"); b.put(421002, "沙市区"); b.put(421003, "荆州区"); b.put(421022, "公安县"); b.put(421023, "监利县"); b.put(421024, "江陵县"); b.put(421081, "石首市"); b.put(421083, "洪湖市"); b.put(421087, "松滋市"); b.put(421100, "黄冈市"); b.put(421102, "黄州区"); b.put(421121, "团风县"); b.put(421122, "红安县"); b.put(421123, "罗田县"); b.put(421124, "英山县"); b.put(421125, "浠水县"); b.put(421126, "蕲春县"); b.put(421127, "黄梅县"); b.put(421181, "麻城市"); b.put(421182, "武穴市"); b.put(421200, "咸宁市"); b.put(421202, "咸安区"); b.put(421221, "嘉鱼县"); b.put(421222, "通城县"); b.put(421223, "崇阳县"); b.put(421224, "通山县"); b.put(421281, "赤壁市"); b.put(421300, "随州市"); b.put(421303, "曾都区"); b.put(421321, "随县"); b.put(421381, "广水市"); b.put(422800, "恩施土家族苗族自治州"); b.put(422801, "恩施市"); b.put(422802, "利川市"); b.put(422822, "建始县"); b.put(422823, "巴东县"); b.put(422825, "宣恩县"); b.put(422826, "咸丰县"); b.put(422827, "来凤县"); b.put(422828, "鹤峰县"); b.put(429004, "仙桃市"); b.put(429005, "潜江市"); b.put(429006, "天门市"); b.put(429021, "神农架林区"); b.put(430000, "湖南省"); b.put(430100, "长沙市"); b.put(430102, "芙蓉区"); b.put(430103, "天心区"); b.put(430104, "岳麓区"); b.put(430105, "开福区"); b.put(430111, "雨花区"); b.put(430112, "望城区"); b.put(430121, "长沙县"); b.put(430181, "浏阳市"); b.put(430182, "宁乡市"); b.put(430200, "株洲市"); b.put(430202, "荷塘区"); b.put(430203, "芦淞区"); b.put(430204, "石峰区"); b.put(430211, "天元区"); b.put(430212, "渌口区"); b.put(430223, "攸县"); b.put(430224, "茶陵县"); b.put(430225, "炎陵县"); b.put(430281, "醴陵市"); b.put(430300, "湘潭市"); b.put(430302, "雨湖区"); b.put(430304, "岳塘区"); b.put(430321, "湘潭县"); b.put(430381, "湘乡市"); b.put(430382, "韶山市"); b.put(430400, "衡阳市"); b.put(430405, "珠晖区"); b.put(430406, "雁峰区"); b.put(430407, "石鼓区"); b.put(430408, "蒸湘区"); b.put(430412, "南岳区"); b.put(430421, "衡阳县"); b.put(430422, "衡南县"); b.put(430423, "衡山县"); b.put(430424, "衡东县"); b.put(430426, "祁东县"); b.put(430481, "耒阳市"); b.put(430482, "常宁市"); b.put(430500, "邵阳市"); b.put(430502, "双清区"); b.put(430503, "大祥区"); b.put(430511, "北塔区"); b.put(430521, "邵东县"); b.put(430522, "新邵县"); b.put(430523, "邵阳县"); b.put(430524, "隆回县"); b.put(430525, "洞口县"); b.put(430527, "绥宁县"); b.put(430528, "新宁县"); b.put(430529, "城步苗族自治县"); b.put(430581, "武冈市"); b.put(430600, "岳阳市"); b.put(430602, "岳阳楼区"); b.put(430603, "云溪区"); b.put(430611, "君山区"); b.put(430621, "岳阳县"); b.put(430623, "华容县"); b.put(430624, "湘阴县"); b.put(430626, "平江县"); b.put(430681, "汨罗市"); b.put(430682, "临湘市"); b.put(430700, "常德市"); b.put(430702, "武陵区"); b.put(430703, "鼎城区"); b.put(430721, "安乡县"); b.put(430722, "汉寿县"); b.put(430723, "澧县"); b.put(430724, "临澧县"); b.put(430725, "桃源县"); b.put(430726, "石门县"); b.put(430781, "津市市"); b.put(430800, "张家界市"); b.put(430802, "永定区"); b.put(430811, "武陵源区"); b.put(430821, "慈利县"); b.put(430822, "桑植县"); b.put(430900, "益阳市"); b.put(430902, "资阳区"); b.put(430903, "赫山区"); b.put(430921, "南县"); b.put(430922, "桃江县"); b.put(430923, "安化县"); b.put(430981, "沅江市"); b.put(431000, "郴州市"); b.put(431002, "北湖区"); b.put(431003, "苏仙区"); b.put(431021, "桂阳县"); b.put(431022, "宜章县"); b.put(431023, "永兴县"); b.put(431024, "嘉禾县"); b.put(431025, "临武县"); b.put(431026, "汝城县"); b.put(431027, "桂东县"); b.put(431028, "安仁县"); b.put(431081, "资兴市"); b.put(431100, "永州市"); b.put(431102, "零陵区"); b.put(431103, "冷水滩区"); b.put(431121, "祁阳县"); b.put(431122, "东安县"); b.put(431123, "双牌县"); b.put(431124, "道县"); b.put(431125, "江永县"); b.put(431126, "宁远县"); b.put(431127, "蓝山县"); b.put(431128, "新田县"); b.put(431129, "江华瑶族自治县"); b.put(431200, "怀化市"); b.put(431202, "鹤城区"); b.put(431221, "中方县"); b.put(431222, "沅陵县"); b.put(431223, "辰溪县"); b.put(431224, "溆浦县"); b.put(431225, "会同县"); b.put(431226, "麻阳苗族自治县"); b.put(431227, "新晃侗族自治县"); b.put(431228, "芷江侗族自治县"); b.put(431229, "靖州苗族侗族自治县"); b.put(431230, "通道侗族自治县"); b.put(431281, "洪江市"); b.put(431300, "娄底市"); b.put(431302, "娄星区"); b.put(431321, "双峰县"); b.put(431322, "新化县"); b.put(431381, "冷水江市"); b.put(431382, "涟源市"); b.put(433100, "湘西土家族苗族自治州"); b.put(433101, "吉首市"); b.put(433122, "泸溪县"); b.put(433123, "凤凰县"); b.put(433124, "花垣县"); b.put(433125, "保靖县"); b.put(433126, "古丈县"); b.put(433127, "永顺县"); b.put(433130, "龙山县"); b.put(440000, "广东省"); b.put(440100, "广州市"); b.put(440103, "荔湾区"); b.put(440104, "越秀区"); b.put(440105, "海珠区"); b.put(440106, "天河区"); b.put(440111, "白云区"); b.put(440112, "黄埔区"); b.put(440113, "番禺区"); b.put(440114, "花都区"); b.put(440115, "南沙区"); b.put(440117, "从化区"); b.put(440118, "增城区"); b.put(440200, "韶关市"); b.put(440203, "武江区"); b.put(440204, "浈江区"); b.put(440205, "曲江区"); b.put(440222, "始兴县"); b.put(440224, "仁化县"); b.put(440229, "翁源县"); b.put(440232, "乳源瑶族自治县"); b.put(440233, "新丰县"); b.put(440281, "乐昌市"); b.put(440282, "南雄市"); b.put(440300, "深圳市"); b.put(440303, "罗湖区"); b.put(440304, "福田区"); b.put(440305, "南山区"); b.put(440306, "宝安区"); b.put(440307, "龙岗区"); b.put(440308, "盐田区"); b.put(440309, "龙华区"); b.put(440310, "坪山区"); b.put(440311, "光明区"); b.put(440400, "珠海市"); b.put(440402, "香洲区"); b.put(440403, "斗门区"); b.put(440404, "金湾区"); b.put(440500, "汕头市"); b.put(440507, "龙湖区"); b.put(440511, "金平区"); b.put(440512, "濠江区"); b.put(440513, "潮阳区"); b.put(440514, "潮南区"); b.put(440515, "澄海区"); b.put(440523, "南澳县"); b.put(440600, "佛山市"); b.put(440604, "禅城区"); b.put(440605, "南海区"); b.put(440606, "顺德区"); b.put(440607, "三水区"); b.put(440608, "高明区"); b.put(440700, "江门市"); b.put(440703, "蓬江区"); b.put(440704, "江海区"); b.put(440705, "新会区"); b.put(440781, "台山市"); b.put(440783, "开平市"); b.put(440784, "鹤山市"); b.put(440785, "恩平市"); b.put(440800, "湛江市"); b.put(440802, "赤坎区"); b.put(440803, "霞山区"); b.put(440804, "坡头区"); b.put(440811, "麻章区"); b.put(440823, "遂溪县"); b.put(440825, "徐闻县"); b.put(440881, "廉江市"); b.put(440882, "雷州市"); b.put(440883, "吴川市"); b.put(440900, "茂名市"); b.put(440902, "茂南区"); b.put(440904, "电白区"); b.put(440981, "高州市"); b.put(440982, "化州市"); b.put(440983, "信宜市"); b.put(441200, "肇庆市"); b.put(441202, "端州区"); b.put(441203, "鼎湖区"); b.put(441204, "高要区"); b.put(441223, "广宁县"); b.put(441224, "怀集县"); b.put(441225, "封开县"); b.put(441226, "德庆县"); b.put(441284, "四会市"); b.put(441300, "惠州市"); b.put(441302, "惠城区"); b.put(441303, "惠阳区"); b.put(441322, "博罗县"); b.put(441323, "惠东县"); b.put(441324, "龙门县"); b.put(441400, "梅州市"); b.put(441402, "梅江区"); b.put(441403, "梅县区"); b.put(441422, "大埔县"); b.put(441423, "丰顺县"); b.put(441424, "五华县"); b.put(441426, "平远县"); b.put(441427, "蕉岭县"); b.put(441481, "兴宁市"); b.put(441500, "汕尾市"); b.put(441502, "城区"); b.put(441521, "海丰县"); b.put(441523, "陆河县"); b.put(441581, "陆丰市"); b.put(441600, "河源市"); b.put(441602, "源城区"); b.put(441621, "紫金县"); b.put(441622, "龙川县"); b.put(441623, "连平县"); b.put(441624, "和平县"); b.put(441625, "东源县"); b.put(441700, "阳江市"); b.put(441702, "江城区"); b.put(441704, "阳东区"); b.put(441721, "阳西县"); b.put(441781, "阳春市"); b.put(441800, "清远市"); b.put(441802, "清城区"); b.put(441803, "清新区"); b.put(441821, "佛冈县"); b.put(441823, "阳山县"); b.put(441825, "连山壮族瑶族自治县"); b.put(441826, "连南瑶族自治县"); b.put(441881, "英德市"); b.put(441882, "连州市"); b.put(441900, "东莞市"); b.put(442000, "中山市"); b.put(445100, "潮州市"); b.put(445102, "湘桥区"); b.put(445103, "潮安区"); b.put(445122, "饶平县"); b.put(445200, "揭阳市"); b.put(445202, "榕城区"); b.put(445203, "揭东区"); b.put(445222, "揭西县"); b.put(445224, "惠来县"); b.put(445281, "普宁市"); b.put(445300, "云浮市"); b.put(445302, "云城区"); b.put(445303, "云安区"); b.put(445321, "新兴县"); b.put(445322, "郁南县"); b.put(445381, "罗定市"); b.put(450000, "广西壮族自治区"); b.put(450100, "南宁市"); b.put(450102, "兴宁区"); b.put(450103, "青秀区"); b.put(450105, "江南区"); b.put(450107, "西乡塘区"); b.put(450108, "良庆区"); b.put(450109, "邕宁区"); b.put(450110, "武鸣区"); b.put(450123, "隆安县"); b.put(450124, "马山县"); b.put(450125, "上林县"); b.put(450126, "宾阳县"); b.put(450127, "横县"); b.put(450200, "柳州市"); b.put(450202, "城中区"); b.put(450203, "鱼峰区"); b.put(450204, "柳南区"); b.put(450205, "柳北区"); b.put(450206, "柳江区"); b.put(450222, "柳城县"); b.put(450223, "鹿寨县"); b.put(450224, "融安县"); b.put(450225, "融水苗族自治县"); b.put(450226, "三江侗族自治县"); b.put(450300, "桂林市"); b.put(450302, "秀峰区"); b.put(450303, "叠彩区"); b.put(450304, "象山区"); b.put(450305, "七星区"); b.put(450311, "雁山区"); b.put(450312, "临桂区"); b.put(450321, "阳朔县"); b.put(450323, "灵川县"); b.put(450324, "全州县"); b.put(450325, "兴安县"); b.put(450326, "永福县"); b.put(450327, "灌阳县"); b.put(450328, "龙胜各族自治县"); b.put(450329, "资源县"); b.put(450330, "平乐县"); b.put(450381, "荔浦市"); b.put(450332, "恭城瑶族自治县"); b.put(450400, "梧州市"); b.put(450403, "万秀区"); b.put(450405, "长洲区"); b.put(450406, "龙圩区"); b.put(450421, "苍梧县"); b.put(450422, "藤县"); b.put(450423, "蒙山县"); b.put(450481, "岑溪市"); b.put(450500, "北海市"); b.put(450502, "海城区"); b.put(450503, "银海区"); b.put(450512, "铁山港区"); b.put(450521, "合浦县"); b.put(450600, "防城港市"); b.put(450602, "港口区"); b.put(450603, "防城区"); b.put(450621, "上思县"); b.put(450681, "东兴市"); b.put(450700, "钦州市"); b.put(450702, "钦南区"); b.put(450703, "钦北区"); b.put(450721, "灵山县"); b.put(450722, "浦北县"); b.put(450800, "贵港市"); b.put(450802, "港北区"); b.put(450803, "港南区"); b.put(450804, "覃塘区"); b.put(450821, "平南县"); b.put(450881, "桂平市"); b.put(450900, "玉林市"); b.put(450902, "玉州区"); b.put(450903, "福绵区"); b.put(450921, "容县"); b.put(450922, "陆川县"); b.put(450923, "博白县"); b.put(450924, "兴业县"); b.put(450981, "北流市"); b.put(451000, "百色市"); b.put(451002, "右江区"); b.put(451021, "田阳县"); b.put(451022, "田东县"); b.put(451023, "平果县"); b.put(451024, "德保县"); b.put(451026, "那坡县"); b.put(451027, "凌云县"); b.put(451028, "乐业县"); b.put(451029, "田林县"); b.put(451030, "西林县"); b.put(451031, "隆林各族自治县"); b.put(451081, "靖西市"); b.put(451100, "贺州市"); b.put(451102, "八步区"); b.put(451103, "平桂区"); b.put(451121, "昭平县"); b.put(451122, "钟山县"); b.put(451123, "富川瑶族自治县"); b.put(451200, "河池市"); b.put(451202, "金城江区"); b.put(451203, "宜州区"); b.put(451221, "南丹县"); b.put(451222, "天峨县"); b.put(451223, "凤山县"); b.put(451224, "东兰县"); b.put(451225, "罗城仫佬族自治县"); b.put(451226, "环江毛南族自治县"); b.put(451227, "巴马瑶族自治县"); b.put(451228, "都安瑶族自治县"); b.put(451229, "大化瑶族自治县"); b.put(451300, "来宾市"); b.put(451302, "兴宾区"); b.put(451321, "忻城县"); b.put(451322, "象州县"); b.put(451323, "武宣县"); b.put(451324, "金秀瑶族自治县"); b.put(451381, "合山市"); b.put(451400, "崇左市"); b.put(451402, "江州区"); b.put(451421, "扶绥县"); b.put(451422, "宁明县"); b.put(451423, "龙州县"); b.put(451424, "大新县"); b.put(451425, "天等县"); b.put(451481, "凭祥市"); b.put(460000, "海南省"); b.put(460100, "海口市"); b.put(460105, "秀英区"); b.put(460106, "龙华区"); b.put(460107, "琼山区"); b.put(460108, "美兰区"); b.put(460200, "三亚市"); b.put(460202, "海棠区"); b.put(460203, "吉阳区"); b.put(460204, "天涯区"); b.put(460205, "崖州区"); b.put(460300, "三沙市"); b.put(460400, "儋州市"); b.put(469001, "五指山市"); b.put(469002, "琼海市"); b.put(469005, "文昌市"); b.put(469006, "万宁市"); b.put(469007, "东方市"); b.put(469021, "定安县"); b.put(469022, "屯昌县"); b.put(469023, "澄迈县"); b.put(469024, "临高县"); b.put(469025, "白沙黎族自治县"); b.put(469026, "昌江黎族自治县"); b.put(469027, "乐东黎族自治县"); b.put(469028, "陵水黎族自治县"); b.put(469029, "保亭黎族苗族自治县"); b.put(469030, "琼中黎族苗族自治县"); b.put(500000, "重庆市"); b.put(500101, "万州区"); b.put(500102, "涪陵区"); b.put(500103, "渝中区"); b.put(500104, "大渡口区"); b.put(500105, "江北区"); b.put(500106, "沙坪坝区"); b.put(500107, "九龙坡区"); b.put(500108, "南岸区"); b.put(500109, "北碚区"); b.put(500110, "綦江区"); b.put(500111, "大足区"); b.put(500112, "渝北区"); b.put(500113, "巴南区"); b.put(500114, "黔江区"); b.put(500115, "长寿区"); b.put(500116, "江津区"); b.put(500117, "合川区"); b.put(500118, "永川区"); b.put(500119, "南川区"); b.put(500120, "璧山区"); b.put(500151, "铜梁区"); b.put(500152, "潼南区"); b.put(500153, "荣昌区"); b.put(500154, "开州区"); b.put(500155, "梁平区"); b.put(500156, "武隆区"); b.put(500229, "城口县"); b.put(500230, "丰都县"); b.put(500231, "垫江县"); b.put(500233, "忠县"); b.put(500235, "云阳县"); b.put(500236, "奉节县"); b.put(500237, "巫山县"); b.put(500238, "巫溪县"); b.put(500240, "石柱土家族自治县"); b.put(500241, "秀山土家族苗族自治县"); b.put(500242, "酉阳土家族苗族自治县"); b.put(500243, "彭水苗族土家族自治县"); b.put(510000, "四川省"); b.put(510100, "成都市"); b.put(510104, "锦江区"); b.put(510105, "青羊区"); b.put(510106, "金牛区"); b.put(510107, "武侯区"); b.put(510108, "成华区"); b.put(510112, "龙泉驿区"); b.put(510113, "青白江区"); b.put(510114, "新都区"); b.put(510115, "温江区"); b.put(510116, "双流区"); b.put(510117, "郫都区"); b.put(510121, "金堂县"); b.put(510129, "大邑县"); b.put(510131, "蒲江县"); b.put(510132, "新津县"); b.put(510181, "都江堰市"); b.put(510182, "彭州市"); b.put(510183, "邛崃市"); b.put(510184, "崇州市"); b.put(510185, "简阳市"); b.put(510300, "自贡市"); b.put(510302, "自流井区"); b.put(510303, "贡井区"); b.put(510304, "大安区"); b.put(510311, "沿滩区"); b.put(510321, "荣县"); b.put(510322, "富顺县"); b.put(510400, "攀枝花市"); b.put(510402, "东区"); b.put(510403, "西区"); b.put(510411, "仁和区"); b.put(510421, "米易县"); b.put(510422, "盐边县"); b.put(510500, "泸州市"); b.put(510502, "江阳区"); b.put(510503, "纳溪区"); b.put(510504, "龙马潭区"); b.put(510521, "泸县"); b.put(510522, "合江县"); b.put(510524, "叙永县"); b.put(510525, "古蔺县"); b.put(510600, "德阳市"); b.put(510603, "旌阳区"); b.put(510604, "罗江区"); b.put(510623, "中江县"); b.put(510681, "广汉市"); b.put(510682, "什邡市"); b.put(510683, "绵竹市"); b.put(510700, "绵阳市"); b.put(510703, "涪城区"); b.put(510704, "游仙区"); b.put(510705, "安州区"); b.put(510722, "三台县"); b.put(510723, "盐亭县"); b.put(510725, "梓潼县"); b.put(510726, "北川羌族自治县"); b.put(510727, "平武县"); b.put(510781, "江油市"); b.put(510800, "广元市"); b.put(510802, "利州区"); b.put(510811, "昭化区"); b.put(510812, "朝天区"); b.put(510821, "旺苍县"); b.put(510822, "青川县"); b.put(510823, "剑阁县"); b.put(510824, "苍溪县"); b.put(510900, "遂宁市"); b.put(510903, "船山区"); b.put(510904, "安居区"); b.put(510921, "蓬溪县"); b.put(510922, "射洪县"); b.put(510923, "大英县"); b.put(511000, "内江市"); b.put(511002, "市中区"); b.put(511011, "东兴区"); b.put(511024, "威远县"); b.put(511025, "资中县"); b.put(511083, "隆昌市"); b.put(511100, "乐山市"); b.put(511102, "市中区"); b.put(511111, "沙湾区"); b.put(511112, "五通桥区"); b.put(511113, "金口河区"); b.put(511123, "犍为县"); b.put(511124, "井研县"); b.put(511126, "夹江县"); b.put(511129, "沐川县"); b.put(511132, "峨边彝族自治县"); b.put(511133, "马边彝族自治县"); b.put(511181, "峨眉山市"); b.put(511300, "南充市"); b.put(511302, "顺庆区"); b.put(511303, "高坪区"); b.put(511304, "嘉陵区"); b.put(511321, "南部县"); b.put(511322, "营山县"); b.put(511323, "蓬安县"); b.put(511324, "仪陇县"); b.put(511325, "西充县"); b.put(511381, "阆中市"); b.put(511400, "眉山市"); b.put(511402, "东坡区"); b.put(511403, "彭山区"); b.put(511421, "仁寿县"); b.put(511423, "洪雅县"); b.put(511424, "丹棱县"); b.put(511425, "青神县"); b.put(511500, "宜宾市"); b.put(511502, "翠屏区"); b.put(511503, "南溪区"); b.put(511521, "宜宾县"); b.put(511523, "江安县"); b.put(511524, "长宁县"); b.put(511525, "高县"); b.put(511526, "珙县"); b.put(511527, "筠连县"); b.put(511528, "兴文县"); b.put(511529, "屏山县"); b.put(511600, "广安市"); b.put(511602, "广安区"); b.put(511603, "前锋区"); b.put(511621, "岳池县"); b.put(511622, "武胜县"); b.put(511623, "邻水县"); b.put(511681, "华蓥市"); b.put(511700, "达州市"); b.put(511702, "通川区"); b.put(511703, "达川区"); b.put(511722, "宣汉县"); b.put(511723, "开江县"); b.put(511724, "大竹县"); b.put(511725, "渠县"); b.put(511781, "万源市"); b.put(511800, "雅安市"); b.put(511802, "雨城区"); b.put(511803, "名山区"); b.put(511822, "荥经县"); b.put(511823, "汉源县"); b.put(511824, "石棉县"); b.put(511825, "天全县"); b.put(511826, "芦山县"); b.put(511827, "宝兴县"); b.put(511900, "巴中市"); b.put(511902, "巴州区"); b.put(511903, "恩阳区"); b.put(511921, "通江县"); b.put(511922, "南江县"); b.put(511923, "平昌县"); b.put(512000, "资阳市"); b.put(512002, "雁江区"); b.put(512021, "安岳县"); b.put(512022, "乐至县"); b.put(513200, "阿坝藏族羌族自治州"); b.put(513201, "马尔康市"); b.put(513221, "汶川县"); b.put(513222, "理县"); b.put(513223, "茂县"); b.put(513224, "松潘县"); b.put(513225, "九寨沟县"); b.put(513226, "金川县"); b.put(513227, "小金县"); b.put(513228, "黑水县"); b.put(513230, "壤塘县"); b.put(513231, "阿坝县"); b.put(513232, "若尔盖县"); b.put(513233, "红原县"); b.put(513300, "甘孜藏族自治州"); b.put(513301, "康定市"); b.put(513322, "泸定县"); b.put(513323, "丹巴县"); b.put(513324, "九龙县"); b.put(513325, "雅江县"); b.put(513326, "道孚县"); b.put(513327, "炉霍县"); b.put(513328, "甘孜县"); b.put(513329, "新龙县"); b.put(513330, "德格县"); b.put(513331, "白玉县"); b.put(513332, "石渠县"); b.put(513333, "色达县"); b.put(513334, "理塘县"); b.put(513335, "巴塘县"); b.put(513336, "乡城县"); b.put(513337, "稻城县"); b.put(513338, "得荣县"); b.put(513400, "凉山彝族自治州"); b.put(513401, "西昌市"); b.put(513422, "木里藏族自治县"); b.put(513423, "盐源县"); b.put(513424, "德昌县"); b.put(513425, "会理县"); b.put(513426, "会东县"); b.put(513427, "宁南县"); b.put(513428, "普格县"); b.put(513429, "布拖县"); b.put(513430, "金阳县"); b.put(513431, "昭觉县"); b.put(513432, "喜德县"); b.put(513433, "冕宁县"); b.put(513434, "越西县"); b.put(513435, "甘洛县"); b.put(513436, "美姑县"); b.put(513437, "雷波县"); b.put(520000, "贵州省"); b.put(520100, "贵阳市"); b.put(520102, "南明区"); b.put(520103, "云岩区"); b.put(520111, "花溪区"); b.put(520112, "乌当区"); b.put(520113, "白云区"); b.put(520115, "观山湖区"); b.put(520121, "开阳县"); b.put(520122, "息烽县"); b.put(520123, "修文县"); b.put(520181, "清镇市"); b.put(520200, "六盘水市"); b.put(520201, "钟山区"); b.put(520203, "六枝特区"); b.put(520221, "水城县"); b.put(520281, "盘州市"); b.put(520300, "遵义市"); b.put(520302, "红花岗区"); b.put(520303, "汇川区"); b.put(520304, "播州区"); b.put(520322, "桐梓县"); b.put(520323, "绥阳县"); b.put(520324, "正安县"); b.put(520325, "道真仡佬族苗族自治县"); b.put(520326, "务川仡佬族苗族自治县"); b.put(520327, "凤冈县"); b.put(520328, "湄潭县"); b.put(520329, "余庆县"); b.put(520330, "习水县"); b.put(520381, "赤水市"); b.put(520382, "仁怀市"); b.put(520400, "安顺市"); b.put(520402, "西秀区"); b.put(520403, "平坝区"); b.put(520422, "普定县"); b.put(520423, "镇宁布依族苗族自治县"); b.put(520424, "关岭布依族苗族自治县"); b.put(520425, "紫云苗族布依族自治县"); b.put(520500, "毕节市"); b.put(520502, "七星关区"); b.put(520521, "大方县"); b.put(520522, "黔西县"); b.put(520523, "金沙县"); b.put(520524, "织金县"); b.put(520525, "纳雍县"); b.put(520526, "威宁彝族回族苗族自治县"); b.put(520527, "赫章县"); b.put(520600, "铜仁市"); b.put(520602, "碧江区"); b.put(520603, "万山区"); b.put(520621, "江口县"); b.put(520622, "玉屏侗族自治县"); b.put(520623, "石阡县"); b.put(520624, "思南县"); b.put(520625, "印江土家族苗族自治县"); b.put(520626, "德江县"); b.put(520627, "沿河土家族自治县"); b.put(520628, "松桃苗族自治县"); b.put(522300, "黔西南布依族苗族自治州"); b.put(522301, "兴义市"); b.put(522322, "兴仁县"); b.put(522323, "普安县"); b.put(522324, "晴隆县"); b.put(522325, "贞丰县"); b.put(522326, "望谟县"); b.put(522327, "册亨县"); b.put(522328, "安龙县"); b.put(522600, "黔东南苗族侗族自治州"); b.put(522601, "凯里市"); b.put(522622, "黄平县"); b.put(522623, "施秉县"); b.put(522624, "三穗县"); b.put(522625, "镇远县"); b.put(522626, "岑巩县"); b.put(522627, "天柱县"); b.put(522628, "锦屏县"); b.put(522629, "剑河县"); b.put(522630, "台江县"); b.put(522631, "黎平县"); b.put(522632, "榕江县"); b.put(522633, "从江县"); b.put(522634, "雷山县"); b.put(522635, "麻江县"); b.put(522636, "丹寨县"); b.put(522700, "黔南布依族苗族自治州"); b.put(522701, "都匀市"); b.put(522702, "福泉市"); b.put(522722, "荔波县"); b.put(522723, "贵定县"); b.put(522725, "瓮安县"); b.put(522726, "独山县"); b.put(522727, "平塘县"); b.put(522728, "罗甸县"); b.put(522729, "长顺县"); b.put(522730, "龙里县"); b.put(522731, "惠水县"); b.put(522732, "三都水族自治县"); b.put(530000, "云南省"); b.put(530100, "昆明市"); b.put(530102, "五华区"); b.put(530103, "盘龙区"); b.put(530111, "官渡区"); b.put(530112, "西山区"); b.put(530113, "东川区"); b.put(530114, "呈贡区"); b.put(530115, "晋宁区"); b.put(530124, "富民县"); b.put(530125, "宜良县"); b.put(530126, "石林彝族自治县"); b.put(530127, "嵩明县"); b.put(530128, "禄劝彝族苗族自治县"); b.put(530129, "寻甸回族彝族自治县"); b.put(530181, "安宁市"); b.put(530300, "曲靖市"); b.put(530302, "麒麟区"); b.put(530303, "沾益区"); b.put(530304, "马龙区"); b.put(530322, "陆良县"); b.put(530323, "师宗县"); b.put(530324, "罗平县"); b.put(530325, "富源县"); b.put(530326, "会泽县"); b.put(530381, "宣威市"); b.put(530400, "玉溪市"); b.put(530402, "红塔区"); b.put(530403, "江川区"); b.put(530422, "澄江县"); b.put(530423, "通海县"); b.put(530424, "华宁县"); b.put(530425, "易门县"); b.put(530426, "峨山彝族自治县"); b.put(530427, "新平彝族傣族自治县"); b.put(530428, "元江哈尼族彝族傣族自治县"); b.put(530500, "保山市"); b.put(530502, "隆阳区"); b.put(530521, "施甸县"); b.put(530523, "龙陵县"); b.put(530524, "昌宁县"); b.put(530581, "腾冲市"); b.put(530600, "昭通市"); b.put(530602, "昭阳区"); b.put(530621, "鲁甸县"); b.put(530622, "巧家县"); b.put(530623, "盐津县"); b.put(530624, "大关县"); b.put(530625, "永善县"); b.put(530626, "绥江县"); b.put(530627, "镇雄县"); b.put(530628, "彝良县"); b.put(530629, "威信县"); b.put(530681, "水富市"); b.put(530700, "丽江市"); b.put(530702, "古城区"); b.put(530721, "玉龙纳西族自治县"); b.put(530722, "永胜县"); b.put(530723, "华坪县"); b.put(530724, "宁蒗彝族自治县"); b.put(530800, "普洱市"); b.put(530802, "思茅区"); b.put(530821, "宁洱哈尼族彝族自治县"); b.put(530822, "墨江哈尼族自治县"); b.put(530823, "景东彝族自治县"); b.put(530824, "景谷傣族彝族自治县"); b.put(530825, "镇沅彝族哈尼族拉祜族自治县"); b.put(530826, "江城哈尼族彝族自治县"); b.put(530827, "孟连傣族拉祜族佤族自治县"); b.put(530828, "澜沧拉祜族自治县"); b.put(530829, "西盟佤族自治县"); b.put(530900, "临沧市"); b.put(530902, "临翔区"); b.put(530921, "凤庆县"); b.put(530922, "云县"); b.put(530923, "永德县"); b.put(530924, "镇康县"); b.put(530925, "双江拉祜族佤族布朗族傣族自治县"); b.put(530926, "耿马傣族佤族自治县"); b.put(530927, "沧源佤族自治县"); b.put(532300, "楚雄彝族自治州"); b.put(532301, "楚雄市"); b.put(532322, "双柏县"); b.put(532323, "牟定县"); b.put(532324, "南华县"); b.put(532325, "姚安县"); b.put(532326, "大姚县"); b.put(532327, "永仁县"); b.put(532328, "元谋县"); b.put(532329, "武定县"); b.put(532331, "禄丰县"); b.put(532500, "红河哈尼族彝族自治州"); b.put(532501, "个旧市"); b.put(532502, "开远市"); b.put(532503, "蒙自市"); b.put(532504, "弥勒市"); b.put(532523, "屏边苗族自治县"); b.put(532524, "建水县"); b.put(532525, "石屏县"); b.put(532527, "泸西县"); b.put(532528, "元阳县"); b.put(532529, "红河县"); b.put(532530, "金平苗族瑶族傣族自治县"); b.put(532531, "绿春县"); b.put(532532, "河口瑶族自治县"); b.put(532600, "文山壮族苗族自治州"); b.put(532601, "文山市"); b.put(532622, "砚山县"); b.put(532623, "西畴县"); b.put(532624, "麻栗坡县"); b.put(532625, "马关县"); b.put(532626, "丘北县"); b.put(532627, "广南县"); b.put(532628, "富宁县"); b.put(532800, "西双版纳傣族自治州"); b.put(532801, "景洪市"); b.put(532822, "勐海县"); b.put(532823, "勐腊县"); b.put(532900, "大理白族自治州"); b.put(532901, "大理市"); b.put(532922, "漾濞彝族自治县"); b.put(532923, "祥云县"); b.put(532924, "宾川县"); b.put(532925, "弥渡县"); b.put(532926, "南涧彝族自治县"); b.put(532927, "巍山彝族回族自治县"); b.put(532928, "永平县"); b.put(532929, "云龙县"); b.put(532930, "洱源县"); b.put(532931, "剑川县"); b.put(532932, "鹤庆县"); b.put(533100, "德宏傣族景颇族自治州"); b.put(533102, "瑞丽市"); b.put(533103, "芒市"); b.put(533122, "梁河县"); b.put(533123, "盈江县"); b.put(533124, "陇川县"); b.put(533300, "怒江傈僳族自治州"); b.put(533301, "泸水市"); b.put(533323, "福贡县"); b.put(533324, "贡山独龙族怒族自治县"); b.put(533325, "兰坪白族普米族自治县"); b.put(533400, "迪庆藏族自治州"); b.put(533401, "香格里拉市"); b.put(533422, "德钦县"); b.put(533423, "维西傈僳族自治县"); b.put(540000, "西藏自治区"); b.put(540100, "拉萨市"); b.put(540102, "城关区"); b.put(540103, "堆龙德庆区"); b.put(540104, "达孜区"); b.put(540121, "林周县"); b.put(540122, "当雄县"); b.put(540123, "尼木县"); b.put(540124, "曲水县"); b.put(540127, "墨竹工卡县"); b.put(540200, "日喀则市"); b.put(540202, "桑珠孜区"); b.put(540221, "南木林县"); b.put(540222, "江孜县"); b.put(540223, "定日县"); b.put(540224, "萨迦县"); b.put(540225, "拉孜县"); b.put(540226, "昂仁县"); b.put(540227, "谢通门县"); b.put(540228, "白朗县"); b.put(540229, "仁布县"); b.put(540230, "康马县"); b.put(540231, "定结县"); b.put(540232, "仲巴县"); b.put(540233, "亚东县"); b.put(540234, "吉隆县"); b.put(540235, "聂拉木县"); b.put(540236, "萨嘎县"); b.put(540237, "岗巴县"); b.put(540300, "昌都市"); b.put(540302, "卡若区"); b.put(540321, "江达县"); b.put(540322, "贡觉县"); b.put(540323, "类乌齐县"); b.put(540324, "丁青县"); b.put(540325, "察雅县"); b.put(540326, "八宿县"); b.put(540327, "左贡县"); b.put(540328, "芒康县"); b.put(540329, "洛隆县"); b.put(540330, "边坝县"); b.put(540400, "林芝市"); b.put(540402, "巴宜区"); b.put(540421, "工布江达县"); b.put(540422, "米林县"); b.put(540423, "墨脱县"); b.put(540424, "波密县"); b.put(540425, "察隅县"); b.put(540426, "朗县"); b.put(540500, "山南市"); b.put(540502, "乃东区"); b.put(540521, "扎囊县"); b.put(540522, "贡嘎县"); b.put(540523, "桑日县"); b.put(540524, "琼结县"); b.put(540525, "曲松县"); b.put(540526, "措美县"); b.put(540527, "洛扎县"); b.put(540528, "加查县"); b.put(540529, "隆子县"); b.put(540530, "错那县"); b.put(540531, "浪卡子县"); b.put(540600, "那曲市"); b.put(540602, "色尼区"); b.put(540621, "嘉黎县"); b.put(540622, "比如县"); b.put(540623, "聂荣县"); b.put(540624, "安多县"); b.put(540625, "申扎县"); b.put(540626, "索县"); b.put(540627, "班戈县"); b.put(540628, "巴青县"); b.put(540629, "尼玛县"); b.put(540630, "双湖县"); b.put(542500, "阿里地区"); b.put(542521, "普兰县"); b.put(542522, "札达县"); b.put(542523, "噶尔县"); b.put(542524, "日土县"); b.put(542525, "革吉县"); b.put(542526, "改则县"); b.put(542527, "措勤县"); b.put(610000, "陕西省"); b.put(610100, "西安市"); b.put(610102, "新城区"); b.put(610103, "碑林区"); b.put(610104, "莲湖区"); b.put(610111, "灞桥区"); b.put(610112, "未央区"); b.put(610113, "雁塔区"); b.put(610114, "阎良区"); b.put(610115, "临潼区"); b.put(610116, "长安区"); b.put(610117, "高陵区"); b.put(610118, "鄠邑区"); b.put(610122, "蓝田县"); b.put(610124, "周至县"); b.put(610200, "铜川市"); b.put(610202, "王益区"); b.put(610203, "印台区"); b.put(610204, "耀州区"); b.put(610222, "宜君县"); b.put(610300, "宝鸡市"); b.put(610302, "渭滨区"); b.put(610303, "金台区"); b.put(610304, "陈仓区"); b.put(610322, "凤翔县"); b.put(610323, "岐山县"); b.put(610324, "扶风县"); b.put(610326, "眉县"); b.put(610327, "陇县"); b.put(610328, "千阳县"); b.put(610329, "麟游县"); b.put(610330, "凤县"); b.put(610331, "太白县"); b.put(610400, "咸阳市"); b.put(610402, "秦都区"); b.put(610403, "杨陵区"); b.put(610404, "渭城区"); b.put(610422, "三原县"); b.put(610423, "泾阳县"); b.put(610424, "乾县"); b.put(610425, "礼泉县"); b.put(610426, "永寿县"); b.put(610428, "长武县"); b.put(610429, "旬邑县"); b.put(610430, "淳化县"); b.put(610431, "武功县"); b.put(610481, "兴平市"); b.put(610482, "彬州市"); b.put(610500, "渭南市"); b.put(610502, "临渭区"); b.put(610503, "华州区"); b.put(610522, "潼关县"); b.put(610523, "大荔县"); b.put(610524, "合阳县"); b.put(610525, "澄城县"); b.put(610526, "蒲城县"); b.put(610527, "白水县"); b.put(610528, "富平县"); b.put(610581, "韩城市"); b.put(610582, "华阴市"); b.put(610600, "延安市"); b.put(610602, "宝塔区"); b.put(610603, "安塞区"); b.put(610621, "延长县"); b.put(610622, "延川县"); b.put(610623, "子长县"); b.put(610625, "志丹县"); b.put(610626, "吴起县"); b.put(610627, "甘泉县"); b.put(610628, "富县"); b.put(610629, "洛川县"); b.put(610630, "宜川县"); b.put(610631, "黄龙县"); b.put(610632, "黄陵县"); b.put(610700, "汉中市"); b.put(610702, "汉台区"); b.put(610703, "南郑区"); b.put(610722, "城固县"); b.put(610723, "洋县"); b.put(610724, "西乡县"); b.put(610725, "勉县"); b.put(610726, "宁强县"); b.put(610727, "略阳县"); b.put(610728, "镇巴县"); b.put(610729, "留坝县"); b.put(610730, "佛坪县"); b.put(610800, "榆林市"); b.put(610802, "榆阳区"); b.put(610803, "横山区"); b.put(610822, "府谷县"); b.put(610824, "靖边县"); b.put(610825, "定边县"); b.put(610826, "绥德县"); b.put(610827, "米脂县"); b.put(610828, "佳县"); b.put(610829, "吴堡县"); b.put(610830, "清涧县"); b.put(610831, "子洲县"); b.put(610881, "神木市"); b.put(610900, "安康市"); b.put(610902, "汉滨区"); b.put(610921, "汉阴县"); b.put(610922, "石泉县"); b.put(610923, "宁陕县"); b.put(610924, "紫阳县"); b.put(610925, "岚皋县"); b.put(610926, "平利县"); b.put(610927, "镇坪县"); b.put(610928, "旬阳县"); b.put(610929, "白河县"); b.put(611000, "商洛市"); b.put(611002, "商州区"); b.put(611021, "洛南县"); b.put(611022, "丹凤县"); b.put(611023, "商南县"); b.put(611024, "山阳县"); b.put(611025, "镇安县"); b.put(611026, "柞水县"); b.put(620000, "甘肃省"); b.put(620100, "兰州市"); b.put(620102, "城关区"); b.put(620103, "七里河区"); b.put(620104, "西固区"); b.put(620105, "安宁区"); b.put(620111, "红古区"); b.put(620121, "永登县"); b.put(620122, "皋兰县"); b.put(620123, "榆中县"); b.put(620200, "嘉峪关市"); b.put(620300, "金昌市"); b.put(620302, "金川区"); b.put(620321, "永昌县"); b.put(620400, "白银市"); b.put(620402, "白银区"); b.put(620403, "平川区"); b.put(620421, "靖远县"); b.put(620422, "会宁县"); b.put(620423, "景泰县"); b.put(620500, "天水市"); b.put(620502, "秦州区"); b.put(620503, "麦积区"); b.put(620521, "清水县"); b.put(620522, "秦安县"); b.put(620523, "甘谷县"); b.put(620524, "武山县"); b.put(620525, "张家川回族自治县"); b.put(620600, "武威市"); b.put(620602, "凉州区"); b.put(620621, "民勤县"); b.put(620622, "古浪县"); b.put(620623, "天祝藏族自治县"); b.put(620700, "张掖市"); b.put(620702, "甘州区"); b.put(620721, "肃南裕固族自治县"); b.put(620722, "民乐县"); b.put(620723, "临泽县"); b.put(620724, "高台县"); b.put(620725, "山丹县"); b.put(620800, "平凉市"); b.put(620802, "崆峒区"); b.put(620821, "泾川县"); b.put(620822, "灵台县"); b.put(620823, "崇信县"); b.put(620825, "庄浪县"); b.put(620826, "静宁县"); b.put(620881, "华亭市"); b.put(620900, "酒泉市"); b.put(620902, "肃州区"); b.put(620921, "金塔县"); b.put(620922, "瓜州县"); b.put(620923, "肃北蒙古族自治县"); b.put(620924, "阿克塞哈萨克族自治县"); b.put(620981, "玉门市"); b.put(620982, "敦煌市"); b.put(621000, "庆阳市"); b.put(621002, "西峰区"); b.put(621021, "庆城县"); b.put(621022, "环县"); b.put(621023, "华池县"); b.put(621024, "合水县"); b.put(621025, "正宁县"); b.put(621026, "宁县"); b.put(621027, "镇原县"); b.put(621100, "定西市"); b.put(621102, "安定区"); b.put(621121, "通渭县"); b.put(621122, "陇西县"); b.put(621123, "渭源县"); b.put(621124, "临洮县"); b.put(621125, "漳县"); b.put(621126, "岷县"); b.put(621200, "陇南市"); b.put(621202, "武都区"); b.put(621221, "成县"); b.put(621222, "文县"); b.put(621223, "宕昌县"); b.put(621224, "康县"); b.put(621225, "西和县"); b.put(621226, "礼县"); b.put(621227, "徽县"); b.put(621228, "两当县"); b.put(622900, "临夏回族自治州"); b.put(622901, "临夏市"); b.put(622921, "临夏县"); b.put(622922, "康乐县"); b.put(622923, "永靖县"); b.put(622924, "广河县"); b.put(622925, "和政县"); b.put(622926, "东乡族自治县"); b.put(622927, "积石山保安族东乡族撒拉族自治县"); b.put(623000, "甘南藏族自治州"); b.put(623001, "合作市"); b.put(623021, "临潭县"); b.put(623022, "卓尼县"); b.put(623023, "舟曲县"); b.put(623024, "迭部县"); b.put(623025, "玛曲县"); b.put(623026, "碌曲县"); b.put(623027, "夏河县"); b.put(630000, "青海省"); b.put(630100, "西宁市"); b.put(630102, "城东区"); b.put(630103, "城中区"); b.put(630104, "城西区"); b.put(630105, "城北区"); b.put(630121, "大通回族土族自治县"); b.put(630122, "湟中县"); b.put(630123, "湟源县"); b.put(630200, "海东市"); b.put(630202, "乐都区"); b.put(630203, "平安区"); b.put(630222, "民和回族土族自治县"); b.put(630223, "互助土族自治县"); b.put(630224, "化隆回族自治县"); b.put(630225, "循化撒拉族自治县"); b.put(632200, "海北藏族自治州"); b.put(632221, "门源回族自治县"); b.put(632222, "祁连县"); b.put(632223, "海晏县"); b.put(632224, "刚察县"); b.put(632300, "黄南藏族自治州"); b.put(632321, "同仁县"); b.put(632322, "尖扎县"); b.put(632323, "泽库县"); b.put(632324, "河南蒙古族自治县"); b.put(632500, "海南藏族自治州"); b.put(632521, "共和县"); b.put(632522, "同德县"); b.put(632523, "贵德县"); b.put(632524, "兴海县"); b.put(632525, "贵南县"); b.put(632600, "果洛藏族自治州"); b.put(632621, "玛沁县"); b.put(632622, "班玛县"); b.put(632623, "甘德县"); b.put(632624, "达日县"); b.put(632625, "久治县"); b.put(632626, "玛多县"); b.put(632700, "玉树藏族自治州"); b.put(632701, "玉树市"); b.put(632722, "杂多县"); b.put(632723, "称多县"); b.put(632724, "治多县"); b.put(632725, "囊谦县"); b.put(632726, "曲麻莱县"); b.put(632800, "海西蒙古族藏族自治州"); b.put(632801, "格尔木市"); b.put(632802, "德令哈市"); b.put(632803, "茫崖市"); b.put(632821, "乌兰县"); b.put(632822, "都兰县"); b.put(632823, "天峻县"); b.put(640000, "宁夏回族自治区"); b.put(640100, "银川市"); b.put(640104, "兴庆区"); b.put(640105, "西夏区"); b.put(640106, "金凤区"); b.put(640121, "永宁县"); b.put(640122, "贺兰县"); b.put(640181, "灵武市"); b.put(640200, "石嘴山市"); b.put(640202, "大武口区"); b.put(640205, "惠农区"); b.put(640221, "平罗县"); b.put(640300, "吴忠市"); b.put(640302, "利通区"); b.put(640303, "红寺堡区"); b.put(640323, "盐池县"); b.put(640324, "同心县"); b.put(640381, "青铜峡市"); b.put(640400, "固原市"); b.put(640402, "原州区"); b.put(640422, "西吉县"); b.put(640423, "隆德县"); b.put(640424, "泾源县"); b.put(640425, "彭阳县"); b.put(640500, "中卫市"); b.put(640502, "沙坡头区"); b.put(640521, "中宁县"); b.put(640522, "海原县"); b.put(650000, "新疆维吾尔自治区"); b.put(650100, "乌鲁木齐市"); b.put(650102, "天山区"); b.put(650103, "沙依巴克区"); b.put(650104, "新市区"); b.put(650105, "水磨沟区"); b.put(650106, "头屯河区"); b.put(650107, "达坂城区"); b.put(650109, "米东区"); b.put(650121, "乌鲁木齐县"); b.put(650200, "克拉玛依市"); b.put(650202, "独山子区"); b.put(650203, "克拉玛依区"); b.put(650204, "白碱滩区"); b.put(650205, "乌尔禾区"); b.put(650400, "吐鲁番市"); b.put(650402, "高昌区"); b.put(650421, "鄯善县"); b.put(650422, "托克逊县"); b.put(650500, "哈密市"); b.put(650502, "伊州区"); b.put(650521, "巴里坤哈萨克自治县"); b.put(650522, "伊吾县"); b.put(652300, "昌吉回族自治州"); b.put(652301, "昌吉市"); b.put(652302, "阜康市"); b.put(652323, "呼图壁县"); b.put(652324, "玛纳斯县"); b.put(652325, "奇台县"); b.put(652327, "吉木萨尔县"); b.put(652328, "木垒哈萨克自治县"); b.put(652700, "博尔塔拉蒙古自治州"); b.put(652701, "博乐市"); b.put(652702, "阿拉山口市"); b.put(652722, "精河县"); b.put(652723, "温泉县"); b.put(652800, "巴音郭楞蒙古自治州"); b.put(652801, "库尔勒市"); b.put(652822, "轮台县"); b.put(652823, "尉犁县"); b.put(652824, "若羌县"); b.put(652825, "且末县"); b.put(652826, "焉耆回族自治县"); b.put(652827, "和静县"); b.put(652828, "和硕县"); b.put(652829, "博湖县"); b.put(652900, "阿克苏地区"); b.put(652901, "阿克苏市"); b.put(652922, "温宿县"); b.put(652923, "库车县"); b.put(652924, "沙雅县"); b.put(652925, "新和县"); b.put(652926, "拜城县"); b.put(652927, "乌什县"); b.put(652928, "阿瓦提县"); b.put(652929, "柯坪县"); b.put(653000, "克孜勒苏柯尔克孜自治州"); b.put(653001, "阿图什市"); b.put(653022, "阿克陶县"); b.put(653023, "阿合奇县"); b.put(653024, "乌恰县"); b.put(653100, "喀什地区"); b.put(653101, "喀什市"); b.put(653121, "疏附县"); b.put(653122, "疏勒县"); b.put(653123, "英吉沙县"); b.put(653124, "泽普县"); b.put(653125, "莎车县"); b.put(653126, "叶城县"); b.put(653127, "麦盖提县"); b.put(653128, "岳普湖县"); b.put(653129, "伽师县"); b.put(653130, "巴楚县"); b.put(653131, "塔什库尔干塔吉克自治县"); b.put(653200, "和田地区"); b.put(653201, "和田市"); b.put(653221, "和田县"); b.put(653222, "墨玉县"); b.put(653223, "皮山县"); b.put(653224, "洛浦县"); b.put(653225, "策勒县"); b.put(653226, "于田县"); b.put(653227, "民丰县"); b.put(654000, "伊犁哈萨克自治州"); b.put(654002, "伊宁市"); b.put(654003, "奎屯市"); b.put(654004, "霍尔果斯市"); b.put(654021, "伊宁县"); b.put(654022, "察布查尔锡伯自治县"); b.put(654023, "霍城县"); b.put(654024, "巩留县"); b.put(654025, "新源县"); b.put(654026, "昭苏县"); b.put(654027, "特克斯县"); b.put(654028, "尼勒克县"); b.put(654200, "塔城地区"); b.put(654201, "塔城市"); b.put(654202, "乌苏市"); b.put(654221, "额敏县"); b.put(654223, "沙湾县"); b.put(654224, "托里县"); b.put(654225, "裕民县"); b.put(654226, "和布克赛尔蒙古自治县"); b.put(654300, "阿勒泰地区"); b.put(654301, "阿勒泰市"); b.put(654321, "布尔津县"); b.put(654322, "富蕴县"); b.put(654323, "福海县"); b.put(654324, "哈巴河县"); b.put(654325, "青河县"); b.put(654326, "吉木乃县"); b.put(659001, "石河子市"); b.put(659002, "阿拉尔市"); b.put(659003, "图木舒克市"); b.put(659004, "五家渠市"); b.put(659005, "北屯市"); b.put(659006, "铁门关市"); b.put(659007, "双河市"); b.put(659008, "可克达拉市"); b.put(659009, "昆玉市"); b.put(710000, "台湾省"); b.put(810000, "香港特别行政区"); b.put(820000, "澳门特别行政区"); DISTRICT_CODE_MAPPING = b.build(); AREA_CODE_LIST = new ArrayList<>(DISTRICT_CODE_MAPPING.keySet()); } /* 香港身份首字母对应数字 */ /*private static final Map HK_FIRST_CODE = new ImmutableMap.Builder() .put("A", 1) .put("B", 2) .put("C", 3) .put("R", 18) .put("U", 21) .put("Z", 26) .put("X", 24) .put("W", 23) .put("O", 15) .put("N", 14) .build();*/ } ================================================ FILE: src/main/java/cn/ponfee/commons/util/ImageUtils.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import cn.ponfee.commons.io.Closeables; import javax.imageio.ImageIO; import javax.imageio.ImageReader; import javax.imageio.stream.MemoryCacheImageInputStream; import javax.swing.ImageIcon; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * 图片工具类 * * @author Ponfee */ public class ImageUtils { /** * 获取图片大小 * @param input * @return [width, height] */ public static int[] getImageSize(InputStream input) { try { BufferedImage image = ImageIO.read(input); return new int[] { image.getWidth(), image.getHeight() }; } catch (IOException e) { throw new RuntimeException(e); } finally { Closeables.console(input); } } /** * 横向合并图片 * @param format * @param imgs * @return */ public static byte[] mergeHorizontal(String format, InputStream... imgs) { int width = 0, height = 0; try { List list = new ArrayList<>(); for (InputStream img : imgs) { BufferedImage i = ImageIO.read(img); width += i.getWidth();// 图片宽度 height = Math.max(height, i.getHeight()); list.add(i); } BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); width = 0; for (BufferedImage i : list) { int[] array = new int[i.getWidth() * i.getHeight()];// 从图片中读取RGB array = i.getRGB(0, 0, i.getWidth(), i.getHeight(), array, 0, i.getWidth()); result.setRGB(width, 0, i.getWidth(), i.getHeight(), array, 0, i.getWidth());// 设置左半部分的RGB width += i.getWidth();// 图片宽度 i.flush(); } ByteArrayOutputStream out = new ByteArrayOutputStream(); ImageIO.write(result, format, out); out.flush(); return out.toByteArray(); } catch (IOException e) { throw new RuntimeException("图片合并失败", e); } } /** * 纵向合并图片 * @param format png,jpeg,gif * @param imgs * @return */ public static byte[] mergeVertical(String format, InputStream... imgs) { try { int width = 0, height = 0; List list = new ArrayList<>(); for (InputStream img : imgs) { BufferedImage i = ImageIO.read(img); height += i.getHeight();// 图片宽度 width = Math.max(width, i.getWidth()); list.add(i); } BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); height = 0; for (BufferedImage i : list) { int[] array = new int[i.getWidth() * i.getHeight()];// 从图片中读取RGB array = i.getRGB(0, 0, i.getWidth(), i.getHeight(), array, 0, i.getWidth()); result.setRGB(0, height, i.getWidth(), i.getHeight(), array, 0, i.getWidth());// 设置左半部分的RGB height += i.getHeight();// 图片宽度 i.flush(); } ByteArrayOutputStream out = new ByteArrayOutputStream(); ImageIO.write(result, format, out); out.flush(); return out.toByteArray(); } catch (IOException e) { throw new RuntimeException("图片合并失败", e); } } /** *
         * 图片透明处理
         *     白    rgb:-1-->255,255,255
         *     红    rgb:-65536-->255,0,0
         *   透明    rgb:0-->0,0,0
         *     红    rgb:-922812416-->255,0,0
         *     黑    rgb:-16777216-->0,0,0
         *     黑    rgb:-939524096-->0,0,0
         *  
    * @param image * @return */ public static byte[] transparent(InputStream image, int refer, int normal) { try { ImageIcon icon = new ImageIcon(ImageIO.read(image)); BufferedImage img = new BufferedImage( icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_4BYTE_ABGR ); Graphics2D g2D = (Graphics2D) img.getGraphics(); g2D.drawImage(icon.getImage(), 0, 0, icon.getImageObserver()); for (int alpha, rgb, j, i = img.getMinX(); i < img.getWidth(); i++) { for (j = img.getMinY(); j < img.getHeight(); j++) { rgb = img.getRGB(i, j); if (rgb != 0) { // 0为透明 if (compare(rgb, refer)) { alpha = 0; // -1为白色:255 255 255 } else { alpha = normal; // 默认设置半透明 } rgb = (alpha << 24) | (rgb & 0x00ffffff); // 计算rgb img.setRGB(i, j, rgb); // 重新设置rgb } } } //g2D.drawImage(bufferedImage, 0, 0, imageIcon.getImageObserver()); ByteArrayOutputStream bos = new ByteArrayOutputStream(); ImageIO.write(img, "png", bos); return bos.toByteArray(); } catch (Exception e) { throw new RuntimeException(e); } } /** * 图片类型 * @param img * @return * @throws IOException */ public static String[] getImageType(InputStream img) throws IOException { List types = new ArrayList<>(); try (MemoryCacheImageInputStream m = new MemoryCacheImageInputStream(img)) { for (Iterator i = ImageIO.getImageReaders(m); i.hasNext(); ) { types.add(i.next().getFormatName()); } return types.isEmpty() ? null : types.toArray(new String[0]); } } private static boolean compare(int color, int colorRange) { int r = (color & 0xff0000) >> 16; int g = (color & 0x00ff00) >> 8; int b = (color & 0x0000ff); return (r >= colorRange && g >= colorRange && b >= colorRange); } } ================================================ FILE: src/main/java/cn/ponfee/commons/util/LazyLoader.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.InvocationHandler; import java.util.Objects; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; /** * Lazy loader * * @author Ponfee */ public class LazyLoader implements Supplier { private final Supplier loader; private Optional holder; private LazyLoader(Supplier loader) { this.loader = Objects.requireNonNull(loader); } public static LazyLoader of(Supplier loader) { return new LazyLoader<>(loader); } public static R of(Class type, Supplier loader) { return of(type, of(loader)); } public static LazyLoader of(Function loader, A arg) { return new LazyLoader<>(() -> loader.apply(arg)); } public static R of(Class type, Function loader, A arg) { return of(type, of(loader, arg)); } @Override public T get() { return holder().get(); } public void orElse(T defaultValue) { holder().orElse(defaultValue); } public void orElseGet(Supplier other) { holder().orElseGet(other); } public void ifPresent(Consumer consumer) { holder().ifPresent(consumer); } // ------------------------------------------------------------------------private methods private Optional holder() { if (holder == null) { holder = Optional.ofNullable(loader.get()); } return holder; } private static R of(Class type, final LazyLoader lazyLoader) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(type); enhancer.setUseCache(true); enhancer.setInterceptDuringConstruction(false); //enhancer.setCallback(org.springframework.cglib.proxy.Proxy.getInvocationHandler(proxy)); // occur error //enhancer.setCallback((org.springframework.cglib.proxy.MethodInterceptor) (beanProxy, method, args, methodProxy) -> method.invoke(lazyLoader.get(), args)); enhancer.setCallback((InvocationHandler) (beanProxy, method, args) -> method.invoke(lazyLoader.get(), args)); return (R) enhancer.create(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/util/MavenProjects.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import cn.ponfee.commons.io.Files; import java.io.File; import java.io.IOException; /** *
     *  maven标准的项目文件工具类
     *  only use in test case
     *
     *  new File("src/test/resources/test.txt");
     *  new File("src/test/java/test/test1.java");
     *  new File("src/main/resources/log4j2.xml");
     *  new File("src/main/java/code/ponfee/commons/util/Asserts.java");
     * 
    * * @author Ponfee */ public class MavenProjects { private static final String EXCLUSION_STRING = "[\r\n]"; // "\r|\n|\\s+" public static String getProjectBaseDir() { String path = Thread.currentThread().getContextClassLoader().getResource("").getFile(); return Strings.cleanPath(new File(path).getParentFile().getParentFile().getPath()); } // --------------------------------------------------------------------------------------java public static File getMainJavaFile(Class clazz) { return new File(getMainJavaPath("") + clazz.getCanonicalName().replace('.', '/') + ".java"); } public static byte[] getMainJavaFileAsBytes(Class clazz) { return Files.toByteArray(MavenProjects.getMainJavaFile(clazz)); } public static String getMainJavaFileAsString(Class clazz) { try { return Files.toString(MavenProjects.getMainJavaFile(clazz)).replaceAll(EXCLUSION_STRING, ""); } catch (IOException e) { throw new RuntimeException(e); } } public static File getTestJavaFile(Class clazz) { return new File(getTestJavaPath("") + clazz.getCanonicalName().replace('.', '/') + ".java"); } public static byte[] getTestJavaFileAsBytes(Class clazz) { return Files.toByteArray(MavenProjects.getTestJavaFile(clazz)); } public static String getTestJavaFileAsString(Class clazz) { try { return Files.toString(MavenProjects.getTestJavaFile(clazz)).replaceAll(EXCLUSION_STRING, ""); } catch (IOException e) { throw new RuntimeException(e); } } public static String getMainJavaPath(String basePackage) { return getProjectBaseDir() + "/src/main/java/" + basePackage.replace('.', '/'); } public static String getMainJavaPath(String basePackage, String filename) { return getMainJavaPath(basePackage) + "/" + filename; } public static String getTestJavaPath(String basePackage) { return getProjectBaseDir() + "/src/test/java/" + basePackage.replace('.', '/'); } public static String getTestJavaPath(String basePackage, String filename) { return getTestJavaPath(basePackage) + "/" + filename; } // --------------------------------------------------------------------------------------scala public static File getMainScalaFile(Class clazz) { return new File(getMainScalaPath("") + clazz.getCanonicalName().replace('.', '/') + ".scala"); } public static byte[] getMainScalaFileAsBytes(Class clazz) { return Files.toByteArray(MavenProjects.getMainScalaFile(clazz)); } public static String getMainScalaFileAsString(Class clazz) { try { return Files.toString(MavenProjects.getMainScalaFile(clazz)).replaceAll(EXCLUSION_STRING, ""); } catch (IOException e) { throw new RuntimeException(e); } } public static File getTestScalaFile(Class clazz) { return new File(getTestScalaPath("") + clazz.getCanonicalName().replace('.', '/') + ".scala"); } public static byte[] getTestScalaFileAsBytes(Class clazz) { return Files.toByteArray(MavenProjects.getTestScalaFile(clazz)); } public static String getTestScalaFileAsString(Class clazz) { try { return Files.toString(MavenProjects.getTestScalaFile(clazz)).replaceAll(EXCLUSION_STRING, ""); } catch (IOException e) { throw new RuntimeException(e); } } public static String getMainScalaPath(String basePackage) { return getProjectBaseDir() + "/src/main/scala/" + basePackage.replace('.', '/'); } public static String getMainScalaPath(String basePackage, String filename) { return getMainScalaPath(basePackage) + "/" + filename; } public static String getTestScalaPath(String basePackage) { return getProjectBaseDir() + "/src/test/scala/" + basePackage.replace('.', '/'); } public static String getTestScalaPath(String basePackage, String filename) { return getTestScalaPath(basePackage) + "/" + filename; } // --------------------------------------------------------------------------------------resources public static String getMainResourcesPath() { return getProjectBaseDir() + "/src/main/resources/"; } public static String getMainResourcesPath(String followPath) { return getMainResourcesPath() + followPath; } public static String getTestResourcesPath() { return getProjectBaseDir() + "/src/test/resources/"; } public static String getTestResourcesPath(String followPath) { return getTestResourcesPath() + followPath; } // --------------------------------------------------------------------------------------webapp public static String getWebAppPath() { return getProjectBaseDir() + "/src/main/webapp/"; } public static String getWebAppPath(String webappPath) { return getWebAppPath() + webappPath; } } ================================================ FILE: src/main/java/cn/ponfee/commons/util/MessageFormats.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import cn.ponfee.commons.collect.Maps; import java.text.MessageFormat; import java.util.*; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 消息格式化 * * @author Ponfee */ public final class MessageFormats { private static final String PREFIX = "#\\{(\\s|\\t)*"; private static final String SUFFIX = "(\\s|\\t)*\\}"; private static final Pattern PATTERN = Pattern.compile(PREFIX + "(\\w+)" + SUFFIX); public static String format(String text, Map args) { List arguments = new ArrayList<>(args.size()); int i = 0; for (Entry entry : args.entrySet()) { text = text.replaceAll(PREFIX + entry.getKey() + SUFFIX, "{" + i++ + "}"); // toString reason:MessageFormat.format("{0}", 10000) -> 10,000 arguments.add(Objects.toString(entry.getValue(), "")); } return MessageFormat.format(text, arguments.toArray()); } public static String format(String text, Object... args) { Map map = new HashMap<>(args.length << 1); Matcher matcher = PATTERN.matcher(text); for (int n = args.length, i = 0; i < n && matcher.find(); i++) { map.put(matcher.group(2), args[i]); } return format(text, map); } public static String formatPair(String text, Object... args) { return format(text, Maps.toMap(args)); } } ================================================ FILE: src/main/java/cn/ponfee/commons/util/Money.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import cn.ponfee.commons.collect.Comparators; import cn.ponfee.commons.reflect.GenericUtils; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.annotation.JSONType; import com.alibaba.fastjson.parser.DefaultJSONParser; import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer; import com.alibaba.fastjson.serializer.JSONSerializer; import com.alibaba.fastjson.serializer.ObjectSerializer; import com.alibaba.fastjson.serializer.SerializeWriter; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.node.BaseJsonNode; import com.fasterxml.jackson.databind.node.NullNode; import org.apache.commons.lang3.builder.HashCodeBuilder; import java.io.IOException; import java.io.Serializable; import java.lang.reflect.Type; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.Currency; import java.util.stream.LongStream; /** * Money definition based on {@link Long} numeric for minor currency unit representation. * * @author Ponfee */ @JSONType(serializer = Money.Fastjson.class, deserializer = Money.Fastjson.class) // fastjson @JsonSerialize(using = Money.JacksonSerializer.class) // jackson @JsonDeserialize(using = Money.JacksonDeserializer.class) // jackson public class Money implements Serializable, Comparable, Cloneable { private static final long serialVersionUID = 7743331479636754564L; public static final String FIELD_NAME_CURRENCY = "currency"; public static final String FIELD_NAME_NUMBER = "number"; /** * scaling mode, defaultRoundingMode.HALF_EVEN *

    银行家舍入法: 四舍六入,当小数为0.5时,则取最近的偶数 */ private static final RoundingMode DEFAULT_ROUNDING_MODE = RoundingMode.HALF_EVEN; /** * Current unit scaling factor */ private static final int[] FACTORS = {1, 10, 100, 1000, 10000, 100000}; /** * the currency */ private final Currency currency; /** * the number */ private long number; /** * Creates a MultiCurrencyMoney with majorUnitNumber, minorUnitNumber and currency * * @param currency the currency * @param majorUnitNumber the majorUnitNumber * @param minorUnitNumber the minorUnitNumber */ public Money(Currency currency, long majorUnitNumber, int minorUnitNumber) { if (currency == null) { throw new IllegalArgumentException("Currency cannot null."); } // check minorUnitNumber whether overflow int factor = getFactor(); if (minorUnitNumber >= factor) { throw new RuntimeException("Minor[" + minorUnitNumber + "] must less than factor[" + factor + "]."); } this.currency = currency; this.number = majorUnitNumber * factor + minorUnitNumber; } public Money(Currency currency, long number) { if (currency == null) { throw new IllegalArgumentException("Currency cannot null."); } this.currency = currency; this.number = number; } // -------------------------------------------------------------------------------------of methods public static Money of(Currency currency, long number) { return new Money(currency, number); } public static Money of(CurrencyEnum currencyEnum, long number) { return new Money(currencyEnum.currency(), number); } public static Money of(String currencyCode, long number) { return new Money(Currency.getInstance(currencyCode), number); } public Money ofMajor(CurrencyEnum currencyEnum, String majorUnitNumber, RoundingMode roundingMode) { return ofMajor(currencyEnum.currency(), new BigDecimal(majorUnitNumber), roundingMode); } public Money ofMajor(String currencyCode, String majorUnitNumber, RoundingMode roundingMode) { return ofMajor(Currency.getInstance(currencyCode), new BigDecimal(majorUnitNumber), roundingMode); } public Money ofMajor(Currency currency, String majorUnitNumber, RoundingMode roundingMode) { return ofMajor(currency, new BigDecimal(majorUnitNumber), roundingMode); } public static Money ofMajor(CurrencyEnum currencyEnum, BigDecimal majorUnitNumber, RoundingMode roundingMode) { return ofMajor(currencyEnum.currency(), majorUnitNumber, roundingMode); } public static Money ofMajor(String currencyCode, BigDecimal majorUnitNumber, RoundingMode roundingMode) { return ofMajor(Currency.getInstance(currencyCode), majorUnitNumber, roundingMode); } /** * Creates a new Money instance with the specified currency, major number and rounding mode. * * @param currency the currency * @param majorUnitNumber the major unit number * @param roundingMode the rounding mode */ public static Money ofMajor(Currency currency, BigDecimal majorUnitNumber, RoundingMode roundingMode) { long number = rounding(majorUnitNumber.movePointRight(currency.getDefaultFractionDigits()), roundingMode); return new Money(currency, number); } /** * Obtains an instance of {@code Money} representing zero at a specific currency. *

    * For example, {@code zero(USD)} creates the instance {@code USD 0.00}. * * @param currency the currency, not null * @return the instance representing zero, never null */ public static Money zero(Currency currency) { return new Money(currency, 0); } public static Money zero(CurrencyEnum currencyEnum) { return zero(currencyEnum.currency()); } public static Money zero(String currencyCode) { return zero(Currency.getInstance(currencyCode)); } // -------------------------------------------------------------------------------------getter/setter methods /** * Returns currency * * @return {@code java.util.Currency} object */ public Currency getCurrency() { return currency; } public long getNumber() { return number; } /** * Returns currency code * * @return currency code */ public final String getCurrencyCode() { return currency.getCurrencyCode(); } /** * Set number * * @param number the number */ public void setNumber(long number) { this.number = number; } /** * Returns currency scaling factor * * @return scaling factor */ public final int getFactor() { return FACTORS[currency.getDefaultFractionDigits()]; } // -------------------------------------------------------------------------------------to methods /** * Returns BigDecimal of major number string * * @return major number BigDecimal */ public BigDecimal toMajorNumber() { return BigDecimal.valueOf(number, currency.getDefaultFractionDigits()); } /** * Returns major number string * * @return major number string(e.g. USD$1.23 -> 1.23) */ public String toMajorString() { return toMajorNumber().toString(); } // -------------------------------------------------------------------------------------基本对象方法 /** * Returns the specified object is the same currency and has same amount * * @param other the other object * @return {@code true} if equals * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object other) { return (other instanceof Money) && equals((Money) other); } /** * Returns boolean of equals other money object * * @param other the other money * @return {@code true} if equals */ public boolean equals(Money other) { if (other == null) { return false; } return currency.equals(other.currency) && (number == other.number); } /** * Return this object's hash code * * @return int value of hash code * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return new HashCodeBuilder().append(currency).append(number).toHashCode(); } /** * Returns this object's clone * * @see java.lang.Object#clone() */ @Override public Money clone() { return new Money(currency, number); } /** * Returns the string representation of this Money. * * @return money as string(e.g. $1.23) */ @Override public String toString() { return CurrencyEnum.ofCurrency(currency).currencySymbol() + toMajorString(); } /** * Compares with other money object. * * @param other the other money. * @return -1: less, 0: equals, 1: greater */ @Override public int compareTo(Money other) { assertSameCurrency(other); return Long.compare(number, other.number); } /** * Compares is greater than other money. * * @param other the other money. * @return {@code true} if greater than other */ public boolean greaterThan(Money other) { return compareTo(other) > Comparators.EQ; } // -------------------------------------------------------------------------------------货币算术 /** * Returns new Money object of the two money addition * * @param other the other money. * @return new Money object of the two money addition */ public Money add(Money other) { assertSameCurrency(other); return create(number + other.number); } /** * This money addition other money * * @param other the other money * @return the caller money object(chain program) */ public Money addTo(Money other) { assertSameCurrency(other); this.number += other.number; return this; } /** * Returns new Money object of the two money subtraction * * @param other the other money. * @return new Money object of the two money subtraction */ public Money subtract(Money other) { assertSameCurrency(other); return create(number - other.number); } /** * This money subtraction other money * * @param other the other money * @return the caller money object(chain program) */ public Money subtractFrom(Money other) { assertSameCurrency(other); this.number -= other.number; return this; } /** * Returns new Money object of this money multiply value * * @param val the value * @return new Money object of multiply result */ public Money multiply(long val) { return create(number * val); } /** * This money multiply value factor * * @param val the value * @return the caller money object(chain program) */ public Money multiplyBy(long val) { this.number *= val; return this; } /** * Returns new Money object of this money multiply value * * @param val the value * @return new Money object of multiply result */ public Money multiply(BigDecimal val) { return multiply(val, DEFAULT_ROUNDING_MODE); } /** * This money multiply value factor * * @param val the value * @return the caller money object(chain program) */ public Money multiplyBy(BigDecimal val) { return multiplyBy(val, DEFAULT_ROUNDING_MODE); } /** * Returns new Money object of this money multiply value * * @param val the value * @param roundingMode the rounding mode * @return new Money object of multiply result */ public Money multiply(BigDecimal val, RoundingMode roundingMode) { return create(rounding(BigDecimal.valueOf(number).multiply(val), roundingMode)); } /** * This money multiply value factor * * @param val the value * @param roundingMode the rounding mode * @return the caller money object(chain program) */ public Money multiplyBy(BigDecimal val, RoundingMode roundingMode) { this.number = rounding(BigDecimal.valueOf(number).multiply(val), roundingMode); return this; } public Money divide(BigDecimal val) { return divide(val, DEFAULT_ROUNDING_MODE); } public Money divide(BigDecimal val, RoundingMode roundingMode) { return create(BigDecimal.valueOf(number).divide(val, roundingMode).longValue()); } public Money divideBy(BigDecimal val) { return divideBy(val, DEFAULT_ROUNDING_MODE); } public Money divideBy(BigDecimal val, RoundingMode roundingMode) { this.number = BigDecimal.valueOf(number).divide(val, roundingMode).longValue(); return this; } // -------------------------------------------------------------------------------------slice /** * Average slice this money to segment part * * @param segment the segment * @return Money array */ public Money[] slice(int segment) { Money[] results = new Money[segment]; long low = number / segment, high = low + 1; int remainder = (int) number % segment; for (int i = 0; i < remainder; i++) { results[i] = create(high); } for (int i = remainder; i < segment; i++) { results[i] = create(low); } return results; } /** * Slice this money with specified ratios * * @param ratios the ratio * @return Money array */ public Money[] slice(long[] ratios) { Money[] results = new Money[ratios.length]; long total = LongStream.of(ratios).sum(); long remainder = number; for (int i = 0; i < results.length; i++) { results[i] = create((number * ratios[i]) / total); remainder -= results[i].number; } for (int i = 0; i < remainder; i++) { results[i].number++; } return results; } // -------------------------------------------------------------------------------------private methods private void assertSameCurrency(Money other) { if (!currency.equals(other.currency)) { throw new IllegalArgumentException("Money math currency mismatch."); } } private Money create(long number) { return new Money(currency, number); } /** * Returns BigDecimal to long value with specified rounding mode. * * @param val the BigDecimal value * @param roundingMode the rounding mode * @return long value */ private static long rounding(BigDecimal val, RoundingMode roundingMode) { return val.setScale(0, roundingMode).longValue(); } // -------------------------------------------------------------------------------------custom fastjson deserialize /** * Custom deserialize Money based fastjson. */ public static class Fastjson implements ObjectSerializer, ObjectDeserializer { @Override public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException { SerializeWriter writer = serializer.getWriter(); if (object == null) { serializer.writeNull(); } else { Money money = (Money) object; writer.write("{\"" + FIELD_NAME_CURRENCY + "\":\""); writer.write(money.getCurrency().getCurrencyCode()); writer.write("\",\"" + FIELD_NAME_NUMBER + "\":"); writer.writeLong(money.getNumber()); writer.write("}"); } } @Override public Money deserialze(DefaultJSONParser parser, Type type, Object fieldName) { if (GenericUtils.getRawType(type) != Money.class) { throw new UnsupportedOperationException("Cannot supported deserialize type: " + type); } JSONObject jsonObject = parser.parseObject(); String currencyCode = jsonObject.getString(FIELD_NAME_CURRENCY); long number = jsonObject.getLongValue(FIELD_NAME_NUMBER); return new Money(CurrencyEnum.ofCurrencyCode(currencyCode).currency(), number); } @Override public int getFastMatchToken() { return 0 /*JSONToken.RBRACKET*/; } } // -------------------------------------------------------------------------------------custom jackson serializer & deserialize /** * Custom serialize Money based jackson. */ public static class JacksonSerializer extends JsonSerializer { @Override public void serialize(Money money, JsonGenerator generator, SerializerProvider serializerProvider) throws IOException { if (money == null) { generator.writeNull(); } else { generator.writeStartObject(); generator.writeStringField(FIELD_NAME_CURRENCY, money.getCurrencyCode()); generator.writeNumberField(FIELD_NAME_NUMBER, money.getNumber()); generator.writeEndObject(); } } } /** * Custom deserialize Money based jackson. */ public static class JacksonDeserializer extends JsonDeserializer { @Override public Money deserialize(JsonParser parser, DeserializationContext ctx) throws IOException { BaseJsonNode jsonNode = parser.readValueAsTree(); if (jsonNode == null || jsonNode instanceof NullNode) { return null; } String currencyCode = jsonNode.required(FIELD_NAME_CURRENCY).textValue(); long number = jsonNode.required(FIELD_NAME_NUMBER).longValue(); return new Money(CurrencyEnum.ofCurrencyCode(currencyCode).currency(), number); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/util/Networks.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.net.*; import java.util.Enumeration; /** * 网络工具类 *

     *  isAnyLocalAddress  通配符地址        IPv4的通配符地址是0.0.0.0
     *  isLoopbackAddress  回环地址          IPv4的的范围是127.0.0.0 ~ 127.255.255.255    IPv6的是0:0:0:0:0:0:0:1,也可以简写成::1
     *  isLinkLocalAddress 本地连接地址       IPv4的的范围是169.254.0.0 ~ 169.254.255.255  IPv6的前12位是FE8,其他的位可以是任意取值
     *  isSiteLocalAddress 地区本地地址       IPv4的分为三段:10.0.0.0 ~ 10.255.255.255等   IPv6的地区本地地址的前12位是FEC,其他的位可以是任意取值
     *  isMulticastAddress 广播地址          IPv4的范围是224.0.0.0 ~ 239.255.255.255     IPv6的第一个字节是FF,其他的字节可以是任意值
     *  isMCGlobal         全球范围的广播地址
     *  isMCLinkLocal      子网广播地址
     *  isMCNodeLocal      本地接口广播地址
     *  isMCOrgLocal       组织范围的广播地址
     *  isMCSiteLocal      站点范围的广播地址
     * 
    * * @author Ponfee */ public final class Networks { private static final Logger LOG = LoggerFactory.getLogger(Networks.class); private static final String LOCALHOST_IP = "127.0.0.1"; private static final String LOCALHOST_NAME = "localhost"; private static final String EMPTY_IP = "0.0.0.0"; /** * the max ip value *

    toLong("255.255.255.255") */ public static final long MAX_IP_VALUE = (1L << 32) - 1; /** * 掩码 */ private static final long[] MASK = {0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF}; /** * local ip */ public static final String HOST_IP = getHostIp(); /** * getMachineNetworkFlag 获取机器的MAC或者IP,优先获取MAC * * @param ia InetAddress * @return mac or ip */ public static String getMacOrIp(InetAddress ia) { if (ia == null) { try { ia = InetAddress.getLocalHost(); } catch (UnknownHostException e) { throw new RuntimeException(e); } } String mac = getMacAddress(ia); return StringUtils.isBlank(mac) ? getIpAddress(ia) : mac; } /** * 获取指定地址的mac地址,不指定默认取本机的mac地址 * * @param ia InetAddress * @return mac or ip */ public static String getMacAddress(InetAddress ia) { byte[] mac; try { if (ia == null) { ia = InetAddress.getLocalHost(); } mac = NetworkInterface.getByInetAddress(ia).getHardwareAddress(); } catch (SocketException | UnknownHostException e) { throw new RuntimeException(e); } if (mac == null) { return ""; } StringBuilder sb = new StringBuilder(17); for (int i = 0; i < mac.length; i++) { if (i != 0) { sb.append("-"); } sb.append(Bytes.toHex(mac[i], false)); } return sb.toString(); } public static String getHostIp() { InetAddress address = getHostAddress(); return address == null ? LOCALHOST_IP : address.getHostAddress(); } public static String getHostName() { InetAddress address = getHostAddress(); return address == null ? LOCALHOST_NAME : address.getHostName(); } /** * Check the port is available * * @param port 待测试端口 * @return if @{code true} is available, else unavailable */ public static boolean isAvailablePort(int port) { try (ServerSocket ss = new ServerSocket(port)) { return true; } catch (IOException ignored) { return false; } } /** * Returns this server available port * * @param startPort * @return if -1 then not find available port * else returns available port */ public static int findAvailablePort(int startPort) { if (startPort < 0 || startPort > 65535) { return -1; } for (int port = startPort; port <= 65535; port++) { if (isAvailablePort(port)) { return port; } } for (int port = startPort - 1; port >= 0; port--) { if (isAvailablePort(port)) { return port; } } return -1; } /** * Convert ipv4 to long,max value is 4294967295 * * @param ip the ip address * @return */ public static long toLong(String ip) { if (!RegexUtils.isIpv4(ip)) { throw new IllegalArgumentException("invalid ip address[" + ip + "]"); } String[] ipNums = ip.split("\\.", 4); return (Long.parseLong(ipNums[0]) << 24) + (Long.parseLong(ipNums[1]) << 16) + (Long.parseLong(ipNums[2]) << 8) + (Long.parseLong(ipNums[3]) ); } /** * Convert long value to ipv4 address string * * @param ip * @return */ public static String fromLong(long ip) { return new StringBuilder(15) .append((ip & MASK[0]) >> 24).append('.') .append((ip & MASK[1]) >> 16).append('.') .append((ip & MASK[2]) >> 8).append('.') .append((ip & MASK[3]) ).toString(); } /** * 获取指定地址的ip地址,不指定默认取本机的ip地址 * * @param ia InetAddress * @return mac or ip */ private static String getIpAddress(InetAddress ia) { return ia.getHostAddress(); } private static InetAddress getHostAddress() { InetAddress localAddress = null; try { localAddress = InetAddress.getLocalHost(); if (isValidHostAddress(localAddress)) { return localAddress; } } catch (Exception e) { LOG.warn("Failed to get local host address: {} ", e.getMessage()); } try { Enumeration inters = NetworkInterface.getNetworkInterfaces(); if (inters == null) { return localAddress; } while (inters.hasMoreElements()) { try { Enumeration addresses = inters.nextElement().getInetAddresses(); while (addresses.hasMoreElements()) { try { InetAddress address = addresses.nextElement(); if (isValidHostAddress(address)) { return address; } } catch (Exception e) { LOG.warn("Failed to get host address: {}", e.getMessage()); } } } catch (Exception e) { LOG.warn("Failed to get network address: {}", e.getMessage()); } } } catch (Exception e) { LOG.warn("Failed to get network interface: {}", e.getMessage()); } LOG.warn("Could not get host ip address, will use 127.0.0.1 instead."); return localAddress; } /** * Returns the host address is valid * * @param address * @return if {@code true} is valid, else invalid */ private static boolean isValidHostAddress(InetAddress address) { if (address == null || address.isLoopbackAddress()) { return false; } String ip = address.getHostAddress(); return ip != null && !EMPTY_IP.equals(ip) && !LOCALHOST_IP.equals(ip) && RegexUtils.isIpv4(ip); } } ================================================ FILE: src/main/java/cn/ponfee/commons/util/ObjectUtils.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import cn.ponfee.commons.base.PrimitiveTypes; import cn.ponfee.commons.date.JavaUtilDateFormat; import cn.ponfee.commons.math.Numbers; import cn.ponfee.commons.reflect.ClassUtils; import cn.ponfee.commons.reflect.Fields; import com.google.common.base.Preconditions; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.EnumUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.ToStringStyle; import java.lang.reflect.Array; import java.text.ParseException; import java.util.*; import static cn.ponfee.commons.collect.Comparators.*; import static org.apache.commons.lang3.builder.ToStringBuilder.reflectionToString; /** * Object utilities * * @author Ponfee */ public final class ObjectUtils { private static final char[] URL_SAFE_BASE64_CODES = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".toCharArray(); /** * Returns object toString * * @param obj the target object * @return the string of object */ public static String toString(Object obj) { return toString(obj, "null"); } public static String toString(Object obj, String defaultStr) { return (obj == null) ? defaultStr : reflectionToString(obj, ToStringStyle.JSON_STYLE); } /** * Compare two object numerically * * @param a the object a * @param b the object b * @return 0(a==b), 1(a>b), -1(a Class typeOf(T obj) { return obj != null ? (Class) obj.getClass() : null; } /** * Returns true if this object value is complex json type(Object or Array) * * @param value the value * @return {@code true} if the value is complex json type */ public static boolean isComplexType(Object value) { if (value == null) { return false; } return value instanceof Map // Object || value instanceof List // List || value.getClass().isArray(); // Array } /** * 判断对象是否为空 * * @param o the object * @return {@code true} is empty */ public static boolean isEmpty(Object o) { if (o == null) { return true; } if (o instanceof CharSequence) { return ((CharSequence) o).length() == 0; } if (o instanceof Collection) { return ((Collection) o).isEmpty(); } if (o.getClass().isArray()) { return Array.getLength(o) == 0; } if (o instanceof Map) { return ((Map) o).isEmpty(); } if (o instanceof Dictionary) { return ((Dictionary) o).isEmpty(); } return false; } /** * Gets the target's name value * * @param obj the object * @param name the field name * @return a value */ public static Object getValue(Object obj, String name) { if (obj == null) { return null; } if (obj instanceof Map) { return ((Map) obj).get(name); } if (obj instanceof Dictionary) { return ((Dictionary) obj).get(name); } return Fields.get(obj, name); } /** * Returns target type value from origin value cast * * @param value source object * @param type target object type * @return target type object * * @see com.alibaba.fastjson.util.TypeUtils#castToInt(Object) */ @SuppressWarnings({ "unchecked", "rawtypes" }) public static T cast(Object value, Class type) { if (type.isInstance(value)) { return (T) value; } PrimitiveOrWrapperConvertors convertor = PrimitiveOrWrapperConvertors.of(type); if (convertor != null) { return convertor.to(value); } if (value == null) { return null; } if (type.isEnum()) { return (value instanceof Number) ? type.getEnumConstants()[((Number) value).intValue()] : (T) EnumUtils.getEnumIgnoreCase((Class) type, value.toString()); } if (Date.class == type) { if (value instanceof Number) { return (T) new Date(((Number) value).longValue()); } String text = value.toString(); if (StringUtils.isNumeric(text) && !RegexUtils.isDatePattern(text)) { return (T) new Date(Numbers.toLong(text)); } try { return (T) JavaUtilDateFormat.DEFAULT.parse(text); } catch (ParseException e) { throw new RuntimeException(e); } } return ClassUtils.newInstance(type, new Object[]{value.toString()}); } /** * 获取堆栈信息 * * @param deepPath the deep path * @return stack trace */ public static String getStackTrace(int deepPath) { // 获取当前方法 // Method currentMethod = new Object() {}.getClass().getEnclosingMethod(); StackTraceElement[] traces = Thread.currentThread().getStackTrace(); if (traces.length <= deepPath) { return "warning: out of stack trace."; } return traces[deepPath].toString(); } public static String getStackTrace() { return buildStackTrace(Thread.currentThread().getStackTrace()); } public static String getStackTrace(Thread thread) { return buildStackTrace(thread.getStackTrace()); } private static String buildStackTrace(StackTraceElement[] traces) { StringBuilder builder = new StringBuilder(); for (int i = 2, n = traces.length; i < n; i++) { builder.append("--\t").append(traces[i].toString()).append("\n"); } return builder.toString(); } /** * Copies source fields value to target fields * * @param source the source * @param target the target * @param fields the fields of String array */ public static void copy(T source, T target, String... fields) { Preconditions.checkState(ArrayUtils.isNotEmpty(fields)); for (String field : fields) { Fields.put(target, field, Fields.get(source, field)); } } /** * Returns a new instance of copy from spec fields * * @param source the source * @param fields the fields * @return */ @SuppressWarnings("unchecked") public static T copyOf(T source, String... fields) { Preconditions.checkState(ArrayUtils.isNotEmpty(fields)); T target = (T) newInstance(source.getClass()); copy(source, target, fields); return target; } /** * Returns a new instance of type * * @param type the type class * @return a new instance */ @SuppressWarnings("unchecked") public static T newInstance(Class type) { if (Map.class == type) { return (T) new HashMap<>(); } if (Set.class == type) { return (T) new HashSet<>(); } if (Collection.class == type || List.class == type) { return (T) new ArrayList<>(); } if (Dictionary.class == type) { return (T) new Hashtable<>(); } if (type.isPrimitive() || PrimitiveTypes.isWrapperType(type)) { Class wrapper = PrimitiveTypes.ofPrimitiveOrWrapper(type).wrapper(); // new Boolean("0") -> false return (T) ClassUtils.newInstance(wrapper, new Class[]{String.class}, new Object[]{"0"}); } return ClassUtils.newInstance(type); } /** * Returns the type is not a bean type * * @param type the type class * @return {@code true} assert is not a bean type */ public static boolean isNotBeanType(Class type) { return null == type || Object.class == type || type.isArray() || PrimitiveTypes.ofPrimitiveOrWrapper(type) != null || CharSequence.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type) || Dictionary.class.isAssignableFrom(type) || Enumeration.class.isAssignableFrom(type) || Iterable.class.isAssignableFrom(type) || Iterator.class.isAssignableFrom(type); } // -------------------------------------------------------------------------- private class private enum PrimitiveOrWrapperConvertors { BOOLEAN(boolean.class) { @Override public Boolean to(Object value) { return Numbers.toBoolean(value); } }, WRAP_BOOLEAN(Boolean.class) { @Override public Boolean to(Object value) { return Numbers.toWrapBoolean(value); } }, BYTE(byte.class) { @Override public Byte to(Object value) { return Numbers.toByte(value); } }, WRAP_BYTE(Byte.class) { @Override public Byte to(Object value) { return Numbers.toWrapByte(value); } }, SHORT(short.class) { @Override public Short to(Object value) { return Numbers.toShort(value); } }, WRAP_SHORT(Short.class) { @Override public Short to(Object value) { return Numbers.toWrapShort(value); } }, CHAR(char.class) { @Override public Character to(Object value) { return Numbers.toChar(value); } }, WRAP_CHAR(Character.class) { @Override public Character to(Object value) { return Numbers.toWrapChar(value); } }, INT(int.class) { @Override public Integer to(Object value) { return Numbers.toInt(value); } }, WRAP_INT(Integer.class) { @Override public Integer to(Object value) { return Numbers.toWrapInt(value); } }, LONG(long.class) { @Override public Long to(Object value) { return Numbers.toLong(value); } }, WRAP_LONG(Long.class) { @Override public Long to(Object value) { return Numbers.toWrapLong(value); } }, FLOAT(float.class) { @Override public Float to(Object value) { return Numbers.toFloat(value); } }, WRAP_FLOAT(Float.class) { @Override public Float to(Object value) { return Numbers.toWrapFloat(value); } }, DOUBLE(double.class) { @Override public Double to(Object value) { return Numbers.toDouble(value); } }, WRAP_DOUBLE(Double.class) { @Override public Double to(Object value) { return Numbers.toWrapDouble(value); } }; private static final Map, PrimitiveOrWrapperConvertors> MAPPING = Enums.toMap(PrimitiveOrWrapperConvertors.class, PrimitiveOrWrapperConvertors::type); private final Class type; PrimitiveOrWrapperConvertors(Class type) { this.type = type; } abstract T to(Object value); public Class type() { return this.type; } static PrimitiveOrWrapperConvertors of(Class targetType) { return MAPPING.get(targetType); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/util/PropertiesUtils.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import cn.ponfee.commons.reflect.ClassUtils; import cn.ponfee.commons.reflect.Fields; import java.lang.reflect.Field; import java.util.List; import java.util.Objects; import java.util.Properties; /** * Properties Utility * * @author Ponfee */ public final class PropertiesUtils { public static Properties filterProperties(Properties props, String keyPrefix) { Properties properties = new Properties(); int prefixLen = keyPrefix.length(); props.forEach((k, v) -> { String key = k.toString(); if (key.startsWith(keyPrefix)) { properties.put(key.substring(prefixLen), v); } }); return properties; } public static T extract(Properties props, Class beanType, String prefix, char... separators) { T bean = ClassUtils.newInstance(beanType); List fields = Objects.requireNonNull(ClassUtils.listFields(beanType)); for (Field field : fields) { for (char separator : separators) { String name = prefix + Strings.toSeparatedName(field.getName(), separator); if (props.containsKey(name)) { Fields.put(bean, field, ObjectUtils.cast(props.get(name), field.getType())); break; } } } return bean; } } ================================================ FILE: src/main/java/cn/ponfee/commons/util/RegexUtils.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import org.apache.commons.lang3.StringUtils; import java.util.concurrent.ExecutionException; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 正则工具类 * http://blog.csdn.net/carechere/article/details/52315728 * * @author Ponfee */ public final class RegexUtils { /** * Username regexp */ private static final Pattern PATTERN_USERNAME = Pattern.compile("^[0-9A-Za-z_\\-]{4,20}$"); private static final String SYMBOL = "@#!%&_\\.\\?\\-\\$\\^\\*"; /** * Password regexp */ private static final Pattern PATTERN_PASSWORD = Pattern.compile("^((?=.*\\d)(?=.*[A-Za-z])|(?=.*\\d)(?=.*[" + SYMBOL + "])|(?=.*[A-Za-z])(?=.*[" + SYMBOL + "]))[\\dA-Za-z" + SYMBOL + "]{8,20}$"); /** * Mobile phone regexp */ private static final Pattern PATTERN_MOBILE = Pattern.compile("^\\s*(((\\+)?86)|(\\((\\+)?86\\)))?1\\d{10}\\s*$"); /** * 中国电信号码格式验证 手机段: 133,153,180,181,189,177,1700,173,199 **/ private static final Pattern CHINA_TELECOM_PATTERN = Pattern.compile("(^1(33|53|77|73|99|8[019])\\d{8}$)|(^1700\\d{7}$)"); /** * 中国联通号码格式验证 手机段:130,131,132,155,156,185,186,145,176,1709 **/ private static final Pattern CHINA_UNICOM_PATTERN = Pattern.compile("(^1(3[0-2]|4[5]|5[56]|7[6]|8[56])\\d{8}$)|(^1709\\d{7}$)"); /** * 中国移动号码格式验证 * 手机段:134,135,136,137,138,139,150,151,152,157,158,159,182,183,184,187,188,147,178,1705 **/ private static final Pattern CHINA_MOBILE_PATTERN = Pattern.compile("(^1(3[4-9]|4[7]|5[0-27-9]|7[8]|8[2-478])\\d{8}$)|(^1705\\d{7}$)"); /** * Email regexp */ private static final Pattern PATTERN_EMAIL = Pattern.compile("^\\w+((-\\w+)|(\\.\\w+))*\\@[A-Za-z0-9]+((\\.|-)[A-Za-z0-9]+)*\\.[A-Za-z0-9]+$"); /** * IP V4 pattern */ private static final Pattern PATTERN_IPV4 = Pattern.compile("(?:(?:2[0-4][0-9]\\.)|(?:25[0-5]\\.)|(?:1[0-9][0-9]\\.)|(?:[1-9][0-9]\\.)|(?:[0-9]\\.)){3}(?:(?:2[0-4][0-9])|(?:25[0-5])|(?:1[0-9][0-9])|(?:[1-9][0-9])|(?:[0-9]))"); /** * IP V4 pattern */ private static final Pattern PATTERN_IPV6 = Pattern.compile("^([0-9a-fA-F]{1,4}:){7}([0-9a-fA-F]{1,4}|:)|([0-9a-fA-F]{1,4}:){1,6}(:[0-9a-fA-F]{1,4}|:)|([0-9a-fA-F]{1,4}:){1,5}((:[0-9a-fA-F]{1,4}){1,2}|:)|([0-9a-fA-F]{1,4}:){1,4}((:[0-9a-fA-F]{1,4}){1,3}|:)|([0-9a-fA-F]{1,4}:){1,3}((:[0-9a-fA-F]{1,4}){1,4}|:)|([0-9a-fA-F]{1,4}:){1,2}((:[0-9a-fA-F]{1,4}){1,5}|:)|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6}|:)|:((:[0-9a-fA-F]{1,4}){1,7}|:)"); /** * yyyyMMdd(HHmmss(SSS)) */ private static final Pattern PATTERN_DATE = Pattern.compile("^([1-9]\\d{3}((0[1-9]|1[012])(0[1-9]|1\\d|2[0-8])|(0[13456789]|1[012])(29|30)|(0[13578]|1[02])31)|(([2-9]\\d)(0[48]|[2468][048]|[13579][26])|(([2468][048]|[3579][26])00))0229)(([0-1][0-9]|2[0-3])([0-5][0-9])([0-5][0-9])(\\d{3})?)?$"); private static final LoadingCache PATTERNS = CacheBuilder.newBuilder().softValues().build( new CacheLoader() { @Override public Pattern load(String pattern) { return Pattern.compile(pattern/*, Pattern.CASE_INSENSITIVE*/); } } ); /** * Finds the first match string from originalStr use regex * * @param originalStr the origin str * @param regex the regex * @return the first match string */ public static String findFirst(String originalStr, String regex) { return findGroup(originalStr, regex, 0); } public static String findGroup(String originalStr, String regex, int group) { if (originalStr == null || regex == null) { return StringUtils.EMPTY; } try { Matcher matcher = PATTERNS.get(regex).matcher(originalStr); return matcher.find() ? matcher.group(group) : StringUtils.EMPTY; } catch (ExecutionException e) { throw new RuntimeException(e); } } public static boolean matches(String originalStr, String regex) { if (originalStr == null || regex == null) { return false; } try { return PATTERNS.get(regex).matcher(originalStr).matches(); } catch (ExecutionException e) { throw new RuntimeException(e); } } /** * check is china mobile phone * @param text * @return {@code true} is mobile phone */ public static boolean isMobilePhone(String text) { return text != null && PATTERN_MOBILE.matcher(text).matches(); } /** * 校验是否邮箱地址 * @param text * @return {@code true} is email address */ public static boolean isEmail(String text) { return text != null && PATTERN_EMAIL.matcher(text).matches(); } /** * 校验是否ipv4地址 * * @param text * @return {@code true} is ipv4 address */ public static boolean isIpv4(String text) { return text != null && PATTERN_IPV4.matcher(text).matches(); } /** * 校验是否ipv6地址 * * @param text * @return {@code true} is ipv6 address */ public static boolean isIpv6(String text) { return text != null && PATTERN_IPV6.matcher(text).matches(); } /** * 校验是否是有效的用户名 * 数据库用户名字段最好不要区分大小写 * @param text * @return {@code true} is valid user name */ public static boolean isValidUserName(String text) { return text != null && PATTERN_USERNAME.matcher(text).matches(); } /** * 校验是否是有效的密码: * > 8-20位 * > 必须包含字母、数字、符号中至少2种(可选的符号包括:@#!%&_.?-$^*) * > 其它模式:^(?=.*\\d)(?=.*[A-Z])(?=.*[a-z])[\\dA-Za-z@#!%&_\\.\\?\\-\\$\\^\\*]{8,20}$ * :^(?=.*\\d)(?=.*[A-Za-z])[\\dA-Za-z@#!%&_\\.\\?\\-\\$\\^\\*]{8,20}$ * * isValidPassword("12131111") // false: 只有数字 * isValidPassword("@#.@#.$^") // false: 只有字符 * isValidPassword("aaaaaaaa") // false: 只有字母 * isValidPassword("121311@1") // true: 数字字符 * isValidPassword("121311A1") // true: 数字字母 * isValidPassword("aaaaaa.a") // true: 字母字符 * @param text * @return {@code true} is valid password */ public static boolean isValidPassword(String text) { return text != null && PATTERN_PASSWORD.matcher(text).matches(); } /** * Validates the text whether date pattern * * @param text the string * @return if returns {@code true} then is a valid date pattern */ public static boolean isDatePattern(String text) { return text != null && PATTERN_DATE.matcher(text).matches(); } /** * 获取移动号码运营商类型 * * @param mobilePhone the mobile phone * @return 0未知;1移动;2联通;3电信; */ public static int getPhoneCarrier(String mobilePhone) { if (StringUtils.isBlank(mobilePhone)) { return 0; } else if (CHINA_MOBILE_PATTERN.matcher(mobilePhone).matches()) { return 1; } else if (CHINA_UNICOM_PATTERN.matcher(mobilePhone).matches()) { return 2; } else if (CHINA_TELECOM_PATTERN.matcher(mobilePhone).matches()) { return 3; } else { return 0; } } } ================================================ FILE: src/main/java/cn/ponfee/commons/util/SecureRandoms.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import cn.ponfee.commons.reflect.Fields; import java.math.BigInteger; import java.nio.ByteBuffer; import java.security.SecureRandom; import java.util.UUID; /** * 安全随机数生成工具类 * * @author Ponfee */ public final class SecureRandoms { /** * SHA1PRNG */ private static final SecureRandom SECURE_RANDOM; static { ByteBuffer buffer = ByteBuffer.allocate(56); buffer.putLong(System.currentTimeMillis()); buffer.putLong(System.nanoTime()); buffer.putLong(Thread.currentThread().getId()); buffer.putLong(Fields.addressOf(SecureRandoms.class)); buffer.putLong(Fields.addressOf(buffer)); UUID uuid = UUID.randomUUID(); buffer.putLong(uuid.getMostSignificantBits()); buffer.putLong(uuid.getLeastSignificantBits()); buffer.flip(); SECURE_RANDOM = new SecureRandom(new SecureRandom(buffer.array()).generateSeed(20)); } /** * random byte[] array by SecureRandom * @param numOfByte * @return */ public static byte[] nextBytes(int numOfByte) { byte[] bytes = new byte[numOfByte]; SECURE_RANDOM.nextBytes(bytes); return bytes; } /** * returns a pseudo random int, between 0 and bound * @param bound * @return */ public static int nextInt(int bound) { return SECURE_RANDOM.nextInt(bound); } public static int nextInt() { return SECURE_RANDOM.nextInt(); } public static long nextLong() { return SECURE_RANDOM.nextLong(); } public static float nextFloat() { return SECURE_RANDOM.nextFloat(); } public static double nextDouble() { return SECURE_RANDOM.nextDouble(); } public static boolean nextBoolean() { return SECURE_RANDOM.nextBoolean(); } /** * Returns a pseudo random BigInteger specified bit length * * @param bitLen specified bit length * @return a pseudo random BigInteger */ public static BigInteger random(int bitLen) { BigInteger rnd; do { rnd = new BigInteger(bitLen, SECURE_RANDOM); } while (rnd.bitLength() != bitLen); return rnd; } /** * Returns a pseudo random BigInteger, the bit length * equals mod's bit length - 1 * * @param mod the modulo of maximum bounds * @return a pseudo random BigInteger */ public static BigInteger random(BigInteger mod) { return random(mod.bitLength() - 1); } public static byte[] generateSeed(int numBytes) { return SECURE_RANDOM.generateSeed(numBytes); } } ================================================ FILE: src/main/java/cn/ponfee/commons/util/Snowflake.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import cn.ponfee.commons.math.Maths; /** *

     * 基于snowflake算法的ID生成器
     *
     * BINARY(Long.MAX_VALUE         )=0111111111111111111111111111111111111111111111111111111111111111
     * BINARY(2039-09-07 23:47:35.551)=0000000000000000000000011111111111111111111111111111111111111111
     * 
     * 0 | 0000000000 0000000000 0000000000 0000000000 0 | 00000 | 00000 | 0000000000 00
     * - | ------------------timestamp------------------ | -did- | -wid- | -----seq-----
     * 
     * 00 ~ 00:1位未使用(实际上也是作为long的符号位)
     * 01 ~ 41:41位为毫秒级时间(能到“2039-09-07 23:47:35.551”,41位bit的最大Long值,超过会溢出)
     * 42 ~ 46:5位datacenterId
     * 47 ~ 51:5位workerId(并不算标识符,实际是为线程标识),
     * 52 ~ 63:12位该毫秒内的当前毫秒内的计数
     *
     * 毫秒内序列 (由datacenter和机器ID作区分),并且效率较高。经测试,
     * snowflake每秒能够产生26万ID左右,完全满足需要。
     *
     * 计算掩码的三种方式:
     *   a:(1 << bits) - 1
     *   b:-1L ^ (-1L << bits)
     *   c:Long.MAX_VALUE >>> (63 - bits)
     * 
    * * @author Ponfee */ public final class Snowflake { // Long.toBinaryString(Long.MAX_VALUE).length() private static final int SIZE = Long.SIZE - 1; // 63位(除去最开头的一个符号位) private static final long TWEPOCH = 1514736000000L; // 起始基准时间点(2018-01-01) private final int datacenterId; // 数据中心ID private final int workerId; // 工作机器ID private final int workerIdShift; private final int datacenterIdShift; private final int timestampShift; private final long sequenceMask; private final long timestampMask; private long lastTimestamp = -1L; private long sequence = 0L; public Snowflake(int workerId, int datacenterId, int sequenceBits, int workerIdBits, int datacenterIdBits) { long maxWorkerId = Maths.bitsMask(workerIdBits); if (workerId > maxWorkerId || workerId < 0) { throw new IllegalArgumentException( String.format("worker Id can't be greater than %d or less than 0", maxWorkerId) ); } long maxDatacenterId = Maths.bitsMask(datacenterIdBits); if (datacenterId > maxDatacenterId || datacenterId < 0) { throw new IllegalArgumentException( String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId) ); } this.workerIdShift = sequenceBits; this.datacenterIdShift = sequenceBits + workerIdBits; this.timestampShift = sequenceBits + workerIdBits + datacenterIdBits; this.sequenceMask = Maths.bitsMask(sequenceBits); this.timestampMask = Maths.bitsMask(SIZE - this.timestampShift); this.workerId = workerId; this.datacenterId = datacenterId; } /** * sequenceBits: 12 bit, value range of 0 ~ 4095(111111111111) * workerIdBits: 5 bit, value range of 0 ~ 31(11111) * datacenterIdBits: 5 bit, value range of 0 ~ 31(11111) * * workerIdShift: sequenceBits,左移12位(seq12位) * datacenterIdShift: sequenceBits+workerIdBits,即左移17位(wid5位+seq12位) * timestampShift: sequenceBits+workerIdBits+datacenterIdBits, * 即左移22位(did5位+wid5位+seq12位) * timestampMask: (1L<<(MAX_SIZE-timestampShift))-1 = (1L<<41)-1 * * @param workerId * @param datacenterId */ public Snowflake(int workerId, int datacenterId) { this(workerId, datacenterId, 12, 5, 5); } /** * no datacenterId * max sequence count: 16384 * max work count : 32 * max time at : 2527-06-23 14:20:44.415 * * @param workerId */ public Snowflake(int workerId) { this(workerId, 0, 14, 5, 0); } public synchronized long nextId() { long timestamp = timeGen(); if (timestamp < this.lastTimestamp) { // 时间戳只能单调递增 throw new RuntimeException( String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", this.lastTimestamp - timestamp) ); } if (this.lastTimestamp == timestamp) { // sequence递增 this.sequence = (this.sequence + 1) & this.sequenceMask; if (this.sequence == 0) { // 当前毫秒的sequence已用完,需要循环等待获取下一毫秒 timestamp = untilNextMillis(this.lastTimestamp); this.lastTimestamp = timestamp; } } else { // 上一毫秒的sequence未超用,当前毫秒第一次使用 this.sequence = 0L; this.lastTimestamp = timestamp; } return (((timestamp - TWEPOCH) << this.timestampShift) & this.timestampMask) | (this.datacenterId << this.datacenterIdShift) | (this.workerId << this.workerIdShift) | this.sequence; } /** * 获取下一个时间戳毫秒,一直循环直到获取到为止 * * @param lastTimestamp the lastTimestamp * @return */ private long untilNextMillis(long lastTimestamp) { long timestamp; do { timestamp = timeGen(); } while (timestamp <= lastTimestamp); return timestamp; } private long timeGen() { return System.currentTimeMillis(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/util/SqlUtils.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import org.apache.commons.lang3.StringUtils; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Sql utility * * @author Ponfee */ public final class SqlUtils { public static String trim(String sql) { if (StringUtils.isEmpty(sql)) { return sql; } //sql = sql.replaceAll("\\s{2,}", " "); int start = 0, end = (sql.length() - 1), n = end; while (start < n && isBlank(sql.charAt(start))) { start++; } if (start == n) { return ""; } while (end > start && isBlankOrSemicolon(sql.charAt(end))) { end--; } return start == end ? "" : sql.substring(start, end + 1); } // --------------------------------------------------------------limit mysql private static final Pattern LIMIT_MYSQL = Pattern.compile( "^(.+)(\\s+(?i)LIMIT\\s+(\\d+)(\\s*,\\s*(\\d+))?\\s*)$"/*, Pattern.CASE_INSENSITIVE*/ ); public static String limitMysql(String sql, int limit) { String outermostSql = outermostSql(sql); Matcher matcher = LIMIT_MYSQL.matcher(outermostSql); if (!matcher.matches()) { return sql + " LIMIT " + limit; } // (LIMIT a) OR (LIMIT a, b) String a = matcher.group(3), b = matcher.group(5); if (Integer.parseInt(Optional.ofNullable(b).orElse(a)) <= limit) { return sql; } String replace = b == null ? Integer.toString(limit) : a + "," + limit; return sql.substring(0, sql.length() - outermostSql.length()) + matcher.group(1) + " LIMIT " + replace; } // --------------------------------------------------------------limit pgsql private static final Pattern LIMIT_PGSQL = Pattern.compile( "^(.+)(?i)(\\s+LIMIT\\s+(\\d+)(\\s+OFFSET\\s+(\\d+))?\\s*)$" ); public static String limitPgsql(String sql, int limit) { String outermostSql = outermostSql(sql); Matcher matcher = LIMIT_PGSQL.matcher(outermostSql); if (!matcher.matches()) { return sql + " LIMIT " + limit; } // (LIMIT a) OR (LIMIT a OFFSET b) String a = matcher.group(3), b = matcher.group(5); if (Integer.parseInt(a) <= limit) { return sql; } String limitStr = limit + (b == null ? "" : " OFFSET " + b); return sql.substring(0, sql.length() - outermostSql.length()) + matcher.group(1) + " LIMIT " + limitStr; } // --------------------------------------------------------------limit oracle // Oracle不支持ROWNUM>(=)number语法:SELECT * FROM t_table_name WHERE ROWNUM>2 AND ROWNUM<=4 // 因为第一条数据行号为1,不符合>2的条件所以第一行被去掉,之前的第二行又变为新的第一行,如此下去到最后一条数据也查不出来 // 所以此处正则表达式不考虑“ROWNUM>(=)number”的情况(因为正常oracle sql不会这么写) private static final Pattern LIMIT_ORACLE = Pattern.compile( "^(.+)(\\s+(?i)ROWNUM\\s*<=?\\s*(\\d+))(\\s+(?i)AND\\s+.+|\\s*)$" ); public static String limitOracle(String sql, int limit) { String outermostSql = outermostSql(sql); Matcher matcher = LIMIT_ORACLE.matcher(outermostSql); if (!matcher.matches()) { return sql + completeWhere(outermostSql) + " ROWNUM<" + limit; } // ROWNUM ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import cn.ponfee.commons.io.Files; import cn.ponfee.commons.math.Numbers; import com.google.common.base.CaseFormat; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; /** * 字符串工具类 * * @author Ponfee */ public class Strings { private static final char[] REGEX_SPECIALS = { '\\', '$', '(', ')', '*', '+', '.', '[', ']', '?', '^', '{', '}', '|' }; public static String join(Collection coll) { return join(coll, ",", String::valueOf, "", ""); } public static String join(Collection coll, String delimiter) { return join(coll, delimiter, String::valueOf, "", ""); } /** * Convert to hexadecimal string array * * @param text the text * @return hexadecimal string array */ public static String[] hexadecimal(String text) { // Integer.toString(text.charAt(i), 16) return IntStream.range(0, text.length()) .mapToObj(i -> "0x" + Integer.toHexString(text.charAt(i))) .toArray(String[]::new); } /** * 集合拼接为字符串

    * join(Arrays.asList("a","b","c"), ",", "(", ")") -> (a),(b),(c) * * @param coll 集合对象 * @param delimiter 分隔符 * @param mapper 对象转String * @param open 每个元素添加的前缀 * @param close 每个元素添加的后缀 * @return a String with joined * * @see org.apache.commons.collections4.IteratorUtils#toString(Iterator, org.apache.commons.collections4.Transformer, String, String, String) * @see org.apache.commons.collections4.IterableUtils#toString(Iterable, org.apache.commons.collections4.Transformer, String, String, String) * * @see java.lang.String#join(CharSequence, CharSequence...) * @see java.util.stream.Collectors#joining(CharSequence, CharSequence, CharSequence) * @see org.apache.commons.lang3.StringUtils#join(List, String, int, int) * @see com.google.common.base.Joiner#join(Object, Object, Object...) * @see java.util.StringJoiner#StringJoiner(CharSequence, CharSequence, CharSequence) */ public static String join(Collection coll, String delimiter, Function mapper, String open, String close) { if (coll == null) { return null; } if (coll.isEmpty()) { return ""; } StringBuilder builder = new StringBuilder(128); for (T o : coll) { builder.append(open).append(mapper.apply(o)).append(close).append(delimiter); } builder.setLength(builder.length() - delimiter.length()); return builder.toString(); } /** * Parse main method args, such as: [name1=value,name2=value2,...] * * @param args the args * @return a map object params */ public static Map fromArgs(String[] args) { if (args == null) { return null; } return Arrays.stream(args) .filter(s -> s != null && s.contains("=")) .map(s -> s.split("=", 2)) .collect(Collectors.toMap(p -> p[0], p -> p[1], (v1, v2) -> v1)); } public static String mask(String text, String regex, String replacement) { if (text == null) { return null; } return text.replaceAll(regex, replacement); } public static String mask(String text, int start, int len) { return mask(text, start, len, '*'); } /** * 遮掩(如手机号中间4位加*) * @param text 需要处理的字符串 * @param start 开始位置 * @param len 要处理的字数长度 * @param maskChar 替换的字符 * @return */ public static String mask(String text, int start, int len, char maskChar) { int length; if (len < 1 || StringUtils.isEmpty(text) || (length = text.length()) < start) { return text; } if (start < 0) { start = 0; } if (length < start + len) { len = length - start; } int end = length - start - len; String regex = "(\\w{" + start + "})\\w{" + len + "}(\\w{" + end + "})"; return mask(text, regex, "$1" + StringUtils.repeat(maskChar, len) + "$2"); } /** * Count str occur on text. * * @param text the text * @param str the string * @return number of occur count */ public static int count(String text, String str) { int count = 0; for (int len = str.length(), index=-len; (index = text.indexOf(str, index + len)) != -1; ) { count++; } return count; } /** * 字符串分片 * slice("abcdefghijklmn", 5) -> ["abc","def","ghi","jkl","mn"] * @param str * @param segment * @return */ public static String[] slice(String str, int segment) { int[] array = Numbers.slice(str.length(), segment); String[] result = new String[array.length]; for (int j = 0, i = 0; i < array.length; i++) { result[i] = str.substring(j, (j += array[i])); } return result; } /** *

         * '?' Matches any single character.
         * '*' Matches any sequence of characters (including the empty sequence).
         *
         * isMatch("aa","a")       = false
         * isMatch("aa","aa")      = true
         * isMatch("aaa","aa")     = false
         * isMatch("aa", "*")      = true
         * isMatch("aa", "a*")     = true
         * isMatch("ab", "?*")     = true
         * isMatch("aab", "c*a*b") = false
         * 
    * * @param s the text * @param p the wildcard pattern * @return {@code true} if the string match pattern */ public static boolean isMatch(String s, String p) { // 状态 dp[i][j] : 表示 s 的前 i 个字符和 p 的前 j 个字符是否匹配 (true 的话表示匹配) // 状态转移方程: // 1. 当 s[i] == p[j],或者 p[j] == ? 那么 dp[i][j] = dp[i - 1][j - 1]; // 2. 当 p[j] == * 那么 dp[i][j] = dp[i][j - 1] || dp[i - 1][j] 其中: // dp[i][j - 1] 表示 * 代表的是空字符,例如 ab, ab* // dp[i - 1][j] 表示 * 代表的是非空字符,例如 abcd, ab* // 初始化: // 1. dp[0][0] 表示什么都没有,其值为 true // 2. 第一行 dp[0][j],换句话说,s 为空,与 p 匹配,所以只要 p 开始为 * 才为 true // 3. 第一列 dp[i][0],当然全部为 false int m = s.length(), n = p.length(); boolean[][] dp = new boolean[m + 1][n + 1]; dp[0][0] = true; for (int i = 1; i <= n; ++i) { if (p.charAt(i - 1) == '*') { dp[0][i] = true; } else { break; } } for (int i = 1; i <= m; ++i) { for (int j = 1; j <= n; ++j) { if (p.charAt(j - 1) == '*') { dp[i][j] = dp[i][j - 1] || dp[i - 1][j]; } else if (p.charAt(j - 1) == '?' || s.charAt(i - 1) == p.charAt(j - 1)) { dp[i][j] = dp[i - 1][j - 1]; } } } return dp[m][n]; } /** * Returns a safe file system path that forbid access parent dir * * @param path the path * @return a safe path */ public static String safePath(String path) { if (path == null) { return null; } return cleanPath(path).replace("../", ""); } /** * 文件路径规范化,如“path/..”内部的点号 * 注意:windows的文件分隔符“\”会替换为“/” * * @param path 文件路径 * @return 规范的文件路径 */ public static String cleanPath(String path) { if (path == null) { return null; } String pathToUse = StringUtils.replace(path, Files.WINDOWS_FOLDER_SEPARATOR, Files.UNIX_FOLDER_SEPARATOR); // Strip prefix from path to analyze, to not treat it as part of the // first path element. This is necessary to correctly parse paths like // "file:core/../core/io/Resource.class", where the ".." should just // strip the first "core" directory while keeping the "file:" prefix. int prefixIndex = pathToUse.indexOf(":"); String prefix = ""; if (prefixIndex != -1) { prefix = pathToUse.substring(0, prefixIndex + 1); if (prefix.contains("/")) { prefix = ""; } else { pathToUse = pathToUse.substring(prefixIndex + 1); } } if (pathToUse.startsWith(Files.UNIX_FOLDER_SEPARATOR)) { prefix = prefix + Files.UNIX_FOLDER_SEPARATOR; pathToUse = pathToUse.substring(1); } String[] pathArray = StringUtils.split(pathToUse, Files.UNIX_FOLDER_SEPARATOR); List pathElements = new LinkedList<>(); int tops = 0; for (int i = pathArray.length - 1; i >= 0; i--) { String element = pathArray[i]; if (Files.CURRENT_PATH.equals(element)) { // Points to current directory - drop it. } else if (Files.TOP_PATH.equals(element)) { // Registering top path found. tops++; } else { if (tops > 0) { // Merging path element with element corresponding to top path. tops--; } else { // Normal path element found. pathElements.add(0, element); } } } // Remaining top paths need to be retained. for (int i = 0; i < tops; i++) { pathElements.add(0, Files.TOP_PATH); } return prefix + String.join(Files.UNIX_FOLDER_SEPARATOR, pathElements); } /** * 驼峰转为带分隔符名字,如驼峰转换为下划线:CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, camelCaseName); * * @param camelcaseName the camelcase name * @param separator the separator * @return with separator name * @see CaseFormat#to(CaseFormat, String) */ public static String toSeparatedName(String camelcaseName, char separator) { if (StringUtils.isEmpty(camelcaseName)) { return camelcaseName; } StringBuilder result = new StringBuilder(camelcaseName.length() << 1); result.append(Character.toLowerCase(camelcaseName.charAt(0))); for (int i = 1, len = camelcaseName.length(); i < len; i++) { char ch = camelcaseName.charAt(i); if (Character.isUpperCase(ch)) { result.append(separator).append(Character.toLowerCase(ch)); } else { result.append(ch); } } return result.toString(); } /** * 带分隔符名字转驼峰,如下划线转换为驼峰:CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, underscoreName); * 1 LOWER_HYPHEN 连字符的变量命名规范如lower-hyphen * 2 LOWER_UNDERSCORE c++变量命名规范如lower_underscore * 3 LOWER_CAMEL java变量命名规范如lowerCamel * 4 UPPER_CAMEL java和c++类的命名规范如UpperCamel * 5 UPPER_UNDERSCORE java和c++常量的命名规范如UPPER_UNDERSCORE * * @param separatedName the separated name * @param separator the separator * @return camelcase name * @see CaseFormat#to(CaseFormat, String) */ public static String toCamelcaseName(String separatedName, char separator) { if (StringUtils.isEmpty(separatedName)) { return separatedName; } StringBuilder result = new StringBuilder(separatedName.length()); for (int i = 0, len = separatedName.length(); i < len; i++) { char ch = separatedName.charAt(i); if (separator == ch) { if (++i < len) { result.append(Character.toUpperCase(separatedName.charAt(i))); } } else { result.append(ch); } } return result.toString(); } /** * 如果为空则设置默认 * * @param str * @param defaultStr * @return */ public static String ifEmpty(String str, String defaultStr) { return StringUtils.isEmpty(str) ? defaultStr : str; } public static String ifBlank(String str, String defaultStr) { return StringUtils.isBlank(str) ? defaultStr : str; } // ---------------------------------------------------------------------------escape /** *
         * Escapes the characters in a String to be suitable to pass to
         * an SQL query.
         *
         * For example,
         *  statement.executeQuery("SELECT * FROM MOVIES WHERE TITLE='" +
         *  StringEscapeUtils.escapeSql("McHale's Navy") +  "'");
         *
         * At present, this method only turns single-quotes into doubled single-quotes
         * ("McHale's Navy" => "McHale''s Navy"). It does not
         * handle the cases of percent (%) or underscore (_) for use in LIKE clauses.
         *
         * see http://www.jguru.com/faq/view.jsp?EID=8881
         * 
    * * @param str the string to escape, may be null * @return a new String, escaped for SQL, null if null string input */ public static String escapeSql(String str) { if (str == null) { return null; } return StringUtils.replace(str, "'", "''"); } /** * Escape the regex characters: $()*+.[]?\^{},| * * @param text the text string * @return a new String, escaped for regex */ public static String escapeRegex(String text) { if (StringUtils.isBlank(text)) { return text; } StringBuilder escaped = new StringBuilder(text.length() + 8); char c; for (int i = 0, n = text.length(); i < n; i++) { c = text.charAt(i); if (ArrayUtils.contains(REGEX_SPECIALS, c)) { escaped.append('\\'); } escaped.append(c); } return escaped.toString(); } public static boolean containsAny(String str, List searches) { if (StringUtils.isEmpty(str) || CollectionUtils.isEmpty(searches)) { return false; } for (String search : searches) { if (StringUtils.contains(str, search)) { return true; } } return false; } // ---------------------------------------------------------------------------csv split /** * Parse a CSV string using {@link #csvSplit(List,String, int, int)} * use in {@link cn.ponfee.commons.web.WebContext.WebContextFilter) * * @param s The string to parse * @return An array of parsed values. */ public static String[] csvSplit(String s) { if (s == null) { return null; } return csvSplit(s, 0, s.length()); } /** * Parse a CSV string using {@link #csvSplit(List, String, int, int)} * * @param s The string to parse * @param off The offset into the string to start parsing * @param len The len in characters to parse * @return An array of parsed values. */ public static String[] csvSplit(String s, int off, int len) { if (s == null) { return null; } if (off < 0 || len < 0 || off > s.length()) { throw new IllegalArgumentException(); } List list = new ArrayList<>(); csvSplit(list, s, off, len); return list.toArray(new String[0]); } private enum CsvSplitState { PRE_DATA, QUOTE, SLOSH, DATA, WHITE, POST_DATA } /** Split a quoted comma separated string to a list *

    Handle rfc4180-like * CSV strings, with the exceptions:

      *
    • quoted values may contain double quotes escaped with back-slash *
    • Non-quoted values are trimmed of leading trailing white space *
    • trailing commas are ignored *
    • double commas result in a empty string value *
    * @param list The Collection to split to (or null to get a new list) * @param s The string to parse * @param off The offset into the string to start parsing * @param len The len in characters to parse * @return list containing the parsed list values */ public static List csvSplit(List list, String s, int off, int len) { if (list == null) { list = new ArrayList<>(); } CsvSplitState state = CsvSplitState.PRE_DATA; StringBuilder out = new StringBuilder(); int last = -1; while (len > 0) { char ch = s.charAt(off++); len--; switch (state) { case PRE_DATA: if (Character.isWhitespace(ch)) { continue; } if ('"' == ch) { state = CsvSplitState.QUOTE; continue; } if (',' == ch) { list.add(""); continue; } state = CsvSplitState.DATA; out.append(ch); continue; case DATA: if (Character.isWhitespace(ch)) { last = out.length(); out.append(ch); state = CsvSplitState.WHITE; continue; } if (',' == ch) { list.add(out.toString()); out.setLength(0); state = CsvSplitState.PRE_DATA; continue; } out.append(ch); continue; case WHITE: if (Character.isWhitespace(ch)) { out.append(ch); continue; } if (',' == ch) { out.setLength(last); list.add(out.toString()); out.setLength(0); state = CsvSplitState.PRE_DATA; continue; } state = CsvSplitState.DATA; out.append(ch); last = -1; continue; case QUOTE: if ('\\' == ch) { state = CsvSplitState.SLOSH; continue; } if ('"' == ch) { list.add(out.toString()); out.setLength(0); state = CsvSplitState.POST_DATA; continue; } out.append(ch); continue; case SLOSH: out.append(ch); state = CsvSplitState.QUOTE; continue; case POST_DATA: if (',' == ch) { state = CsvSplitState.PRE_DATA; continue; } continue; default: throw new UnsupportedOperationException("Unsupported state " + state); } } switch (state) { case PRE_DATA: case POST_DATA: break; case DATA: case QUOTE: case SLOSH: list.add(out.toString()); break; case WHITE: out.setLength(last); list.add(out.toString()); break; default: throw new UnsupportedOperationException("Unsupported state " + state); } return list; } } ================================================ FILE: src/main/java/cn/ponfee/commons/util/SynchronizedCaches.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import java.util.Map; import java.util.function.Function; import java.util.function.Supplier; /** * Synchronized cache * * @author Ponfee */ public final class SynchronizedCaches { public static V get(K key, Map cache, Function mapper) { V val = cache.get(key); if (val != null) { return val; } synchronized (cache) { if ((val = cache.get(key)) == null) { if ((val = mapper.apply(key)) != null) { cache.put(key, val); } } } return val; } public static V get(K key, Map cache, Supplier supplier) { V val = cache.get(key); if (val != null) { return val; } synchronized (cache) { if ((val = cache.get(key)) == null) { if ((val = supplier.get()) != null) { cache.put(key, val); } } } return val; } } ================================================ FILE: src/main/java/cn/ponfee/commons/util/TimingWheel.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import java.util.ArrayList; import java.util.List; import java.util.concurrent.PriorityBlockingQueue; /** * Timing wheel structure. * * @author Ponfee */ public class TimingWheel> implements java.io.Serializable { private static final long serialVersionUID = -3950831738037257527L; /** * Millis number of per second. */ private static final int MILLIS_PER_SECOND = 1000; /** * Second number of per minute. */ private static final int SECONDS_PER_MINUTE = 60; /** * Millis number of per minute. */ private static final int MILLIS_PER_MINUTE = MILLIS_PER_SECOND * SECONDS_PER_MINUTE; private final TimingQueue[] ringBuffer; public TimingWheel() { this(11); } public TimingWheel(int priorityQueueInitialCapacity) { TimingQueue[] array = (TimingQueue[]) new TimingQueue[SECONDS_PER_MINUTE]; // initialize 0 ~ 59 slots for (int i = 0; i < SECONDS_PER_MINUTE; i++) { array[i] = new TimingQueue<>(priorityQueueInitialCapacity); } this.ringBuffer = array; } /** * Verifies the timing data * * @param timing the timing data * @return if {@code true} verify success */ protected boolean verify(T timing) { return true; } public final boolean offer(T timing) { // 毫秒在[000 ~ 900]放入下一个刻度 // 毫秒在[901 ~ 999]放入下两个刻度 return offer(timing, System.currentTimeMillis() + 1099); } /** * Puts to timing wheel. * * @param timing the timing data * @param leastTimeMillis the least time millis * @return if {@code true} put success */ public final boolean offer(T timing, long leastTimeMillis) { if (!verify(timing)) { return false; } // 如果小于leastTimeMillis,则放入leastTimeMillis所在的槽位 long slotTimeMillis = Math.max(timing.timing(), leastTimeMillis); int ringSecond = secondOfMinute(slotTimeMillis); return ringBuffer[ringSecond].offer(timing); } public final List poll() { return poll(System.currentTimeMillis()); } /** * Gets from timing wheel. * * @param latestTimeMillis the latest time millis * @return list of Timing */ public final List poll(long latestTimeMillis) { List ringTrigger = new ArrayList<>(); int ringSecond = secondOfMinute(latestTimeMillis); long maximumTiming = (latestTimeMillis / MILLIS_PER_SECOND) * MILLIS_PER_SECOND + 999; // process current and previous tick timingQueue for (int i = 0; i < 2; i++) { TimingQueue ringTick = ringBuffer[(ringSecond - i + SECONDS_PER_MINUTE) % SECONDS_PER_MINUTE]; T first; while ((first = ringTick.peek()) != null && first.timing() <= maximumTiming) { first = ringTick.poll(); // if run in single thread, there code block unnecessary if (first == null) { break; } if (first.timing() > maximumTiming) { ringTick.offer(first); break; } ringTrigger.add(first); } } return ringTrigger; } private static int secondOfMinute(long timeMillis) { return (int) ((timeMillis % MILLIS_PER_MINUTE) / MILLIS_PER_SECOND); } /** * Timing of TimingWheel elements */ public interface Timing> extends Comparable { /** * Returns millis timestamp * * @return millis timestamp */ long timing(); /** * Provides default compare * * @param other the other * @return the value 0 if this == other; a value less than 0 if this < other; and a value greater than 0 if this > other */ @Override default int compareTo(T other) { return Long.compare(this.timing(), other.timing()); } } /** * Timing queue * * @param element type */ public static class TimingQueue> extends PriorityBlockingQueue { public TimingQueue() { super(); } public TimingQueue(int initialCapacity) { super(initialCapacity); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/util/URLCodes.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import cn.ponfee.commons.io.Files; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; /** * URL encode/decode utility class. * * @author Ponfee */ public final class URLCodes { public static String encodeURI(String url) { return encodeURI(url, Files.UTF_8); } /** *
         * 相当于javascript中的encodeURI
         * 不会被此方法编码的字符:! @ # $& * ( ) = : / ; ? + '
         * encodeURI("http://www.oschina.net/search?scope=bbs&q=C语言", "UTF-8") -> http://www.oschina.net/search?scope=bbs&q=C%E8%AF%AD%E8%A8%80
         * 
    * * @param url the url string * @param charset the charset * @return encoded url string */ public static String encodeURI(String url, String charset) { if (url == null) { return null; } StringBuilder builder = new StringBuilder(url.length() * 3 / 2); byte[] b; for (int n = url.length(), i = 0; i < n; i++) { char c = url.charAt(i); if (c >= 0 && c <= 255) { builder.append(c); } else { try { b = Character.toString(c).getBytes(charset); } catch (Exception ex) { b = new byte[0]; } for (int k, j = 0; j < b.length; j++) { k = b[j]; if (k < 0) { k += 256; } builder.append('%').append(Integer.toHexString(k).toUpperCase()); } } } return builder.toString(); } public static String decodeURI(String url) { return decodeURI(url, Files.UTF_8); } /** * 相当于javascript的decodeURI * * @param url the url string * @param charset the charset * @return decoded url string */ public static String decodeURI(String url, String charset) { if (url == null) { return null; } try { return URLDecoder.decode(url, charset); } catch (UnsupportedEncodingException e) { throw new IllegalArgumentException(e); } } // ------------------------------------------------------------------------------encode/decode uri component public static String encodeURIComponent(String url) { return encodeURIComponent(url, Files.UTF_8); } /** *
         * 相当于javascript中的encodeURIComponent
         * 不会被此方法编码的字符:! * ( )
         * encodeURIComponent("http://www.oschina.net/search?scope=bbs&q=C语言", "UTF-8") -> http%3A%2F%2Fwww.oschina.net%2Fsearch%3Fscope%3Dbbs%26q%3DC%E8%AF%AD%E8%A8%80
         * 
    * * @param url the uri string * @param charset the charset * @return encoded uri component string */ public static String encodeURIComponent(String url, String charset) { if (url == null) { return null; } try { return URLEncoder.encode(url, charset); } catch (UnsupportedEncodingException e) { throw new IllegalArgumentException(e); } } public static String decodeURIComponent(String url) { return decodeURI(url, Files.UTF_8); } /** * 相当于javascript中的decodeURIComponent * * @param url the url string * @param charset the charset * @return decoded uri component string */ public static String decodeURIComponent(String url, String charset) { return decodeURI(url, charset); } } ================================================ FILE: src/main/java/cn/ponfee/commons/util/UuidUtils.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import java.util.Base64; import java.util.UUID; /** * UUID utility * * @author Ponfee */ public final class UuidUtils { /** * Returns 16 length byte array uuid * * @return 16 length uuid byte array */ public static byte[] uuid() { UUID uuid = UUID.randomUUID(); byte[] value = new byte[16]; Bytes.put(uuid.getMostSignificantBits(), value, 0); Bytes.put(uuid.getLeastSignificantBits(), value, 8); return value; } /** * Returns 32 length string uuid, use hex encoding * * @return 32 length uuid string */ public static String uuid32() { UUID uuid = UUID.randomUUID(); return Bytes.toHex(uuid.getMostSignificantBits(), true) + Bytes.toHex(uuid.getLeastSignificantBits(), true); } /** * Returns 22 length string uuid, use base64 url encoding and without padding * * @return 22 length uuid string */ public static String uuid22() { return Base64.getUrlEncoder().withoutPadding().encodeToString(uuid()); } } ================================================ FILE: src/main/java/cn/ponfee/commons/util/Wechats.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import cn.ponfee.commons.http.Http; import cn.ponfee.commons.http.HttpParams; import cn.ponfee.commons.io.Files; import cn.ponfee.commons.jce.digest.DigestUtils; import cn.ponfee.commons.json.Jsons; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; /** * 微信工具类:https://www.cnblogs.com/txw1958/p/weixin76-user-info.html * OAuth2.0:https://www.jianshu.com/p/6392420faf99 * * @author Ponfee */ @SuppressWarnings("unchecked") public class Wechats { // -------------------------------------------------------构建微信授权地址 public static String buildAuthorizeUrl(String appid, String redirect, String state) { return buildAuthorizeUrl(appid, Files.UTF_8, redirect, state); } public static String buildAuthorizeUrl(String appid, String charset, String redirect, String state) { return buildAuthorizeUrl(appid, charset, redirect, state, "snsapi_base"); } /** * 构建授权地址 * * @param appid the appid * * @param charset the charset for params encoding * * @param redirect the service url * * @param state 在发送state之后,可以把state保存到Session以便用于后续回调时的比较。 * 这样做的目的是防止应用接受任意伪造的授权码(CSRF)。 * * @param scope snsapi_base :不弹出授权页面,直接跳转,只能获取用户openid

    * snsapi_userinfo:弹出授权页面,可通过openid拿到昵称、性别、所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息

    * snsapi_login :登录 * * @return a url of wechat auth */ public static String buildAuthorizeUrl(String appid, String charset, String redirect, String state, String scope) { Map params = new LinkedHashMap<>(); params.put("appid", appid); params.put("redirect_uri", redirect); params.put("response_type", "code"); params.put("scope", scope); params.put("state", state); return HttpParams.buildUrlPath("https://open.weixin.qq.com/connect/oauth2/authorize", charset, params) + "#wechat_redirect"; } // -------------------------------------------------------通过授权地址的回调参数code换取网页授权access_token和openId /** *

         * 1、构建的微信授权地址返回到客户端
         * 2、用户客户端访问此微信授权地址进行登录授权
         * 3、微信会让用户客户端重定向到redirect(应用的回调地址)并附带code参数
         * 4、应用通过code换取网页授权access_token和openId
         *
         * scope=snsapi_userinfo
         *  {
         *    "access_token":"OezXcEiiBSKSxW0eow",
         *    "expires_in":7200,
         *    "refresh_token":"OezXcqDQy52232WDXB3Msuzq1A",
         *    "openid":"oLVPpjqs9BhvzwPj5A-vTYAX3GLc",
         *    "scope":"snsapi_userinfo,"
         *  }
         *  
         * scope=snsapi_base
         *  {
         *    "access_token": "OezXcEiiBSKSxW0eoylIeAsR0GmYd1awCffdHgb4fhS_KKf2CotGj2cBNUKQQvj-oJ9VmO-0Z-_izfnSAX_s0aqDsYkW4s8W5dLZ4iyNj5Y6vey3dgDtFki5C8r6D0E6mSVxxtb8BjLMhb-mCyT_Yg",
         *    "expires_in": 7200,
         *    "refresh_token": "OezXcEiiBSKSxW0eoylIeAsR0GmYd1awCffdHgb4fhS_KKf2CotGj2cBNUKQQvj-oJ9VmO-0Z-_izfnSAX_s0aqDsYkW4s8W5dLZ4iyNj5YBkF0ZUH1Ew8Iqea6x_itq13sYDqP1D7ieaDy9u2AHHw",
         *    "openid": "oLVPpjqs9BhvzwPj5A-vTYAX3GLc",
         *    "scope": "snsapi_base"
         *  }
         * 
    * * 获取微信openID及授权access_token * * * password模式:https://api.oauth2server.com/token?grant_type=password&username=USERNAME&password=PASSWORD&client_id=CLIENT_ID * * @param appid the appid * @param secret the secret * @param code the aut url callback result data, * {@link #buildAuthorizeUrl(String, String, String, String, String)} * * @return wechat oauth info */ public static Map getOAuth2(String appid, String secret, String code) { Map params = new LinkedHashMap<>(); params.put("appid", appid); params.put("secret", secret); params.put("code", code); params.put("grant_type", "authorization_code"); Map result = Http.post("https://api.weixin.qq.com/sns/oauth2/access_token") .data(params).request(Map.class); checkError(result); return result; } /** * Refresh the access token by refresh_token * * @param appid the appid * @param refreshToken the refresh_token * @return refresed a new access_token by refresh_token */ public static Map refreshAccessToken(String appid, String refreshToken) { Map params = new LinkedHashMap<>(); params.put("grant_type", "refresh_token"); params.put("appid", appid); params.put("refresh_token", refreshToken); Map result = Http.post("https://api.weixin.qq.com/sns/oauth2/refresh_token") .data(params).request(Map.class); checkError(result); return result; } /** *
         *  {
         *    "openid":"oLVPpjqs9BhvzwPj5A-vTYAX3GLc",
         *    "nickname":"方倍",
         *    "sex":1,
         *    "language":"zh_CN",
         *    "city":"Shenzhen",
         *    "province":"Guangdong",
         *    "country":"CN",
         *    "headimgurl":"http://wx.qlogo.cn/mmopen/utpBBg18/0",
         *    "privilege":[]
         *  }
         * 
    * * 通过OAuth2.0方式弹出授权页面获得用户基本信息(因scope=snsapi_userinfo会弹出授权页面) * * @param accessToken the access token, {@link #getOAuth2(String, String, String)} * @param openid the openid, {@link #getOAuth2(String, String, String)} * * @return wechat user info * * @see Wechats#buildAuthorizeUrl(String, String, String, String, String) set scope=snsapi_userinfo */ public static Map getUserInfoByOAuth2(String accessToken, String openid) { Map params = new LinkedHashMap<>(); params.put("access_token", accessToken); params.put("openid", openid); params.put("lang", "zh_CN"); // this param can be unset Map result = Http.post("https://api.weixin.qq.com/sns/userinfo") .data(params).request(Map.class); checkError(result); return result; } // -------------------------------------------------------获取全局access_token /** * Gets global access token * * @param appid * @param secret * * @return {access_token=token, expires_in=7200} */ public static String getAccessToken(String appid, String secret) { Map params = new LinkedHashMap<>(); params.put("grant_type", "client_credential"); params.put("appid", appid); params.put("secret", secret); Map result = Http.post("https://api.weixin.qq.com/cgi-bin/token") .data(params).request(Map.class); checkError(result); return (String) result.get("access_token"); } // -------------------------------------------------------通过全局access token获取用户信息 /** *
         *  1、用户关注以及回复消息的时候,均可以获得用户的OpenID
         *   
         *     
         *     
         *     1372307736
         *     
         *     
         *     
         *   
         *  其中的FromUserName就是OpenID
         * 
         *  2、返回数据格式
         *  {
         *    "subscribe": 1,
         *    "openid": "oLVPpjqs2BhvzwPj5A-vTYAX4GLc",
         *    "nickname": "nickname",
         *    "sex": 1,
         *    "language": "zh_CN",
         *    "unionid": "unionid" // 只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段
         *    "city": "深圳",
         *    "province": "广东",
         *    "country": "中国",
         *    "headimgurl": "http://wx.qlogo.cn/mmopen/JcDicrZBlREhnNXZRudod9PmibRkIs5K2f1tUQ7lFjC63pYHaXGxNDgMzjGDEuvzYZbFOqtUXaxSdoZG6iane5ko9H30krIbzGv/0",
         *    "subscribe_time": 1386160805 // 用户关注时间,为时间戳。如果用户曾多次关注,则取最后关注时间
         *  }
         * 
    * * 通过全局Access Token获取用户基本信息 * * @param accessToken the global access token, {@link #getAccessToken(String, String)} * @param openid the openid, 用户关注以及回复消息时可获取此openid * * @return wechat user info */ public static Map getUserInfoByGlobal(String accessToken, String openid) { Map params = new LinkedHashMap<>(); params.put("access_token", accessToken); params.put("openid", openid); Map result = Http.post("https://api.weixin.qq.com/cgi-bin/user/info") .data(params).request(Map.class); checkError(result); return result; } // -------------------------------------------------------获取api_ticket public static String getJsapiTicket(String accessToken) { return getTicket("jsapi", accessToken); } /** * Gets jsapi ticket * * @param type wx_card 卡券;jsapi js接口票据; * @param accessToken the global accessToken, {@link #getAccessToken(String, String)} * * @return {errcode=0, errmsg=ok, ticket=ticket, expires_in=7200} */ public static String getTicket(String type, String accessToken) { Map params = new LinkedHashMap<>(); params.put("access_token", accessToken); params.put("type", type); Map result = Http.post("https://api.weixin.qq.com/cgi-bin/ticket/getticket") .data(params).request(Map.class); checkError(result); return (String) result.get("ticket"); } /** * Returns the share url link to the wechat friend moments * * @param jsapiTicket the jsapi ticket, {@link #getJsapiTicket(String)} * @param appid the wechat appid * @param url the url link * @return share url data with signurate give to client use */ public static Map shareUrl(String jsapiTicket, String appid, String url) { Map map = new HashMap<>(); map.put("jsapi_ticket", jsapiTicket); map.put("noncestr", UuidUtils.uuid22()); map.put("timestamp", Long.toString(System.currentTimeMillis() / 1000)); map.put("url", !url.contains("#") ? url : url.substring(0, url.indexOf("#"))); // generate sigin data map.put("signature", DigestUtils.sha1Hex(HttpParams.buildSigning(map))); map.put("appid", appid); return map; } // -------------------------------------------------------private methods private static void checkError(Map result) { Object errcode = result.get("errcode"); if (errcode != null && !"0".equals(errcode.toString())) { throw new RuntimeException("Wechat server response error:" + Jsons.toJson(result)); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/util/ZipUtils.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import cn.ponfee.commons.io.Files; import net.lingala.zip4j.ZipFile; import net.lingala.zip4j.exception.ZipException; import net.lingala.zip4j.model.FileHeader; import net.lingala.zip4j.model.ZipParameters; import net.lingala.zip4j.model.enums.AesKeyStrength; import net.lingala.zip4j.model.enums.CompressionLevel; import net.lingala.zip4j.model.enums.CompressionMethod; import net.lingala.zip4j.model.enums.EncryptionMethod; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; import java.io.File; import java.util.ArrayList; import java.util.List; /** * zip utility based zip4j * * @see apache commons-compress * * @author Ponfee */ public class ZipUtils { private static final String SUFFIX = ".zip"; // -----------------------------------压缩----------------------------------- /** * 压缩指定文件到当前文件夹,压缩后的文件名为:待压缩文件名+.zip * @param src 要压缩的指定文件 * @return 最终的压缩文件存放的绝对路径 */ public static String zip(String src) throws ZipException { File srcFile = new File(src); if (!srcFile.exists()) { throw new ZipException("source file not found: " + src); } String dest = srcFile.getParent() + File.separator; if (srcFile.isFile()) { dest += FilenameUtils.getBaseName(srcFile.getName()); // 文件名去除后缀 } else { dest += srcFile.getName(); // 以目录名作为文件名 } return zip(src, dest + SUFFIX); } /** * 压缩文件到指定路径 * @param src 待压缩的文件 * @param dest 压缩文件存放路径 * @return 最终的压缩文件存放的绝对路径 */ public static String zip(String src, String dest) throws ZipException { return zip(src, dest, null); } /** * 使用给定密码压缩文件到指定路径 * @param src 要压缩的文件 * @param dest 压缩文件存放路径 * @param passwd 压缩使用的密码 * @return 最终的压缩文件存放的绝对路径 */ public static String zip(String src, String dest, String passwd) throws ZipException { return zip(src, dest, true, passwd, null); } /** * 压缩文件到指定路径 * @param src 待压缩的文件名或文件夹路径名 * @param dest 压缩文件存放路径 * @param recursion 是否递归压缩(只对待压缩文件为文件夹时有效):true是;false否; * @param passwd 压缩使用的密码 * @param comment 注释信息 * @return 最终的压缩文件存放的绝对路径 */ public static String zip(String src, String dest, boolean recursion, String passwd, String comment) throws ZipException { // validate source file File srcFile = new File(src); if (!srcFile.exists()) { throw new ZipException("source file not found: " + src); } // validate dest file if (StringUtils.isEmpty(dest)) { throw new ZipException("dest file cannot be null"); } File destFile = new File(dest); if (destFile.exists()) { throw new ZipException("dest file exists: " + dest); } Files.mkdir(destFile.getParent()); // 创建父路径(如果不存在) // create zip parameters ZipParameters parameters = new ZipParameters(); parameters.setCompressionMethod(CompressionMethod.DEFLATE); // 压缩方式 parameters.setCompressionLevel(CompressionLevel.NORMAL); // 压缩级别 if (!StringUtils.isEmpty(passwd)) { parameters.setEncryptFiles(true); parameters.setEncryptionMethod(EncryptionMethod.AES); // 加密方式 parameters.setAesKeyStrength(AesKeyStrength.KEY_STRENGTH_256); //parameters.setEncryptionMethod(EncryptionMethod.ZIP_STANDARD); // 加密方式 } ZipFile zipFile = new ZipFile(destFile, toCharArray(passwd)); // 开始压缩 if (srcFile.isFile()) { // 压缩文件 zipFile.addFile(srcFile, parameters); } else { // 压缩目录 File[] files = srcFile.listFiles(); if (files == null || files.length == 0) { return null; } for (File file : files) { if (file.isFile()) { zipFile.addFile(file, parameters); } else if (recursion) { // 递归压缩目录 zipFile.addFolder(file, parameters); } } } if (comment != null) { zipFile.setComment(comment); } return destFile.getAbsolutePath(); } // -----------------------------------解压缩----------------------------------- /** * 解压缩文件到当前目录 * @param zipFile 压缩文件 * @return 解压后文件数组 * @throws ZipException */ public static String unzip(String zipFile) throws ZipException { if (StringUtils.isBlank(zipFile)) { throw new ZipException("zip file cannot be null"); } String lowercasePath = zipFile.toLowerCase(); if (lowercasePath.endsWith(SUFFIX)) { String dest = zipFile.substring(0, lowercasePath.indexOf(SUFFIX)); unzip(zipFile, dest); return dest; } else { throw new ZipException("the zip file name must be end with .zip"); } } /** * 解压缩文件到指定目录 * @param zipFile 指定的压缩文件 * @param dest 解压缩存放的目录 * @return 解压后文件数组 * @throws ZipException 压缩文件有损坏或者解压缩失败抛出 */ public static File[] unzip(String zipFile, String dest) throws ZipException { return unzip(zipFile, dest, null); } /** * 使用给定密码解压指定的压缩文件到指定目录

    * 如果指定目录不存在,可以自动创建,不合法的路径将导致异常被抛出 * @param zipFile 指定的压缩文件 * @param dest 解压目录 * @param passwd 压缩文件的密码 * @return 解压后文件数组 * @throws ZipException 压缩文件有损坏或者解压缩失败抛出 */ public static File[] unzip(String zipFile, String dest, String passwd) throws ZipException { return unzip(new File(zipFile), dest, passwd); } /** * 使用给定密码解压指定的压缩文件到指定目录

    * 如果指定目录不存在,可以自动创建,不合法的路径将导致异常被抛出 * @param zipFile 指定的压缩文件 * @param dest 解压目录 * @param passwd 压缩文件的密码 * @return 解压后文件数组 * @throws ZipException 压缩文件有损坏或者解压缩失败抛出 */ public static File[] unzip(File zipFile, String dest, String passwd) throws ZipException { // validate zip file if (!zipFile.exists()) { throw new ZipException("zip file not found: " + zipFile.getAbsolutePath()); } ZipFile zFile = new ZipFile(zipFile, toCharArray(passwd)); if (!zFile.isValidZipFile()) { throw new ZipException("invalid zip file."); } if (zFile.isEncrypted() && StringUtils.isEmpty(passwd)) { throw new ZipException("passwd can't be null"); } // validate dest file path if (StringUtils.isEmpty(dest)) { throw new ZipException("dest file path can't be null"); } else if (new File(dest).exists()) { throw new ZipException("dest file is exists: " + dest); } else { Files.mkdir(dest); // 校验并创建解压缩存放目录 } // unpack zip file zFile.extractAll(dest); List fileEntries = new ArrayList<>(); for (FileHeader fileHeader : zFile.getFileHeaders()) { if (!fileHeader.isDirectory()) { fileEntries.add(new File(dest, fileHeader.getFileName())); } } return fileEntries.toArray(new File[0]); } private static char[] toCharArray(String str) { return StringUtils.isEmpty(str) ? null : str.toCharArray(); } } ================================================ FILE: src/main/java/cn/ponfee/commons/web/AbstractWebExceptionHandler.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.web; import cn.ponfee.commons.exception.BaseUncheckedException; import cn.ponfee.commons.exception.UnauthorizedException; import cn.ponfee.commons.model.Result; import cn.ponfee.commons.model.ResultCode; import com.google.common.base.Throwables; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.TypeMismatchException; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.validation.BindException; import org.springframework.validation.ObjectError; import org.springframework.web.HttpMediaTypeNotSupportedException; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.ServletRequestBindingException; import org.springframework.web.bind.annotation.ExceptionHandler; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; import java.io.IOException; import java.util.List; import java.util.stream.Collectors; /** * Spring mvc global exception handler for web application * * * `@ControllerAdvice * public class WebExceptionHandler extends AbstractWebExceptionHandler {} * * * * 2、必须在Controller类中注解@org.springframework.validation.annotation.Validated * 3、public `@NotNull Result testValidate3(@Range(min = 1, max = 9, message = "年级只能从1-9") @RequestParam(name = "grade", required = true) int grade) {} * 4、若是接口实现类,则参数约束注解只能放在接口方法参数上(返回值可放在实现类方法上) */ @ExceptionHandler(ConstraintViolationException.class) //@ResponseBody @ResponseStatus(HttpStatus.BAD_REQUEST) public void handle(HttpServletRequest req, HttpServletResponse resp, ConstraintViolationException e) { LOGGER.debug("Constraint violation", e); String message = e.getConstraintViolations() .stream() .map(ConstraintViolation::getMessage) .collect(Collectors.joining(",", "[", "]")); handle(req, resp, BAD_REQUEST, message); } /** * 400 - Bad Request */ @ExceptionHandler({ IllegalArgumentException.class, IllegalStateException.class, TypeMismatchException.class, HttpMessageNotReadableException.class, ServletRequestBindingException.class, }) //@ResponseBody @ResponseStatus(HttpStatus.BAD_REQUEST) public void handle(HttpServletRequest req, HttpServletResponse resp, Exception e) { LOGGER.debug("Bad request", e); handle(req, resp, BAD_REQUEST, e.getMessage()); } /** * 405 - Method Not Allowed */ @ExceptionHandler(HttpRequestMethodNotSupportedException.class) //@ResponseBody @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED) public void handle(HttpServletRequest req, HttpServletResponse resp, HttpRequestMethodNotSupportedException e) { LOGGER.debug("Request method not supported", e); handle(req, resp, NOT_ALLOWED, e.getMessage()); } /** * 415 - Unsupported Media Type */ @ExceptionHandler(HttpMediaTypeNotSupportedException.class) //@ResponseBody @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE) public void handle(HttpServletRequest req, HttpServletResponse resp, HttpMediaTypeNotSupportedException e) { LOGGER.debug("Media type not supported", e); handle(req, resp, UNSUPPORT_MEDIA, e.getMessage()); } /* @ExceptionHandler(org.apache.commons.fileupload.FileUploadException.class) //@ResponseBody @ResponseStatus(HttpStatus.PAYLOAD_TOO_LARGE) public void handle(HttpServletRequest req, HttpServletResponse resp, org.apache.commons.fileupload.FileUploadException e) { LOGGER.debug("File upload fail.", e); handle(req, resp, HttpStatus.PAYLOAD_TOO_LARGE.value(), e.getMessage()); } */ /** * 500 - Biz operate failure */ @ExceptionHandler(BaseUncheckedException.class) //@ResponseBody @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public void handle(HttpServletRequest req, HttpServletResponse resp, BaseUncheckedException e) { LOGGER.debug("Biz operate failure", e); handle(req, resp, e.getCode(), e.getMessage()); } /** * 500 - Internal Server Error */ @ExceptionHandler(Throwable.class) //@ResponseBody @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public void handle(HttpServletRequest req, HttpServletResponse resp, Throwable t) { LOGGER.error("Server error", t); String message = LOGGER.isDebugEnabled() ? Throwables.getStackTraceAsString(t) : this.defaultErrorMsg; handle(req, resp, this.serverErrorPage, SERVER_ERROR, message); } // -----------------------------------------------------------------------protected methods protected void handle(HttpServletRequest req, HttpServletResponse resp, String page, int code, String message) { if (page == null || LOGGER.isDebugEnabled() || WebUtils.isAjax(req)) { // resp.setStatus(code); HttpStatus.valueOf(code); WebUtils.respJson(resp, Result.failure(code, message)); } else { handErrorPage(req, resp, page); } } protected void handErrorPage(HttpServletRequest req, HttpServletResponse resp, String page) { try { req.getRequestDispatcher(page).forward(req, resp); //resp.sendRedirect(resp.encodeRedirectURL(WebUtils.getContextPath(req) + page)); } catch (IOException | ServletException e) { LOGGER.error("Forward page occur error.", e); } } // -----------------------------------------------------------------------private methods private void handle(HttpServletRequest req, HttpServletResponse resp, List errors) { String message = errors.stream() .map(ObjectError::getDefaultMessage) .collect(Collectors.joining(",", "[", "]")); handle(req, resp, BAD_REQUEST, message); } private void handle(HttpServletRequest req, HttpServletResponse resp, int code, String message) { handle(req, resp, null, code, message); } } ================================================ FILE: src/main/java/cn/ponfee/commons/web/DevicePlatform.java ================================================ /* * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cn.ponfee.commons.web; /** * Enumeration for the platform of device that has been resolved * * @author Onur Kagan Ozcan * * Modify from org.springframework.mobile.device.DevicePlatform * @see org.springframework.mobile.device.DevicePlatform */ public enum DevicePlatform { /** * Represents an apple platform */ IOS, /** * Represents an android platform */ ANDROID, /** * Represents unknown platform */ UNKNOWN } ================================================ FILE: src/main/java/cn/ponfee/commons/web/DeviceType.java ================================================ /* * Copyright 2010-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cn.ponfee.commons.web; /** * Enumeration for the type of device that has been resolved * * @author Roy Clarkson * * Modify from org.springframework.mobile.device.DeviceType * @see org.springframework.mobile.device.DeviceType */ public enum DeviceType { /** * Represents a normal device. i.e. a browser on a desktop or laptop computer */ NORMAL, /** * Represents a mobile device, such as an iPhone */ MOBILE, /** * Represents a tablet device, such as an iPad */ TABLET } ================================================ FILE: src/main/java/cn/ponfee/commons/web/GlobalExceptionHandler.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ //package cn.ponfee.commons.web; // //import java.io.IOException; //import java.io.PrintWriter; // //import javax.servlet.ServletConfig; //import javax.servlet.ServletException; //import javax.servlet.http.HttpServlet; //import javax.servlet.http.HttpServletRequest; //import javax.servlet.http.HttpServletResponse; // //import org.apache.commons.lang3.StringUtils; //import org.slf4j.Logger; //import org.slf4j.LoggerFactory; // //import com.google.common.collect.ImmutableMap; // //import cn.ponfee.commons.exception.BasicException; //import cn.ponfee.commons.http.HttpParams; //import cn.ponfee.commons.io.Files; //import cn.ponfee.commons.json.Jsons; //import cn.ponfee.commons.model.Result; //import cn.ponfee.commons.model.ResultCode; //import cn.ponfee.commons.web.WebUtils; // ///** // * 全局异常处理

    // * https://www.runoob.com/servlet/servlet-exception-handling.html // * // *

    {@code 
    // * 
    // *   
    // *     globalExceptionHandler
    // *     cn.ponfee.web.framework.web.GlobalExceptionHandler
    // *     
    // *       handlerType
    // *       application/json
    // *     
    // *   
    // *   
    // *     globalExceptionHandler
    // *     /globalExceptionHandler
    // *   
    // *   
    // *     404
    // *     /globalExceptionHandler
    // *   
    // *   
    // *     java.lang.Throwable
    // *     /globalExceptionHandler
    // *   
    // *   
    // *     403
    // *     /globalExceptionHandler
    // *   
    // * 
    // * }
    // * // * @author Ponfee // */ ///*@WebServlet( // name = "cn.ponfee.web.framework.web.GlobalExceptionHandler", // urlPatterns = {"/globalExceptionHandler"}, // initParams = { // @WebInitParam(name="handlerType", value="application/json") // }, // asyncSupported=true //)*/ //public class GlobalExceptionHandler extends HttpServlet { // // private static final long serialVersionUID = 6067653829035388068L; // private static Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); // private static final String DEFAULT_HANDLER_TYPE = "application/json"; // public static final String ERROR_MSG = "系统异常,请与管理员联系"; // // private String handlerType; // private String errorPage; // // @Override // protected void doGet(HttpServletRequest req, HttpServletResponse resp) { // this.doPost(req, resp); // } // // @Override // protected void doPost(HttpServletRequest req, HttpServletResponse resp) { // Throwable throwable = (Throwable) req.getAttribute("javax.servlet.error.exception"); // Integer statusCode = (Integer) req.getAttribute("javax.servlet.error.status_code"); // String servletName = (String) req.getAttribute("javax.servlet.error.servlet_name"); // String requestUri = (String) req.getAttribute("javax.servlet.error.request_uri"); // //Class message = (Class) req.getAttribute("javax.servlet.error.message"); // //Class type = (Class) req.getAttribute("javax.servlet.error.exception_type"); // logger.info("{}-{}-{}-{}", throwable, statusCode, servletName, requestUri); // // try { // if (throwable != null) { // if (throwable instanceof BasicException // || throwable instanceof IllegalArgumentException) { // logger.info("", throwable); // } else { // logger.error("", throwable); // } // throw throwable; // } else { // throw new WebException(ResultCode.NOT_FOUND.getCode(), "file not found"); // } // } catch (Throwable e) { // String errorMsg = (e instanceof BasicException) || logger.isInfoEnabled() // ? e.getMessage() : ERROR_MSG; // errorMsg = StringUtils.isBlank(errorMsg) ? ERROR_MSG : errorMsg; // switch (handlerType) { // case "application/json": // case "text/html": // case "text/plain": // int code = (e instanceof BasicException) // ? ((BasicException) e).getCode() // : ResultCode.SERVER_ERROR.getCode(); // resp.setContentType(handlerType + ";charset=" + Files.UTF_8); // try (PrintWriter writer = resp.getWriter()) { // writer.print(Jsons.toJson(Result.failure(code, errorMsg))); // } catch (IOException ex) { // logger.error("", ex); // } // break; // default: // if (errorPage != null) { // String context = WebUtils.getContextPath(req); // String url = HttpParams.buildUrlPath(context + errorPage, Files.UTF_8, // ImmutableMap.of("msg", errorMsg)); // try { // resp.sendRedirect(resp.encodeRedirectURL(url)); // } catch (IOException ex) { // logger.error("response send redirect occur error", ex); // } // } else { // resp.setStatus(ResultCode.SERVER_ERROR.getCode()); // try (PrintWriter writer = resp.getWriter()) { // writer.append(errorMsg); // } catch (IOException ex) { // logger.error("", ex); // } // } // break; // } // } // } // // @Override // public void init(ServletConfig config) throws ServletException { // super.init(config); // handlerType = config.getInitParameter("handlerType"); // if (StringUtils.isBlank(handlerType)) { // handlerType = DEFAULT_HANDLER_TYPE; // } // handlerType = handlerType.toLowerCase(); // // errorPage = config.getInitParameter("errorPage"); // if (StringUtils.isBlank(errorPage)) { // errorPage = null; // } // } // //} ================================================ FILE: src/main/java/cn/ponfee/commons/web/GlobalExceptionResolver.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ //package cn.ponfee.commons.web; // //import java.io.IOException; //import java.io.PrintWriter; //import java.util.Collections; //import java.util.Enumeration; //import java.util.HashMap; //import java.util.Map; //import java.util.Properties; // //import javax.servlet.http.HttpServletRequest; //import javax.servlet.http.HttpServletResponse; // //import org.apache.commons.lang3.StringUtils; //import org.springframework.web.servlet.ModelAndView; //import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver; //import org.springframework.web.util.WebUtils; // //import cn.ponfee.commons.exception.BasicException; //import cn.ponfee.commons.exception.Throwables; //import cn.ponfee.commons.json.Jsons; //import cn.ponfee.commons.model.Result; //import cn.ponfee.commons.model.ResultCode; // ///** // * Spring mvc 全局异常处理方式 // * 更改自:org.springframework.web.servlet.handler.SimpleMappingExceptionResolver // * // * @author Ponfee // */ //public class GlobalExceptionResolver extends AbstractHandlerExceptionResolver { // // public static final String ERROR_MSG = "系统异常,请与管理员联系"; // // /** The default name of the exception attribute: "exception". */ // private Properties exceptionMappings; // // private Class[] excludedExceptions; // // private String defaultErrorView; // // private Integer defaultStatusCode; // // private Map statusCodes = new HashMap<>(); // // private String exceptionAttribute = "exception"; // // private String stackTraceAttribute = "stackTrace"; // // private String resolverType = "modeView"; // // //private static Logger logger = LoggerFactory.getLogger(CespHandlerExceptionResolver.class); // // /** // * Set the mappings between exception class names and error view names. The exception class name can be a substring, with no wildcard support at present. A // * value of "ServletException" would match {@code javax.servlet.ServletException} and subclasses, for example. // *

    // * NB: Consider carefully how specific the pattern is, and whether to include package information (which isn't mandatory). For example, "Exception" // * will match nearly anything, and will probably hide other rules. "java.lang.Exception" would be correct if "Exception" was meant to define a rule for all // * checked exceptions. With more unusual exception names such as "BaseBusinessException" there's no need to use a FQN. // * @param mappings exception patterns (can also be fully qualified class names) as keys, and error view names as values // */ // public void setExceptionMappings(Properties mappings) { // this.exceptionMappings = mappings; // } // // /** // * Set one or more exceptions to be excluded from the exception mappings. Excluded exceptions are checked first and if one of them equals the actual // * exception, the exception will remain unresolved. // * @param excludedExceptions one or more excluded exception types // */ // public void setExcludedExceptions(Class... excludedExceptions) { // this.excludedExceptions = excludedExceptions; // } // // /** // * Set the name of the default error view. This view will be returned if no specific mapping was found. // *

    // * Default is none. // */ // public void setDefaultErrorView(String defaultErrorView) { // this.defaultErrorView = defaultErrorView; // } // // /** // * Set the HTTP status code that this exception resolver will apply for a given resolved error view. Keys are view names; values are status codes. // *

    // * Note that this error code will only get applied in case of a top-level request. It will not be set for an include request, since the HTTP status cannot // * be modified from within an include. // *

    // * If not specified, the default status code will be applied. // * @see #setDefaultStatusCode(int) // */ // public void setStatusCodes(Properties statusCodes) { // for (Enumeration enumeration = statusCodes.propertyNames(); enumeration.hasMoreElements();) { // String viewName = (String) enumeration.nextElement(); // Integer statusCode = new Integer(statusCodes.getProperty(viewName)); // this.statusCodes.put(viewName, statusCode); // } // } // // /** // * An alternative to {@link #setStatusCodes(Properties)} for use with Java-based configuration. // */ // public void addStatusCode(String viewName, int statusCode) { // this.statusCodes.put(viewName, statusCode); // } // // /** // * Returns the HTTP status codes provided via {@link #setStatusCodes(Properties)}. Keys are view names; values are status codes. // */ // public Map getStatusCodesAsMap() { // return Collections.unmodifiableMap(statusCodes); // } // // /** // * Set the default HTTP status code that this exception resolver will apply if it resolves an error view and if there is no status code mapping defined. // *

    // * Note that this error code will only get applied in case of a top-level request. It will not be set for an include request, since the HTTP status cannot // * be modified from within an include. // *

    // * If not specified, no status code will be applied, either leaving this to the controller or view, or keeping the servlet engine's default of 200 (OK). // * @param defaultStatusCode HTTP status code value, for example 500 ({@link HttpServletResponse#SC_INTERNAL_SERVER_ERROR}) or 404 ( // * {@link HttpServletResponse#SC_NOT_FOUND}) // * @see #setStatusCodes(Properties) // */ // public void setDefaultStatusCode(int defaultStatusCode) { // this.defaultStatusCode = defaultStatusCode; // } // // /** // * Set the name of the model attribute as which the exception should be exposed. Default is "exception". // *

    // * This can be either set to a different attribute name or to {@code null} for not exposing an exception attribute at all. // */ // public void setExceptionAttribute(String exceptionAttribute) { // this.exceptionAttribute = exceptionAttribute; // } // // public void setStackTraceAttribute(String stackTraceAttribute) { // this.stackTraceAttribute = stackTraceAttribute; // } // // public void setResolverType(String resolverType) { // this.resolverType = resolverType; // } // // /** // * Actually resolve the given exception that got thrown during on handler execution, returning a ModelAndView that represents a specific error page if // * appropriate. // *

    // * May be overridden in subclasses, in order to apply specific exception checks. Note that this template method will be invoked after checking // * whether this resolved applies ("mappedHandlers" etc), so an implementation may simply proceed with its actual exception handling. // * @param request current HTTP request // * @param response current HTTP response // * @param handler the executed handler, or {@code null} if none chosen at the time of the exception (for example, if multipart resolution failed) // * @param ex the exception that got thrown during handler execution // * @return a corresponding ModelAndView to forward to, or {@code null} for default processing // */ // @Override // protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { // if (ex instanceof BasicException || ex instanceof IllegalArgumentException) { // logger.info("", ex); // } else { // logger.error("", ex); // } // // switch (resolverType) { // case "application/json": // case "text/html": // case "text/plain": // int code = (ex instanceof BasicException) // ? ((BasicException) ex).getCode() // : ResultCode.SERVER_ERROR.getCode(); // String msg = (ex instanceof BasicException) || logger.isInfoEnabled() // ? ex.getMessage() : ERROR_MSG; // msg = StringUtils.isBlank(msg) ? ERROR_MSG : msg; // response.setContentType(resolverType + ";charset=UTF-8"); // try (PrintWriter writer = response.getWriter()) { // writer.print(Jsons.toJson(Result.failure(code, msg))); // } catch (IOException e) { // logger.error("response write occur error", e); // } // return null; // default: // // Expose ModelAndView for chosen error view. // String viewName = determineViewName(ex, request); // ModelAndView mv = null; // if (viewName != null) { // // Apply HTTP status code for error views, if specified. // // Only apply it if we're processing a top-level request. // Integer statusCode = determineStatusCode(request, viewName); // if (statusCode != null) { // applyStatusCodeIfPossible(request, response, statusCode); // } // // mv = new ModelAndView(viewName); // if (stackTraceAttribute != null) { // mv.addObject(stackTraceAttribute, Throwables.getStackTrace(ex)); // } // if (exceptionAttribute != null) { // mv.addObject(exceptionAttribute, ex); // } // } // return mv; // } // } // // /** // * Determine the view name for the given exception, first checking against the {@link #setExcludedExceptions(Class[]) "excludedExecptions"}, then searching // * the {@link #setExceptionMappings "exceptionMappings"}, and finally using the {@link #setDefaultErrorView "defaultErrorView"} as a fallback. // * @param ex the exception that got thrown during handler execution // * @param request current HTTP request (useful for obtaining metadata) // * @return the resolved view name, or {@code null} if excluded or none found // */ // protected String determineViewName(Exception ex, HttpServletRequest request) { // String viewName = null; // if (this.excludedExceptions != null) { // for (Class excludedEx : this.excludedExceptions) { // if (excludedEx.equals(ex.getClass())) { // return null; // } // } // } // // Check for specific exception mappings. // if (this.exceptionMappings != null) { // viewName = findMatchingViewName(this.exceptionMappings, ex); // } // // Return default error view else, if defined. // if (viewName == null && this.defaultErrorView != null) { // logger.debug("Resolving to default view '" + this.defaultErrorView + "' for exception of type [" + ex.getClass().getName() + "]"); // viewName = this.defaultErrorView; // } // return viewName; // } // // /** // * Find a matching view name in the given exception mappings. // * @param exceptionMappings mappings between exception class names and error view names // * @param ex the exception that got thrown during handler execution // * @return the view name, or {@code null} if none found // * @see #setExceptionMappings // */ // protected String findMatchingViewName(Properties exceptionMappings, Exception ex) { // String viewName = null; // String dominantMapping = null; // int deepest = Integer.MAX_VALUE; // for (Enumeration names = exceptionMappings.propertyNames(); names.hasMoreElements();) { // String exceptionMapping = (String) names.nextElement(); // int depth = getDepth(exceptionMapping, ex); // if (depth >= 0 && (depth < deepest || (depth == deepest && dominantMapping != null && exceptionMapping.length() > dominantMapping.length()))) { // deepest = depth; // dominantMapping = exceptionMapping; // viewName = exceptionMappings.getProperty(exceptionMapping); // } // } // if (viewName != null) { // logger.debug("Resolving to view '" + viewName + "' for exception of type [" + ex.getClass().getName() + "], based on exception mapping [" // + dominantMapping + "]"); // } // return viewName; // } // // /** // * Return the depth to the superclass matching. // *

    // * 0 means ex matches exactly. Returns -1 if there's no match. Otherwise, returns depth. Lowest depth wins. // */ // protected int getDepth(String exceptionMapping, Exception ex) { // return getDepth(exceptionMapping, ex.getClass(), 0); // } // // private int getDepth(String exceptionMapping, Class exceptionClass, int depth) { // if (exceptionClass.getName().contains(exceptionMapping)) { // // Found it! // return depth; // } // // If we've gone as far as we can go and haven't found it... // if (exceptionClass.equals(Throwable.class)) { // return -1; // } // return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1); // } // // /** // * Determine the HTTP status code to apply for the given error view. // *

    // * The default implementation returns the status code for the given view name (specified through the {@link #setStatusCodes(Properties) statusCodes} // * property), or falls back to the {@link #setDefaultStatusCode defaultStatusCode} if there is no match. // *

    // * Override this in a custom subclass to customize this behavior. // * @param request current HTTP request // * @param viewName the name of the error view // * @return the HTTP status code to use, or {@code null} for the servlet container's default (200 in case of a standard error view) // * @see #setDefaultStatusCode // * @see #applyStatusCodeIfPossible // */ // protected Integer determineStatusCode(HttpServletRequest request, String viewName) { // if (this.statusCodes.containsKey(viewName)) { // return this.statusCodes.get(viewName); // } // return this.defaultStatusCode; // } // // /** // * Apply the specified HTTP status code to the given response, if possible (that is, if not executing within an include request). // * @param request current HTTP request // * @param response current HTTP response // * @param statusCode the status code to apply // * @see #determineStatusCode // * @see #setDefaultStatusCode // * @see HttpServletResponse#setStatus // */ // protected void applyStatusCodeIfPossible(HttpServletRequest request, HttpServletResponse response, int statusCode) { // if (!WebUtils.isIncludeRequest(request)) { // logger.debug("Applying HTTP status code " + statusCode); // response.setStatus(statusCode); // request.setAttribute(WebUtils.ERROR_STATUS_CODE_ATTRIBUTE, statusCode); // } // } //} ================================================ FILE: src/main/java/cn/ponfee/commons/web/LiteDevice.java ================================================ /* * Copyright 2010-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cn.ponfee.commons.web; /** * A lightweight Device implementation suitable for use as support code. * Typically used to hold the output of a device resolution invocation. * * @author Keith Donald * @author Roy Clarkson * @author Scott Rossillo * @author Onur Kagan Ozcan * * Modify from org.springframework.mobile.device.LiteDevice * @see org.springframework.mobile.device.LiteDevice */ public class LiteDevice { public static final LiteDevice NORMAL_INSTANCE = new LiteDevice(DeviceType.NORMAL, DevicePlatform.UNKNOWN); public static final LiteDevice MOBILE_INSTANCE = new LiteDevice(DeviceType.MOBILE, DevicePlatform.UNKNOWN); public static final LiteDevice TABLET_INSTANCE = new LiteDevice(DeviceType.TABLET, DevicePlatform.UNKNOWN); public boolean isNormal() { return this.deviceType == DeviceType.NORMAL; } public boolean isMobile() { return this.deviceType == DeviceType.MOBILE; } public boolean isTablet() { return this.deviceType == DeviceType.TABLET; } public DevicePlatform getDevicePlatform() { return this.devicePlatform; } public DeviceType getDeviceType() { return this.deviceType; } public static LiteDevice from(DeviceType deviceType, DevicePlatform devicePlatform) { return new LiteDevice(deviceType, devicePlatform); } @Override public String toString() { return new StringBuilder().append("[LiteDevice type=") .append(this.deviceType).append("]").toString(); } private final DeviceType deviceType; private final DevicePlatform devicePlatform; /** * Creates a LiteDevice with DevicePlatform. */ private LiteDevice(DeviceType deviceType, DevicePlatform devicePlatform) { this.deviceType = deviceType; this.devicePlatform = devicePlatform; } } ================================================ FILE: src/main/java/cn/ponfee/commons/web/LiteDeviceResolver.java ================================================ /* * Copyright 2010-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cn.ponfee.commons.web; import org.apache.commons.collections4.CollectionUtils; import javax.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import static com.google.common.collect.ImmutableList.of; /** * A "lightweight" device resolver algorithm based on Wordpress's Mobile pack. Detects the * presence of a mobile device and works for a large percentage of mobile browsers. Does * not perform any device capability mapping, if you need that consider WURFL. * * The code is based primarily on a list of approximately 90 well-known mobile browser UA * string snippets, with a couple of special cases for Opera Mini, the W3C default * delivery context and certain other Windows browsers. The code also looks to see if the * browser advertises WAP capabilities as a hint. * * Tablet resolution is also performed based on known tablet browser UA strings. Android * tablets are detected based on Google's recommendations. * * @author Keith Donald * @author Roy Clarkson * @author Scott Rossillo * @author Yuri Mednikov * @author Onur Kagan Ozcan * * Modify from org.springframework.mobile.device.LiteDeviceResolver * @see org.springframework.mobile.device.LiteDeviceResolver */ public class LiteDeviceResolver { private final List normalUserAgentKeywords = new ArrayList<>(); public LiteDeviceResolver() { this(null); } public LiteDeviceResolver(List normalUserAgentKeywords) { if (CollectionUtils.isNotEmpty(normalUserAgentKeywords)) { this.normalUserAgentKeywords.addAll(normalUserAgentKeywords); } } public LiteDevice resolveDevice(HttpServletRequest request) { String userAgent = request.getHeader("User-Agent"); // UserAgent keyword detection of Normal devices if (userAgent != null) { userAgent = userAgent.toLowerCase(); for (String keyword : normalUserAgentKeywords) { if (userAgent.contains(keyword)) { return resolveFallback(request); } } } // UserAgent keyword detection of Tablet devices if (userAgent != null) { userAgent = userAgent.toLowerCase(); // Android special case if (userAgent.contains("android") && !userAgent.contains("mobile")) { return resolveWithPlatform(DeviceType.TABLET, DevicePlatform.ANDROID); } // Apple special case if (userAgent.contains("ipad")) { return resolveWithPlatform(DeviceType.TABLET, DevicePlatform.IOS); } // Kindle Fire special case if (userAgent.contains("silk") && !userAgent.contains("mobile")) { return resolveWithPlatform(DeviceType.TABLET, DevicePlatform.UNKNOWN); } for (String keyword : KNOWN_TABLET_USER_AGENT_KEYWORDS) { if (userAgent.contains(keyword)) { return resolveWithPlatform(DeviceType.TABLET, DevicePlatform.UNKNOWN); } } } // UAProf detection if (request.getHeader("x-wap-profile") != null || request.getHeader("Profile") != null) { if (userAgent != null) { // Android special case if (userAgent.contains("android")) { return resolveWithPlatform(DeviceType.MOBILE, DevicePlatform.ANDROID); } // Apple special case if (userAgent.contains("iphone") || userAgent.contains("ipod") || userAgent.contains("ipad")) { return resolveWithPlatform(DeviceType.MOBILE, DevicePlatform.IOS); } } return resolveWithPlatform(DeviceType.MOBILE, DevicePlatform.UNKNOWN); } // User-Agent prefix detection if (userAgent != null && userAgent.length() >= 4) { String prefix = userAgent.substring(0, 4).toLowerCase(); if (KNOWN_MOBILE_USER_AGENT_PREFIXES.contains(prefix)) { return resolveWithPlatform(DeviceType.MOBILE, DevicePlatform.UNKNOWN); } } // Accept-header based detection String accept = request.getHeader("Accept"); if (accept != null && accept.contains("wap")) { return resolveWithPlatform(DeviceType.MOBILE, DevicePlatform.UNKNOWN); } // UserAgent keyword detection for Mobile devices if (userAgent != null) { // Android special case if (userAgent.contains("android")) { return resolveWithPlatform(DeviceType.MOBILE, DevicePlatform.ANDROID); } // Apple special case if (userAgent.contains("iphone") || userAgent.contains("ipod") || userAgent.contains("ipad")) { return resolveWithPlatform(DeviceType.MOBILE, DevicePlatform.IOS); } for (String keyword : KNOWN_MOBILE_USER_AGENT_KEYWORDS) { if (userAgent.contains(keyword)) { return resolveWithPlatform(DeviceType.MOBILE, DevicePlatform.UNKNOWN); } } } // OperaMini special case @SuppressWarnings("rawtypes") Enumeration headers = request.getHeaderNames(); while (headers.hasMoreElements()) { String header = (String) headers.nextElement(); if (header.contains("OperaMini")) { /*return LiteDevice.MOBILE_INSTANCE;*/ return resolveWithPlatform(DeviceType.MOBILE, DevicePlatform.UNKNOWN); } } return resolveFallback(request); } // subclassing hooks /** * Wrapper method for allow subclassing platform based resolution */ protected LiteDevice resolveWithPlatform(DeviceType deviceType, DevicePlatform devicePlatform) { return LiteDevice.from(deviceType, devicePlatform); } /** * Fallback called if no mobile device is matched by this resolver. The default * implementation of this method returns a "normal" {@link Device} that is neither * mobile or a tablet. Subclasses may override to try additional mobile or tablet * device matching before falling back to a "normal" device. */ protected LiteDevice resolveFallback(HttpServletRequest request) { return LiteDevice.NORMAL_INSTANCE; } // internal helpers private static final List KNOWN_MOBILE_USER_AGENT_PREFIXES = of( "w3c ", "w3c-", "acs-", "alav", "alca", "amoi", "audi", "avan", "benq", "bird", "blac", "blaz", "brew", "cell", "cldc", "cmd-", "dang", "doco", "eric", "hipt", "htc_", "inno", "ipaq", "ipod", "jigs", "kddi", "keji", "leno", "lg-c", "lg-d", "lg-g", "lge-", "lg/u", "maui", "maxo", "midp", "mits", "mmef", "mobi", "mot-", "moto", "mwbp", "nec-", "newt", "noki", "palm", "pana", "pant", "phil", "play", "port", "prox", "qwap", "sage", "sams", "sany", "sch-", "sec-", "send", "seri", "sgh-", "shar", "sie-", "siem", "smal", "smar", "sony", "sph-", "symb", "t-mo", "teli", "tim-", "tosh", "tsm-", "upg1", "upsi", "vk-v", "voda", "wap-", "wapa", "wapi", "wapp", "wapr", "webc", "winw", "winw", "xda ", "xda-" ); private static final List KNOWN_MOBILE_USER_AGENT_KEYWORDS = of( "blackberry", "webos", "ipod", "lge vx", "midp", "maemo", "mmp", "mobile", "netfront", "hiptop", "nintendo DS", "novarra", "openweb", "opera mobi", "opera mini", "palm", "psp", "phone", "smartphone", "symbian", "up.browser", "up.link", "wap", "windows ce" ); private static final List KNOWN_TABLET_USER_AGENT_KEYWORDS = of( "ipad", "playbook", "hp-tablet", "kindle" ); } ================================================ FILE: src/main/java/cn/ponfee/commons/web/WebContext.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.web; import cn.ponfee.commons.io.Files; import cn.ponfee.commons.util.RegexUtils; import cn.ponfee.commons.util.Strings; import org.apache.commons.lang3.ArrayUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.*; /** * 本地线程级的web上下文持有类 * * https://github.com/alibaba/transmittable-thread-local * * @author Ponfee */ public final class WebContext { /** HTTP请求与响应 */ private static final ThreadLocal REQUEST = new ThreadLocal<>(); private static final ThreadLocal RESPONSE = new ThreadLocal<>(); /** 用于非用户访问请求:程序内部反射调用controller方法 */ private static final ThreadLocal> INJECTED_PARAMS = ThreadLocal.withInitial(HashMap::new); // -----------------------getter public static HttpServletRequest getRequest() { //org.springframework.web.context.request.RequestContextListener //return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); return REQUEST.get(); } public static HttpServletResponse getResponse() { //return ((ServletWebRequest)RequestContextHolder.getRequestAttributes()).getResponse(); return RESPONSE.get(); } /** * 设置参数 * @param name * @param value */ public static void setParameter(String name, String value) { String[] values = INJECTED_PARAMS.get().get(name); if (values == null || values.length == 0) { values = new String[] { value }; } else { values = ArrayUtils.add(values, value); } INJECTED_PARAMS.get().put(name, values); } public static void clearParameter() { INJECTED_PARAMS.remove(); } /** * 获取参数 * @param name * @return */ public static String getParameter(String name) { HttpServletRequest request = getRequest(); if (null != request) { return request.getParameter(name); } else { String[] values = INJECTED_PARAMS.get().get(name); // INJECTED_PARAMS.get().remove(name) return (values == null || values.length == 0) ? null : values[0]; } } /** * 获取参数 * @param name * @return */ public static String[] getParameterValues(String name) { HttpServletRequest request = getRequest(); if (null != request) { return request.getParameterValues(name); } else { return INJECTED_PARAMS.get().get(name); } } public String getText() { return getText(Files.DEFAULT_CHARSET_NAME); } /** * Gets the text string from request input stream * * @param charset the string encoding * @return string */ public String getText(String charset) { return WebUtils.getText(getRequest(), charset); } /** * 获取客户端IP * @return */ public static String getClientIp() { return WebUtils.getClientIp(getRequest()); } /** * 获取客户端设备类型 * @return */ public static LiteDevice getClientDevice() { return WebUtils.getClientDevice(getRequest()); } // --------------------------setter/remover private static void setRequest(HttpServletRequest req) { REQUEST.set(req); } private static void setResponse(HttpServletResponse resp) { RESPONSE.set(resp); } private static void removeRequest() { REQUEST.remove(); } private static void removeResponse() { RESPONSE.remove(); } /** * Implementation of the * cross-origin resource sharing. *

    * A typical example is to use this filter to allow cross-domain * cometd communication using the standard * long polling transport instead of the JSONP transport (that is less * efficient and less reactive to failures). *

    * This filter allows the following configuration parameters: *

    *
    allowedOrigins
    *
    a comma separated list of origins that are * allowed to access the resources. Default value is *, meaning all * origins. Note that using wild cards can result in security problems * for requests identifying hosts that do not exist. *

    * If an allowed origin contains one or more * characters (for example * http://*.domain.com), then "*" characters are converted to ".*", "." * characters are escaped to "\." and the resulting allowed origin * interpreted as a regular expression. *

    * Allowed origins can therefore be more complex expressions such as * https?://*.domain.[a-z]{3} that matches http or https, multiple subdomains * and any 3 letter top-level domain (.com, .net, .org, etc.).

    * *
    allowedTimingOrigins
    *
    a comma separated list of origins that are * allowed to time the resource. Default value is the empty string, meaning * no origins. *

    * The check whether the timing header is set, will be performed only if * the user gets general access to the resource using the allowedOrigins. * *

    allowedMethods
    *
    a comma separated list of HTTP methods that * are allowed to be used when accessing the resources. Default value is * GET,POST,HEAD
    * * *
    allowedHeaders
    *
    a comma separated list of HTTP headers that * are allowed to be specified when accessing the resources. Default value * is X-Requested-With,Content-Type,Accept,Origin. If the value is a single "*", * this means that any headers will be accepted.
    * *
    preflightMaxAge
    *
    the number of seconds that preflight requests * can be cached by the client. Default value is 1800 seconds, or 30 * minutes
    * *
    allowCredentials
    *
    a boolean indicating if the resource allows * requests with credentials. Default value is true
    * *
    exposedHeaders
    *
    a comma separated list of HTTP headers that * are allowed to be exposed on the client. Default value is the * empty list
    * *
    chainPreflight
    *
    if true preflight requests are chained to their * target resource for normal handling (as an OPTION request). Otherwise the * filter will response to the preflight. Default is true.
    * *
    * A typical configuration could be: * * * * ------------------------------------------------------------------------------- * Plan A: *
         *  〈filter>
         *    〈filter-name>00000-web-context-filter
         *    〈filter-class>cn.ponfee.commons.web.WebContext$WebContextFilter
         *    〈init-param>
         *      allowedOrigins
         *      〈param-value>http://localhost:8080,http://127.0.0.1:8080  
         *    〈/init-param>
         *    〈init-param>
         *      〈param-name>allowedMethods
         *      〈param-value>GET,POST,HEAD
         *    〈/init-param>
         *    〈init-param>
         *      〈param-name>allowedHeaders
         *      〈param-value>X-Requested-With,Content-Type,Accept,Origin
         *    〈/init-param>
         *  〈/filter>
         *  〈filter-mapping>
         *    〈filter-name>00000-web-context-filter
         *    〈url-pattern>/*
         *  〈/filter-mapping>
         * 
    * * Reference from org.eclipse.jetty.servlets.CrossOriginFilter * @see org.eclipse.jetty.servlets.CrossOriginFilter * * * Plan B: Spring mvc xml config * * * * * * * 过滤器:根据filterName的属性值的首字母排序的顺序执行 */ /*@WebFilter( filterName = "0000.cn.ponfee.commons.web.WebContext$WebContextFilter", dispatcherTypes = { DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ASYNC, DispatcherType.ERROR }, urlPatterns = { "/*" }, initParams = { @WebInitParam(name = "allowedOrigins", value = "http://localhost:8080,http://127.0.0.1:8080"), @WebInitParam(name = "allowedMethods", value = "GET,POST,HEAD") }, asyncSupported = true // 支持异步Servlet )*/ public static class WebContextFilter implements Filter { private static final Logger LOG = LoggerFactory.getLogger(WebContextFilter.class); // -------------------------------------------------------------Request headers private static final String ORIGIN_HEADER = "Origin"; public static final String ACCESS_CONTROL_REQUEST_METHOD_HEADER = "Access-Control-Request-Method"; public static final String ACCESS_CONTROL_REQUEST_HEADERS_HEADER = "Access-Control-Request-Headers"; // -------------------------------------------------------------Response headers public static final String ACCESS_CONTROL_ALLOW_ORIGIN_HEADER = "Access-Control-Allow-Origin"; public static final String ACCESS_CONTROL_ALLOW_METHODS_HEADER = "Access-Control-Allow-Methods"; public static final String ACCESS_CONTROL_ALLOW_HEADERS_HEADER = "Access-Control-Allow-Headers"; public static final String ACCESS_CONTROL_MAX_AGE_HEADER = "Access-Control-Max-Age"; public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER = "Access-Control-Allow-Credentials"; public static final String ACCESS_CONTROL_EXPOSE_HEADERS_HEADER = "Access-Control-Expose-Headers"; public static final String TIMING_ALLOW_ORIGIN_HEADER = "Timing-Allow-Origin"; // -------------------------------------------------------------Implementation constants public static final String ALLOWED_ORIGINS_PARAM = "allowedOrigins"; public static final String ALLOWED_TIMING_ORIGINS_PARAM = "allowedTimingOrigins"; public static final String ALLOWED_METHODS_PARAM = "allowedMethods"; public static final String ALLOWED_HEADERS_PARAM = "allowedHeaders"; public static final String PREFLIGHT_MAX_AGE_PARAM = "preflightMaxAge"; public static final String ALLOW_CREDENTIALS_PARAM = "allowCredentials"; public static final String EXPOSED_HEADERS_PARAM = "exposedHeaders"; public static final String CHAIN_PREFLIGHT_PARAM = "chainPreflight"; private static final String ANY_ORIGIN = "*"; private static final String DEFAULT_ALLOWED_ORIGINS = "*"; private static final String DEFAULT_ALLOWED_TIMING_ORIGINS = ""; private static final List SIMPLE_HTTP_METHODS = Arrays.asList("GET", "POST", "HEAD"); private static final List DEFAULT_ALLOWED_METHODS = Arrays.asList("GET", "POST", "HEAD"); private static final List DEFAULT_ALLOWED_HEADERS = Arrays.asList("X-Requested-With", "Content-Type", "Accept", "Origin"); private boolean corsEnable; private boolean anyOriginAllowed; private boolean anyTimingOriginAllowed; private boolean anyHeadersAllowed; private final List allowedOrigins = new ArrayList<>(); private final List allowedTimingOrigins = new ArrayList<>(); private final List allowedMethods = new ArrayList<>(); private final List allowedHeaders = new ArrayList<>(); private final List exposedHeaders = new ArrayList<>(); private int preflightMaxAge; private boolean allowCredentials; private boolean chainPreflight; @Override public void init(FilterConfig config) { String allowedOriginsConfig = config.getInitParameter(ALLOWED_ORIGINS_PARAM); String allowedTimingOriginsConfig = config.getInitParameter(ALLOWED_TIMING_ORIGINS_PARAM); corsEnable = Boolean.parseBoolean(Strings.ifBlank(config.getInitParameter("cors"), "true")); // default enable cors anyOriginAllowed = generateAllowedOrigins(allowedOrigins, allowedOriginsConfig, DEFAULT_ALLOWED_ORIGINS); anyTimingOriginAllowed = generateAllowedOrigins(allowedTimingOrigins, allowedTimingOriginsConfig, DEFAULT_ALLOWED_TIMING_ORIGINS); String allowedMethodsConfig = config.getInitParameter(ALLOWED_METHODS_PARAM); if (allowedMethodsConfig == null) { allowedMethods.addAll(DEFAULT_ALLOWED_METHODS); } else { allowedMethods.addAll(Arrays.asList(Strings.csvSplit(allowedMethodsConfig))); } String allowedHeadersConfig = config.getInitParameter(ALLOWED_HEADERS_PARAM); if (allowedHeadersConfig == null) { allowedHeaders.addAll(DEFAULT_ALLOWED_HEADERS); } else if ("*".equals(allowedHeadersConfig)) { anyHeadersAllowed = true; } else { allowedHeaders.addAll(Arrays.asList(Strings.csvSplit(allowedHeadersConfig))); } String preflightMaxAgeConfig = config.getInitParameter(PREFLIGHT_MAX_AGE_PARAM); if (preflightMaxAgeConfig == null) { preflightMaxAgeConfig = "1800"; // Default is 30 minutes } try { preflightMaxAge = Integer.parseInt(preflightMaxAgeConfig); } catch (NumberFormatException x) { LOG.info( "Cross-origin filter, could not parse '{}' parameter as integer: {}", PREFLIGHT_MAX_AGE_PARAM, preflightMaxAgeConfig ); } String allowedCredentialsConfig = config.getInitParameter(ALLOW_CREDENTIALS_PARAM); if (allowedCredentialsConfig == null) { allowedCredentialsConfig = "true"; } allowCredentials = Boolean.parseBoolean(allowedCredentialsConfig); String exposedHeadersConfig = config.getInitParameter(EXPOSED_HEADERS_PARAM); if (exposedHeadersConfig == null) { exposedHeadersConfig = ""; } exposedHeaders.addAll(Arrays.asList(Strings.csvSplit(exposedHeadersConfig))); String chainPreflightConfig = config.getInitParameter(CHAIN_PREFLIGHT_PARAM); if (chainPreflightConfig == null) { chainPreflightConfig = "true"; } chainPreflight = Boolean.parseBoolean(chainPreflightConfig); if (LOG.isDebugEnabled()) { LOG.debug( "Cross-origin filter configuration: {}={}, {}={}, {}={}, {}={}, {}={}, {}={}, {}={}, {}={}", ALLOWED_ORIGINS_PARAM, allowedOriginsConfig, ALLOWED_TIMING_ORIGINS_PARAM, allowedTimingOriginsConfig, ALLOWED_METHODS_PARAM, allowedMethodsConfig, ALLOWED_HEADERS_PARAM, allowedHeadersConfig, PREFLIGHT_MAX_AGE_PARAM, preflightMaxAgeConfig, ALLOW_CREDENTIALS_PARAM, allowedCredentialsConfig, EXPOSED_HEADERS_PARAM, exposedHeadersConfig, CHAIN_PREFLIGHT_PARAM, chainPreflightConfig ); } } @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; try { //WebUtils.cors(request, response); if (this.cros(request, response, chain)) { WebContext.setRequest(request); WebContext.setResponse(response); chain.doFilter(request, response); } } finally { WebContext.removeRequest(); WebContext.removeResponse(); } } @Override public void destroy() { anyOriginAllowed = false; allowedOrigins.clear(); allowedMethods.clear(); allowedHeaders.clear(); preflightMaxAge = 0; allowCredentials = false; } protected boolean isEnabled(HttpServletRequest request) { // WebSocket clients such as Chrome 5 implement a version of the WebSocket // protocol that does not accept extra response headers on the upgrade response for (Enumeration elm = request.getHeaders("Connection"); elm.hasMoreElements();) { String connection = elm.nextElement(); if ("Upgrade".equalsIgnoreCase(connection)) { for (Enumeration upg = request.getHeaders("Upgrade"); upg.hasMoreElements();) { String upgrade = upg.nextElement(); if ("WebSocket".equalsIgnoreCase(upgrade)) { return false; } } } } return true; } // ----------------------------------------------------------------------------------private methods private boolean generateAllowedOrigins(List allowedOriginStore, String allowedOriginsConfig, String defaultOrigin) { if (allowedOriginsConfig == null) { allowedOriginsConfig = defaultOrigin; } String[] allowedOrigins = Strings.csvSplit(allowedOriginsConfig); for (String allowedOrigin : allowedOrigins) { if (allowedOrigin.length() > 0) { if (ANY_ORIGIN.equals(allowedOrigin)) { allowedOriginStore.clear(); return true; } else { allowedOriginStore.add(allowedOrigin); } } } return false; } private boolean cros(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException { String origin = request.getHeader(ORIGIN_HEADER); // Is it a cross origin request ? if (origin == null || !corsEnable || !isEnabled(request)) { return true; } if (!anyOriginAllowed && !originMatches(allowedOrigins, origin)) { LOG.debug( "Cross-origin request to {} with origin {} does not match allowed origins {}", request.getRequestURI(), origin, allowedOrigins ); response.sendError(HttpServletResponse.SC_FORBIDDEN, "Invalid CORS request."); return false; } if (isSimpleRequest(request)) { LOG.debug("Cross-origin request to {} is a simple cross-origin request", request.getRequestURI()); handleSimpleResponse(request, response, origin); } else if (isPreflightRequest(request)) { LOG.debug("Cross-origin request to {} is a preflight cross-origin request", request.getRequestURI()); handlePreflightResponse(request, response, origin); if (chainPreflight) { LOG.debug("Preflight cross-origin request to {} forwarded to application", request.getRequestURI()); } else { return false; } } else { LOG.debug("Cross-origin request to {} is a non-simple cross-origin request", request.getRequestURI()); handleSimpleResponse(request, response, origin); } if (anyTimingOriginAllowed || originMatches(allowedTimingOrigins, origin)) { response.setHeader(TIMING_ALLOW_ORIGIN_HEADER, origin); } else { LOG.debug( "Cross-origin request to {} with origin {} does not match allowed timing origins {}", request.getRequestURI(), origin, allowedTimingOrigins ); } return true; } private boolean originMatches(List allowedOrigins, String originList) { if (originList.trim().length() == 0) { return false; } String[] origins = originList.split(" "); for (String origin : origins) { if (origin.trim().length() == 0) { continue; } for (String allowedOrigin : allowedOrigins) { if (allowedOrigin.contains("*")) { // we want to be greedy here to match multiple subdomains, thus we use .* String regex = allowedOrigin.replace(".", "\\.").replace("*", ".*"); if (RegexUtils.matches(origin, regex)) { return true; } } else if (allowedOrigin.equals(origin)) { return true; } } } return false; } private boolean isSimpleRequest(HttpServletRequest request) { String method = request.getMethod(); if (SIMPLE_HTTP_METHODS.contains(method)) { // TODO: implement better detection of simple headers // The specification says that for a request to be simple, custom request headers must be simple. // Here for simplicity I just check if there is a Access-Control-Request-Method header, // which is required for preflight requests return request.getHeader(ACCESS_CONTROL_REQUEST_METHOD_HEADER) == null; } return false; } private boolean isPreflightRequest(HttpServletRequest request) { String method = request.getMethod(); if (!"OPTIONS".equalsIgnoreCase(method)) { return false; } return request.getHeader(ACCESS_CONTROL_REQUEST_METHOD_HEADER) != null; } private void handleSimpleResponse(HttpServletRequest request, HttpServletResponse response, String origin) { response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, origin); //W3C CORS spec http://www.w3.org/TR/cors/#resource-implementation if (!anyOriginAllowed) { response.addHeader("Vary", ORIGIN_HEADER); } if (allowCredentials) { response.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER, "true"); } if (!exposedHeaders.isEmpty()) { response.setHeader(ACCESS_CONTROL_EXPOSE_HEADERS_HEADER, commify(exposedHeaders)); } } private void handlePreflightResponse(HttpServletRequest request, HttpServletResponse response, String origin) { boolean methodAllowed = isMethodAllowed(request); if (!methodAllowed) { return; } List headersRequested = getAccessControlRequestHeaders(request); boolean headersAllowed = areHeadersAllowed(headersRequested); if (!headersAllowed) { return; } response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, origin); //W3C CORS spec http://www.w3.org/TR/cors/#resource-implementation if (!anyOriginAllowed) { response.addHeader("Vary", ORIGIN_HEADER); } if (allowCredentials) { response.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER, "true"); } if (preflightMaxAge > 0) { response.setHeader(ACCESS_CONTROL_MAX_AGE_HEADER, String.valueOf(preflightMaxAge)); } response.setHeader(ACCESS_CONTROL_ALLOW_METHODS_HEADER, commify(allowedMethods)); if (anyHeadersAllowed) { response.setHeader(ACCESS_CONTROL_ALLOW_HEADERS_HEADER, commify(headersRequested)); } else { response.setHeader(ACCESS_CONTROL_ALLOW_HEADERS_HEADER, commify(allowedHeaders)); } } private boolean isMethodAllowed(HttpServletRequest request) { String accessControlRequestMethod = request.getHeader(ACCESS_CONTROL_REQUEST_METHOD_HEADER); LOG.debug("{} is {}", ACCESS_CONTROL_REQUEST_METHOD_HEADER, accessControlRequestMethod); boolean result = false; if (accessControlRequestMethod != null) { result = allowedMethods.contains(accessControlRequestMethod); } LOG.debug( "Method {} is{} among allowed methods {}", accessControlRequestMethod, result ? "" : " not", allowedMethods ); return result; } private List getAccessControlRequestHeaders(HttpServletRequest request) { String accessControlRequestHeaders = request.getHeader(ACCESS_CONTROL_REQUEST_HEADERS_HEADER); LOG.debug("{} is {}", ACCESS_CONTROL_REQUEST_HEADERS_HEADER, accessControlRequestHeaders); if (accessControlRequestHeaders == null) { return Collections.emptyList(); } List requestedHeaders = new ArrayList<>(); String[] headers = Strings.csvSplit(accessControlRequestHeaders); for (String header : headers) { String h = header.trim(); if (h.length() > 0) { requestedHeaders.add(h); } } return requestedHeaders; } private boolean areHeadersAllowed(List requestedHeaders) { if (anyHeadersAllowed) { LOG.debug("Any header is allowed"); return true; } boolean result = true; for (String requestedHeader : requestedHeaders) { boolean headerAllowed = false; for (String allowedHeader : allowedHeaders) { if (requestedHeader.equalsIgnoreCase(allowedHeader.trim())) { headerAllowed = true; break; } } if (!headerAllowed) { result = false; break; } } LOG.debug( "Headers {} are{} among allowed headers {}", requestedHeaders, result ? "" : " not", allowedHeaders ); return result; } private String commify(List strings) { StringBuilder builder = new StringBuilder(); for (int i = 0; i < strings.size(); ++i) { if (i > 0) { builder.append(","); } builder.append(strings.get(i)); } return builder.toString(); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/web/WebUtils.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.web; import cn.ponfee.commons.http.ContentType; import cn.ponfee.commons.io.ByteOrderMarks; import cn.ponfee.commons.io.Files; import cn.ponfee.commons.io.GzipProcessor; import cn.ponfee.commons.json.Jsons; import cn.ponfee.commons.util.Networks; import cn.ponfee.commons.util.URLCodes; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import javax.servlet.ServletContext; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.nio.charset.Charset; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; import java.util.regex.Pattern; /** * web工具类 * * @author Ponfee */ public final class WebUtils { private final static Pattern PATTERN_SUFFIX = Pattern.compile("\\S*[?]\\S*"); public static final String INCLUDE_CONTEXT_PATH_ATTRIBUTE = "javax.servlet.include.context_path"; /*private static final Pattern PATTERN_MOBILE = Pattern.compile( "\\b(ip(hone|od)|android|opera m(ob|in)i|windows (phone|ce)|blackberry|s(ymbian|eries60|amsung)" + "|p(laybook|alm|rofile/midp|laystation portable)|nokia|fennec|htc[-_]|mobile|up.browser" + "|[1-4][0-9]{2}x[1-4][0-9]{2})\\b", Pattern.CASE_INSENSITIVE); private static final Pattern PATTERN_IPAD = Pattern.compile( "\\b(ipad|tablet|(Nexus 7)|up.browser|[1-4][0-9]{2}x[1-4][0-9]{2})\\b", Pattern.CASE_INSENSITIVE );*/ /** authorization */ public static final String AUTH_HEADER = "X-Auth-Token"; public static final String AUTH_COOKIE = "auth_token"; public static final String AUTH_PARAME = "authToken"; public static final String COOKIE_ROOT_PATH = "/"; /** * Gets the http servlet request parameters * the parameter value is {@link java.lang.String} type * if is array parameter, then the value is based-join on "," as String * * @param request * @return Map, the array param value use "," to join */ public static Map getParams(HttpServletRequest request) { Map params = new TreeMap<>(); for (Entry entry : request.getParameterMap().entrySet()) { params.put(entry.getKey(), StringUtils.join(entry.getValue(), ",")); } return params; } public static String getText(HttpServletRequest request) { return getText(request, Files.UTF_8); } /** * get the text string from request input stream * @param request the HttpServletRequest * @param charset the string encoding * @return string */ public static String getText(HttpServletRequest request, String charset) { try (InputStream input = request.getInputStream()) { return IOUtils.toString(input, charset); } catch (Exception e) { throw new RuntimeException("read request input stream error", e); } } private static final List LOCAL_IPS = Arrays.asList( "127.0.0.1", "0:0:0:0:0:0:0:1", "::1" ); /** * 获取客户端ip * @param req * @return */ public static String getClientIp(HttpServletRequest req) { boolean invalid; String ip = req.getHeader("x-forwarded-for"); if (invalid = isInvalidIp(ip)) { ip = req.getHeader("Proxy-Client-IP"); } if (invalid && (invalid = isInvalidIp(ip))) { ip = req.getHeader("WL-Proxy-Client-IP"); } if (invalid && (invalid = isInvalidIp(ip))) { ip = req.getHeader("HTTP_CLIENT_IP"); } if (invalid && (invalid = isInvalidIp(ip))) { ip = req.getHeader("HTTP_X_FORWARDED_FOR"); } if (invalid && (invalid = isInvalidIp(ip))) { ip = req.getHeader("X-Real-IP"); } if (invalid && (invalid = isInvalidIp(ip))) { ip = req.getRemoteAddr(); } if (ip != null && ip.indexOf(",") > 0) { // 对于通过多个代理的情况,第一个ip为客户端真实ip,多个ip按照','分割 ip = ip.substring(0, ip.indexOf(",")); } if (LOCAL_IPS.contains(ip)) { ip = Networks.HOST_IP; // 如果是本机ip } return ip; } /** * 获取客户端设备类型 * @return */ public static LiteDevice getClientDevice(HttpServletRequest req) { return new LiteDeviceResolver().resolveDevice(req); /*String userAgent = Objects.toString(userAgent(req), ""); if (PATTERN_MOBILE.matcher(userAgent).find()) { return DeviceType.MOBILE; } else if (PATTERN_IPAD.matcher(userAgent).find()) { return DeviceType.TABLET; } else { return DeviceType.NORMAL; }*/ } /** * 判断是否ajax请求 * @param req * @return */ public static boolean isAjax(HttpServletRequest req) { return "XMLHttpRequest".equals(req.getHeader("X-Requested-With")); } /** * Returns the web browser user-agent * * @param req the HttpServletRequest * @return web browser user-agent */ public static String userAgent(HttpServletRequest req) { return req.getHeader("User-Agent"); } /** * 响应数据到请求客户端 * @param resp * @param contentType * @param text * @param charset */ public static void response(HttpServletResponse resp, ContentType contentType, String text, String charset) { resp.setContentType(contentType.value() + ";charset=" + charset); resp.setCharacterEncoding(charset); try (PrintWriter writer = resp.getWriter()) { writer.write(text); } catch (IOException e) { // cannot happened throw new RuntimeException("response " + contentType + " occur error", e); } } /** * 响应json数据 * @param resp * @param data */ public static void respJson(HttpServletResponse resp, Object data) { respJson(resp, data, Files.UTF_8); } public static void respJson(HttpServletResponse resp, Object data, String charset) { response(resp, ContentType.APPLICATION_JSON, toJson(data), charset); } public static void respJsonp(HttpServletResponse response, String callback, Object data) { respJsonp(response, callback, data, Files.UTF_8); } /** * 响应jsonp数据 * @param resp * @param callback * @param data * @param charset */ public static void respJsonp(HttpServletResponse resp, String callback, Object data, String charset) { respJson(resp, callback + "(" + toJson(data) + ");", charset); } public static void download(HttpServletResponse resp, byte[] data, String filename) { download(resp, data, filename, Files.UTF_8, false, false); } /** * Response as a stream attachment * * @param resp the HttpServletResponse * @param data the resp byte array data * @param filename the resp attachment filename * @param charset the attachment filename encoding * @param isGzip {@code true} to use gzip compress * @param withBom {@code true} with bom header */ public static void download(HttpServletResponse resp, byte[] data, String filename, String charset, boolean isGzip, boolean withBom) { download(resp, new ByteArrayInputStream(data), filename, charset, isGzip, withBom); } /** * response to input stream * @param resp the HttpServletResponse * @param input the input stream * @param filename the resp attachment filename */ public static void download(HttpServletResponse resp, InputStream input, String filename) { download(resp, input, filename, Files.UTF_8, false, false); } /** * Response as a stream attachment * * @param resp the HttpServletResponse * @param input the input stream * @param filename the resp attachment filename * @param charset the attachment filename encoding * @param isGzip {@code true} to use gzip compress * @param withBom {@code true} with bom header */ public static void download(HttpServletResponse resp, InputStream input, String filename, String charset, boolean isGzip, boolean withBom) { try (InputStream in = input; OutputStream out = resp.getOutputStream() ) { addStreamHeader(resp, filename, charset); byte[] bom = withBom ? ByteOrderMarks.get(Charset.forName(charset)) : null; if (isGzip) { resp.setHeader("Content-Encoding", "gzip"); if (bom != null) { GzipProcessor.compress( new SequenceInputStream(new ByteArrayInputStream(bom), in), out ); } else { GzipProcessor.compress(in, out); } } else { if (bom != null) { out.write(bom); } IOUtils.copyLarge(in, out); } } catch (IOException e) { // cannot happened throw new RuntimeException("response input stream occur error", e); } } /** * 响应数据流,如图片数据 * * response(resp, image_byte_array, ContentType.IMAGE_JPEG.value(), false); * * @param resp the HttpServletResponse * @param data the byte array data * @param contentType the content-type * @param isGzip whether to encode gzip */ public static void response(HttpServletResponse resp, byte[] data, ContentType contentType, boolean isGzip) { response(resp, new ByteArrayInputStream(data), contentType, isGzip); } /** * 响应数据流,如图片数据 * * response(resp, image_file_input_stream, ContentType.IMAGE_JPEG.value(), false); * * @param resp the HttpServletResponse * @param input the input stream * @param contentType the content-type * @param isGzip whether to encode gzip */ public static void response(HttpServletResponse resp, InputStream input, ContentType contentType, boolean isGzip) { try (OutputStream output = resp.getOutputStream()) { resp.setContentType(contentType.value()); if (isGzip) { resp.setHeader("Content-Encoding", "gzip"); GzipProcessor.compress(input, output); } else { IOUtils.copy(input, output); } } catch (IOException e) { // cannot happened throw new RuntimeException("response byte array data occur error", e); } } /** * ross-Origin Resource Sharing * * @param req the HttpServletRequest * @param resp the HttpServletResponse */ public static void cors(HttpServletRequest req, HttpServletResponse resp) { String origin = req.getHeader("Origin"); origin = StringUtils.isEmpty(origin) ? "*" : origin; resp.setHeader("Access-Control-Allow-Origin", origin); String headers = req.getHeader("Access-Control-Allow-Headers"); headers = StringUtils.isEmpty(headers) ? "Origin,No-Cache,X-Requested-With,If-Modified-Since,Pragma," + "Expires,Last-Modified,Cache-Control,Content-Type,X-E4M-With" : headers; resp.setHeader("Access-Control-Allow-Headers", headers); resp.setHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,HEAD,OPTIONS"); resp.setHeader("Access-Control-Max-Age", "0"); resp.setHeader("Access-Control-Allow-Credentials", "true"); resp.setHeader("XDomainRequestAllowed", "1"); } /** * 获取请求地址后缀名 * @param req * @return */ public static String getUrlSuffix(HttpServletRequest req) { String url = req.getRequestURI(); if (!url.contains(".")) { return null; } String[] pathInfos = url.split("/"); String endUrl = pathInfos[pathInfos.length - 1]; if (PATTERN_SUFFIX.matcher(url).find()) { String[] spEndUrl = endUrl.split("\\?"); return spEndUrl[0].split("\\.")[1]; } else { return endUrl.split("\\.")[1]; } } public static String xssReplace(String text) { return StringUtils.replaceEach( text, new String[] { "<", ">", "%3c", "%3e" }, new String[] { "<", ">", "<", ">" } ); } /** * get cookie value * @param req * @param name * @return */ public static String getCookie(HttpServletRequest req, String name) { Cookie[] cookies = req.getCookies(); if (cookies == null) { return null; } for (Cookie cookie : cookies) { if (name.equals(cookie.getName())) { return cookie.getValue(); } } return null; } public static void delCookie(HttpServletRequest req, HttpServletResponse resp, String name) { delCookie(req, resp, COOKIE_ROOT_PATH, name); } /** * Delete the cookie by spec name * * @param req the HttpServletRequest * @param resp the HttpServletResponse * @param path the cookie path * @param name the cookie name */ public static void delCookie(HttpServletRequest req, HttpServletResponse resp, String path, String name) { Cookie[] cookies = req.getCookies(); if (cookies == null) { return; } for (Cookie cookie : cookies) { if (name.equals(cookie.getName())) { cookie.setPath(path); cookie.setMaxAge(0); cookie.setValue(""); resp.addCookie(cookie); return; } } } /** * 设置cookie * @param response * @param name * @param value */ public static void addCookie(HttpServletResponse response, String name, String value) { addCookie(response, name, value, COOKIE_ROOT_PATH, 24 * 60 * 60); } /** * 设置cookie * @param resp * @param name * @param value * @param path * @param maxAge */ public static void addCookie(HttpServletResponse resp, String name, String value, String path, int maxAge) { resp.addCookie(createCookie(name, value, path, maxAge)); } /** * Creates a cookie * * @param name * @param value * @param path * @param maxAge * @return an object of cookie */ public static Cookie createCookie(String name, String value, String path, int maxAge) { Cookie cookie = new Cookie(name, value); cookie.setPath(path); cookie.setMaxAge(maxAge); //cookie.setHttpOnly(true); return cookie; } /** * 会话跟踪 */ public static void setSessionTrace(HttpServletResponse response, String token) { int maxAge = (token == null) ? 0 : 86400; //result.setAuthToken(token); // to response body WebUtils.addCookie(response, AUTH_COOKIE, token, COOKIE_ROOT_PATH, maxAge); // to cookie response.addHeader(AUTH_HEADER, token); // to header } /** * 会话跟踪 */ public static String getSessionTrace(HttpServletRequest request) { String authToken = request.getParameter(AUTH_PARAME); // from param if (authToken != null) { return authToken; } authToken = WebUtils.getCookie(request, AUTH_COOKIE); // from cooike if (authToken != null) { return authToken; } return request.getHeader(AUTH_HEADER); // from header; } public static String getContextPath(ServletContext context) { String contextPath = normalize(URLCodes.decodeURI(context.getContextPath())); if ("/".equals(contextPath)) { contextPath = ""; } return contextPath; } /** * Return the context path for the given request, detecting an include request * URL if called within a RequestDispatcher include. *

    As the value returned by request.getContextPath() is not * decoded by the servlet container, this method will decode it. * * @param request current HTTP request * @return the context path */ public static String getContextPath(HttpServletRequest request) { //String contextPath = request.getContextPath(); //return StringUtils.isEmpty(contextPath) ? "/" : contextPath; String contextPath = (String) request.getAttribute(INCLUDE_CONTEXT_PATH_ATTRIBUTE); if (contextPath == null) { contextPath = request.getContextPath(); } String encoding = request.getCharacterEncoding(); contextPath = (encoding == null) ? URLCodes.decodeURI(contextPath) : URLCodes.decodeURI(contextPath, encoding); contextPath = normalize(contextPath); if ("/".equals(contextPath)) { contextPath = ""; // the normalize method will return a "/" and includes on Jetty, will also be a "/". } return contextPath; } /** * Normalize a relative URI path that may have relative values ("/./", * "/../", and so on ) it it. WARNING - This method is * useful only for normalizing application-generated paths. It does not * try to perform security checks for malicious input. * Normalize operations were was happily taken from org.apache.catalina.util.RequestUtil in * Tomcat trunk, r939305 * * @param path Relative path to be normalized * @return normalized path */ public static String normalize(String path) { return normalize(path, true); } /** * Normalize a relative URI path that may have relative values ("/./", * "/../", and so on ) it it. WARNING - This method is * useful only for normalizing application-generated paths. It does not * try to perform security checks for malicious input. * Normalize operations were was happily taken from org.apache.catalina.util.RequestUtil in * Tomcat trunk, r939305 * * @param path Relative path to be normalized * @param replaceBackSlash Should '\\' be replaced with '/' * @return normalized path */ public static String normalize(String path, boolean replaceBackSlash) { if (path == null) { return null; } // Create a place for the normalized path String normalized = path; if (replaceBackSlash && normalized.indexOf('\\') >= 0) { normalized = normalized.replace('\\', '/'); } if ("/.".equals(normalized)) { return "/"; } // Add a leading "/" if necessary if (!normalized.startsWith("/")) { normalized = "/" + normalized; } // Resolve occurrences of "//" in the normalized path while (true) { int index = normalized.indexOf("//"); if (index < 0) { break; } normalized = normalized.substring(0, index) + normalized.substring(index + 1); } // Resolve occurrences of "/./" in the normalized path while (true) { int index = normalized.indexOf("/./"); if (index < 0) { break; } normalized = normalized.substring(0, index) + normalized.substring(index + 2); } // Resolve occurrences of "/../" in the normalized path while (true) { int index = normalized.indexOf("/../"); if (index < 0) { break; } if (index == 0) { // Trying to go outside our context return (null); } int index2 = normalized.lastIndexOf('/', index - 1); normalized = normalized.substring(0, index2) + normalized.substring(index + 3); } // Return the normalized path that we have completed return (normalized); } // --------------------------------------------------------------------private methods /** * to json string * @param data * @return */ private static String toJson(Object data) { return (data instanceof CharSequence) ? data.toString() : Jsons.toJson(data); } private static void addStreamHeader(HttpServletResponse resp, String filename, String charset) { filename = URLCodes.encodeURIComponent(filename, charset); // others web browse //filename = new String(filename.getBytes(charset), ISO_8859_1); // firefox web browse resp.setContentType(ContentType.APPLICATION_OCTET_STREAM.value()); //resp.setHeader("Content-Length", Long.toString(length)); resp.setHeader("Content-Disposition", "attachment; filename=\"" + filename + "\""); //resp.setHeader("Content-Disposition", "form-data; name=\"attachment\"; filename=\"" + filename + "\""); //resp.setHeader("Set-Cookie", "fileDownload=true; path=/"); // JQuery $.fileDownload plugin resp.setCharacterEncoding(charset); } private static boolean isInvalidIp(String ip) { return StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip); } } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/JAXWS.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.ws; import javax.xml.namespace.QName; import javax.xml.ws.Endpoint; import javax.xml.ws.Service; import java.net.MalformedURLException; import java.net.URL; /** * jax-ws工具类 * * 错误:java.lang.NoSuchMethodError: javax.wsdl.xml.WSDLReader.readWSDL * (Ljavax/wsdl/xml/WSDLLocator;Lorg/w3c/dom/Element;)Ljavax/wsdl/Definition * * 原因:两个依赖的jar包有冲突(axis-wsdl4j-1.5.1.jar与wsdl4j-1.6.3.jar冲突) * [axis:axis:1.4]依赖[axis:axis-wsdl4j:1.5.1] axis-wsdl4j-1.5.1.jar * [org.apache.cxf:cxf-api:2.7.15]依赖[wsdl4j:wsdl4j:1.6.3] wsdl4j-1.6.3.jar * * 解决:排除依赖axis:axis-wsdl4j * * axis * axis * 1.4 * * * axis * axis-wsdl4j * * * * * @author Ponfee */ public class JAXWS { /** * Returns a JAX-WS client * * @param clazz the webservice interface, as use {@code WebService} annotation * @param address the wsdl url like as http://ip:port/ws/webserviceName?wsdl * @param namespaceURI the targetNamespace of element <wsdl:definitions> attribute * @param localPart the name of element <wsdl:definitions> attribute * @return client object can calls rpc */ public static T client(Class clazz, String address, String namespaceURI, String localPart) { return client(clazz, address, new QName(namespaceURI, localPart)); } public static T client(Class clazz, String address, QName qname) { try { // clazz为接口类 return Service.create(new URL(address), qname).getPort(clazz); } catch (MalformedURLException e) { // cannot happened throw new IllegalArgumentException("Invalid url: " + address, e); } } /** * Server publish the webservice * * @param address * @param implementor the webservice interface implements class instance */ public static void publish(String address, Object implementor) { Endpoint.publish(address, implementor); } } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/ListMapAdapter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.ws.adapter; import cn.ponfee.commons.reflect.GenericUtils; import cn.ponfee.commons.ws.adapter.model.MapEntry; import cn.ponfee.commons.ws.adapter.model.MapItem; import cn.ponfee.commons.ws.adapter.model.MapItemArray; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import javax.xml.bind.annotation.adapters.XmlAdapter; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; /** * List转换器 * @param * @param * * @author Ponfee */ //@XmlSeeAlso({ Object[][].class }) 在@WebService注解的接口中加上此注解 @SuppressWarnings("unchecked") public abstract class ListMapAdapter extends XmlAdapter>> { protected final Class ktype; protected final Class vtype; protected ListMapAdapter() { ktype = GenericUtils.getActualTypeArgument(this.getClass(), 0); vtype = GenericUtils.getActualTypeArgument(this.getClass(), 1); } @Override public List> unmarshal(MapItemArray v) { if (v == null) { return null; } else if (v.getItems() == null) { return Lists.newArrayList(); } List> list = new ArrayList<>(); for (MapItem items : v.getItems()) { if (items == null) { continue; } Map map = Maps.newLinkedHashMap(); for (MapEntry item : items.getItem()) { map.put(item.getKey(), item.getValue()); } list.add(map); } return list; } @Override public MapItemArray marshal(List> v) { if (v == null) { return null; } MapItem[] items = new MapItem[v.size()]; int i = 0; for (Map map : v) { if (map == null) { continue; } MapEntry[] item = new MapEntry[map.size()]; int j = 0; for (Entry entry : map.entrySet()) { item[j++] = new MapEntry<>(entry); } items[i++] = new MapItem(item); } return new MapItemArray(items); } } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/ListMapNormalAdapter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.ws.adapter; /** * List转换器 * * @author Ponfee */ public class ListMapNormalAdapter extends ListMapAdapter { } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/MapAdapter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.ws.adapter; import cn.ponfee.commons.reflect.GenericUtils; import cn.ponfee.commons.ws.adapter.model.MapEntry; import com.google.common.collect.Maps; import javax.xml.bind.annotation.adapters.XmlAdapter; import java.util.Map; /** * Map转换器 * @param * @param * * @author Ponfee */ @SuppressWarnings({ "unchecked", "rawtypes" }) public abstract class MapAdapter extends XmlAdapter> { protected final Class ktype; protected final Class vtype; protected MapAdapter() { ktype = GenericUtils.getActualTypeArgument(this.getClass(), 0); vtype = GenericUtils.getActualTypeArgument(this.getClass(), 1); } @Override public MapEntry[] marshal(Map map) { if (map == null) { return null; } MapEntry[] entries = new MapEntry[map.size()]; int i = 0; for (Map.Entry entry : map.entrySet()) { entries[i++] = new MapEntry<>(entry); } return entries; } @Override public Map unmarshal(MapEntry[] entries) { if (entries == null) { return null; } Map map = Maps.newLinkedHashMap(); for (MapEntry e : entries) { map.put(e.getKey(), e.getValue()); } return map; } } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/MapNormalAdapter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.ws.adapter; /** * Map转换器 * * @author Ponfee */ public class MapNormalAdapter extends MapAdapter { } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/MarshalJsonAdapter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.ws.adapter; import cn.ponfee.commons.json.Jsons; import cn.ponfee.commons.reflect.ClassUtils; import javax.xml.bind.annotation.adapters.XmlAdapter; /** * MarshalJsonResult -> MarshalJsonXml * * `@XmlJavaTypeAdapter(MarshalJsonAdapter.class) * * @author Ponfee * * @param */ public class MarshalJsonAdapter extends XmlAdapter { @Override public MarshalJsonXml marshal(Object v) { if (v == null) { return null; } return new MarshalJsonXml(ClassUtils.getClassName(v.getClass()), Jsons.toJson(v)); } @Override @SuppressWarnings("unchecked") public Object unmarshal(MarshalJsonXml v) throws Exception { if (v == null) { return null; } Class type = Class.forName(v.getType()); if (!MarshalJsonResult.class.isAssignableFrom(type)) { return Jsons.fromJson(v.getData(), type); } else { // must has default no args construct return ((Class) type).newInstance().fromJson(v.getData()); } } } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/MarshalJsonResult.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.ws.adapter; import cn.ponfee.commons.json.Jsons; /** * Market a bean type defined * * @author Ponfee */ public interface MarshalJsonResult/**/ { default /*E*/ MarshalJsonResult fromJson(String json) { return Jsons.fromJson(json, this.getClass()); } } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/MarshalJsonXml.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.ws.adapter; import java.io.Serializable; /** * Wrapped for a bean marshal to xml * * @author Ponfee */ public class MarshalJsonXml implements Serializable { private static final long serialVersionUID = 4570006014299314019L; private String type; private String data; public MarshalJsonXml() {} public MarshalJsonXml(String type, String data) { super(); this.type = type; this.data = data; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getData() { return data; } public void setData(String data) { this.data = data; } } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/ResultDataJsonAdapter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.ws.adapter; import cn.ponfee.commons.json.Jsons; import cn.ponfee.commons.model.Result; import cn.ponfee.commons.reflect.GenericUtils; import org.apache.commons.lang3.StringUtils; import javax.xml.bind.annotation.adapters.XmlAdapter; /** * Result -> Result * @param * * @author Ponfee */ public abstract class ResultDataJsonAdapter extends XmlAdapter, Result> { protected final Class type; protected ResultDataJsonAdapter() { type = GenericUtils.getActualTypeArgument(this.getClass()); } @Override public Result unmarshal(Result v) { if (StringUtils.isEmpty(v.getData())) { return v.from(null); } return v.from(Jsons.fromJson(v.getData(), type)); } @Override public Result marshal(Result v) { if (v.getData() == null) { return v.from(null); } return v.from(Jsons.toJson(v.getData())); } } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/ResultDataJsonPageAdapter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.ws.adapter; import cn.ponfee.commons.model.Page; /** * Result> -> Result * * @author Ponfee */ public class ResultDataJsonPageAdapter extends ResultDataJsonAdapter> { } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/ResultListAdapter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.ws.adapter; import cn.ponfee.commons.model.Result; import cn.ponfee.commons.reflect.GenericUtils; import cn.ponfee.commons.ws.adapter.model.ArrayItem; import com.google.common.collect.Lists; import javax.xml.bind.annotation.adapters.XmlAdapter; import java.lang.reflect.Array; import java.util.List; /** * Result>转换器 * @param * * @author Ponfee */ //ParameterizedTypeImpl cannot be cast to TypeVariable //abstract class ResultListAdapter extends XmlAdapter[]>, Result>> { public abstract class ResultListAdapter extends XmlAdapter>, Result>> { protected final Class type; protected ResultListAdapter() { type = GenericUtils.getActualTypeArgument(this.getClass()); } @Override public Result> unmarshal(Result> v) { if (v.getData() == null) { return v.from(null); } else if (v.getData().getItem() == null) { return v.from(Lists.newArrayList()); } List list = Lists.newArrayList(v.getData().getItem()); return v.from(list); } @Override @SuppressWarnings("unchecked") public Result> marshal(Result> v) { if (v.getData() == null) { return v.from(null); } T[] array = v.getData().toArray((T[]) Array.newInstance(type, v.getData().size())); return v.from(new ArrayItem<>(array)); } } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/ResultListMapAdapter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.ws.adapter; import cn.ponfee.commons.model.Result; import cn.ponfee.commons.reflect.GenericUtils; import cn.ponfee.commons.ws.adapter.model.MapEntry; import cn.ponfee.commons.ws.adapter.model.MapItem; import cn.ponfee.commons.ws.adapter.model.MapItemArray; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import javax.xml.bind.annotation.adapters.XmlAdapter; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; /** * Result>转换器 * @param * @param * * @author Ponfee */ @SuppressWarnings("unchecked") public abstract class ResultListMapAdapter extends XmlAdapter, Result>>> { protected final Class ktype; protected final Class vtype; protected ResultListMapAdapter() { ktype = GenericUtils.getActualTypeArgument(this.getClass(), 0); vtype = GenericUtils.getActualTypeArgument(this.getClass(), 1); } @Override public Result>> unmarshal(Result v) { if (v.getData() == null) { return v.from(null); } else if (v.getData().getItems() == null) { return v.from(Lists.newArrayList()); } List> list = new ArrayList<>(); for (MapItem items : v.getData().getItems()) { if (items == null) { continue; } Map map = Maps.newLinkedHashMap(); for (MapEntry item : items.getItem()) { if (item == null) { continue; } map.put(item.getKey(), item.getValue()); } list.add(map); } return v.from(list); } @Override public Result marshal(Result>> v) { if (v.getData() == null) { return v.from(null); } MapItem[] items = new MapItem[v.getData().size()]; int i = 0; for (Map map : v.getData()) { if (map == null) { continue; } MapEntry[] item = new MapEntry[map.size()]; int j = 0; for (Entry entry : map.entrySet()) { item[j++] = new MapEntry<>(entry); } items[i++] = new MapItem(item); } return v.from(new MapItemArray(items)); } } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/ResultListMapNormalAdapter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.ws.adapter; /** * Result>转换器 * * @author Ponfee */ public class ResultListMapNormalAdapter extends ResultListMapAdapter { } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/ResultListObjectAdapter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.ws.adapter; /** * Result转换器 * * @author Ponfee */ public class ResultListObjectAdapter extends ResultListAdapter { } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/ResultListObjectArrayAdapter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.ws.adapter; /** * Result转换器 * * @author Ponfee */ public class ResultListObjectArrayAdapter extends ResultListAdapter { } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/ResultListStringAdapter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.ws.adapter; /** * Result转换器 * * @author Ponfee */ public class ResultListStringAdapter extends ResultListAdapter { } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/ResultMapAdapter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.ws.adapter; import cn.ponfee.commons.model.Result; import cn.ponfee.commons.reflect.GenericUtils; import cn.ponfee.commons.ws.adapter.model.MapEntry; import cn.ponfee.commons.ws.adapter.model.MapItem; import com.google.common.collect.Maps; import javax.xml.bind.annotation.adapters.XmlAdapter; import java.util.Map; /** * Result>转换器 * @param * @param * * @author Ponfee */ @SuppressWarnings("unchecked") public abstract class ResultMapAdapter extends XmlAdapter, Result>> { protected final Class ktype; protected final Class vtype; protected ResultMapAdapter() { ktype = GenericUtils.getActualTypeArgument(this.getClass(), 0); vtype = GenericUtils.getActualTypeArgument(this.getClass(), 1); } @Override public Result> unmarshal(Result v) { if (v.getData() == null || v.getData().getItem() == null) { return v.from(null); } Map map = Maps.newLinkedHashMap(); for (MapEntry e : v.getData().getItem()) { map.put(e.getKey(), e.getValue()); } return v.from(map); } @Override public Result marshal(Result> v) { if (v.getData() == null) { return v.from(null); } MapEntry[] entries = new MapEntry[v.getData().size()]; int i = 0; for (Map.Entry entry : v.getData().entrySet()) { entries[i++] = new MapEntry<>(entry); } return v.from(new MapItem(entries)); } } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/ResultMapNormalAdapter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.ws.adapter; /** * Result>转换器 * * @author Ponfee */ public class ResultMapNormalAdapter extends ResultMapAdapter { } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/ResultPageAdapter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.ws.adapter; import cn.ponfee.commons.model.Page; import cn.ponfee.commons.model.Result; import cn.ponfee.commons.reflect.GenericUtils; import cn.ponfee.commons.ws.adapter.model.TransitPage; import javax.xml.bind.annotation.adapters.XmlAdapter; /** * Result>转换器 * @param * * @see org.springframework.data.domain.jaxb.PageAdapter * * @author Ponfee */ public abstract class ResultPageAdapter extends XmlAdapter>, Result>> { protected final Class type; protected ResultPageAdapter() { type = GenericUtils.getActualTypeArgument(this.getClass()); } @Override public Result> unmarshal(Result> v) { if (v.getData() == null) { return v.from(null); } else if ( v.getData().getRows() == null || v.getData().getRows().getItem() == null) { return v.from(new Page<>()); } return v.from(TransitPage.recover(v.getData())); } @Override public Result> marshal(Result> v) { if (v.getData() == null) { return v.from(null); } else if (v.getData().getRows() == null) { return v.from(new TransitPage<>()); } return v.from(TransitPage.transform(v.getData(), type)); } } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/ResultPageMapAdapter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.ws.adapter; import cn.ponfee.commons.model.Page; import cn.ponfee.commons.model.Result; import cn.ponfee.commons.reflect.GenericUtils; import cn.ponfee.commons.ws.adapter.model.MapEntry; import cn.ponfee.commons.ws.adapter.model.MapItem; import org.apache.commons.collections4.CollectionUtils; import javax.xml.bind.annotation.adapters.XmlAdapter; import java.util.Arrays; import java.util.Map; import java.util.stream.Collectors; /** * Result>>转换器 * @param * @param * * @see org.springframework.data.domain.jaxb.PageAdapter * * @author Ponfee */ public abstract class ResultPageMapAdapter extends XmlAdapter>, Result>>> { protected final Class ktype; protected final Class vtype; protected ResultPageMapAdapter() { ktype = GenericUtils.getActualTypeArgument(this.getClass(), 0); vtype = GenericUtils.getActualTypeArgument(this.getClass(), 1); } @Override public Result>> unmarshal(Result> v) { if (v.getData() == null || CollectionUtils.isEmpty(v.getData().getRows())) { return (Result>>) ((Result) v); } return v.from(v.getData().map(items -> { if (items == null) { return null; } return Arrays.stream(items.getItem()) .collect(Collectors.toMap(MapEntry::getKey, MapEntry::getValue)); })); } @Override public Result> marshal(Result>> v) { if (v.getData() == null || CollectionUtils.isEmpty(v.getData().getRows())) { return (Result>) ((Result) v); } return v.from(v.getData().map(e -> { if (e == null) { return null; } return new MapItem(e.entrySet().stream().map(MapEntry::new).toArray(MapEntry[]::new)); })); } } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/ResultPageMapAdapter.java.bak ================================================ package cn.ponfee.commons.ws.adapter; import java.util.List; import java.util.Map; import javax.xml.bind.annotation.adapters.XmlAdapter; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import cn.ponfee.commons.model.Page; import cn.ponfee.commons.model.Result; import cn.ponfee.commons.reflect.GenericUtils; import cn.ponfee.commons.ws.adapter.model.MapEntry; import cn.ponfee.commons.ws.adapter.model.MapItem; import cn.ponfee.commons.ws.adapter.model.TransitPage; /** * Result>>转换器 * @param * @param * * @see org.springframework.data.domain.jaxb.PageAdapter * * @author Ponfee */ @SuppressWarnings("unchecked") public abstract class ResultPageMapAdapter extends XmlAdapter>, Result>>> { protected final Class ktype; protected final Class vtype; protected ResultPageMapAdapter() { ktype = GenericUtils.getActualTypeArgument(this.getClass(), 0); vtype = GenericUtils.getActualTypeArgument(this.getClass(), 1); } @Override public Result>> unmarshal(Result> v) { if (v.getData() == null) { return v.copy(); } else if (v.getData().getRows() == null || v.getData().getRows().getItem() == null) { return v.copy(new Page<>()); } return v.copy(TransitPage.recover(v.getData()).transform(items -> { if (items == null) { return null; } Map map = Maps.newLinkedHashMap(); for (MapEntry item : items.getItem()) { map.put(item.getKey(), item.getValue()); } return map; })); } @Override public Result> marshal(Result>> v) { if (v.getData() == null || v.getData().getRows() == null) { return v.copy(null); } List list = Lists.newArrayList(); Page> page = v.getData(); for (Map map : page.getRows()) { if (map == null) { continue; } MapEntry[] item = new MapEntry[map.size()]; int index = 0; for (Map.Entry entry : map.entrySet()) { item[index++] = new MapEntry<>(entry); } list.add(new MapItem(item)); } return v.copy(TransitPage.transform(page, list.toArray(new MapItem[list.size()]))); } } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/ResultPageMapNormalAdapter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.ws.adapter; /** * Result>>转换器 * * @author Ponfee */ public class ResultPageMapNormalAdapter extends ResultPageMapAdapter { } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/ResultPageObjectAdapter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.ws.adapter; /** * ResultPageAdapter转换器 * * @author Ponfee */ public class ResultPageObjectAdapter extends ResultPageAdapter { } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/ResultPageObjectArrayAdapter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.ws.adapter; /** * ResultPageAdapter转换器 * * @author Ponfee */ public class ResultPageObjectArrayAdapter extends ResultPageAdapter { } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/ResultSetAdapter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.ws.adapter; import cn.ponfee.commons.model.Result; import cn.ponfee.commons.reflect.GenericUtils; import cn.ponfee.commons.ws.adapter.model.ArrayItem; import com.google.common.collect.Sets; import javax.xml.bind.annotation.adapters.XmlAdapter; import java.lang.reflect.Array; import java.util.Set; /** * Result>转换器 * @param * * @author Ponfee */ public abstract class ResultSetAdapter extends XmlAdapter>, Result>> { protected final Class type; protected ResultSetAdapter() { type = GenericUtils.getActualTypeArgument(this.getClass()); } @Override public Result> unmarshal(Result> v) { if (v.getData() == null) { return v.from(null); } else if (v.getData().getItem() == null) { return v.from(Sets.newHashSet()); } Set set = Sets.newHashSet(v.getData().getItem()); return v.from(set); } @Override @SuppressWarnings("unchecked") public Result> marshal(Result> v) { if (v.getData() == null) { return v.from(null); } T[] array = v.getData().toArray((T[]) Array.newInstance(type, v.getData().size())); return v.from(new ArrayItem<>(array)); } } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/ResultSetStringAdapter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.ws.adapter; /** * Result转换器 * * @author Ponfee */ public class ResultSetStringAdapter extends ResultSetAdapter { } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/model/ArrayItem.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.ws.adapter.model; /** * 封装数组对象 * * @author Ponfee * @param */ public class ArrayItem { private T[] item; public ArrayItem() {} public ArrayItem(T[] item) { this.item = item; } public T[] getItem() { return item; } public void setItem(T[] item) { this.item = item; } } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/model/MapEntry.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.ws.adapter.model; import java.util.Map; /** * 对应Map.Entry数据 * * @author Ponfee * @param * @param */ public class MapEntry { private K key; private V value; public MapEntry() { this(null, null); } public MapEntry(Map.Entry entry) { this(entry.getKey(), entry.getValue()); } public MapEntry(K key, V value) { super(); this.key = key; this.value = value; } public K getKey() { return key; } public void setKey(K key) { this.key = key; } public V getValue() { return value; } public void setValue(V value) { this.value = value; } } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/model/MapItem.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.ws.adapter.model; /** * cannot with generic like as MapItem * ParameterizedTypeImpl cannot be cast to TypeVariable * * @author Ponfee */ @SuppressWarnings("rawtypes") public class MapItem { private MapEntry[] item; public MapItem() {} public MapItem(MapEntry[] item) { this.item = item; } public MapEntry[] getItem() { return item; } public void setItem(MapEntry[] item) { this.item = item; } } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/model/MapItemArray.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.ws.adapter.model; /** * 封装MapItem数组 * * @author Ponfee */ public class MapItemArray { private MapItem[] items; public MapItemArray() {} public MapItemArray(MapItem[] items) { this.items = items; } public MapItem[] getItems() { return items; } public void setItems(MapItem[] items) { this.items = items; } } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/model/TransitPage.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.ws.adapter.model; import cn.ponfee.commons.model.Page; import com.google.common.collect.Lists; import java.lang.reflect.Array; import java.util.List; /** * Page转换 * * @author Ponfee * @param */ public class TransitPage { private ArrayItem rows; private int pageNum; // 当前页 private int pageSize; // 每页的数量 private int size; // 当前页的数量 private long startRow; // 当前页面第一个元素在数据库中的行号 private long endRow; // 当前页面最后一个元素在数据库中的行号 private long total; // 总记录数 private int pages; // 总页数 private int prePage; // 前一页 private int nextPage; // 下一页 private boolean isFirstPage = false; // 是否为第一页 private boolean isLastPage = false; // 是否为最后一页 private boolean hasPreviousPage = false; // 是否有前一页 private boolean hasNextPage = false; // 是否有下一页 private int navigatePages; // 导航页码数 private int[] navigatePageNums; // 所有导航页号 private int navigateFirstPage; // 导航条上的第一页 private int navigateLastPage; // 导航条上的最后一页 public ArrayItem getRows() { return rows; } public void setRows(ArrayItem rows) { this.rows = rows; } public int getPageNum() { return pageNum; } public void setPageNum(int pageNum) { this.pageNum = pageNum; } public int getPageSize() { return pageSize; } public void setPageSize(int pageSize) { this.pageSize = pageSize; } public int getSize() { return size; } public void setSize(int size) { this.size = size; } public long getStartRow() { return startRow; } public void setStartRow(long startRow) { this.startRow = startRow; } public long getEndRow() { return endRow; } public void setEndRow(long endRow) { this.endRow = endRow; } public long getTotal() { return total; } public void setTotal(long total) { this.total = total; } public int getPages() { return pages; } public void setPages(int pages) { this.pages = pages; } public int getPrePage() { return prePage; } public void setPrePage(int prePage) { this.prePage = prePage; } public int getNextPage() { return nextPage; } public void setNextPage(int nextPage) { this.nextPage = nextPage; } public boolean isFirstPage() { return isFirstPage; } public void setFirstPage(boolean isFirstPage) { this.isFirstPage = isFirstPage; } public boolean isLastPage() { return isLastPage; } public void setLastPage(boolean isLastPage) { this.isLastPage = isLastPage; } public boolean isHasPreviousPage() { return hasPreviousPage; } public void setHasPreviousPage(boolean hasPreviousPage) { this.hasPreviousPage = hasPreviousPage; } public boolean isHasNextPage() { return hasNextPage; } public void setHasNextPage(boolean hasNextPage) { this.hasNextPage = hasNextPage; } public int getNavigatePages() { return navigatePages; } public void setNavigatePages(int navigatePages) { this.navigatePages = navigatePages; } public int[] getNavigatePageNums() { return navigatePageNums; } public void setNavigatePageNums(int[] navigatePageNums) { this.navigatePageNums = navigatePageNums; } public int getNavigateFirstPage() { return navigateFirstPage; } public void setNavigateFirstPage(int navigateFirstPage) { this.navigateFirstPage = navigateFirstPage; } public int getNavigateLastPage() { return navigateLastPage; } public void setNavigateLastPage(int navigateLastPage) { this.navigateLastPage = navigateLastPage; } @SuppressWarnings("unchecked") public static TransitPage transform(Page page, Class type) { T[] array = (T[]) Array.newInstance(type, page.getRows().size()); return transform(page, page.getRows().toArray(array)); } public static TransitPage transform(Page page, T[] t) { TransitPage transit = new TransitPage<>(); transit.setRows(new ArrayItem<>(t)); copy(transit, page); return transit; } public static Page recover(TransitPage transit) { Page page = new Page<>(); List list = Lists.newArrayList(transit.getRows().getItem()); page.setRows(list); page.setPageNum(transit.getPageNum()); page.setPageSize(transit.getPageSize()); page.setSize(transit.getSize()); page.setStartRow(transit.getStartRow()); page.setEndRow(transit.getEndRow()); page.setTotal(transit.getTotal()); page.setPages(transit.getPages()); page.setPrePage(transit.getPrePage()); page.setNextPage(transit.getNextPage()); page.setFirstPage(transit.isFirstPage()); page.setLastPage(transit.isLastPage()); page.setHasPreviousPage(transit.isHasPreviousPage()); page.setHasNextPage(transit.isHasNextPage()); page.setNavigatePages(transit.getNavigatePages()); page.setNavigatePageNums(transit.getNavigatePageNums()); page.setNavigateFirstPage(transit.getNavigateFirstPage()); page.setNavigateLastPage(transit.getNavigateLastPage()); return page; } private static void copy(TransitPage transit, Page page) { transit.setPageNum(page.getPageNum()); transit.setPageSize(page.getPageSize()); transit.setSize(page.getSize()); transit.setStartRow(page.getStartRow()); transit.setEndRow(page.getEndRow()); transit.setTotal(page.getTotal()); transit.setPages(page.getPages()); transit.setPrePage(page.getPrePage()); transit.setNextPage(page.getNextPage()); transit.setFirstPage(page.getFirstPage()); transit.setLastPage(page.getLastPage()); transit.setHasPreviousPage(page.getHasPreviousPage()); transit.setHasNextPage(page.getHasNextPage()); transit.setNavigatePages(page.getNavigatePages()); transit.setNavigatePageNums(page.getNavigatePageNums()); transit.setNavigateFirstPage(page.getNavigateFirstPage()); transit.setNavigateLastPage(page.getNavigateLastPage()); } } ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/model/package-info.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ /** * model类 * * @author Ponfee */ package cn.ponfee.commons.ws.adapter.model; ================================================ FILE: src/main/java/cn/ponfee/commons/ws/adapter/package-info.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ /** *
     * 解决WebService输入与输出数据无法转换问题
     *  1.当形参或返回值是String、基本数据类型时,CXF可以处理
     *  2.当形参或返回值是JavaBean式的复合类型、List集合、数组时,CXF可以处理
     *  3.当形参或返回值是一些如Map、非Javabean等复合类型时,CXF无法处理
     *
     *  若还无法转换,可在接口类(interface)上加注解:@XmlSeeAlso({ String[].class, Object[].class, Object[][].class, SomeBean[].class })
     *  
     *  `@XmlJavaTypeAdapter(MarshalJsonAdapter.class)
     *  
     * 
    * * @author Ponfee */ package cn.ponfee.commons.ws.adapter; ================================================ FILE: src/main/java/cn/ponfee/commons/xml/SimpleXmlHandler.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.xml; import org.dom4j.Attribute; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; import org.dom4j.io.SAXValidator; import org.dom4j.util.XMLErrorHandler; import org.xml.sax.SAXException; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.*; /** * xml工具类 * * @author Ponfee */ public class SimpleXmlHandler { private static final int MAX_ERROR_SIZE = 500; /** *
         * 待解析XML文件 格式必须符合如下规范:
         *   1.最多三级,每级的node名称自定义,一级节点为根节点,不能包含属性,如:encryptors; 
         *   2.二级节点支持节点属性,属性将被视作子节点,二级节点如:encryptor;
         *   3.三级节点不能包含属性,三级节点如:encryptorId;
         *   4.CDATA必须包含在节点中,不能单独出现;
         *
         *  xml文件:
         *  <?xml version="1.0" encoding="UTF-8"?>
         *  <encryptors xmlns="http://cn.ponfee/encryptor" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         *              xsi:schemaLocation="http://cn.ponfee/encryptor encryptor.xsd">
         *    <encryptor>
         *      <!-- require -->
         *      <encryptorId>1</encryptorId>
         *      <!--元素:require;规则:以(classpath:或classpath*:或file:或context:开头,默认以classpath开头 )-->
         *      <keyStore><![CDATA[classpath:META-INF/encrypt/encryptors/1.pfx]]></keyStore>
         *      <!-- require -->
         *      <storePass>1234</storePass>
         *      <!-- require -->
         *      <keyPass>1234</keyPass>
         *      <!-- optional[可选],值:[pfx|jks] -->
         *      <storeType>pfx</storeType>
         *      <!-- implied[可选],不填默认选第1个密钥对 -->
         *      <alias>45e4ea70a3589e96cd670c8e5c8c7be5_28766470-a40c-4c9e-b312-d7a5618db23b</alias>
         *    </encryptor>
         *    ...
         *    <encryptor>...</encryptor>
         *  </encryptors>
         *
         *  xsd文件:
         *  <?xml version="1.0" encoding="UTF-8" standalone="no"?>
         *  <schema xmlns="http://www.w3.org/2001/XMLSchema"
         *          xmlns:tns="http://cn.ponfee/encryptor"
         *          attributeFormDefault="unqualified"
         *          elementFormDefault="qualified"
         *          targetNamespace="http://cn.ponfee/encryptor">
         *    <element name="encryptors">
         *      <complexType>
         *        <sequence>
         *          <element maxOccurs="unbounded" name="encryptor" type="tns:encryptorType"/>
         *        </sequence>
         *      </complexType>
         *    </element>
         *    <complexType name="encryptorType">
         *      <sequence>
         *        <element name="encryptorId" type="string"/>
         *        <element name="keyStore" type="tns:resourceType"/>
         *        <element name="storePass" type="string"/>
         *        <element name="keyPass" type="string"/>
         *        <element minOccurs="0" name="storeType" type="tns:storeTypeType"/>
         *        <element minOccurs="0" name="alias" type="string"/>
         *      </sequence>
         *    </complexType>
         *    <simpleType name="storeTypeType">
         *      <restriction base="string">
         *        <enumeration value="jks"/>
         *        <enumeration value="pfx"/>
         *      </restriction>
         *    </simpleType>
         *    <simpleType name="resourceType">
         *      <restriction base="string">
         *        <pattern value="(classpath:|classpath\*:|file:(([c-zC-Z]:)(/|\\\\)){0,1}|context:){0,1}[^:\?\|\*]*" />
         *      </restriction>
         *    </simpleType>
         *  </schema>
         *
         * 
    */ public static List> parse(InputStream input) { try (InputStream xml = input) { List> results = new LinkedList<>(); Element root = new SAXReader().read(xml).getRootElement(); for (Iterator seconds = root.elementIterator(); seconds.hasNext(); ) { Element second = seconds.next(); Map element = new HashMap<>(); for (Attribute attr : second.attributes()) { element.put(attr.getName(), attr.getValue()); // 添加二级节点属性 } for (Iterator thirds = second.elementIterator(); thirds.hasNext(); ) { Element third = thirds.next(); // 添加三级节点 element.put(third.getName().trim(), third.getText().trim()); } results.add(element); } return results; } catch (DocumentException e) { throw new IllegalArgumentException("Invalid xml data.", e); } catch (IOException e) { throw new RuntimeException(e); } } public static List> parse(String xml) { return parse(new ByteArrayInputStream(xml.getBytes())); } /** * 通过Schema验证xml文件 */ public static void validate(InputStream xmlIn, InputStream xsdIn) { try (InputStream xml = xmlIn; InputStream xsd = xsdIn) { SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setValidating(true); factory.setNamespaceAware(true); SAXParser parser = factory.newSAXParser(); parser.setProperty(JAXPConstants.JAXP_SCHEMA_LANGUAGE, JAXPConstants.W3C_XML_SCHEMA); //parser.setProperty(JAXPConstants.JAXP_SCHEMA_SOURCE, "file:" + xsdPath); parser.setProperty(JAXPConstants.JAXP_SCHEMA_SOURCE, xsd); if (!parser.isValidating()) { throw new IllegalStateException("Invalid xsd definition."); } XMLErrorHandler errorHandler = new XMLErrorHandler(); SAXValidator validator = new SAXValidator(parser.getXMLReader()); validator.setErrorHandler(errorHandler); validator.validate(new SAXReader().read(xml)); // 校验 if (errorHandler.getErrors().hasContent()) { // 校验失败 // 校验失败则打印错误信息 StringBuilder errors = new StringBuilder(128); Set exists = new HashSet<>(); for (Element e : errorHandler.getErrors().elements()) { String position = e.attributeValue("line") + "#" + e.attributeValue("column"); if (!exists.add(position)) { continue; } errors.append(position).append(":").append(e.getTextTrim()).append("\n"); if (errors.length() > MAX_ERROR_SIZE) { break; // break output error } } if (errors.length() > MAX_ERROR_SIZE) { errors.setLength(MAX_ERROR_SIZE - 3); errors.append("..."); } throw new IllegalStateException(errors.toString()); } } catch (ParserConfigurationException | SAXException | DocumentException e) { throw new IllegalStateException("Invalid xml data.", e); } catch (IOException e) { throw new RuntimeException(e); } } public static void validate(String xml, String xsd) { validate(new ByteArrayInputStream(xml.getBytes()), new ByteArrayInputStream(xsd.getBytes())); } private static final class JAXPConstants { static final String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage"; static final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema"; static final String JAXP_SCHEMA_SOURCE = "http://java.sun.com/xml/jaxp/properties/schemaSource"; } } ================================================ FILE: src/main/java/cn/ponfee/commons/xml/XmlException.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.xml; /** * xml文件异常 * * @author Ponfee */ public class XmlException extends RuntimeException { private static final long serialVersionUID = 1112070147872432069L; public XmlException() { super(); } public XmlException(String message) { super(message); } public XmlException(String message, Throwable cause) { super(message, cause); } public XmlException(Throwable cause) { super(cause); } protected XmlException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } } ================================================ FILE: src/main/java/cn/ponfee/commons/xml/XmlMap.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.xml; import org.apache.commons.lang3.StringUtils; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; /** * xml和map相互转换工具 * * @author Ponfee */ public final class XmlMap extends LinkedHashMap { private static final long serialVersionUID = 2775335692799838871L; private String root; public XmlMap(Map map) { this(map, "xml"); } public XmlMap(Map map, String root) { this.root = root; super.putAll(map); } public XmlMap(String xml) { Map map; if (StringUtils.isEmpty(xml)) { map = Collections.emptyMap(); } else { map = read(XmlReader.create(xml)); } super.putAll(map); } public XmlMap(XmlReader reader) { super.putAll(read(reader)); } /** * 返回Map * @return */ public Map toMap() { return this; } /** * 返回Xml * @return */ public String toXml() { XmlWriter writers = XmlWriter.create(); for (Map.Entry param : this.entrySet()) { if (!StringUtils.isEmpty(param.getValue())) { writers.element(param.getKey(), param.getValue()); } } return writers.build(this.root); } /** * XML为Map(仅支持2级) * @param reader xmlReader * @return Map对象 */ private Map read(XmlReader reader) { this.root = reader.getRoot(); Node rootNode = reader.getNode(this.root); NodeList children; if (rootNode == null || (children = rootNode.getChildNodes()).getLength() == 0) { return Collections.emptyMap(); } Map data = new HashMap<>(children.getLength()); Node n; for (int i = 0; i < children.getLength(); i++) { n = children.item(i); if (Node.TEXT_NODE != n.getNodeType()) { data.put(n.getNodeName(), n.getTextContent()); } } return data; } } ================================================ FILE: src/main/java/cn/ponfee/commons/xml/XmlReader.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.xml; import cn.ponfee.commons.io.Closeables; import org.apache.commons.lang3.StringUtils; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import java.io.ByteArrayInputStream; import java.io.InputStream; /** * xml读取 * * @author Ponfee */ public final class XmlReader { private static final DocumentBuilderFactory FACTORY = DocumentBuilderFactory.newInstance(); static { FACTORY.setExpandEntityReferences(false); // XXE漏洞 } private Document document; private String root; private XPath xpath; private XmlReader() {} public static XmlReader create(String xml) { if (StringUtils.isEmpty(xml)) { throw new IllegalArgumentException("xml can't be empty."); } //xml = xml.replaceAll("(\\r|\\n)", ""); return create(new ByteArrayInputStream(xml.getBytes())); } public static XmlReader create(InputStream inputStream) { try { XmlReader readers = new XmlReader(); readers.document = FACTORY.newDocumentBuilder().parse(inputStream); readers.root = readers.document.getFirstChild().getNodeName(); readers.xpath = XPathFactory.newInstance().newXPath(); return readers; } catch (Exception e) { throw new XmlException("Xmls create fail", e); } finally { Closeables.console(inputStream); } } /** * 获取根节点名称 * @return */ public String getRoot() { return this.root; } /** * 通过xpath取值 * @param xpathExp 表达式 * @return */ public String evaluate(String xpathExp) { try { return this.xpath.evaluate(xpathExp, document); } catch (XPathExpressionException e) { throw new RuntimeException("xpath evaluate error", e); } } /** * 获取节点 * @param tagName * @return */ public Node getNode(String tagName) { NodeList nodes = document.getElementsByTagName(tagName); if (nodes.getLength() <= 0) { return null; } else { return nodes.item(0); } } /** * 获取节点列表 * @param tagName * @return */ public NodeList getNodes(String tagName) { NodeList nodes = document.getElementsByTagName(tagName); if (nodes.getLength() <= 0) { return null; } else { return nodes; } } /** * 获取某个节点的文本内容,若有多个该节点,只会返回第一个 * @param tagName 标签名 * @return 文本内容,或NULL */ public String getNodeText(String tagName) { Node node = getNode(tagName); return node == null ? null : node.getTextContent(); } /** * 获取某个节点的Integer,若有多个该节点,只会返回第一个 * @param tagName 标签名 * @return Integer值,或NULL */ public Integer getNodeInt(String tagName) { String nodeContent = getNodeText(tagName); return nodeContent == null ? null : Integer.valueOf(nodeContent); } /** * 获取某个节点的Long值,若有多个该节点,只会返回第一个 * @param tagName 标签名 * @return Long值,或NULL */ public Long getNodeLong(String tagName) { String nodeContent = getNodeText(tagName); return nodeContent == null ? null : Long.valueOf(nodeContent); } /** * 获取某个节点的Float,若有多个该节点,只会返回第一个 * @param tagName 标签名 * @return Float值,或NULL */ public Float getNodeFloat(String tagName) { String nodeContent = getNodeText(tagName); return nodeContent == null ? null : Float.valueOf(nodeContent); } } ================================================ FILE: src/main/java/cn/ponfee/commons/xml/XmlWriter.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.xml; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; /** * xml构建 * * @author Ponfee */ public final class XmlWriter { private static final String XML_DECLARATION = ""; private final List> elements = new ArrayList<>(); private XmlWriter() {} public static XmlWriter create() { return new XmlWriter(); } public XmlWriter element(String name, String text) { elements.add(new TextE(name, text)); return this; } public XmlWriter element(String name, Number number) { elements.add(new NumberE(name, number)); return this; } public XmlWriter element(String parentName, String childName, String childText) { return element(parentName, new TextE(childName, childText)); } public XmlWriter element(String parentName, String childName, Number childNumber) { return element(parentName, new NumberE(childName, childNumber)); } /** * 构建包含多个子元素的元素 * @param parentName 父元素名 * @param childPairs * @return this */ public XmlWriter element(String parentName, Object... childPairs) { return element(parentName, newElement(childPairs)); } public XmlWriter element(String parentName, E child) { return element(parentName, Collections.singletonList(child)); } public XmlWriter element(String parentName, List> children) { elements.add(new NodeE(parentName, children)); return this; } public String build() { return build("xml"); } public String build(String root) { StringBuilder xml = new StringBuilder(XML_DECLARATION) .append("<").append(root).append(">"); for (E e : elements) { xml.append(e.render()); } return xml.append("").toString(); } /** * 创建多个元素的节点列表 * @param childPairs childName1, childValue1, childName2, childValu2, ...,长度必须为2的倍数 * @return */ public static List> newElement(Object... childPairs) { if ((childPairs.length & 0x01) == 1) { throw new XmlException("args Object array must be pair"); } List> nodes = new ArrayList<>(); for (int i = 0; i < childPairs.length; i = i + 2) { nodes.add(newElement((String) childPairs[i], childPairs[i + 1])); } return nodes; } /** * 创建元素 * @param name 元素名 * @param value 元素值 * @return */ public static E newElement(String name, Object value) { if (value instanceof Number) { return new NumberE(name, (Number) value); } else if (value instanceof E) { return new NodeE(name, Collections.singletonList((NodeE) value)); } else { return new TextE(name, Objects.toString(value, null)); } } /** * 元素抽象类 * @param */ public static abstract class E { protected final String name; protected final T value; public E(String name, T value) { if (name == null) { throw new IllegalArgumentException("element name cannot be null."); } this.name = name; this.value = value; } private String render() { StringBuilder content = new StringBuilder("<").append(name).append(">"); if (value != null) { content.append(value()); } return content.append("").toString(); } protected abstract String value(); } /** * 文本元素类 */ public static class TextE extends E { public TextE(String name, String content) { super(name, content); } @Override protected String value() { return new StringBuilder("").toString(); } } /** * 数值元素类 */ public static class NumberE extends E { public NumberE(String name, Number value) { super(name, value); } @Override protected String value() { return value.toString(); } } /** * 节点元素类 */ public static class NodeE extends E>> { public NodeE(String name, List> nodes) { super(name, nodes); } @Override protected String value() { StringBuilder content = new StringBuilder(); for (E e : value) { if (e != null) { content.append(e.render()); } } return content.toString(); } } } ================================================ FILE: src/main/resources/log4j2.xml.template ================================================ ${log.home:-../logs} commons-core %msg%n" /> --> ================================================ FILE: src/main/resources/mybatis-conf.xml.template ================================================ ================================================ FILE: src/test/java/cn/ponfee/commons/Options.java ================================================ package cn.ponfee.commons; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.Map; import com.google.common.collect.ImmutableMap; /** * Options template code * * @author Ponfee */ public class Options { public static final Type BOOLEAN = new Type<>(); public static final Type INTEGER = new Type<>(); public static final Type LONG = new Type<>(); public static final Type DOUBLE = new Type<>(); public static final Type FLOAT = new Type<>(); public static final Type STRING = new Type<>(); private final Map options = new HashMap<>(); @SuppressWarnings("unchecked") public T option(Type key) { return (T) options.get(MAPPER.get(key)); } public void option(Type key, T value) { options.put(MAPPER.get(key), value); } public static class Type { private Type() {} } private static final Map, String> MAPPER; static { ImmutableMap.Builder, String> builder = ImmutableMap.builder(); try { for (Field field : Options.class.getDeclaredFields()) { int m = field.getModifiers(); Object value; if ( Modifier.isPublic(m) && Modifier.isStatic(m) && Modifier.isFinal(m) && Type.class.isInstance(value = field.get(null)) ) { builder.put((Type) value, field.getName()); } } } catch (Exception e) { // cannot happend throw new AssertionError(e); } MAPPER = builder.build(); } } ================================================ FILE: src/test/java/cn/ponfee/commons/SpringBaseTest.java ================================================ package cn.ponfee.commons; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.junit.After; import org.junit.Before; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; import cn.ponfee.commons.json.Jsons; import cn.ponfee.commons.reflect.GenericUtils; import cn.ponfee.commons.spring.SpringContextHolder; /** * 测试基类 * @author Ponfee * @param */ @RunWith(SpringRunner.class) // SpringJUnit4ClassRunner.class @ContextConfiguration(locations = { "classpath:spring-context.xml" }) public abstract class SpringBaseTest { private static final Class[] EXCLUDE_CLASSES = {Void.class, Object.class}; private T bean; private final String beanName; public SpringBaseTest() { this(null); } public SpringBaseTest(String beanName) { this.beanName = beanName; } protected final T getBean() { return bean; } @Before public final void setUp() { Class type = GenericUtils.getActualTypeArgument(getClass(), 0); if (!ArrayUtils.contains(EXCLUDE_CLASSES, type)) { bean = StringUtils.isBlank(beanName) ? SpringContextHolder.getBean(type) : SpringContextHolder.getBean(beanName, type); } initialize(); } @After public final void tearDown() { destroy(); } protected void initialize() { // do no thing } protected void destroy() { // do no thing } public static void consoleJson(Object obj) { try { Thread.sleep(100); System.err.println(Jsons.toJson(obj)); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } public static void console(Object obj) { try { Thread.sleep(100); System.err.println(obj); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } ================================================ FILE: src/test/java/cn/ponfee/commons/SpringBootTest.java ================================================ //package cn.ponfee.commons; // //import org.junit.After; //import org.junit.Before; //import org.junit.runner.RunWith; //import org.springframework.boot.test.context.SpringBootTest; //import org.springframework.test.context.junit4.SpringRunner; // //import cn.ponfee.commons.json.Jsons; //import cn.ponfee.commons.reflect.GenericUtils; //import cn.ponfee.commons.spring.SpringContextHolder; // ///** // * 测试基类 // * // * @param // * @author Ponfee // */ //@RunWith(SpringRunner.class) //@SpringBootTest //public abstract class SpringBootBaseTest { // private static final Class[] EXCLUDE_CLASSES = {Void.class, Object.class}; // // private T bean; // private final String beanName; // // public SpringBootBaseTest() { // this(null); // } // // public SpringBootBaseTest(String beanName) { // this.beanName = beanName; // } // // protected final T getBean() { // return bean; // } // // @Before // public final void setUp() { // Class type = GenericUtils.getActualTypeArgument(getClass(), 0); // if (!ArrayUtils.contains(EXCLUDE_CLASSES, type)) { // bean = StringUtils.isBlank(beanName) // ? SpringContextHolder.getBean(type) // : SpringContextHolder.getBean(beanName, type); // } // initiate(); // } // // @After // public final void tearDown() { // destory(); // } // // protected void initiate() { // // do no thing // } // // protected void destory() { // // do no thing // } // // public static void consoleJson(Object obj) { // try { // Thread.sleep(100); // System.err.println(Jsons.toJson(obj)); // Thread.sleep(100); // } catch (InterruptedException e) { // e.printStackTrace(); // } // } // // public static void console(Object obj) { // try { // Thread.sleep(100); // System.err.println(obj); // Thread.sleep(100); // } catch (InterruptedException e) { // e.printStackTrace(); // } // } //} ================================================ FILE: src/test/java/cn/ponfee/commons/WebServiceCxfTest.java ================================================ //package cn.ponfee.commons; // //import javax.xml.ws.Endpoint; // //import org.apache.cxf.jaxws.JaxWsProxyFactoryBean; //import org.junit.After; //import org.junit.Before; //import org.junit.runner.RunWith; //import org.springframework.test.context.ContextConfiguration; //import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; // //import cn.ponfee.commons.json.Jsons; //import cn.ponfee.commons.reflect.GenericUtils; //import cn.ponfee.commons.util.Networks; //import cn.ponfee.commons.util.ObjectUtils; //import cn.ponfee.commons.spring.SpringContextHolder; // //@RunWith(SpringJUnit4ClassRunner.class) //@ContextConfiguration(locations = { "classpath:spring/application-config.xml" }) //public abstract class WebServiceCxfTest { // // private final Class clazz; // private final String addressUrl; // // private volatile boolean isPublished = false; // private T client; // // protected WebServiceCxfTest() { // this("http://localhost:" + Networks.findAvailablePort(8000) + "/testws/" + ObjectUtils.uuid32()); // } // // protected WebServiceCxfTest(String url) { // clazz = GenericUtils.getActualTypeArgument(this.getClass()); // addressUrl = url; // } // // protected final T client() { // return client; // } // // @Before // public final synchronized void setUp() { // if (isPublished) { // return; // } // // // 发布web service // Endpoint.publish(addressUrl, SpringContextHolder.getBean(clazz)); // isPublished = true; // // // 创建客户端 // JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean(); // factory.setServiceClass(clazz); // factory.setAddress(addressUrl); // client = (T) factory.create(); // // initiate(); // } // // @After // public final void tearDown() { // destory(); // } // // protected void initiate() { // // do no thing // } // // protected void destory() { // // do no thing // } // // public static void consoleJson(Object obj) { // try { // Thread.sleep(100); // System.err.println(Jsons.toJson(obj)); // Thread.sleep(100); // } catch (InterruptedException e) { // e.printStackTrace(); // } // } // // public static void console(Object obj) { // try { // Thread.sleep(100); // System.err.println(obj); // Thread.sleep(100); // } catch (InterruptedException e) { // e.printStackTrace(); // } // } // //} ================================================ FILE: src/test/java/cn/ponfee/commons/WebServiceJaxTest.java ================================================ package cn.ponfee.commons; import java.util.HashSet; import java.util.Set; import cn.ponfee.commons.util.UuidUtils; import org.apache.commons.lang3.StringUtils; import org.junit.After; import org.junit.Before; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; import cn.ponfee.commons.json.Jsons; import cn.ponfee.commons.reflect.GenericUtils; import cn.ponfee.commons.util.Networks; import cn.ponfee.commons.util.ObjectUtils; import cn.ponfee.commons.spring.SpringContextHolder; import cn.ponfee.commons.ws.JAXWS; /** * addressUrl: http://localhost:8888/testws/address * * Endpoint.publish(addressUrl, new WebServiceImpl()); * * wsdl-url: http://localhost:8888/testws/address?wsdl * * xmlns:ns1="http://service.ws.ponfee.cn/" name="TestService" targetNamespace="http://impl.service.ws.ponfee.cn/"> * * namespaceURI: targetNamespace="http://impl.service.ws.ponfee.cn/" * localPart: name="TestService" * * @author Ponfee * @param */ @RunWith(SpringRunner.class) @ContextConfiguration(locations = { "classpath:spring-context.xml" }) public abstract class WebServiceJaxTest { private static final Set PUBLISHED = new HashSet<>(); private T client; private final String addressUrl; private final String namespaceURI; // targetNamespace="http://impl.service.ws.ponfee.cn/" private final String localPart; // name="TestService" protected WebServiceJaxTest(String namespaceURI, String localPart) { this("http://localhost:" + Networks.findAvailablePort(8000) + "/testws/" + UuidUtils.uuid32(), namespaceURI, localPart); } protected WebServiceJaxTest(String url, String namespaceURI, String localPart) { this.addressUrl = url; this.namespaceURI = namespaceURI; this.localPart = localPart; } protected final T client() { return client; } @Before public final void setUp() { Class clazz = GenericUtils.getActualTypeArgument(this.getClass()); synchronized (WebServiceJaxTest.class) { int pos = StringUtils.ordinalIndexOf(addressUrl, "/", 3); if (pos == -1) { pos = addressUrl.length(); } String prefixUrl = addressUrl.substring(0, pos); // http://domain:port if (PUBLISHED.add(prefixUrl)) { // 发布web service JAXWS.publish(addressUrl, SpringContextHolder.getBean(clazz)); } else { System.err.println("The web service: " + prefixUrl + " are already published."); } } // 创建客户端 client = JAXWS.client(clazz, addressUrl + "?wsdl", namespaceURI, localPart); initiate(); } @After public final void tearDown() { destory(); } protected void initiate() { // do no thing } protected void destory() { // do no thing } public static void consoleJson(Object obj) { try { Thread.sleep(100); System.err.println(Jsons.toJson(obj)); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } public static void console(Object obj) { try { Thread.sleep(100); System.err.println(obj); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } ================================================ FILE: src/test/java/cn/ponfee/commons/base/MethodInvokerTest.java ================================================ package cn.ponfee.commons.base; import com.alibaba.druid.pool.DruidDataSource; import cn.ponfee.commons.util.ObjectUtils; public class MethodInvokerTest { public static void main(String[] args) { DruidDataSource ds = new DruidDataSource(); try { Releasable.release(ds); } catch (Exception e) { } try { Initializable.init(ds); } catch (Exception e) { } System.out.println("\n\n\n\n=============================="); try { System.out.println(Releasable.class.getMethod("release")); } catch (Exception e) { } try { System.out.println(ObjectUtils.class.getMethod("uuid")); } catch (Exception e) { } try { System.out.println(MethodInvokerTest.class.getMethod("toStr")); } catch (Exception e) { } try { System.out.println(MethodInvokerTest.class.getMethod("toString")); } catch (Exception e) { } } protected String toStr() { return ""; } } ================================================ FILE: src/test/java/cn/ponfee/commons/base/TupleTest.java ================================================ package cn.ponfee.commons.base; import cn.ponfee.commons.base.tuple.*; import org.junit.Assert; import org.junit.Test; public class TupleTest { @Test public void test() { Assert.assertEquals(new Tuple0(), new Tuple0()); Assert.assertEquals(Tuple1.of(1), Tuple1.of(1)); Assert.assertEquals(Tuple2.of(1, 2), Tuple2.of(1, 2)); Assert.assertTrue(Tuple2.of(1, 2).equals(1, 2)); StringBuilder builder = new StringBuilder(); for (Object e : Tuple4.of(1, 2, 3, 4)) { builder.append(e); } Assert.assertEquals(builder.toString(), "1234"); Assert.assertEquals(Tuple3.of(1, 2, 3).join(", ", String::valueOf, "(", ")"), "(1, 2, 3)"); Assert.assertEquals("()", Tuple0.of().toString()); Assert.assertEquals("(1)", Tuple1.of(1).toString()); Assert.assertEquals("(1, 2)", Tuple2.of(1, 2).toString()); } } ================================================ FILE: src/test/java/cn/ponfee/commons/boolm/GuavaBloomFilterTest.java ================================================ package cn.ponfee.commons.boolm; import java.util.ArrayList; import java.util.List; import com.google.common.hash.BloomFilter; import com.google.common.hash.Funnels; public class GuavaBloomFilterTest { public static void main(String[] args) { BloomFilter filter = BloomFilter.create( //(from, into) -> into.putString(from, Charsets.UTF_8), Funnels.integerFunnel(), 1024 * 1024 * 32, 0.000000001d ); /*filter.test("234"); filter.put("abc"); filter.mightContain("123"); filter.isCompatible("abc");*/ int size = 1000000; for (int i = 0; i < size; i++) { filter.put(i); } for (int i = 0; i < size; i++) { if (!filter.mightContain(i)) { System.out.println("有坏人逃脱了"); } } List list = new ArrayList<>(1000); for (int i = size + 10000; i < size + 20000; i++) { if (filter.mightContain(i)) { list.add(i); } } System.out.println("有误伤的数量:" + list.size()); } } ================================================ FILE: src/test/java/cn/ponfee/commons/boolm/JdkBloomFilter.java ================================================ package cn.ponfee.commons.boolm; import java.net.MalformedURLException; import java.net.URL; import java.util.BitSet; import cn.ponfee.commons.jce.digest.DigestUtils; public class JdkBloomFilter implements VisitedFrontier { private static final int DEFAULT_SIZE = 2 << 24; private static final int[] seeds = new int[] { 7, 11, 13, 19, 23, 31, 37, 61 }; private BitSet bits = new BitSet(DEFAULT_SIZE);//二进制列表32M private Hash[] func = new Hash[seeds.length]; //8个哈希函数 private static int size = 0;//保存已经插入的元素个数 public JdkBloomFilter() { for (int i = 0; i < seeds.length; i++) func[i] = new Hash(DEFAULT_SIZE, seeds[i]); } @Override public void put(URL url) { // TODO Auto-generated method stub if (url != null) put(url.toString()); } @Override public void put(String value) { // TODO Auto-generated method stub size++; for (Hash h : func)//映射位置true bits.set(h.getHash(caculateUrl(value)), true); } @Override public boolean contains(URL url) { // TODO Auto-generated method stub return contains(url.toString()); } @Override public boolean contains(String value) { // TODO Auto-generated method stub if (value == null) return false; boolean ret = true; for (Hash h : func)//检测每一个映射到的bit位是否为true ret &= bits.get(h.getHash(caculateUrl(value))); return ret; } public static class Hash { private int cap;//保证映射范围在BitSet内 private int seed; public Hash(int cap, int seed) { this.cap = cap; this.seed = seed; } public int getHash(String value) { int result = 0; for (int i = 0; i < value.length(); i++) {//每一位加权相加 result = seed * result + value.charAt(i); } return (cap - 1) & result; } } private String caculateUrl(String url) { //将没一个url都映射为128个字节的十六进制数,因为有些url相似度很高 return DigestUtils.md5Hex(url); } public int size() { // TODO Auto-generated method stub return size; } public static void main(String[] args) throws MalformedURLException { System.out.println(33554430 >> 6); // % 64 System.out.println(((33554430 - 1) >> 6) + 1); // % 64 String value = new String("http://www.baidu.com"); JdkBloomFilter filter = new JdkBloomFilter(); System.out.println(filter.contains(value)); filter.put(value); System.out.println(filter.contains(value)); } } ================================================ FILE: src/test/java/cn/ponfee/commons/boolm/RedisBloomFilterTest.java ================================================ package cn.ponfee.commons.boolm; import java.security.DigestException; import java.security.MessageDigest; import org.apache.commons.codec.binary.Hex; import org.junit.Test; import cn.ponfee.commons.jce.DigestAlgorithms; import cn.ponfee.commons.jce.digest.DigestUtils; public class RedisBloomFilterTest { private static final DigestAlgorithms DIGEST = DigestAlgorithms.MD5; @Test public void test1() throws DigestException { MessageDigest md = DigestUtils.getMessageDigest(DIGEST, null); md.update("123".getBytes()); md.update("abc".getBytes()); System.out.println(Hex.encodeHexString(md.digest())); md = DigestUtils.getMessageDigest(DIGEST, null); md.update("123".getBytes()); System.out.println(Hex.encodeHexString(md.digest("abc".getBytes()))); md = DigestUtils.getMessageDigest(DIGEST, null); md.update("123".getBytes()); md.update("abc".getBytes()); byte[] output = new byte[DIGEST.byteSize()]; md.digest(output, 0, output.length); System.out.println(Hex.encodeHexString(output)); } } ================================================ FILE: src/test/java/cn/ponfee/commons/boolm/VisitedFrontier.java ================================================ package cn.ponfee.commons.boolm; import java.net.URL; public interface VisitedFrontier { public void put(URL url); public void put(String value); public boolean contains(URL url); public boolean contains(String value); } ================================================ FILE: src/test/java/cn/ponfee/commons/cache/Cache.java ================================================ package cn.ponfee.commons.cache; import cn.ponfee.commons.base.Releasable; import cn.ponfee.commons.base.TimestampProvider; import cn.ponfee.commons.cache.RemovalNotification.RemovalReason; import cn.ponfee.commons.concurrent.ThreadPoolExecutors; import cn.ponfee.commons.concurrent.ThreadPoolTestUtils; import cn.ponfee.commons.jce.digest.DigestUtils; import cn.ponfee.commons.util.Base64UrlSafe; import com.google.common.base.Preconditions; import java.util.*; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 缓存类 * * @author Ponfee * @param * @param */ public class Cache { public static final long KEEPALIVE_FOREVER = 0; // 为0表示不失效 private final boolean caseSensitiveKey; // 是否忽略大小写(只针对String) private final boolean compressKey; // 是否压缩key(只针对String) private final long keepAliveInMillis; // 默认的数据保存的时间 private final Map> container = new ConcurrentHashMap<>(); // 缓存容器 private volatile boolean isDestroy = false; // 是否被销毁 private final Lock lock = new ReentrantLock(); // 定时清理加锁 private final ScheduledExecutorService scheduler; private final RemovalListener removalListener; private TimestampProvider timestampProvider = TimestampProvider.CURRENT; Cache(boolean caseSensitiveKey, boolean compressKey, long keepAliveInMillis, int autoReleaseInSeconds, ScheduledExecutorService scheduler, RemovalListener removalListener) { Preconditions.checkArgument(keepAliveInMillis >= 0); Preconditions.checkArgument(autoReleaseInSeconds >= 0); this.caseSensitiveKey = caseSensitiveKey; this.compressKey = compressKey; this.keepAliveInMillis = keepAliveInMillis; this.removalListener = removalListener; this.scheduler = scheduler; if (autoReleaseInSeconds > 0) { if (scheduler == null) { scheduler = ThreadPoolTestUtils.CALLER_RUN_SCHEDULER; } // 定时清理 scheduler.scheduleAtFixedRate(() -> { // none exception to throw, so can not wrap try catch if (!lock.tryLock()) { return; } long now = now(); try { //container.entrySet().removeIf(x -> x.getValue().isExpire(now)); for (Iterator>> iter = container.entrySet().iterator(); iter.hasNext();) { Entry> entry = iter.next(); CacheValue cacheValue = entry.getValue(); if (cacheValue.isExpire(now)) { iter.remove(); onRemoval(entry.getKey(), cacheValue, RemovalReason.EXPIRED); } } } finally { lock.unlock(); } }, autoReleaseInSeconds, autoReleaseInSeconds, TimeUnit.SECONDS); } } public boolean isCaseSensitiveKey() { return caseSensitiveKey; } public boolean isCompressKey() { return compressKey; } public long getKeepAliveInMillis() { return keepAliveInMillis; } public RemovalListener getRemovalListener() { return removalListener; } public ScheduledExecutorService getScheduler() { return scheduler; } public TimestampProvider getTimestampProvider() { return timestampProvider; } public void setTimestampProvider(TimestampProvider timestampProvider) { this.timestampProvider = timestampProvider; } private long now() { return timestampProvider.get(); } // ---------------------------------------------------------------cache value public void put(K key) { put(key, null); } public void put(K key, V value) { long expireTimeMillis; if (keepAliveInMillis > 0) { expireTimeMillis = now() + keepAliveInMillis; } else { expireTimeMillis = KEEPALIVE_FOREVER; } put(key, value, expireTimeMillis); } public void putWithAliveInMillis(K key, V value, int aliveInMillis) { Preconditions.checkArgument(aliveInMillis > 0); put(key, value, now() + aliveInMillis); } public void putWithNull(K key, long expireTimeMillis) { put(key, null, expireTimeMillis); } public void put(K key, V value, long expireTimeMillis) { Preconditions.checkState(!isDestroy); if (expireTimeMillis < KEEPALIVE_FOREVER) { expireTimeMillis = KEEPALIVE_FOREVER; } if (expireTimeMillis == KEEPALIVE_FOREVER || expireTimeMillis > now()) { CacheValue newly = new CacheValue<>(value, expireTimeMillis); CacheValue former = container.put(getEffectiveKey(key), newly); onRemoval(key, former, RemovalReason.REPLACED); } } /** * 获取 * @param key * @return */ public V get(K key) { if (isDestroy) { return null; } key = getEffectiveKey(key); CacheValue cacheValue = container.get(key); if (cacheValue == null) { return null; } else if (cacheValue.isExpire(now())) { container.remove(key); onRemoval(key, cacheValue, RemovalReason.EXPIRED); return null; } else { return cacheValue.getValue(); } } /** * Remove key value and return the value if exists * * @param key the key */ public V remove(K key) { if (isDestroy) { return null; } CacheValue cacheValue = container.remove(getEffectiveKey(key)); if (cacheValue == null) { return null; } else if (cacheValue.isExpire(now())) { onRemoval(key, cacheValue, RemovalReason.EXPIRED); return cacheValue.getValue(); } else { onRemoval(key, cacheValue, RemovalReason.EVICTED); return null; } } /** * @param key * @return */ public boolean containsKey(K key) { if (isDestroy) { return false; } key = getEffectiveKey(key); CacheValue cacheValue = container.get(key); if (cacheValue == null) { return false; } else if (cacheValue.isExpire(now())) { container.remove(key); onRemoval(key, cacheValue, RemovalReason.EXPIRED); return false; } else { return true; } } /** * 是否包含指定的value * @param value * @return */ public boolean containsValue(V value) { if (isDestroy) { return false; } CacheValue cacheValue; for (Iterator>> i = container.entrySet().iterator(); i.hasNext();) { Entry> entry = i.next(); cacheValue = entry.getValue(); if (cacheValue.isAlive(now())) { if (value == null) { if (cacheValue.getValue() == null) { return true; } } else if (value.equals(cacheValue.getValue())) { return true; } } else { i.remove(); onRemoval(entry.getKey(), cacheValue, RemovalReason.EXPIRED); } } return false; } /** * Gets for value collection * * @return the collection of values */ public Collection values() { if (isDestroy) { return Collections.emptyList(); } Collection values = new ArrayList<>(); CacheValue value; for (Iterator>> i = container.entrySet().iterator(); i.hasNext();) { Entry> entry = i.next(); value = entry.getValue(); if (value.isAlive(now())) { values.add(value.getValue()); } else { i.remove(); onRemoval(entry.getKey(), value, RemovalReason.EXPIRED); } } return values; } /** * get size of the cache keys * @return */ public int size() { return container.size(); } /** * check is empty * @return */ public boolean isEmpty() { return container.isEmpty(); } /** * clear all */ public void clear() { Preconditions.checkState(!isDestroy); container.clear(); } /** * destory the cache self */ public void destroy() { isDestroy = true; if (scheduler != null) { ThreadPoolExecutors.shutdown(scheduler); } container.clear(); } public boolean isDestroy() { return isDestroy; } /** * get effective key * * @param key * @return */ @SuppressWarnings("unchecked") private K getEffectiveKey(K key) { if (key instanceof CharSequence) { String k = key.toString(); if (!caseSensitiveKey) { k = k.toLowerCase(); // 不区分大小写(转小写) } if (compressKey) { k = Base64UrlSafe.encode(DigestUtils.sha1(k)); // 压缩key } key = (K) k; } return key; } /** * Removing a value * * @param key the key * @param cacheValue the CacheValue * @param removalReason the removalReason */ private void onRemoval(K key, CacheValue cacheValue, RemovalReason removalReason) { V value; if (cacheValue == null || (value = cacheValue.getValue()) == null) { return; } try { Releasable.release(value); } catch (Exception ignored) { ignored.printStackTrace(); } if (this.removalListener != null) { removalListener.onRemoval(new RemovalNotification<>(key, value, removalReason)); } } } ================================================ FILE: src/test/java/cn/ponfee/commons/cache/CacheBuilder.java ================================================ package cn.ponfee.commons.cache; import java.util.concurrent.ScheduledExecutorService; /** * 缓存构建类 * * @author Ponfee */ public final class CacheBuilder { private CacheBuilder() {} private boolean caseSensitiveKey = true; // (默认)区分大小写 private boolean compressKey = false; // (默认)不压缩key private int autoReleaseInSeconds = 0; // (默认0为不清除)清除无效key的的定时时间间隔 private long keepaliveInMillis = 0; // key保留时间,0表示无限制 private ScheduledExecutorService executor; // 定时执行器 private RemovalListener removalListener; // 删除监听器 public CacheBuilder caseSensitiveKey(boolean caseSensitiveKey) { this.caseSensitiveKey = caseSensitiveKey; return this; } public CacheBuilder compressKey(boolean compressKey) { this.compressKey = compressKey; return this; } public CacheBuilder autoReleaseInSeconds(int autoReleaseInSeconds) { this.autoReleaseInSeconds = autoReleaseInSeconds; return this; } public CacheBuilder keepaliveInMillis(long keepaliveInMillis) { this.keepaliveInMillis = keepaliveInMillis; return this; } public CacheBuilder scheduledExecutor(ScheduledExecutorService executor) { this.executor = executor; return this; } public CacheBuilder removalListener(RemovalListener removalListener) { this.removalListener = removalListener; return this; } public Cache build() { return new Cache<>(caseSensitiveKey, compressKey, keepaliveInMillis, autoReleaseInSeconds, executor, removalListener); } public static CacheBuilder newBuilder() { return new CacheBuilder<>(); } } ================================================ FILE: src/test/java/cn/ponfee/commons/cache/CacheValue.java ================================================ package cn.ponfee.commons.cache; /** * 缓存值 * * @author Ponfee * @param */ class CacheValue implements java.io.Serializable { private static final long serialVersionUID = 4266458031910874821L; private final long expireTimeMillis; // 失效时间 private final T value; // 值 CacheValue(T value, long expireTimeMillis) { this.value = value; this.expireTimeMillis = expireTimeMillis; } boolean isAlive(long refTimeMillis) { return Cache.KEEPALIVE_FOREVER == expireTimeMillis || expireTimeMillis > refTimeMillis; } boolean isExpire(long refTimeMillis) { return !isAlive(refTimeMillis); } T getValue() { return value; } } ================================================ FILE: src/test/java/cn/ponfee/commons/cache/RemovalListener.java ================================================ package cn.ponfee.commons.cache; /** * Removal listener * * @author Ponfee * @param cache key * @param cache value */ @FunctionalInterface public interface RemovalListener /*extends Consumer>*/ { void onRemoval(RemovalNotification notification); } ================================================ FILE: src/test/java/cn/ponfee/commons/cache/RemovalNotification.java ================================================ package cn.ponfee.commons.cache; /** * Removal notification * * @author Ponfee * @param * @param */ public class RemovalNotification { public enum RemovalReason { REPLACED, // value was replaced by the user EVICTED, // manually removed by the user EXPIRED // expiration timestamp has passed } private final K key; private final V value; private final RemovalReason removalReason; public RemovalNotification(K key, V value, RemovalReason removalReason) { this.key = key; this.value = value; this.removalReason = removalReason; } public K getKey() { return key; } public V getValue() { return value; } public RemovalReason getRemovalReason() { return removalReason; } } ================================================ FILE: src/test/java/cn/ponfee/commons/collects/AbstractArrayList.java ================================================ package cn.ponfee.commons.collects; import com.google.common.base.Preconditions; import java.io.Serializable; import java.util.AbstractList; import java.util.Collection; import java.util.Iterator; import java.util.ListIterator; import java.util.RandomAccess; /** * The primitive array of abstract list * * primitive array convert to list * * @author Ponfee * @param */ public abstract class AbstractArrayList extends AbstractList implements RandomAccess, Serializable { private static final long serialVersionUID = -964514644899401684L; static final int INDEX_NOT_FOUND = -1; protected final int start; protected final int end; protected final int size; public AbstractArrayList(int start, int end) { Preconditions.checkArgument(start >= 0 && start <= end); this.start = start; this.end = end; this.size = end - start; } @Override public final int size() { return size; } @Override public final boolean isEmpty() { return size == 0; } @Override public final boolean contains(Object target) { return indexOf(target) != INDEX_NOT_FOUND; } @Override public final Iterator iterator() { return new ArrayIterator(0); } @Override public final ListIterator listIterator(int index) { return super.listIterator(index); } private class ArrayIterator implements Iterator { int cursor; ArrayIterator(int cursor) { this.cursor = cursor; } @Override public boolean hasNext() { return cursor != size; } @Override public E next() { return get(cursor++); } } // --------------------------------------------------------deprecated methods @Override @Deprecated public final boolean add(E e) { throw new UnsupportedOperationException(); } @Override @Deprecated public final void add(int index, E element) { throw new UnsupportedOperationException(); } @Override @Deprecated public final E remove(int index) { throw new UnsupportedOperationException(); } @Override @Deprecated public final void clear() { throw new UnsupportedOperationException(); } @Override @Deprecated public final boolean addAll(int index, Collection c) { throw new UnsupportedOperationException(); } @Override @Deprecated protected final void removeRange(int fromIndex, int toIndex) { throw new UnsupportedOperationException(); } @Override @Deprecated public final boolean remove(Object o) { throw new UnsupportedOperationException(); } @Override @Deprecated public final boolean addAll(Collection c) { throw new UnsupportedOperationException(); } @Override @Deprecated public final boolean removeAll(Collection c) { throw new UnsupportedOperationException(); } @Override @Deprecated public final boolean retainAll(Collection c) { throw new UnsupportedOperationException(); } } ================================================ FILE: src/test/java/cn/ponfee/commons/collects/ByteArrayList.java ================================================ package cn.ponfee.commons.collects; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; import static com.google.common.base.Preconditions.checkElementIndex; import static com.google.common.base.Preconditions.checkPositionIndexes; /** * The primitive byte array to list * * Error : Arrays.asList(new byte[] {4,5,6,7}) * Correct: new ByteArrayList(new byte[] {4,5,6,7}) * * IntStream.of(new int[] { 1, 2, 3, 4 }).boxed().collect(Collectors.toList()) * * @author Ponfee */ public class ByteArrayList extends AbstractArrayList { private static final long serialVersionUID = 8638428453599555032L; private final byte[] array; public ByteArrayList(byte... array) { this(array, 0, array.length); } public ByteArrayList(byte[] array, int start, int end) { super(start, end); this.array = Objects.requireNonNull(array); } @Override public Byte get(int index) { checkElementIndex(index, size); return array[start + index]; } @Override public int indexOf(Object target) { return (target instanceof Byte) ? indexOf((byte) target) : INDEX_NOT_FOUND; } @Override public int lastIndexOf(Object target) { return (target instanceof Byte) ? lastIndexOf((byte) target) : INDEX_NOT_FOUND; } @Override public Byte set(int index, Byte element) { checkElementIndex(index, size); byte oldValue = array[start + index]; array[start + index] = Objects.requireNonNull(element); return oldValue; } @Override public List subList(int fromIndex, int toIndex) { checkPositionIndexes(fromIndex, toIndex, size); if (fromIndex == toIndex) { return Collections.emptyList(); } return new ByteArrayList(array, start + fromIndex, start + toIndex); } @Override public boolean equals(Object object) { if (object == this) { return true; } if (object instanceof ByteArrayList) { ByteArrayList that = (ByteArrayList) object; if (this.size != that.size) { return false; } for (int i = 0; i < size; i++) { if (this.array[this.start + i] != that.array[that.start + i]) { return false; } } return true; } return super.equals(object); } @Override public int hashCode() { int result = 1; for (int i = start; i < end; i++) { result = 31 * result + (int) array[i]; } return result; } @Override public String toString() { return Arrays.toString(array); } public byte[] getArray() { return Arrays.copyOfRange(array, start, end); } // ----------------------------------------------------------others methods public int indexOf(byte target) { for (int i = 0; i < size; i++) { if (array[i + start] == target) { return i; } } return INDEX_NOT_FOUND; } public int lastIndexOf(byte target) { for (int i = size - 1; i >= 0; i--) { if (array[i + start] == target) { return i; } } return INDEX_NOT_FOUND; } } ================================================ FILE: src/test/java/cn/ponfee/commons/collects/ByteArrayListTest.java ================================================ package cn.ponfee.commons.collects; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.IntStream; import org.junit.Test; import com.google.common.collect.ImmutableMap; import cn.ponfee.commons.collect.ArrayHashKey; import cn.ponfee.commons.model.Result; /** * * * @author Ponfee */ public class ByteArrayListTest { @Test public void test1() { ByteArrayList list = new ByteArrayList(new byte[] {1,2,3,4,5,6,5,8,9}, 3, 7); System.out.println(list.get(0)); System.out.println(list.isEmpty()); System.out.println(list.size()); System.out.println(); list.iterator().forEachRemaining(System.out::print); System.out.println(); list.listIterator().forEachRemaining(System.out::print); System.out.println(); list.listIterator(2).forEachRemaining(System.out::print); System.out.println(); System.out.println("============================"); System.out.println(list.contains((byte)1)); System.out.println(list.contains((byte)5)); System.out.println(list.contains("x")); System.out.println("============================"); System.out.println(list.indexOf((byte) 1)); System.out.println(list.indexOf((byte) 5)); System.out.println(list.indexOf("x")); System.out.println("============================"); System.out.println(list.lastIndexOf((byte) 1)); System.out.println(list.lastIndexOf((byte) 5)); System.out.println(list.lastIndexOf("x")); System.out.println("============================"); list.set(3, (byte)7); System.out.println(list); System.out.println(list.subList(0, 2)); System.out.println(list.equals(new ByteArrayList(new byte[] {4,5,6,7}))); System.out.println(list.equals(new ByteArrayList(new byte[] {4,5}))); System.out.println(list.hashCode()); System.out.println(Arrays.asList(new byte[] {4,5,6,7})); System.out.println(new ByteArrayList(new byte[] {4,5,6,7})); } @Test public void test2() { ByteArrayList list = new ByteArrayList(new byte[] {1,2,3,4,5,6,5,8,9}); System.out.println(list.containsAll(Arrays.asList((byte) 1, (byte) 2, (byte) 3, (byte) 4))); System.out.println(Arrays.toString(list.toArray(new Byte[] {}))); System.out.println(IntStream.of(new int[] { 1, 2, 3, 4 }).boxed().collect(Collectors.toList())); } @Test public void test3() throws Exception { Method method1 = Result.class.getDeclaredMethod("from", Object.class); Field field1 = Result.class.getDeclaredField("code"); Map map = ImmutableMap.of(ArrayHashKey.of(method1, field1), true); Method method2 = Result.class.getDeclaredMethod("from", Object.class); Field field2 = Result.class.getDeclaredField("code"); System.out.println(method1 == method2); System.out.println(field1 == field2); System.out.println(map.get(ArrayHashKey.of(method2, field2))); } } ================================================ FILE: src/test/java/cn/ponfee/commons/collects/DoubleListViewerTest.java ================================================ package cn.ponfee.commons.collects; import cn.ponfee.commons.collect.DoubleListViewer; import com.google.common.base.Joiner; import org.junit.Assert; import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * @author Ponfee */ public class DoubleListViewerTest { @Test public void test1() { List list1 = new ArrayList<>(); list1.add(1); list1.add(2); list1.add(3); List list2 = new ArrayList<>(); list2.add(3); list2.add(4); list2.add(5); DoubleListViewer viewer = new DoubleListViewer(Arrays.asList(list1, list2)); Assert.assertEquals(viewer.get(0), 1); Assert.assertEquals(viewer.get(5), 5); Assert.assertEquals(viewer.indexOf(1), 0); Assert.assertEquals(viewer.indexOf(10), -1); Assert.assertEquals("[1, 2, 3, 3, 4, 5]", viewer.toString()); Assert.assertEquals("1,2,3,3,4,5", Joiner.on(",").join(viewer)); Assert.assertThrows(IndexOutOfBoundsException.class, () -> viewer.get(6)); } } ================================================ FILE: src/test/java/cn/ponfee/commons/collects/ImmutableArrayList1.java ================================================ package cn.ponfee.commons.collects; import javax.validation.constraints.NotEmpty; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.UnaryOperator; /** * Representing immutable ArrayList * * @param the element type * @author Ponfee */ public abstract class ImmutableArrayList1 extends ArrayList { private static final long serialVersionUID = -3263696598948169517L; @SuppressWarnings("unchecked") public ImmutableArrayList1(@NotEmpty T... array) { super(array.length); for (T e : array) { super.add(e); } } public ImmutableArrayList1(@NotEmpty T[] array, T last) { super(array.length + 1); for (T e : array) { super.add(e); } super.add(last); } public ImmutableArrayList1(@NotEmpty List list) { super(list.size()); for (T e : list) { super.add(e); } } public ImmutableArrayList1(@NotEmpty List list, T last) { super(list.size() + 1); for (T e : list) { super.add(e); } super.add(last); } // --------------------------------------------------------------------------override list methods @Override public final List subList(int fromIndex, int toIndex) { return Collections.unmodifiableList(super.subList(fromIndex, toIndex)); } @Override public final Iterator iterator() { return listIterator(0); } @Override public final ListIterator listIterator() { return listIterator(0); } @Override public final ListIterator listIterator(int index) { final ListIterator iter = super.listIterator(index); return new ListIterator() { public boolean hasNext() {return iter.hasNext();} public T next() {return iter.next();} public boolean hasPrevious() {return iter.hasPrevious();} public T previous() {return iter.previous();} public int nextIndex() {return iter.nextIndex();} public int previousIndex() {return iter.previousIndex();} public void remove() { throw new UnsupportedOperationException(); } public void set(T e) { throw new UnsupportedOperationException(); } public void add(T e) { throw new UnsupportedOperationException(); } @Override public void forEachRemaining(Consumer action) { iter.forEachRemaining(action); } }; } // --------------------------------------------------------------------------unsupported operation @Override @Deprecated public final boolean add(T e) { throw new UnsupportedOperationException(); } @Override @Deprecated public final boolean addAll(Collection c) { throw new UnsupportedOperationException(); } @Override @Deprecated public final T set(int index, T element) { throw new UnsupportedOperationException(); } @Override @Deprecated public final void add(int index, T element) { throw new UnsupportedOperationException(); } @Override @Deprecated public final T remove(int index) { throw new UnsupportedOperationException(); } @Override @Deprecated public final boolean remove(Object o) { throw new UnsupportedOperationException(); } @Override @Deprecated public final void clear() { throw new UnsupportedOperationException(); } @Override @Deprecated public final boolean addAll(int index, Collection c) { throw new UnsupportedOperationException(); } @Override @Deprecated protected final void removeRange(int fromIndex, int toIndex) { throw new UnsupportedOperationException(); } @Override @Deprecated public final boolean removeAll(Collection c) { throw new UnsupportedOperationException(); } @Override @Deprecated public final boolean retainAll(Collection c) { throw new UnsupportedOperationException(); } @Override @Deprecated public final boolean removeIf(Predicate filter) { throw new UnsupportedOperationException(); } @Override @Deprecated public final void replaceAll(UnaryOperator operator) { throw new UnsupportedOperationException(); } @Override @Deprecated public final void sort(Comparator c) { throw new UnsupportedOperationException(); } @Override @Deprecated public final void trimToSize() { throw new UnsupportedOperationException(); } } ================================================ FILE: src/test/java/cn/ponfee/commons/collects/ImmutableArrayListTest.java ================================================ package cn.ponfee.commons.collects; import cn.ponfee.commons.collect.ImmutableArrayList; import cn.ponfee.commons.json.Jsons; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.IteratorUtils; import org.junit.Assert; import org.junit.Test; import java.util.Arrays; public class ImmutableArrayListTest { @Test public void test1() { ImmutableArrayList list = ImmutableArrayList.of(); Assert.assertEquals("[]", list.toString()); Assert.assertEquals("[]", list.subList(0, 0).toString()); } @Test public void test2() { ImmutableArrayList list = ImmutableArrayList.of(1, 2, 3, 4, 5); Assert.assertEquals("[1, 2, 3, 4, 5]", list.toString()); Assert.assertEquals("[]", list.subList(0, 0).toString()); Assert.assertTrue(CollectionUtils.isEqualCollection(list.subList(2, 4), Arrays.asList(1, 2, 3, 4, 5).subList(2, 4))); Assert.assertTrue(CollectionUtils.isEqualCollection(IteratorUtils.toList(list.subList(2, 4).iterator()), IteratorUtils.toList(Arrays.asList(1, 2, 3, 4, 5).subList(2, 4).iterator()))); Assert.assertTrue(CollectionUtils.isEqualCollection(IteratorUtils.toList(list.subList(2, 4).listIterator()), IteratorUtils.toList(Arrays.asList(1, 2, 3, 4, 5).subList(2, 4).listIterator()))); Assert.assertTrue(CollectionUtils.isEqualCollection(IteratorUtils.toList(list.subList(2, 4).listIterator(1)), IteratorUtils.toList(Arrays.asList(1, 2, 3, 4, 5).subList(2, 4).listIterator(1)))); Assert.assertTrue(list.subList(2, 4).contains(3)); Assert.assertFalse(list.subList(2, 4).contains(9)); Assert.assertEquals(list.subList(2, 4).indexOf(1), -1); Assert.assertEquals(ImmutableArrayList.of(0, 1, 2, 3, 1, 5, 1, 6).subList(1, 6).indexOf(1), 0); Assert.assertEquals(ImmutableArrayList.of(0, 1, 2, 3, 1, 5, 1, 6).subList(1, 6).lastIndexOf(1), 3); Assert.assertTrue(ImmutableArrayList.of(0, 1, 2, 3, 1, 5, 1, 6).subList(1, 6).containsAll(Arrays.asList(1, 3, 5))); Assert.assertFalse(ImmutableArrayList.of(0, 1, 2, 3, 1, 5, 1, 6).subList(1, 6).containsAll(Arrays.asList(1, 3, 5, 6))); Assert.assertEquals(Jsons.toJson(Arrays.asList(0, 1, 2, 3, 1, 5, 1, 6).subList(1, 6).toArray(new Integer[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0})), Jsons.toJson(ImmutableArrayList.of(0, 1, 2, 3, 1, 5, 1, 6).subList(1, 6).toArray(new Integer[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}))); Assert.assertEquals(Jsons.toJson(Arrays.asList(0, 1, 2, 3, 1, 5, 1, 6).subList(1, 6).toArray(new Integer[]{})), Jsons.toJson(ImmutableArrayList.of(0, 1, 2, 3, 1, 5, 1, 6).subList(1, 6).toArray(new Integer[]{}))); Assert.assertEquals(Jsons.toJson(Arrays.asList(0, 1, 2, 3, 1, 5, 1, 6).subList(1, 6).toArray()), Jsons.toJson(ImmutableArrayList.of(0, 1, 2, 3, 1, 5, 1, 6).subList(1, 6).toArray())); Assert.assertEquals(Arrays.asList(0, 1, 2, 3, 1, 5, 1, 6).subList(1, 6).hashCode(), ImmutableArrayList.of(0, 1, 2, 3, 1, 5, 1, 6).subList(1, 6).hashCode()); Assert.assertTrue(CollectionUtils.isEqualCollection(Arrays.asList(0, 1, 2), ImmutableArrayList.of(0, 1).concat(2))); StringBuilder builder = new StringBuilder(); Arrays.asList(0, 1, 2, 3, 1, 5, 1, 6).subList(1, 6).spliterator().trySplit().forEachRemaining(builder::append); Assert.assertEquals("12315", builder.toString()); } } ================================================ FILE: src/test/java/cn/ponfee/commons/collects/IntArrayList.java ================================================ package cn.ponfee.commons.collects; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; import static com.google.common.base.Preconditions.checkElementIndex; import static com.google.common.base.Preconditions.checkPositionIndexes; /** * The primitive int array to list * * @author Ponfee */ public class IntArrayList extends AbstractArrayList { private static final long serialVersionUID = -1601389928083241185L; private final int[] array; public IntArrayList(int... array) { this(array, 0, array.length); } public IntArrayList(int[] array, int start, int end) { super(start, end); this.array = Objects.requireNonNull(array); } @Override public Integer get(int index) { checkElementIndex(index, size); return array[start + index]; } @Override public int indexOf(Object target) { return (target instanceof Integer) ? indexOf((int) target) : INDEX_NOT_FOUND; } @Override public int lastIndexOf(Object target) { return (target instanceof Integer) ? lastIndexOf((int) target) : INDEX_NOT_FOUND; } @Override public Integer set(int index, Integer element) { checkElementIndex(index, size); int oldValue = array[start + index]; array[start + index] = Objects.requireNonNull(element); return oldValue; } @Override public List subList(int fromIndex, int toIndex) { checkPositionIndexes(fromIndex, toIndex, size); if (fromIndex == toIndex) { return Collections.emptyList(); } return new IntArrayList(array, start + fromIndex, start + toIndex); } @Override public boolean equals(Object object) { if (object == this) { return true; } if (object instanceof IntArrayList) { IntArrayList that = (IntArrayList) object; if (this.size != that.size) { return false; } for (int i = 0; i < size; i++) { if (this.array[this.start + i] != that.array[that.start + i]) { return false; } } return true; } return super.equals(object); } @Override public int hashCode() { int result = 1; for (int i = start; i < end; i++) { result = 31 * result + array[i]; } return result; } @Override public String toString() { return Arrays.toString(array); } public int[] getArray() { return Arrays.copyOfRange(array, start, end); } // ----------------------------------------------------------others methods public int indexOf(int target) { for (int i = 0; i < size; i++) { if (array[i + start] == target) { return i; } } return INDEX_NOT_FOUND; } public int lastIndexOf(int target) { for (int i = size - 1; i >= 0; i--) { if (array[i + start] == target) { return i; } } return INDEX_NOT_FOUND; } } ================================================ FILE: src/test/java/cn/ponfee/commons/collects/ListTest.java ================================================ package cn.ponfee.commons.collects; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import cn.ponfee.commons.collect.ImmutableArrayList; import cn.ponfee.commons.collect.Maps; import org.apache.commons.lang3.ArrayUtils; import org.junit.Test; import cn.ponfee.commons.collect.Collects; /** * @author Ponfee */ public class ListTest { @Test public void test1() { List list1 = new ArrayList<>(); list1.add(1); list1.add(2); list1.add(3); List list2 = new ArrayList<>(); list2.add(3); list2.add(4); list2.add(5); System.out.println("====求交集==="); System.out.println(Collects.intersect(list1, list2)); System.out.println("====求差集==="); System.out.println(Collects.different(list1, list2)); System.out.println("====求并集==="); System.out.println(Collects.union(list1, list2)); System.out.println(); System.out.println(list1); System.out.println(list2); Map map1 = Maps.toMap("a", 1, "b", 2); Map map2 = Maps.toMap("c", 3, "b", 2); System.out.println(Collects.different(map1, map2)); } @Test public void test2() { ImmutableArrayList.of(); // Object[0] ImmutableArrayList.of((String) null); // Object[] { null } //ImmutableList.of((String[]) null); // null List list1 = new ArrayList<>(); list1.add("s"); System.out.println(list1.toArray().getClass()); // class [Ljava.lang.Object; List list2 = ImmutableArrayList.of("s"); System.out.println(list2.toArray().getClass()); // class [Ljava.lang.String; System.out.println(ArrayUtils.addAll(new String[0], "s").getClass()); // class [Ljava.lang.String; System.out.println(ArrayUtils.addAll(new Object[0], "s").getClass()); // class [Ljava.lang.Object; System.out.println(Arrays.toString(ArrayUtils.addAll(new String[0], "s"))); // [s] ImmutableArrayList.of(1, 2, 3); ImmutableArrayList.of("a"); } @Test public void test3() { ImmutableArrayList.of(new String[]{"a"}, "b"); } } ================================================ FILE: src/test/java/cn/ponfee/commons/collects/LongArrayList.java ================================================ package cn.ponfee.commons.collects; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; import static com.google.common.base.Preconditions.checkElementIndex; import static com.google.common.base.Preconditions.checkPositionIndexes; /** * The primitive long array to list * * @author Ponfee */ public class LongArrayList extends AbstractArrayList { private static final long serialVersionUID = 1648346699558392015L; private final long[] array; public LongArrayList(long... array) { this(array, 0, array.length); } public LongArrayList(long[] array, int start, int end) { super(start, end); this.array = Objects.requireNonNull(array); } @Override public Long get(int index) { checkElementIndex(index, size); return array[start + index]; } @Override public int indexOf(Object target) { return (target instanceof Long) ? indexOf((long) target) : INDEX_NOT_FOUND; } @Override public int lastIndexOf(Object target) { return (target instanceof Long) ? lastIndexOf((long) target) : INDEX_NOT_FOUND; } @Override public Long set(int index, Long element) { checkElementIndex(index, size); long oldValue = array[start + index]; array[start + index] = Objects.requireNonNull(element); return oldValue; } @Override public List subList(int fromIndex, int toIndex) { checkPositionIndexes(fromIndex, toIndex, size); if (fromIndex == toIndex) { return Collections.emptyList(); } return new LongArrayList(array, start + fromIndex, start + toIndex); } @Override public boolean equals(Object object) { if (object == this) { return true; } if (object instanceof LongArrayList) { LongArrayList that = (LongArrayList) object; if (this.size != that.size) { return false; } for (int i = 0; i < size; i++) { if (this.array[this.start + i] != that.array[that.start + i]) { return false; } } return true; } return super.equals(object); } @Override public int hashCode() { int result = 1; for (int i = start; i < end; i++) { result = 31 * result + (int) array[i]; } return result; } @Override public String toString() { return Arrays.toString(array); } public long[] getArray() { return Arrays.copyOfRange(array, start, end); } // ----------------------------------------------------------others methods public int indexOf(long target) { for (int i = 0; i < size; i++) { if (array[i + start] == target) { return i; } } return INDEX_NOT_FOUND; } public int lastIndexOf(long target) { for (int i = size - 1; i >= 0; i--) { if (array[i + start] == target) { return i; } } return INDEX_NOT_FOUND; } } ================================================ FILE: src/test/java/cn/ponfee/commons/concurrent/ThreadPoolTest.java ================================================ package cn.ponfee.commons.concurrent; import java.util.Date; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.IntStream; import com.google.common.base.Stopwatch; import cn.ponfee.commons.date.Dates; public class ThreadPoolTest { public static void main(String[] args) throws Exception { System.out.println("main-thread: " + Thread.currentThread().getName()); //BLOCK_PRODUCER(); //CALLER_RUN(); deadlock(32, () -> { // XXX 33以上会出现死循环 /*try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }*/ //System.out.println("=============thread: "+ Thread.currentThread().getName()); }, 2); } private static void deadlock(int threadNumber, Runnable command, int execSeconds) { Stopwatch watch = Stopwatch.createStarted(); AtomicBoolean flag = new AtomicBoolean(true); CompletableFuture[] futures = IntStream.range(0, threadNumber).mapToObj( x -> CompletableFuture.runAsync(() -> { while (flag.get() && !Thread.currentThread().isInterrupted()) { command.run(); } }, ThreadPoolTestUtils.INFINITY_QUEUE_EXECUTOR) // CALLER_RUN_EXECUTOR:caller run will be dead lock ).toArray(CompletableFuture[]::new); System.err.println("************************************************"); try { Thread.sleep(execSeconds * 1000L); // parent thread sleep flag.set(false); CompletableFuture.allOf(futures).join(); } catch (InterruptedException e) { flag.set(false); throw new RuntimeException(e); } finally { System.out.println("multi thread exec async duration: {}" + watch.stop()); System.exit(0); } } private static class Comsumer implements Runnable { private final int num; public Comsumer(int num) { this.num = num; } @Override public void run() { try { Thread.sleep(1000 + ThreadLocalRandom.current().nextInt(6000)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("consume:=======" + Dates.format(new Date()) + "=========" + num + ", thread:" + Thread.currentThread().getName()); } } } ================================================ FILE: src/test/java/cn/ponfee/commons/concurrent/ThreadPoolTestUtils.java ================================================ package cn.ponfee.commons.concurrent; import java.util.Collection; import java.util.List; import java.util.concurrent.*; /** * Thread pool executor utility * * https://blog.csdn.net/Holmofy/article/details/73237153 * https://blog.csdn.net/holmofy/article/details/77411854 * * @author Ponfee */ public final class ThreadPoolTestUtils { public static final int MAX_CAP = 0x7FFF; // max #workers - 1 // ----------------------------------------------------------build-in scheduler/executor public static final ScheduledExecutorService CALLER_RUN_SCHEDULER = new DelegatedScheduledExecutorService("caller-run-scheduler", ThreadPoolExecutors.CALLER_RUNS); public static final ExecutorService INFINITY_QUEUE_EXECUTOR = new DelegatedExecutorService("infinity-queue-executor", Integer.MAX_VALUE, ThreadPoolExecutors.CALLER_BLOCKS); static { Runtime.getRuntime().addShutdownHook(new Thread(() -> { ThreadPoolExecutors.shutdown(((AbstractDelegatedExecutorService) CALLER_RUN_SCHEDULER).delegate); ThreadPoolExecutors.shutdown(((AbstractDelegatedExecutorService) INFINITY_QUEUE_EXECUTOR).delegate); })); } private static class DelegatedScheduledExecutorService extends AbstractDelegatedExecutorService implements ScheduledExecutorService { private DelegatedScheduledExecutorService(String threadName, RejectedExecutionHandler handler) { super(newScheduledExecutorService(threadName, handler)); } private static ScheduledExecutorService newScheduledExecutorService(String threadName, RejectedExecutionHandler handler) { // maximumPoolSize=Integer.MAX_VALUE, DelayedWorkQueue, keepAliveTime=0 ScheduledThreadPoolExecutor delegate = new ScheduledThreadPoolExecutor( 1, NamedThreadFactory.builder().prefix(threadName).build(), handler ); //delegate.allowCoreThreadTimeOut(true); // Error: Core threads must have nonzero keep alive times return delegate; } @Override public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { return ((ScheduledExecutorService) delegate).schedule(command, delay, unit); } @Override public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { return ((ScheduledExecutorService) delegate).schedule(callable, delay, unit); } @Override public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { return ((ScheduledExecutorService) delegate).scheduleAtFixedRate( command, initialDelay, period, unit ); } @Override public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { return ((ScheduledExecutorService) delegate).scheduleWithFixedDelay( command, initialDelay, delay, unit ); } } private static class DelegatedExecutorService extends AbstractDelegatedExecutorService { private DelegatedExecutorService(String threadName, int queueCapacity, RejectedExecutionHandler handler) { super(newExecutorService(threadName, queueCapacity, handler)); } private static ExecutorService newExecutorService(String threadName, int queueCapacity, RejectedExecutionHandler handler) { int corePoolSize = Math.max(Runtime.getRuntime().availableProcessors(), 1); int maximumPoolSize = Math.min(corePoolSize << 3, MAX_CAP); corePoolSize = Math.min(corePoolSize << 2, maximumPoolSize); BlockingQueue workQueue; if (queueCapacity > 0) { workQueue = new LinkedBlockingQueue<>(queueCapacity); } else { workQueue = new SynchronousQueue<>(); } // create ThreadPoolExecutor instance ThreadPoolExecutor delegate = new ThreadPoolExecutor( corePoolSize, maximumPoolSize, 120, TimeUnit.SECONDS, workQueue, NamedThreadFactory.builder().prefix(threadName).build(), handler ); delegate.allowCoreThreadTimeOut(true); // 设置允许核心线程超时关闭 return delegate; } } private static class AbstractDelegatedExecutorService implements ExecutorService { final ExecutorService delegate; private AbstractDelegatedExecutorService(ExecutorService delegate) { this.delegate = delegate; } @Override @Deprecated public void shutdown() { throw new UnsupportedOperationException(); } @Override @Deprecated public List shutdownNow() { throw new UnsupportedOperationException(); } @Override @Deprecated public boolean awaitTermination(long timeout, TimeUnit unit) { throw new UnsupportedOperationException(); } @Override public boolean isShutdown() { return this.delegate.isShutdown(); } @Override public boolean isTerminated() { return this.delegate.isTerminated(); } @Override public Future submit(Callable task) { return delegate.submit(task); } @Override public Future submit(Runnable task, T result) { return delegate.submit(task, result); } @Override public Future submit(Runnable task) { return delegate.submit(task); } @Override public List> invokeAll(Collection> tasks) throws InterruptedException { return delegate.invokeAll(tasks); } @Override public List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException { return delegate.invokeAll(tasks, timeout, unit); } @Override public T invokeAny(Collection> tasks) throws InterruptedException, ExecutionException { return delegate.invokeAny(tasks); } @Override public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return delegate.invokeAny(tasks, timeout, unit); } @Override public void execute(Runnable command) { delegate.execute(command); } } } ================================================ FILE: src/test/java/cn/ponfee/commons/dag/DAGExpressionParserTest.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.dag; import cn.ponfee.commons.base.tuple.Tuple2; import cn.ponfee.commons.tree.TreeNode; import cn.ponfee.commons.tree.print.MultiwayTreePrinter; import com.google.common.graph.EndpointPair; import com.google.common.graph.Graph; import static org.junit.Assert.*; import org.junit.Test; import java.io.IOException; import java.util.List; import java.util.Set; import static java.util.Arrays.asList; import static org.apache.commons.collections4.CollectionUtils.isEqualCollection; /** * DAGParserTest * * @author Ponfee */ public class DAGExpressionParserTest { private static final MultiwayTreePrinter> TREE_PRINTER = new MultiwayTreePrinter<>(System.out, e -> e.getNid().toString(), TreeNode::getChildren); @Test public void testProcess() { assertEquals("((A)->(((B)->(C)->(D)),((A)->(F)))->((G),(H),(X))->(J))", DAGExpressionParser.completeParenthesis("(A->((B->C->D),(A->F))->(G,H,X)->J)")); assertEquals("(A),(B)->((C)->(D)),(E)->(F)", DAGExpressionParser.completeParenthesis("A,B->(C->D),(E)->F")); assertEquals("(A),(B)->((C)->(D)),(E)->(F)", DAGExpressionParser.completeParenthesis("A,B->(C->D),E->F")); } @Test public void testPartition() { assertTrue(isEqualCollection(asList(Tuple2.of(0, 1), Tuple2.of(7, 1)), DAGExpressionParser.group("(A -> B)"))); assertTrue(isEqualCollection( asList(Tuple2.of(0, 1), Tuple2.of(4, 2), Tuple2.of(12, 2), Tuple2.of(14, 2), Tuple2.of(19, 2), Tuple2.of(22, 2), Tuple2.of(26, 2), Tuple2.of(30, 1)), DAGExpressionParser.group("(A->(B->C->D),(E->F)->(G,H)->J)") )); } @Test public void testValidate() { assertTrue(DAGExpressionParser.checkParenthesis("(A->(B->C->D),(E->F)->(G,H)->J)")); assertTrue(DAGExpressionParser.checkParenthesis("afdsafd")); assertFalse(DAGExpressionParser.checkParenthesis("((A->(B->C->D),(E->F)->(G,H)->J)")); assertFalse(DAGExpressionParser.checkParenthesis(")A->(B->C->D),(E->F)->)G,H(->J(")); assertFalse(DAGExpressionParser.checkParenthesis(")(")); assertFalse(DAGExpressionParser.checkParenthesis("()(")); assertFalse(DAGExpressionParser.checkParenthesis("())")); assertFalse(DAGExpressionParser.checkParenthesis("(()")); assertFalse(DAGExpressionParser.checkParenthesis(")()")); } @Test public void testBuildTree() throws IOException { List> partitions = DAGExpressionParser.group("(A->(B->C->D),(A->F)->(G,H,X)->J)"); TreeNode root = DAGExpressionParser.buildTree(partitions); assertEquals(root.getChildrenCount(), 3); System.out.println("------------------"); TREE_PRINTER.print(root); partitions = DAGExpressionParser.group("((A->((B->C->D),(E->F))->(G,H)->J))"); root = DAGExpressionParser.buildTree(partitions); assertEquals(root.getChildrenCount(), 1); System.out.println("\n------------------"); TREE_PRINTER.print(root); partitions = DAGExpressionParser.group("(A->((B->C->D),(E->F))->(G,H)->J)"); root = DAGExpressionParser.buildTree(partitions); assertEquals(root.getChildrenCount(), 2); System.out.println("\n------------------"); TREE_PRINTER.print(root); } @Test public void testSameExpression() { String text = "(A->((B->C->D),(A->F))->(G,H,X)->J)"; assertSameExpression(text, text); assertSameExpression("(A->((B->C->D),(A->F))->(G,H,X)->J) ", " A->((B->C->D),(A->F))->(G,H,X)->J"); assertSameExpression("(A->(B->C->D),(A->F)->(G,H,X)->J)", "A->((B->C->D),(A->F))->(G,H,X)->J"); assertSameExpression("(A->((B->C->D),(A->F))->G,H,X->J)", " A->((B->C->D),(A->F))->(G,H,X)->J"); assertSameExpression("(A->(B->C->D),(A->F)->G,H,X->J)", "A->((B->C->D),(A->F))->(G,H,X)->J"); } @Test public void testEdgesEquals() { assertEdgesEquals( "(A)->((B),(C))->(E),(F->G)->(H)", "[<0:0:Start -> 1:1:A>, <1:1:A -> 1:1:B>, <1:1:A -> 1:1:C>, <1:1:B -> 1:1:E>, <1:1:B -> 1:1:F>, <1:1:E -> 1:1:H>, <1:1:H -> 0:0:End>, <1:1:F -> 1:1:G>, <1:1:G -> 1:1:H>, <1:1:C -> 1:1:E>, <1:1:C -> 1:1:F>]" ); assertEdgesEquals( "(A->((B->C->D),(A->F))->(G,H,X)->J);(A->Y)", "[<0:0:Start -> 1:1:A>, <0:0:Start -> 2:3:A>, <1:1:A -> 1:1:B>, <1:1:A -> 1:2:A>, <1:1:B -> 1:1:C>, <1:1:C -> 1:1:D>, <1:1:D -> 1:1:G>, <1:1:D -> 1:1:H>, <1:1:D -> 1:1:X>, <1:1:G -> 1:1:J>, <1:1:J -> 0:0:End>, <1:1:H -> 1:1:J>, <1:1:X -> 1:1:J>, <1:2:A -> 1:1:F>, <1:1:F -> 1:1:G>, <1:1:F -> 1:1:H>, <1:1:F -> 1:1:X>, <2:3:A -> 2:1:Y>, <2:1:Y -> 0:0:End>]" ); assertEdgesEquals( "(A,B)->(C->D),(A->E),(B->F)->G", "[<0:0:Start -> 1:1:A>, <0:0:Start -> 1:2:B>, <1:1:A -> 1:1:C>, <1:1:A -> 1:2:A>, <1:1:A -> 1:1:B>, <1:1:C -> 1:1:D>, <1:1:D -> 1:1:G>, <1:1:G -> 0:0:End>, <1:2:A -> 1:1:E>, <1:1:E -> 1:1:G>, <1:1:B -> 1:1:F>, <1:1:F -> 1:1:G>, <1:2:B -> 1:1:C>, <1:2:B -> 1:2:A>, <1:2:B -> 1:1:B>]" ); assertEdgesEquals( "A,B->C,D,C", "[<0:0:Start -> 1:1:A>, <0:0:Start -> 1:1:B>, <1:1:A -> 1:1:C>, <1:1:A -> 1:1:D>, <1:1:A -> 1:2:C>, <1:1:C -> 0:0:End>, <1:1:D -> 0:0:End>, <1:2:C -> 0:0:End>, <1:1:B -> 1:1:C>, <1:1:B -> 1:1:D>, <1:1:B -> 1:2:C>]" ); assertEdgesEquals( "A,B->(C->D),E->F", "[<0:0:Start -> 1:1:A>, <0:0:Start -> 1:1:B>, <1:1:A -> 1:1:C>, <1:1:A -> 1:1:E>, <1:1:C -> 1:1:D>, <1:1:D -> 1:1:F>, <1:1:F -> 0:0:End>, <1:1:E -> 1:1:F>, <1:1:B -> 1:1:C>, <1:1:B -> 1:1:E>]" ); assertEdgesEquals( "A,B->(C->D),(E)->F", "[<0:0:Start -> 1:1:A>, <0:0:Start -> 1:1:B>, <1:1:A -> 1:1:C>, <1:1:A -> 1:1:E>, <1:1:C -> 1:1:D>, <1:1:D -> 1:1:F>, <1:1:F -> 0:0:End>, <1:1:E -> 1:1:F>, <1:1:B -> 1:1:C>, <1:1:B -> 1:1:E>]" ); assertEdgesEquals( "A->B;A->B", "[<0:0:Start -> 1:1:A>, <0:0:Start -> 2:2:A>, <1:1:A -> 1:1:B>, <1:1:B -> 0:0:End>, <2:2:A -> 2:2:B>, <2:2:B -> 0:0:End>]" ); } @Test public void testGraph() { String expression = "(A->((B->C->D),(A->F))->(G,H,X)->J);(A->Y)"; Graph graph = new DAGExpressionParser(expression).parse(); assertEquals("[1:1:A, 2:3:A]", graph.successors(DAGNode.START).toString()); assertTrue(graph.predecessors(DAGNode.START).isEmpty()); assertEquals("[1:1:J, 2:1:Y]", graph.predecessors(DAGNode.END).toString()); assertTrue(graph.successors(DAGNode.END).isEmpty()); assertEquals("[1:1:B, 1:2:A]", graph.successors(DAGNode.of(1, 1, "A")).toString()); //graph.adjacentNodes(); //graph.incidentEdges(); } @Test public void testGraphSequence() { String expression = "A -> B,C -> E,(F->G) -> H"; Graph graph = new DAGExpressionParser(expression).parse(); for (EndpointPair edge : graph.edges()) { System.out.println(edge.source() + " -> " + edge.target()); } Set predecessors = graph.predecessors(DAGNode.START); assertTrue(predecessors instanceof Set); assertTrue(predecessors.isEmpty()); Set successors = graph.successors(DAGNode.END); assertTrue(successors instanceof Set); assertTrue(successors.isEmpty()); } @Test public void testDAGNode() { assertTrue(DAGNode.fromString(DAGNode.START.toString()) == DAGNode.START); assertTrue(DAGNode.fromString(DAGNode.END.toString()) == DAGNode.END); assertEquals(DAGNode.fromString("1:1:test").toString(), "1:1:test"); assertEquals(DAGNode.fromString("1:1:test:ANY").getName(), "test:ANY"); assertEquals(DAGNode.fromString("1:1:test:ALL").getName(), "test:ALL"); assertEquals(DAGNode.fromString("1:1:test:ALL").toString(), "1:1:test:ALL"); } // ------------------------------------------------------------------------ private static void assertSameExpression(String text1, String text2) { System.out.println("\n\n------\n\n"); assertEquals(new DAGExpressionParser(text1).parse(), new DAGExpressionParser(text2).parse()); } private static void assertEdgesEquals(String expression, String edges) { System.out.println("\n\n------\n\n"); System.out.println(expression); Graph graph = new DAGExpressionParser(expression).parse(); assertEquals(edges, graph.edges().toString()); System.out.println(expression + " graph result: " + graph); } } ================================================ FILE: src/test/java/cn/ponfee/commons/data/ExtendedDruidPasswordCallback.java ================================================ package cn.ponfee.commons.data; import java.util.Properties; import com.alibaba.druid.filter.config.ConfigTools; import com.alibaba.druid.util.DruidPasswordCallback; /** * Druid数据源数据库密码解密 * * @author Ponfee */ public class ExtendedDruidPasswordCallback extends DruidPasswordCallback { private static final long serialVersionUID = -4596359636208162436L; @Override public void setProperties(Properties properties) { super.setProperties(properties); super.setPassword(decrypt((String) properties.get("password")).toCharArray()); //super.setUrl(decrypt((String) properties.get("url"))); } private String decrypt(String data) { try { return ConfigTools.decrypt("public-key-text", data); } catch (Exception e) { return data; } } } ================================================ FILE: src/test/java/cn/ponfee/commons/data/JSONExtractUtilsTest.java ================================================ package cn.ponfee.commons.data; import cn.ponfee.commons.schema.DataStructure; import cn.ponfee.commons.schema.json.JsonExtractUtils; import cn.ponfee.commons.schema.json.JsonId; import cn.ponfee.commons.schema.json.JsonTree; import cn.ponfee.commons.json.Jsons; import cn.ponfee.commons.model.Null; import cn.ponfee.commons.tree.TreeNode; import cn.ponfee.commons.util.Asserts; import com.alibaba.fastjson.JSON; import org.junit.Test; import java.text.ParseException; public class JSONExtractUtilsTest { @Test public void testFormat() { System.out.println(String.format("%02d", 1)); System.out.println(String.format("%01d", 10)); System.out.println(String.format("%01d", 1)); System.out.println(String.format("%02d", 10)); System.out.println(String.format("|% 3d|", 1)); System.out.println(String.format("|% 3d|", 10)); } @Test public void parseJson() { System.out.println(JSON.parse("123").getClass()); // class java.lang.Integer //System.out.println(JSON.parseObject("123").getClass()); // 【error】 System.out.println(JSON.parse("[1,2,3]").getClass()); // class com.alibaba.fastjson.JSONArray //System.out.println(JSON.parseObject("[1,2,3]").getClass()); // 【error】 System.out.println(JSON.parse("{}").getClass()); // class com.alibaba.fastjson.JSONObject System.out.println(JSON.parseObject("{}").getClass()); // class com.alibaba.fastjson.JSONObject } // ------------------------------------------------------- @Test public void extractObjectArray() throws ParseException { String text = "[{\"name\":\"Alice\",\"age\":18,\"gender\":\"F\"},{\"name\":\"Bob\",\"age\":20,\"gender\":\"M\"},{\"name\":\"Tom\",\"age\":30,\"gender\":\"M\"}]"; System.out.println("Origin data: " + text); TreeNode treeNode = JsonExtractUtils.extractSchema(text); JsonTree schemaObj = (treeNode == null) ? null : treeNode.convert(JsonTree::convert); String schemaText = JSON.toJSONString(schemaObj); System.out.println("Extracted Schema: " + schemaText); // 拷贝上面的schemaText进行更改部分checked为true(即选中某些想要的数据列) schemaText = "{\"checked\":true,\"children\":[{\"checked\":true,\"children\":[{\"checked\":false,\"name\":\"gender\",\"orders\":2,\"path\":[\"Root\",\"[{}]\",\"gender\"],\"type\":\"STRING\"},{\"checked\":true,\"name\":\"name\",\"orders\":3,\"path\":[\"Root\",\"[{}]\",\"name\"],\"type\":\"STRING\"},{\"checked\":true,\"name\":\"age\",\"orders\":4,\"path\":[\"Root\",\"[{}]\",\"age\"],\"type\":\"INTEGER\"}],\"name\":\"[{}]\",\"orders\":1,\"path\":[\"Root\",\"[{}]\"]}],\"name\":\"Root\",\"orders\":0,\"path\":[\"Root\"]}"; System.out.println("After selected Some Column: " + schemaText); schemaObj = Jsons.fromJson(schemaText, JsonTree.class); Asserts.isTrue(schemaObj.equals(JSON.parseObject(schemaText, JsonTree.class)), "two json not equals"); if (!JsonTree.hasChoose(schemaObj)) { throw new IllegalStateException("Not choose"); } DataStructure extractedData = JsonExtractUtils.extractData(text, schemaObj); System.out.println("Table data: " + JSON.toJSONString(extractedData)); } @Test public void extractDoubleArray() throws ParseException { String text = "[[\"a1\",\"b1\",11,21],[\"a2\",\"b2\",12,22],[\"a3\",\"b3\",13,23],[\"a4\",\"b4\",14,24]]"; System.out.println("Origin data: " + text); TreeNode treeNode = JsonExtractUtils.extractSchema(text); JsonTree schemaObj = (treeNode == null) ? null : treeNode.convert(JsonTree::convert); String schemaText = JSON.toJSONString(schemaObj); System.out.println("Extracted Schema: " + schemaText); // 拷贝上面的schemaText进行更改部分checked为true(即选中某些想要的数据列) schemaText = "{\"checked\":true,\"children\":[{\"checked\":true,\"children\":[{\"checked\":true,\"name\":\"[02]\",\"orders\":2,\"path\":[\"Root\",\"[[]]\",\"[02]\"],\"type\":\"STRING\"},{\"checked\":false,\"name\":\"[03]\",\"orders\":3,\"path\":[\"Root\",\"[[]]\",\"[03]\"],\"type\":\"STRING\"},{\"checked\":true,\"name\":\"[04]\",\"orders\":4,\"path\":[\"Root\",\"[[]]\",\"[04]\"],\"type\":\"INTEGER\"},{\"checked\":true,\"name\":\"[05]\",\"orders\":5,\"path\":[\"Root\",\"[[]]\",\"[05]\"],\"type\":\"INTEGER\"}],\"name\":\"[[]]\",\"orders\":1,\"path\":[\"Root\",\"[[]]\"]}],\"name\":\"Root\",\"orders\":0,\"path\":[\"Root\"]}"; System.out.println("After selected Some Column: " + schemaText); schemaObj = Jsons.fromJson(schemaText, JsonTree.class); if (!JsonTree.hasChoose(schemaObj)) { throw new IllegalStateException("Not choose"); } DataStructure extractedData = JsonExtractUtils.extractData(text, schemaObj); System.out.println("Table data: " + JSON.toJSONString(extractedData)); } @Test public void extractBasicArray() throws ParseException { String text = "[\"a\",\"b\",1,2]"; System.out.println("Origin data: " + text); TreeNode treeNode = JsonExtractUtils.extractSchema(text); JsonTree schemaObj = (treeNode == null) ? null : treeNode.convert(JsonTree::convert); String schemaText = JSON.toJSONString(schemaObj); System.out.println("Extracted Schema: " + schemaText); // 拷贝上面的schemaText进行更改部分checked为true(即选中某些想要的数据列) schemaText = "{\"checked\":true,\"children\":[{\"checked\":true,\"children\":[{\"checked\":true,\"name\":\"[02]\",\"orders\":2,\"path\":[\"Root\",\"[()]\",\"[02]\"],\"type\":\"STRING\"},{\"checked\":false,\"name\":\"[03]\",\"orders\":3,\"path\":[\"Root\",\"[()]\",\"[03]\"],\"type\":\"STRING\"},{\"checked\":false,\"name\":\"[04]\",\"orders\":4,\"path\":[\"Root\",\"[()]\",\"[04]\"],\"type\":\"INTEGER\"},{\"checked\":true,\"name\":\"[05]\",\"orders\":5,\"path\":[\"Root\",\"[()]\",\"[05]\"],\"type\":\"INTEGER\"}],\"name\":\"[()]\",\"orders\":1,\"path\":[\"Root\",\"[()]\"]}],\"name\":\"Root\",\"orders\":0,\"path\":[\"Root\"]}"; System.out.println("After selected Some Column: " + schemaText); schemaObj = Jsons.fromJson(schemaText, JsonTree.class); if (!JsonTree.hasChoose(schemaObj)) { throw new IllegalStateException("Not choose"); } DataStructure extractedData = JsonExtractUtils.extractData(text, schemaObj); System.out.println("Table data: " + JSON.toJSONString(extractedData)); } @Test public void extractObject() throws ParseException { String text = "{\"name\":\"Alice\",\"age\":18,\"gender\":\"F\"}"; System.out.println("Origin data: " + text); TreeNode treeNode = JsonExtractUtils.extractSchema(text); JsonTree schemaObj = (treeNode == null) ? null : treeNode.convert(JsonTree::convert); String schemaText = JSON.toJSONString(schemaObj); System.out.println("Extracted Schema: " + schemaText); // 拷贝上面的schemaText进行更改部分checked为true(即选中某些想要的数据列) schemaText = "{\"checked\":true,\"children\":[{\"checked\":true,\"name\":\"gender\",\"orders\":1,\"path\":[\"Root\",\"gender\"],\"type\":\"STRING\"},{\"checked\":false,\"name\":\"name\",\"orders\":2,\"path\":[\"Root\",\"name\"],\"type\":\"STRING\"},{\"checked\":true,\"name\":\"age\",\"orders\":3,\"path\":[\"Root\",\"age\"],\"type\":\"INTEGER\"}],\"name\":\"Root\",\"orders\":0,\"path\":[\"Root\"]}"; System.out.println("After selected Some Column: " + schemaText); schemaObj = Jsons.fromJson(schemaText, JsonTree.class); if (!JsonTree.hasChoose(schemaObj)) { throw new IllegalStateException("Not choose"); } DataStructure extractedData = JsonExtractUtils.extractData(text, schemaObj); System.out.println("Table data: " + JSON.toJSONString(extractedData)); } @Test public void extractNormalData() throws ParseException { String text = "{\"code\":200,\"msg\":\"成功\",\"success\":true,\"data\":[{\"name\":\"Alice\",\"age\":18,\"gender\":\"F\"},{\"name\":\"Bob\",\"age\":20,\"gender\":\"M\"},{\"name\":\"Tom\",\"age\":30,\"gender\":\"M\"}]}"; System.out.println("Origin data: " + text); TreeNode treeNode = JsonExtractUtils.extractSchema(text); JsonTree schemaObj = (treeNode == null) ? null : treeNode.convert(JsonTree::convert); String schemaText = JSON.toJSONString(schemaObj); System.out.println("Extracted Schema: " + schemaText); // 拷贝上面的schemaText进行更改部分checked为true(即选中某些想要的数据列) schemaText = "{\"checked\":true,\"children\":[{\"checked\":false,\"name\":\"msg\",\"orders\":1,\"path\":[\"Root\",\"msg\"],\"type\":\"STRING\"},{\"checked\":false,\"name\":\"code\",\"orders\":2,\"path\":[\"Root\",\"code\"],\"type\":\"INTEGER\"},{\"checked\":true,\"children\":[{\"checked\":true,\"children\":[{\"checked\":true,\"name\":\"gender\",\"orders\":5,\"path\":[\"Root\",\"data\",\"[{}]\",\"gender\"],\"type\":\"STRING\"},{\"checked\":true,\"name\":\"name\",\"orders\":6,\"path\":[\"Root\",\"data\",\"[{}]\",\"name\"],\"type\":\"STRING\"},{\"checked\":true,\"name\":\"age\",\"orders\":7,\"path\":[\"Root\",\"data\",\"[{}]\",\"age\"],\"type\":\"INTEGER\"}],\"name\":\"[{}]\",\"orders\":4,\"path\":[\"Root\",\"data\",\"[{}]\"]}],\"name\":\"data\",\"orders\":3,\"path\":[\"Root\",\"data\"]},{\"checked\":false,\"name\":\"success\",\"orders\":8,\"path\":[\"Root\",\"success\"],\"type\":\"BOOLEAN\"}],\"name\":\"Root\",\"orders\":0,\"path\":[\"Root\"]}"; System.out.println("After selected Some Column: " + schemaText); schemaObj = Jsons.fromJson(schemaText, JsonTree.class); if (!JsonTree.hasChoose(schemaObj)) { throw new IllegalStateException("Not choose"); } DataStructure extractedData = JsonExtractUtils.extractData(text, schemaObj); System.out.println("Table data: " + JSON.toJSONString(extractedData)); } @Test public void extractNormalData2() throws ParseException { String text = "{\"code\":200,\"msg\":\"成功\",\"success\":true,\"data\":[{\"name\":\"Alice\",\"age\":18,\"gender\":\"F\"},{\"name\":\"Bob\",\"age\":20,\"gender\":\"M\"},{\"name\":\"Tom\",\"age\":30,\"gender\":\"M\"}]}"; System.out.println("Origin data: " + text); TreeNode treeNode = JsonExtractUtils.extractSchema(text); JsonTree schemaObj = (treeNode == null) ? null : treeNode.convert(JsonTree::convert); String schemaText = JSON.toJSONString(schemaObj); System.out.println("Extracted Schema: " + schemaText); // 拷贝上面的schemaText进行更改部分checked为true(即选中某些想要的数据列) schemaText = "{\"checked\":true,\"children\":[{\"checked\":true,\"name\":\"msg\",\"orders\":1,\"path\":[\"Root\",\"msg\"],\"type\":\"STRING\"},{\"checked\":true,\"name\":\"code\",\"orders\":2,\"path\":[\"Root\",\"code\"],\"type\":\"INTEGER\"},{\"checked\":true,\"children\":[{\"checked\":true,\"children\":[{\"checked\":true,\"name\":\"gender\",\"orders\":5,\"path\":[\"Root\",\"data\",\"[{}]\",\"gender\"],\"type\":\"STRING\"},{\"checked\":false,\"name\":\"name\",\"orders\":6,\"path\":[\"Root\",\"data\",\"[{}]\",\"name\"],\"type\":\"STRING\"},{\"checked\":true,\"name\":\"age\",\"orders\":7,\"path\":[\"Root\",\"data\",\"[{}]\",\"age\"],\"type\":\"INTEGER\"}],\"name\":\"[{}]\",\"orders\":4,\"path\":[\"Root\",\"data\",\"[{}]\"]}],\"name\":\"data\",\"orders\":3,\"path\":[\"Root\",\"data\"]},{\"checked\":false,\"name\":\"success\",\"orders\":8,\"path\":[\"Root\",\"success\"],\"type\":\"BOOLEAN\"}],\"name\":\"Root\",\"orders\":0,\"path\":[\"Root\"]}"; System.out.println("After selected Some Column: " + schemaText); schemaObj = Jsons.fromJson(schemaText, JsonTree.class); if (!JsonTree.hasChoose(schemaObj)) { throw new IllegalStateException("Not choose"); } DataStructure extractedData = JsonExtractUtils.extractData(text, schemaObj); System.out.println("Table data: " + JSON.toJSONString(extractedData)); } @Test public void extractComplexData() throws ParseException { String text = "{\"code\":200,\"msg\":\"成功\",\"success\":true,\"data\":{\"friends\":[{\"name\":\"Alice\",\"age\":18,\"gender\":\"F\",\"values\":[\"a\",\"b\",1,2]},{\"name\":\"Bob\",\"age\":20,\"gender\":\"M\",\"values\":[\"c\",\"d\",4]},{\"name\":\"Tom\",\"age\":30,\"gender\":\"M\",\"values\":[\"e\",\"f\"]}],\"darray\":[[\"a1\",\"b1\",11,21],[\"a2\",\"b2\",12,22],[\"a3\",\"b3\",13,23],[\"a4\",\"b4\",14,24]]}}"; System.out.println("Origin data: " + text); TreeNode treeNode = JsonExtractUtils.extractSchema(text); JsonTree schemaObj = (treeNode == null) ? null : treeNode.convert(JsonTree::convert); String schemaText = JSON.toJSONString(schemaObj); System.out.println("Extracted Schema: " + schemaText); // 拷贝上面的schemaText进行更改部分checked为true(即选中某些想要的数据列) schemaText = "{\"checked\":true,\"children\":[{\"checked\":true,\"name\":\"msg\",\"orders\":1,\"path\":[\"Root\",\"msg\"],\"type\":\"STRING\"},{\"checked\":true,\"name\":\"code\",\"orders\":2,\"path\":[\"Root\",\"code\"],\"type\":\"INTEGER\"},{\"checked\":true,\"children\":[{\"checked\":true,\"children\":[{\"checked\":true,\"children\":[{\"checked\":true,\"name\":\"[06]\",\"orders\":6,\"path\":[\"Root\",\"data\",\"darray\",\"[[]]\",\"[06]\"],\"type\":\"STRING\"},{\"checked\":true,\"name\":\"[07]\",\"orders\":7,\"path\":[\"Root\",\"data\",\"darray\",\"[[]]\",\"[07]\"],\"type\":\"STRING\"},{\"checked\":true,\"name\":\"[08]\",\"orders\":8,\"path\":[\"Root\",\"data\",\"darray\",\"[[]]\",\"[08]\"],\"type\":\"INTEGER\"},{\"checked\":true,\"name\":\"[09]\",\"orders\":9,\"path\":[\"Root\",\"data\",\"darray\",\"[[]]\",\"[09]\"],\"type\":\"INTEGER\"}],\"name\":\"[[]]\",\"orders\":5,\"path\":[\"Root\",\"data\",\"darray\",\"[[]]\"]}],\"name\":\"darray\",\"orders\":4,\"path\":[\"Root\",\"data\",\"darray\"]},{\"checked\":true,\"children\":[{\"checked\":true,\"children\":[{\"checked\":true,\"name\":\"gender\",\"orders\":12,\"path\":[\"Root\",\"data\",\"friends\",\"[{}]\",\"gender\"],\"type\":\"STRING\"},{\"checked\":true,\"children\":[{\"checked\":true,\"children\":[{\"checked\":true,\"name\":\"[15]\",\"orders\":15,\"path\":[\"Root\",\"data\",\"friends\",\"[{}]\",\"values\",\"[()]\",\"[15]\"],\"type\":\"STRING\"},{\"checked\":true,\"name\":\"[16]\",\"orders\":16,\"path\":[\"Root\",\"data\",\"friends\",\"[{}]\",\"values\",\"[()]\",\"[16]\"],\"type\":\"STRING\"},{\"checked\":true,\"name\":\"[17]\",\"orders\":17,\"path\":[\"Root\",\"data\",\"friends\",\"[{}]\",\"values\",\"[()]\",\"[17]\"],\"type\":\"INTEGER\"},{\"checked\":true,\"name\":\"[18]\",\"orders\":18,\"path\":[\"Root\",\"data\",\"friends\",\"[{}]\",\"values\",\"[()]\",\"[18]\"],\"type\":\"INTEGER\"}],\"name\":\"[()]\",\"orders\":14,\"path\":[\"Root\",\"data\",\"friends\",\"[{}]\",\"values\",\"[()]\"]}],\"name\":\"values\",\"orders\":13,\"path\":[\"Root\",\"data\",\"friends\",\"[{}]\",\"values\"]},{\"checked\":true,\"name\":\"name\",\"orders\":19,\"path\":[\"Root\",\"data\",\"friends\",\"[{}]\",\"name\"],\"type\":\"STRING\"},{\"checked\":true,\"name\":\"age\",\"orders\":20,\"path\":[\"Root\",\"data\",\"friends\",\"[{}]\",\"age\"],\"type\":\"INTEGER\"}],\"name\":\"[{}]\",\"orders\":11,\"path\":[\"Root\",\"data\",\"friends\",\"[{}]\"]}],\"name\":\"friends\",\"orders\":10,\"path\":[\"Root\",\"data\",\"friends\"]}],\"name\":\"data\",\"orders\":3,\"path\":[\"Root\",\"data\"]},{\"checked\":true,\"name\":\"success\",\"orders\":21,\"path\":[\"Root\",\"success\"],\"type\":\"BOOLEAN\"}],\"name\":\"Root\",\"orders\":0,\"path\":[\"Root\"]}"; System.out.println("After selected Some Column: " + schemaText); schemaObj = Jsons.fromJson(schemaText, JsonTree.class); if (!JsonTree.hasChoose(schemaObj)) { throw new IllegalStateException("Not choose"); } DataStructure extractedData = JsonExtractUtils.extractData(text, schemaObj); System.out.println("Table data: " + JSON.toJSONString(extractedData)); } } ================================================ FILE: src/test/java/cn/ponfee/commons/date/DateFormatTest.java ================================================ package cn.ponfee.commons.date; import com.google.common.collect.Lists; import org.apache.commons.lang3.time.DateUtils; import org.junit.Assert; import org.junit.Test; import java.text.ParseException; import java.time.*; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.time.temporal.ChronoField; import java.util.Date; import java.util.Locale; public class DateFormatTest { @Test public void test1() { LocalDate date = LocalDate.of(2014, 3, 18); String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE); String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE); LocalDate date1 = LocalDate.parse("20140318", DateTimeFormatter.BASIC_ISO_DATE); LocalDate date2 = LocalDate.parse("2014-03-18", DateTimeFormatter.ISO_LOCAL_DATE); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy"); date1 = LocalDate.of(2014, 3, 18); String formattedDate = date1.format(formatter); date2 = LocalDate.parse(formattedDate, formatter); DateTimeFormatter italianFormatter = new DateTimeFormatterBuilder() .appendText(ChronoField.DAY_OF_MONTH) .appendLiteral(". ") .appendText(ChronoField.MONTH_OF_YEAR) .appendLiteral(" ") .appendText(ChronoField.YEAR) .parseCaseInsensitive() .toFormatter(Locale.ITALIAN); ZoneId romeZone = ZoneId.of("Europe/Rome"); date = LocalDate.of(2014, Month.MARCH, 18); ZonedDateTime zdt1 = date.atStartOfDay(romeZone); LocalDateTime dateTime = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45); ZonedDateTime zdt2 = dateTime.atZone(romeZone); Instant instant = Instant.now(); ZonedDateTime zdt3 = instant.atZone(romeZone); System.out.println(zdt3); } @Test public void test2() { LocalDateTime dateTime = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45); Instant instantFromDateTime = dateTime.toInstant(ZoneOffset.UTC); System.out.println(instantFromDateTime); } @Test public void test3() { System.out.println(Runtime.getRuntime().availableProcessors()); System.out.println(Lists.newArrayList(1,2,3).stream().reduce(10, Integer::sum)); System.out.println(Lists.newArrayList(1,2,3).stream().reduce(Integer::sum)); } @Test public void test4() throws ParseException { Date zero = JavaUtilDateFormat.PATTERN_41.parse(Dates.ZERO_DATETIME); Assert.assertEquals(-62170185600000L, zero.getTime()); Assert.assertEquals(zero, JavaUtilDateFormat.DEFAULT.parse(Dates.ZERO_DATETIME)); Assert.assertEquals(zero, DateUtils.parseDate(Dates.ZERO_DATETIME, Dates.DATETIME_PATTERN)); } } ================================================ FILE: src/test/java/cn/ponfee/commons/date/DatePeriodCalculatorTest.java ================================================ package cn.ponfee.commons.date; import org.apache.commons.lang3.time.FastDateFormat; import org.junit.Assert; import org.junit.Test; import java.util.Calendar; import java.util.Date; /** * 周期计算 * * @author Ponfee */ public class DatePeriodCalculatorTest { private static final FastDateFormat FORMAT = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss.SSS"); @Test public void test1() { test(Dates.toDate("2020-02-26 00:00:00", "yyyy-MM-dd HH:mm:ss")); System.out.println("------------------------\n"); test(Dates.toDate("2021-02-26 00:00:00", "yyyy-MM-dd HH:mm:ss")); } private static void test(Date startDate ) { int step = 2; int next = 1; Date target = startDate; String except, actual; except = calc(DatePeriodCalculator.Periods.DAILY, startDate, target, step, next); actual = DatePeriods.DAILY.next(startDate, target, step, next).toString(); System.out.println(actual); Assert.assertEquals(except, actual); except = calc(DatePeriodCalculator.Periods.WEEKLY, startDate, target, step, next); actual = DatePeriods.WEEKLY.next(startDate, target, step, next).toString(); System.out.println(actual); Assert.assertEquals(except, actual); except = calc(DatePeriodCalculator.Periods.MONTHLY, startDate, target, step, next); actual = DatePeriods.MONTHLY.next(startDate, target, step, next).toString(); System.out.println(actual); Assert.assertEquals(except, actual); except = calc(DatePeriodCalculator.Periods.QUARTERLY, startDate, target, step, next); actual = DatePeriods.QUARTERLY.next(startDate, target, step, next).toString(); System.out.println(actual); Assert.assertEquals(except, actual); except = calc(DatePeriodCalculator.Periods.HALF_YEARLY, startDate, target, step, next); actual = DatePeriods.SEMIANNUAL.next(startDate, target, step, next).toString(); System.out.println(actual); Assert.assertEquals(except, actual); except = calc(DatePeriodCalculator.Periods.YEARLY, startDate, target, step, next); actual = DatePeriods.ANNUAL.next(startDate, target, step, next).toString(); System.out.println(actual); Assert.assertEquals(except, actual); } private static class DatePeriodCalculator { private final Date starting; // 最开始的周期(起点)时间 private final Date target; // 待计算时间 private final Periods period; // 周期类型 public DatePeriodCalculator(Date starting, Date target, Periods period) { this.starting = starting; this.target = target; this.period = period; } /** * @param quantity 周期数量 * @param next 目标周期的下next个周期 * @return */ public Date[] calculate(int quantity, int next) { if (quantity < 1) { throw new IllegalArgumentException("quantity must be positive number"); } if (starting.after(target)) { throw new IllegalArgumentException("starting cannot after target date"); } Calendar c1 = Calendar.getInstance(); Calendar c2 = Calendar.getInstance(); c1.setTime(starting); c2.setTime(target); Date startDate = null; int cycleNum, year; Calendar tmp; float days; switch (period) { case WEEKLY: quantity *= 7; case DAILY: days = c2.get(Calendar.DAY_OF_YEAR) - c1.get(Calendar.DAY_OF_YEAR); // 间隔天数 year = c2.get(Calendar.YEAR); tmp = (Calendar) c1.clone(); while (tmp.get(Calendar.YEAR) != year) { days += tmp.getActualMaximum(Calendar.DAY_OF_YEAR);// 得到当年的实际天数 tmp.add(Calendar.YEAR, 1); } cycleNum = (int) Math.floor(days / quantity) + next; // 上一个周期 c1.add(Calendar.DAY_OF_YEAR, cycleNum * quantity); startDate = c1.getTime(); c1.add(Calendar.DAY_OF_YEAR, quantity); break; case QUARTERLY: // 季度 case HALF_YEARLY: // 半年度 case YEARLY: // 年度 case MONTHLY: // 月 switch (period) { case QUARTERLY: // 季度 quantity *= 3; break; case HALF_YEARLY: // 半年度 quantity *= 6; break; case YEARLY: quantity *= 12; // 年度 break; default: // MONTHLY } // 间隔月数 int intervalMonth = (c2.get(Calendar.YEAR) - c1.get(Calendar.YEAR)) * 12 + c2.get(Calendar.MONTH) - c1.get(Calendar.MONTH); cycleNum = (int) Math.floor(intervalMonth / quantity); tmp = (Calendar) c1.clone(); // 跨月问题,当前时间仍属于该周期内,则应减一个周期数,如:(2012-01-15 ~ 2012-02-14,当前时间为2012-02-14,则当前时间属于该周期,而不是下一周期) tmp.add(Calendar.MONTH, cycleNum * quantity); if (tmp.after(c2)) { cycleNum -= 1; } cycleNum += next; // 上一个周期 c1.add(Calendar.MONTH, cycleNum * quantity); startDate = c1.getTime(); // 本周期开始时间 c1.add(Calendar.MONTH, quantity); // 本周期结束时间 break; default: throw new IllegalArgumentException("invalid period type"); } c1.add(Calendar.MILLISECOND, -1); return new Date[]{startDate, c1.getTime()}; } public Date[] calculate(int next) { return calculate(1, next); } private enum Periods { DAILY("每日"), // WEEKLY("每周"), // MONTHLY("每月"), // QUARTERLY("每季度"), // HALF_YEARLY("每半年"), // YEARLY("每年"); private final String desc; Periods(String desc) { this.desc = desc; } } } private static String calc(DatePeriodCalculator.Periods p, Date start, Date target, int step, int next) { Date[] dates = new DatePeriodCalculator(start, target, p).calculate(step, next); return FORMAT.format(dates[0]) + " ~ " + FORMAT.format(dates[1]); } } ================================================ FILE: src/test/java/cn/ponfee/commons/date/DatePeriodsTest.java ================================================ package cn.ponfee.commons.date; import cn.ponfee.commons.util.Bytes; import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.binary.Hex; import org.junit.Assert; import org.junit.Test; import java.util.Date; public class DatePeriodsTest { @Test public void test2() { String format = "yyyy-MM-dd HH:mm:ss.SSS"; Date origin = Dates.toDate("2018-10-21 12:23:32.000", format); Date target = Dates.toDate("2018-10-29 12:23:32.000", format); Assert.assertEquals("2018-10-29 14:23:32.000 ~ 2018-10-29 16:23:31.999", DatePeriods.HOURLY.next(origin, target, 2, 1).toString()); Assert.assertEquals("2018-10-29 16:23:32.000 ~ 2018-10-29 18:23:31.999", DatePeriods.HOURLY.next(origin, Dates.toDate("2018-10-29 14:23:32.000", format), 2, 1).toString()); Assert.assertEquals("2018-10-29 14:23:32.000 ~ 2018-10-29 16:23:31.999", DatePeriods.HOURLY.next(Dates.toDate("2018-10-29 14:23:32.000", format), 2, 0).toString()); Assert.assertEquals("2018-10-29 14:23:32.000 ~ 2018-10-29 16:23:31.999", DatePeriods.HOURLY.next(origin, Dates.plusMillis(Dates.toDate("2018-10-29 14:23:32.000", format),-1), 2, 1).toString()); Assert.assertEquals("2018-10-29 16:23:32.000 ~ 2018-10-29 18:23:31.999", DatePeriods.HOURLY.next(origin, Dates.plusMillis(Dates.toDate("2018-10-29 14:23:32.000", format),1), 2, 1).toString()); Assert.assertEquals("2018-11-07 10:23:32.000 ~ 2018-11-07 17:23:31.999", DatePeriods.HOURLY.next(origin, target, 7, 31).toString()); Assert.assertEquals("2019-06-02 12:23:32.000 ~ 2019-06-09 12:23:31.999", DatePeriods.DAILY.next(origin, target, 7, 31).toString()); Assert.assertEquals("2022-12-18 12:23:32.000 ~ 2023-02-05 12:23:31.999", DatePeriods.WEEKLY.next(origin, target, 7, 31).toString()); Assert.assertEquals("2036-11-21 12:23:32.000 ~ 2037-06-21 12:23:31.999", DatePeriods.MONTHLY.next(origin, target, 7, 31).toString()); Assert.assertEquals("2073-01-21 12:23:32.000 ~ 2074-10-21 12:23:31.999", DatePeriods.QUARTERLY.next(origin, target, 7, 31).toString()); Assert.assertEquals("2127-04-21 12:23:32.000 ~ 2130-10-21 12:23:31.999", DatePeriods.SEMIANNUAL.next(origin, target, 7, 31).toString()); Assert.assertEquals("2235-10-21 12:23:32.000 ~ 2242-10-21 12:23:31.999", DatePeriods.ANNUAL.next(origin, target, 7, 31).toString()); } @Test public void test3() { String format = "yyyy-MM-dd HH:mm:ss.SSS"; Date origin = Dates.toDate("2017-10-21 12:23:32.000", format); Date begin = Dates.toDate("2018-10-21 11:23:32.000", format); int step = 3, next = 1; System.out.println(DatePeriods.HOURLY.next(origin, begin, step, 0)); System.out.println(); DatePeriods.Segment interval = DatePeriods.HOURLY.next(begin, step, next); System.out.println(interval); int i = 4; while (i-- > 0) { begin = interval.begin(); interval = DatePeriods.HOURLY.next(begin, step, next); System.out.println(interval); } } @Test public void testDateMax() { Date a = Dates.toDate("2020-10-12 12:34:23"); Date b = Dates.toDate("2020-10-12 12:34:26"); Assert.assertEquals(null, Dates.max(null, null)); Assert.assertEquals(null, Dates.min(null, null)); Assert.assertEquals(a, Dates.max(a, null)); Assert.assertEquals(a, Dates.max(null, a)); Assert.assertEquals(a, Dates.min(a, null)); Assert.assertEquals(a, Dates.min(null, a)); Assert.assertEquals(b, Dates.max(a, b)); Assert.assertEquals(b, Dates.max(b, a)); Assert.assertEquals(a, Dates.min(a, b)); Assert.assertEquals(a, Dates.min(b, a)); } @Test public void testPeriods() { String format = "yyyy-MM-dd HH:mm:ss.SSS"; Date origin = Dates.toDate("2018-10-21 06:23:32.000", format); Date begin = Dates.toDate("2018-10-21 11:54:12.000", format); int step = 3; DatePeriods.Segment segment = DatePeriods.HOURLY.next(origin, begin, step, 0); Assert.assertEquals("2018-10-21 09:23:32.000 ~ 2018-10-21 12:23:31.999", segment.toString()); segment = DatePeriods.HOURLY.next(segment.begin(), segment.begin(), step, 0); Assert.assertEquals("2018-10-21 09:23:32.000 ~ 2018-10-21 12:23:31.999", segment.toString()); segment = DatePeriods.HOURLY.next(segment.begin(), step, 0); Assert.assertEquals("2018-10-21 09:23:32.000 ~ 2018-10-21 12:23:31.999", segment.toString()); segment = DatePeriods.HOURLY.next(segment.begin(), step, 1); Assert.assertEquals("2018-10-21 12:23:32.000 ~ 2018-10-21 15:23:31.999", segment.toString()); segment = DatePeriods.HOURLY.next(segment.begin(), step, 1); Assert.assertEquals("2018-10-21 15:23:32.000 ~ 2018-10-21 18:23:31.999", segment.toString()); } @Test public void test() throws DecoderException { String hex = "0cb703fbc86a41b0"; byte[] bytes = Hex.decodeHex(hex.toCharArray()); long number = Bytes.toLong(bytes); System.out.println(number); System.out.println(Long.toHexString(number)); Assert.assertEquals("cb703fbc86a41b0", Long.toHexString(number)); Assert.assertEquals("0cb703fbc86a41b0", Bytes.encodeHex(Bytes.toBytes(number))); } } ================================================ FILE: src/test/java/cn/ponfee/commons/date/DatesTest.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.date; import cn.ponfee.commons.json.Jsons; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import org.apache.commons.lang3.time.DateFormatUtils; import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.time.FastDateFormat; import org.junit.Assert; import org.junit.Test; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.Duration; import java.util.Arrays; import java.util.Comparator; import java.util.Date; import java.util.Locale; import static cn.ponfee.commons.date.Dates.*; /** * Dates test * * @author Ponfee */ public class DatesTest { public static final FastDateFormat DATE_FORMAT = FastDateFormat.getInstance(Dates.DATETIME_PATTERN); static int round = 1_000_000; @Test public void test() { String str = "2023-01-03 15:23:45.321"; Assert.assertTrue(isValidDate(str)); Assert.assertTrue(isValidDate(str, DATETIME_PATTERN)); Assert.assertFalse(isValidDate("2020-xx-00 00:00:00", DATETIME_PATTERN)); Assert.assertTrue(isZeroDate(toDate(ZERO_DATETIME))); Assert.assertTrue(isZeroDate(toDate(ZERO_DATETIME, DATETIME_PATTERN))); Assert.assertEquals(19, now(DATETIME_PATTERN).length()); Assert.assertEquals(str, format(toDate(str), DATEFULL_PATTERN)); Assert.assertEquals(str, format(toDate(str, DATEFULL_PATTERN), DATEFULL_PATTERN)); Assert.assertEquals("1970-01-01 08:00:02.312", format(2312, DATEFULL_PATTERN)); Assert.assertEquals("2023-01-03 15:23:45.731", format(plusMillis(toDate(str, DATEFULL_PATTERN), 410), DATEFULL_PATTERN)); Assert.assertEquals("2023-01-03 15:23:48.321", format(plusSeconds(toDate(str, DATEFULL_PATTERN), 3), DATEFULL_PATTERN)); Assert.assertEquals("2023-01-03 15:26:45.321", format(plusMinutes(toDate(str, DATEFULL_PATTERN), 3), DATEFULL_PATTERN)); Assert.assertEquals("2023-01-03 18:23:45.321", format(plusHours(toDate(str, DATEFULL_PATTERN), 3), DATEFULL_PATTERN)); Assert.assertEquals("2023-01-06 15:23:45.321", format(plusDays(toDate(str, DATEFULL_PATTERN), 3), DATEFULL_PATTERN)); Assert.assertEquals("2023-01-24 15:23:45.321", format(plusWeeks(toDate(str, DATEFULL_PATTERN), 3), DATEFULL_PATTERN)); Assert.assertEquals("2023-04-03 15:23:45.321", format(plusMonths(toDate(str, DATEFULL_PATTERN), 3), DATEFULL_PATTERN)); Assert.assertEquals("2026-01-03 15:23:45.321", format(plusYears(toDate(str, DATEFULL_PATTERN), 3), DATEFULL_PATTERN)); Assert.assertEquals("2023-01-03 15:23:45.210", format(minusMillis(toDate(str, DATEFULL_PATTERN), 111), DATEFULL_PATTERN)); Assert.assertEquals("2023-01-03 15:23:42.321", format(minusSeconds(toDate(str, DATEFULL_PATTERN), 3), DATEFULL_PATTERN)); Assert.assertEquals("2023-01-03 15:20:45.321", format(minusMinutes(toDate(str, DATEFULL_PATTERN), 3), DATEFULL_PATTERN)); Assert.assertEquals("2023-01-03 12:23:45.321", format(minusHours(toDate(str, DATEFULL_PATTERN), 3), DATEFULL_PATTERN)); Assert.assertEquals("2022-12-31 15:23:45.321", format(minusDays(toDate(str, DATEFULL_PATTERN), 3), DATEFULL_PATTERN)); Assert.assertEquals("2022-12-13 15:23:45.321", format(minusWeeks(toDate(str, DATEFULL_PATTERN), 3), DATEFULL_PATTERN)); Assert.assertEquals("2022-10-03 15:23:45.321", format(minusMonths(toDate(str, DATEFULL_PATTERN), 3), DATEFULL_PATTERN)); Assert.assertEquals("2020-01-03 15:23:45.321", format(minusYears(toDate(str, DATEFULL_PATTERN), 3), DATEFULL_PATTERN)); Assert.assertEquals("2023-01-03 00:00:00.000", format(startOfDay(toDate(str, DATEFULL_PATTERN)), DATEFULL_PATTERN)); Assert.assertEquals("2023-01-03 23:59:59.000", format(endOfDay(toDate(str, DATEFULL_PATTERN)), DATEFULL_PATTERN)); Assert.assertEquals("2023-01-02 00:00:00.000", format(startOfWeek(toDate(str, DATEFULL_PATTERN)), DATEFULL_PATTERN)); Assert.assertEquals("2023-01-08 23:59:59.000", format(endOfWeek(toDate(str, DATEFULL_PATTERN)), DATEFULL_PATTERN)); Assert.assertEquals("2023-01-01 00:00:00.000", format(startOfMonth(toDate(str, DATEFULL_PATTERN)), DATEFULL_PATTERN)); Assert.assertEquals("2023-01-31 23:59:59.000", format(endOfMonth(toDate(str, DATEFULL_PATTERN)), DATEFULL_PATTERN)); Assert.assertEquals("2023-01-01 00:00:00.000", format(startOfYear(toDate(str, DATEFULL_PATTERN)), DATEFULL_PATTERN)); Assert.assertEquals("2023-12-31 23:59:59.000", format(endOfYear(toDate(str, DATEFULL_PATTERN)), DATEFULL_PATTERN)); Assert.assertEquals("2023-01-06 15:23:45.321", format(withDayOfWeek(toDate(str, DATEFULL_PATTERN), 5), DATEFULL_PATTERN)); Assert.assertEquals("2023-01-05 15:23:45.321", format(withDayOfMonth(toDate(str, DATEFULL_PATTERN), 5), DATEFULL_PATTERN)); Assert.assertEquals("2023-06-05 15:23:45.321", format(withDayOfMonth(toDate("2023-06-25 15:23:45.321", DATEFULL_PATTERN), 5), DATEFULL_PATTERN)); Assert.assertEquals("2023-01-05 15:23:45.321", format(withDayOfYear(toDate(str, DATEFULL_PATTERN), 5), DATEFULL_PATTERN)); Assert.assertEquals("2023-01-05 15:23:45.321", format(withDayOfYear(toDate("2023-06-25 15:23:45.321", DATEFULL_PATTERN), 5), DATEFULL_PATTERN)); Assert.assertEquals(15, hourOfDay(toDate(str, DATEFULL_PATTERN))); Assert.assertEquals(2, dayOfWeek(toDate(str, DATEFULL_PATTERN))); Assert.assertEquals(3, dayOfMonth(toDate(str, DATEFULL_PATTERN))); Assert.assertEquals(156, dayOfYear(toDate("2023-06-05 15:23:45.321", DATEFULL_PATTERN))); Assert.assertEquals(15, daysBetween(toDate("2023-05-21 15:23:45"),toDate("2023-06-05 15:23:45"))); Assert.assertEquals("45 23 15 3 1 ? 2023", toCronExpression(toDate(str))); } @Test public void testSimpleDateFormat() { SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); Date date = new Date(); for (int i = 0; i < round; i++) { format.format(date); } } @Test public void testFastDateFormat() { FastDateFormat format = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss.SSS"); Date date = new Date(); for (int i = 0; i < round; i++) { format.format(date); } } @Test public void testDateFormat() throws ParseException { String json = "{\"createTime\":\"" + Dates.ZERO_DATETIME + "\"}"; DateEntity dateEntity = Jsons.fromJson(json,DateEntity.class); DateEntity entity = Jsons.fromJson("{\"createTime\":\"2000-03-01 00:00:00\"}",DateEntity.class); Assert.assertEquals("2000-03-01 00:00:00", Dates.format(entity.getCreateTime())); // test format Assert.assertEquals("0002-11-30 00:00:00", DateFormatUtils.format(dateEntity.getCreateTime(), Dates.DATETIME_PATTERN)); Assert.assertEquals("0002-11-30 00:00:00", DATE_FORMAT.format(dateEntity.getCreateTime())); //Assert.assertEquals("-0001-11-28 00:05:43", Dates.format(orderDriverEntity.getCreateTime(), Dates.DEFAULT_DATETIME_FORMAT)); error long time = -62170185600000L; Assert.assertEquals(time, new Date(time).getTime()); Assert.assertTrue(Dates.isZeroDate(new Date(time))); // test parse Date zeroDate = new Date(time); Assert.assertEquals(zeroDate, dateEntity.getCreateTime()); Assert.assertEquals(zeroDate, new Date(zeroDate.getTime())); Assert.assertEquals(zeroDate, DateUtils.parseDate(Dates.ZERO_DATETIME, Dates.DATETIME_PATTERN)); Assert.assertEquals(zeroDate, DATE_FORMAT.parse(Dates.ZERO_DATETIME)); //Assert.assertEquals(time, Dates.toDate(zeroDateStr, Dates.DEFAULT_DATETIME_FORMAT).getTime()); error } @Test public void testDateMax() { Date a = Dates.toDate("2021-10-12 12:34:23"); Date b = Dates.toDate("2021-10-12 12:34:24"); Assert.assertEquals(null, Dates.max(null, null)); Assert.assertEquals(null, Dates.min(null, null)); Assert.assertEquals(a, Dates.max(a, null)); Assert.assertEquals(a, Dates.max(null, a)); Assert.assertEquals(a, Dates.min(a, null)); Assert.assertEquals(a, Dates.min(null, a)); Assert.assertEquals(b, Dates.max(a, b)); Assert.assertEquals(b, Dates.max(b, a)); Assert.assertEquals(a, Dates.min(a, b)); Assert.assertEquals(a, Dates.min(b, a)); } @Test public void testPeriods() { String format = "yyyy-MM-dd HH:mm:ss.SSS"; Date origin = Dates.toDate("2017-10-21 12:23:32.000", format); Date target = Dates.toDate("2018-10-21 11:54:12.000", format); int step = 3; DatePeriods periods = DatePeriods.HOURLY; DatePeriods.Segment segment = periods.next(origin, target, step, 0); Assert.assertEquals("2018-10-21 09:23:32.000 ~ 2018-10-21 12:23:31.999", segment.toString()); segment = periods.next(segment.begin(), segment.begin(), step, 0); Assert.assertEquals("2018-10-21 09:23:32.000 ~ 2018-10-21 12:23:31.999", segment.toString()); segment = periods.next(segment.begin(), step, 0); Assert.assertEquals("2018-10-21 09:23:32.000 ~ 2018-10-21 12:23:31.999", segment.toString()); segment = periods.next(segment.begin(), step, 1); Assert.assertEquals("2018-10-21 12:23:32.000 ~ 2018-10-21 15:23:31.999", segment.toString()); segment = periods.next(segment.begin(), step, 1); Assert.assertEquals("2018-10-21 15:23:32.000 ~ 2018-10-21 18:23:31.999", segment.toString()); // SECOND periods = DatePeriods.PER_SECOND; origin = Dates.toDate("2022-05-21 12:23:12.000", format); target = Dates.toDate("2022-05-21 12:23:23.000", format); step = 5; segment = periods.next(origin, target, step, 0); Assert.assertEquals("2022-05-21 12:23:22.000 ~ 2022-05-21 12:23:26.999", segment.toString()); segment = periods.next(segment.begin(), step, 1); Assert.assertEquals("2022-05-21 12:23:27.000 ~ 2022-05-21 12:23:31.999", segment.toString()); segment = periods.next(segment.begin(), step, 1); Assert.assertEquals("2022-05-21 12:23:32.000 ~ 2022-05-21 12:23:36.999", segment.toString()); // QUARTERLY periods = DatePeriods.QUARTERLY; origin = Dates.toDate("2021-08-21 12:23:12.000", format); target = Dates.toDate("2022-05-21 23:34:23.000", format); step = 2; segment = periods.next(origin, target, step, 0); Assert.assertEquals("2022-02-21 12:23:12.000 ~ 2022-08-21 12:23:11.999", segment.toString()); segment = periods.next(segment.begin(), step, 1); Assert.assertEquals("2022-08-21 12:23:12.000 ~ 2023-02-21 12:23:11.999", segment.toString()); segment = periods.next(segment.begin(), step, 1); Assert.assertEquals("2023-02-21 12:23:12.000 ~ 2023-08-21 12:23:11.999", segment.toString()); } @Test public void testDuration() { Assert.assertEquals("PT5M", Duration.ofSeconds(300).toString()); Assert.assertEquals(3600, Duration.parse("PT1H").getSeconds()); } @Test public void testDateString() throws ParseException { Date date = new Date(); System.out.println("Dates.format(date): " + Dates.format(date)); String dateString = date.toString(); System.out.println("date.toString(): " + dateString); Date parsed1 = FastDateFormat.getInstance("EEE MMM dd HH:mm:ss zzz yyyy", Locale.ENGLISH).parse(dateString); System.out.println(Dates.format(parsed1)); Assert.assertNotEquals(date, parsed1); System.out.println("--------"); FastDateFormat format = FastDateFormat.getInstance("EEE MMM dd HH:mm:ss zzz yyyy", Locale.CHINA); dateString = format.format(date); System.out.println("FastDateFormat.format(date): " + dateString); Date parsed2 = format.parse(dateString); System.out.println(Dates.format(parsed2)); Assert.assertNotEquals(date, parsed2); } @Test public void testStreamMax() { Date max = Arrays.stream(new Date[]{Dates.toDate("2020-01-03 00:00:00"), Dates.toDate("2020-01-02 00:00:00")}) .max(Comparator.naturalOrder()) .orElse(null); System.out.println(Dates.format(max)); } @Data public static class DateEntity { @JsonProperty(value = "createTime") @JsonFormat(pattern = Dates.DATETIME_PATTERN) private Date createTime; } } ================================================ FILE: src/test/java/cn/ponfee/commons/date/JavaUtilDateFormatTest.java ================================================ package cn.ponfee.commons.date; import org.apache.commons.lang3.time.FastDateFormat; import org.junit.Assert; import org.junit.Test; import java.text.ParseException; import java.text.ParsePosition; import java.text.SimpleDateFormat; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.Date; import java.util.Locale; import java.util.TimeZone; import static org.junit.Assert.assertEquals; /** * JavaUtilDateFormatTest * * @author Ponfee */ public class JavaUtilDateFormatTest { @Test public void test0() throws ParseException { Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").parse("2022-01-02 03:04:05.678"); assertEquals("Sun Jan 02 03:04:05 CST 2022", date.toString()); assertEquals("Sun Jan 02 17:04:05 CST 2022", new Date(date.toString()).toString()); DateTimeFormatter dtf = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss zzz yyyy", Locale.ROOT); assertEquals("Sun Jan 02 03:04:05 CST 2022", Dates.toDate(LocalDateTime.parse(date.toString(), dtf)).toString()); assertEquals("Sun Jan 02 17:04:05 CST 2022", Date.from(ZonedDateTime.parse(date.toString(), dtf).toInstant()).toString()); assertEquals("202201", JavaUtilDateFormat.PATTERN_11.format(date)); assertEquals("2022-01", JavaUtilDateFormat.PATTERN_12.format(date)); assertEquals("2022/01", JavaUtilDateFormat.PATTERN_13.format(date)); assertEquals("20220102", JavaUtilDateFormat.PATTERN_21.format(date)); assertEquals("2022-01-02", JavaUtilDateFormat.PATTERN_22.format(date)); assertEquals("2022/01/02", JavaUtilDateFormat.PATTERN_23.format(date)); assertEquals("20220102030405", JavaUtilDateFormat.PATTERN_31.format(date)); assertEquals("20220102030405678", JavaUtilDateFormat.PATTERN_32.format(date)); assertEquals("2022-01-02 03:04:05", JavaUtilDateFormat.PATTERN_41.format(date)); assertEquals("2022/01/02 03:04:05", JavaUtilDateFormat.PATTERN_42.format(date)); assertEquals("2022-01-02T03:04:05", JavaUtilDateFormat.PATTERN_43.format(date)); assertEquals("2022/01/02T03:04:05", JavaUtilDateFormat.PATTERN_44.format(date)); assertEquals("2022-01-02 03:04:05.678", JavaUtilDateFormat.PATTERN_51.format(date)); assertEquals("2022/01/02 03:04:05.678", JavaUtilDateFormat.PATTERN_52.format(date)); assertEquals("2022-01-02T03:04:05.678", JavaUtilDateFormat.PATTERN_53.format(date)); assertEquals("2022/01/02T03:04:05.678", JavaUtilDateFormat.PATTERN_54.format(date)); assertEquals("2022-01-02 03:04:05.678Z", JavaUtilDateFormat.PATTERN_61.format(date)); assertEquals("2022/01/02 03:04:05.678Z", JavaUtilDateFormat.PATTERN_62.format(date)); assertEquals("2022-01-02T03:04:05.678Z", JavaUtilDateFormat.PATTERN_63.format(date)); assertEquals("2022/01/02T03:04:05.678Z", JavaUtilDateFormat.PATTERN_64.format(date)); assertEquals("2022-01-02T03:04:05.678+08", JavaUtilDateFormat.PATTERN_73.format(date)); assertEquals("2022/01/02T03:04:05.678+08", JavaUtilDateFormat.PATTERN_74.format(date)); assertEquals("2022-01-02 03:04:05", JavaUtilDateFormat.DEFAULT.format(date)); assertEquals("Sat Jan 01 00:00:00 CST 2022", JavaUtilDateFormat.PATTERN_11.parse("202201").toString()); assertEquals("Sat Jan 01 00:00:00 CST 2022", JavaUtilDateFormat.PATTERN_12.parse("2022-01").toString()); assertEquals("Sat Jan 01 00:00:00 CST 2022", JavaUtilDateFormat.PATTERN_13.parse("2022/01").toString()); assertEquals("Sun Jan 02 00:00:00 CST 2022", JavaUtilDateFormat.PATTERN_21.parse("20220102").toString()); assertEquals("Sun Jan 02 00:00:00 CST 2022", JavaUtilDateFormat.PATTERN_22.parse("2022-01-02").toString()); assertEquals("Sun Jan 02 00:00:00 CST 2022", JavaUtilDateFormat.PATTERN_23.parse("2022/01/02").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.PATTERN_31.parse("20220102030405").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.PATTERN_32.parse("20220102030405678").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.PATTERN_41.parse("2022-01-02 03:04:05").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.PATTERN_42.parse("2022/01/02 03:04:05").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.PATTERN_43.parse("2022-01-02T03:04:05").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.PATTERN_44.parse("2022/01/02T03:04:05").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.PATTERN_51.parse("2022-01-02 03:04:05.678").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.PATTERN_52.parse("2022/01/02 03:04:05.678").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.PATTERN_53.parse("2022-01-02T03:04:05.678").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.PATTERN_54.parse("2022/01/02T03:04:05.678").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.PATTERN_61.parse("2022-01-02 03:04:05.678Z").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.PATTERN_62.parse("2022/01/02 03:04:05.678Z").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.PATTERN_63.parse("2022-01-02T03:04:05.678Z").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.PATTERN_64.parse("2022/01/02T03:04:05.678Z").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.PATTERN_73.parse("2022-01-02T03:04:05.678+08").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.PATTERN_74.parse("2022/01/02T03:04:05.678+08").toString()); assertEquals("Sat Jan 01 00:00:00 CST 2022", JavaUtilDateFormat.DEFAULT.parse("202201").toString()); assertEquals("Sat Jan 01 00:00:00 CST 2022", JavaUtilDateFormat.DEFAULT.parse("2022-01").toString()); assertEquals("Sat Jan 01 00:00:00 CST 2022", JavaUtilDateFormat.DEFAULT.parse("2022/01").toString()); assertEquals("Sun Jan 02 00:00:00 CST 2022", JavaUtilDateFormat.DEFAULT.parse("20220102").toString()); assertEquals("Sun Jan 02 00:00:00 CST 2022", JavaUtilDateFormat.DEFAULT.parse("2022-01-02").toString()); assertEquals("Sun Jan 02 00:00:00 CST 2022", JavaUtilDateFormat.DEFAULT.parse("2022/01/02").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.DEFAULT.parse("20220102030405").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.DEFAULT.parse("20220102030405678").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.DEFAULT.parse("2022-01-02 03:04:05").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.DEFAULT.parse("2022/01/02 03:04:05").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.DEFAULT.parse("2022-01-02T03:04:05").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.DEFAULT.parse("2022/01/02T03:04:05").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.DEFAULT.parse("2022-01-02 03:04:05.678").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.DEFAULT.parse("2022/01/02 03:04:05.678").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.DEFAULT.parse("2022-01-02T03:04:05.678").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.DEFAULT.parse("2022/01/02T03:04:05.678").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.DEFAULT.parse("2022-01-02 03:04:05.678Z").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.DEFAULT.parse("2022/01/02 03:04:05.678Z").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.DEFAULT.parse("2022-01-02T03:04:05.678Z").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.DEFAULT.parse("2022/01/02T03:04:05.678Z").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.DEFAULT.parse("2022-01-02T03:04:05.678+08").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.DEFAULT.parse("2022/01/02T03:04:05.678+08").toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.DEFAULT.parse("Sun Jan 02 03:04:05 CST 2022").toString()); // FIXME assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.DEFAULT.parse(String.valueOf(date.getTime())).toString()); assertEquals("Sun Jan 02 03:04:05 CST 2022", JavaUtilDateFormat.DEFAULT.parse(String.valueOf(date.getTime()/1000)).toString()); } @Test public void test1() throws ParseException { String dateString = "2022-07-19T13:44:27.873Z"; Date date = JavaUtilDateFormat.PATTERN_73.parse(dateString); assertEquals("202207", JavaUtilDateFormat.PATTERN_11.format(date)); assertEquals("2022-07", JavaUtilDateFormat.PATTERN_12.format(date)); assertEquals("2022/07", JavaUtilDateFormat.PATTERN_13.format(date)); assertEquals("20220719", JavaUtilDateFormat.PATTERN_21.format(date)); assertEquals("2022-07-19", JavaUtilDateFormat.PATTERN_22.format(date)); assertEquals("2022/07/19", JavaUtilDateFormat.PATTERN_23.format(date)); assertEquals("20220719214427", JavaUtilDateFormat.PATTERN_31.format(date)); assertEquals("20220719214427873", JavaUtilDateFormat.PATTERN_32.format(date)); assertEquals("2022-07-19 21:44:27", JavaUtilDateFormat.PATTERN_41.format(date)); assertEquals("2022/07/19 21:44:27", JavaUtilDateFormat.PATTERN_42.format(date)); assertEquals("2022-07-19T21:44:27", JavaUtilDateFormat.PATTERN_43.format(date)); assertEquals("2022/07/19T21:44:27", JavaUtilDateFormat.PATTERN_44.format(date)); assertEquals("2022-07-19 21:44:27.873", JavaUtilDateFormat.PATTERN_51.format(date)); assertEquals("2022/07/19 21:44:27.873", JavaUtilDateFormat.PATTERN_52.format(date)); assertEquals("2022-07-19T21:44:27.873", JavaUtilDateFormat.PATTERN_53.format(date)); assertEquals("2022/07/19T21:44:27.873", JavaUtilDateFormat.PATTERN_54.format(date)); assertEquals("2022-07-19 21:44:27.873Z", JavaUtilDateFormat.PATTERN_61.format(date)); assertEquals("2022/07/19 21:44:27.873Z", JavaUtilDateFormat.PATTERN_62.format(date)); assertEquals("2022-07-19T21:44:27.873Z", JavaUtilDateFormat.PATTERN_63.format(date)); assertEquals("2022/07/19T21:44:27.873Z", JavaUtilDateFormat.PATTERN_64.format(date)); assertEquals("2022-07-19T21:44:27.873+08", JavaUtilDateFormat.PATTERN_73.format(date)); assertEquals("2022/07/19T21:44:27.873+08", JavaUtilDateFormat.PATTERN_74.format(date)); } @Test public void test2() throws ParseException { JavaUtilDateFormat format = JavaUtilDateFormat.DEFAULT; FastDateFormat fastDateFormat = JavaUtilDateFormat.PATTERN_51; assertEquals("2022-07-01 00:00:00.000", fastDateFormat.format(format.parse("202207"))); assertEquals("2022-07-01 00:00:00.000", fastDateFormat.format(format.parse("2022-07"))); assertEquals("2022-07-01 00:00:00.000", fastDateFormat.format(format.parse("2022/07"))); assertEquals("2022-07-19 00:00:00.000", fastDateFormat.format(format.parse("20220719"))); assertEquals("2022-07-19 00:00:00.000", fastDateFormat.format(format.parse("2022-07-19"))); assertEquals("2022-07-19 00:00:00.000", fastDateFormat.format(format.parse("2022/07/19"))); assertEquals("2022-07-19 21:44:27.000", fastDateFormat.format(format.parse("20220719214427"))); assertEquals("2022-07-19 21:44:27.873", fastDateFormat.format(format.parse("20220719214427873"))); assertEquals("2022-07-19 21:44:27.000", fastDateFormat.format(format.parse("2022-07-19 21:44:27"))); assertEquals("2022-07-19 21:44:27.000", fastDateFormat.format(format.parse("2022/07/19 21:44:27"))); assertEquals("2022-07-19 21:44:27.000", fastDateFormat.format(format.parse("2022-07-19T21:44:27"))); assertEquals("2022-07-19 21:44:27.000", fastDateFormat.format(format.parse("2022/07/19T21:44:27"))); assertEquals("2022-07-19 21:44:27.873", fastDateFormat.format(format.parse("2022-07-19 21:44:27.873"))); assertEquals("2022-07-19 21:44:27.873", fastDateFormat.format(format.parse("2022/07/19 21:44:27.873"))); assertEquals("2022-07-19 21:44:27.873", fastDateFormat.format(format.parse("2022-07-19T21:44:27.873"))); assertEquals("2022-07-19 21:44:27.873", fastDateFormat.format(format.parse("2022/07/19T21:44:27.873"))); assertEquals("2022-07-19 21:44:27.000", fastDateFormat.format(format.parse("2022-07-19T21:44:27Z"))); assertEquals("2022-07-19 21:44:27.000", fastDateFormat.format(format.parse("2022-07-19T21:44:27.Z"))); assertEquals("2022-07-19 21:44:27.800", fastDateFormat.format(format.parse("2022-07-19T21:44:27.8Z"))); assertEquals("2022-07-19 21:44:27.870", fastDateFormat.format(format.parse("2022-07-19T21:44:27.87Z"))); assertEquals("2022-07-19 21:44:27.873", fastDateFormat.format(format.parse("2022-07-19T21:44:27.873Z"))); assertEquals("2022-07-19 21:44:27.000", fastDateFormat.format(format.parse("2022/07/19T21:44:27Z"))); assertEquals("2022-07-19 21:44:27.000", fastDateFormat.format(format.parse("2022/07/19T21:44:27.Z"))); assertEquals("2022-07-19 21:44:27.300", fastDateFormat.format(format.parse("2022/07/19T21:44:27.3Z"))); assertEquals("2022-07-19 21:44:27.730", fastDateFormat.format(format.parse("2022/07/19T21:44:27.73Z"))); assertEquals("2022-07-19 21:44:27.873", fastDateFormat.format(format.parse("2022/07/19T21:44:27.873Z"))); assertEquals("2022-07-19 21:44:27.000", fastDateFormat.format(format.parse("2022-07-19 21:44:27Z"))); assertEquals("2022-07-19 21:44:27.000", fastDateFormat.format(format.parse("2022-07-19 21:44:27.Z"))); assertEquals("2022-07-19 21:44:27.800", fastDateFormat.format(format.parse("2022-07-19 21:44:27.8Z"))); assertEquals("2022-07-19 21:44:27.870", fastDateFormat.format(format.parse("2022-07-19 21:44:27.87Z"))); assertEquals("2022-07-19 21:44:27.873", fastDateFormat.format(format.parse("2022-07-19 21:44:27.873Z"))); assertEquals("2022-07-19 21:44:27.000", fastDateFormat.format(format.parse("2022/07/19 21:44:27Z"))); assertEquals("2022-07-19 21:44:27.000", fastDateFormat.format(format.parse("2022/07/19 21:44:27.Z"))); assertEquals("2022-07-19 21:44:27.300", fastDateFormat.format(format.parse("2022/07/19 21:44:27.3Z"))); assertEquals("2022-07-19 21:44:27.730", fastDateFormat.format(format.parse("2022/07/19 21:44:27.73Z"))); assertEquals("2022-07-19 21:44:27.873", fastDateFormat.format(format.parse("2022/07/19 21:44:27.873Z"))); assertEquals("2022-07-19 21:44:27.873", fastDateFormat.format(format.parse("2022-07-19T21:44:27.873+08"))); assertEquals("2022-07-19 21:44:27.873", fastDateFormat.format(format.parse("2022/07/19T21:44:27.873+08"))); assertEquals("2022-07-19 21:44:27.000", fastDateFormat.format(format.parse("Tue Jul 19 21:44:27 CST 2022"))); String dateString = "2022-07-19T13:44:27.873Z"; Date date = JavaUtilDateFormat.PATTERN_73.parse(dateString); assertEquals("2022-07-19 21:44:27.000", fastDateFormat.format(format.parse(Long.toString(date.getTime()/1000)))); assertEquals("2022-07-19 21:44:27.873", fastDateFormat.format(format.parse(Long.toString(date.getTime())))); } @Test public void test() throws ParseException { JavaUtilDateFormat format = JavaUtilDateFormat.DEFAULT; Date date = new Date(); System.out.println(format.parse(JavaUtilDateFormat.PATTERN_11.format(date))); System.out.println(format.parse(JavaUtilDateFormat.PATTERN_12.format(date))); System.out.println(format.parse(JavaUtilDateFormat.PATTERN_21.format(date))); System.out.println(format.parse(JavaUtilDateFormat.PATTERN_22.format(date))); System.out.println(format.parse(JavaUtilDateFormat.PATTERN_31.format(date))); System.out.println(format.parse(JavaUtilDateFormat.PATTERN_41.format(date))); System.out.println(format.parse(JavaUtilDateFormat.PATTERN_32.format(date))); System.out.println(format.parse(JavaUtilDateFormat.PATTERN_51.format(date))); System.out.println(format.parse(JavaUtilDateFormat.PATTERN_63.format(date))); System.out.println(format.parse(JavaUtilDateFormat.PATTERN_13.format(date))); System.out.println(format.parse(JavaUtilDateFormat.PATTERN_23.format(date))); System.out.println(format.parse(JavaUtilDateFormat.PATTERN_42.format(date))); System.out.println(format.parse(JavaUtilDateFormat.PATTERN_52.format(date))); System.out.println(format.parse(JavaUtilDateFormat.PATTERN_64.format(date))); System.out.println(format.parse(String.valueOf(date.getTime()))); System.out.println(format.parse(String.valueOf(date.getTime() / 1000))); System.out.println("\n------------------------"); System.out.println(JavaUtilDateFormat.PATTERN_11.format(date)); System.out.println(JavaUtilDateFormat.PATTERN_12.format(date)); System.out.println(JavaUtilDateFormat.PATTERN_21.format(date)); System.out.println(JavaUtilDateFormat.PATTERN_22.format(date)); System.out.println(JavaUtilDateFormat.PATTERN_31.format(date)); System.out.println(JavaUtilDateFormat.PATTERN_41.format(date)); System.out.println(JavaUtilDateFormat.PATTERN_32.format(date)); System.out.println(JavaUtilDateFormat.PATTERN_51.format(date)); System.out.println(JavaUtilDateFormat.PATTERN_63.format(date)); System.out.println(JavaUtilDateFormat.PATTERN_13.format(date)); System.out.println(JavaUtilDateFormat.PATTERN_23.format(date)); System.out.println(JavaUtilDateFormat.PATTERN_42.format(date)); System.out.println(JavaUtilDateFormat.PATTERN_52.format(date)); System.out.println(JavaUtilDateFormat.PATTERN_64.format(date)); System.out.println("\n------------------------"); Assert.assertTrue(JavaUtilDateFormat.DATE_TO_STRING_PATTERN.matcher("Sat Jun 01 22:36:21 CST 2019").matches()); Assert.assertFalse(JavaUtilDateFormat.DATE_TO_STRING_PATTERN.matcher("Sat Jun 0122:36:21 CST 2019").matches()); Assert.assertFalse(JavaUtilDateFormat.DATE_TO_STRING_PATTERN.matcher("sat Jun 01 22:36:21 CST 2019").matches()); Assert.assertFalse(JavaUtilDateFormat.DATE_TO_STRING_PATTERN.matcher("Sat Jun 01 22:36:21 DST 2019").matches()); Assert.assertFalse(JavaUtilDateFormat.DATE_TO_STRING_PATTERN.matcher("Sat jun 01 22:36:21 CST 2019").matches()); Assert.assertFalse(JavaUtilDateFormat.DATE_TO_STRING_PATTERN.matcher("Sat Jun 01 22:36:21CST 2019").matches()); System.out.println(JavaUtilDateFormat.DEFAULT.parse("Sat Jun 01 22:36:21 CST 2019", new ParsePosition(0))); System.out.println(format.parse("Sat Jun 01 22:36:21 CST 2019")); System.out.println(format.parse("2020-12-01 10:33:06")); System.out.println(format.parse("1644894528086")); System.out.println(format.parse("1644894528")); System.out.println(new JavaUtilDateFormat("yyyy").parse("2022")); System.out.println(JavaUtilDateFormat.DEFAULT.format(new Date(0))); Assert.assertTrue(JavaUtilDateFormat.DATE_TIMESTAMP_PATTERN.matcher("0").matches()); Assert.assertTrue(JavaUtilDateFormat.DATE_TIMESTAMP_PATTERN.matcher("1").matches()); Assert.assertTrue(JavaUtilDateFormat.DATE_TIMESTAMP_PATTERN.matcher("9").matches()); Assert.assertTrue(JavaUtilDateFormat.DATE_TIMESTAMP_PATTERN.matcher("1644894528").matches()); Assert.assertFalse(JavaUtilDateFormat.DATE_TIMESTAMP_PATTERN.matcher("01644894528").matches()); System.out.println("\n------------------------"); System.out.println(JavaUtilDateFormat.PATTERN_41.parse("2122-01-01 00:00:00", new ParsePosition(0))); System.out.println(JavaUtilDateFormat.PATTERN_41.parse("2122-01-01 00:00:00", new ParsePosition(1))); System.out.println(JavaUtilDateFormat.DEFAULT.parse("x2122-01-01 00:00:00", new ParsePosition(1))); System.out.println(JavaUtilDateFormat.DEFAULT.parse("2122-01-01 00:00:00", new ParsePosition(0))); Assert.assertEquals("Wed Jan 01 00:00:00 CST 122", JavaUtilDateFormat.DEFAULT.parse("2122-01-01 00:00:00", new ParsePosition(1)).toString()); Assert.assertThrows(ParseException.class, () -> JavaUtilDateFormat.DEFAULT.parse("xxx-01-01 00:00:00")); } @Test public void test3() throws ParseException { System.out.println(FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss.SSS", TimeZone.getTimeZone(ZoneOffset.UTC)).format(new Date())); System.out.println(FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss.SSS", TimeZone.getTimeZone("GMT+8")).format(new Date())); System.out.println("-------"); System.out.println(FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss.SSS", TimeZone.getTimeZone(ZoneOffset.UTC)).parse("2022-12-15 11:53:12.273")); System.out.println(FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss.SSS", TimeZone.getTimeZone("GMT+8")).parse("2022-12-15 11:53:12.273")); System.out.println("-------"); System.out.println(FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSX", TimeZone.getTimeZone(ZoneOffset.UTC)).parse("2022-12-15T11:50:29.855+08")); System.out.println(FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSX", TimeZone.getTimeZone("GMT+8")).parse("2022-12-15T11:50:29.855+08")); } } ================================================ FILE: src/test/java/cn/ponfee/commons/date/LocalDateTimeFormatTest.java ================================================ package cn.ponfee.commons.date; import org.junit.Test; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.Date; import java.util.TimeZone; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; /** * LocalDateTimeFormatTest * * @author Ponfee */ public class LocalDateTimeFormatTest { @Test public void test9() { LocalDateTime localDateTime = LocalDateTime.of(2022, 01, 02, 03, 04, 05, 123000000); Date date = Dates.toDate(localDateTime); assertEquals("2022-01-02T03:04:05.123", localDateTime.toString()); assertEquals("2022-01-02 03:04:05", Dates.format(date).toString()); assertEquals("2022-01-02T07:04:05.123", Dates.zoneConvert(localDateTime, ZoneId.of("UTC+4"), ZoneId.of("UTC+8")).toString()); assertEquals("2022-01-02 11:04:05", Dates.format(Dates.zoneConvert(date, ZoneId.of("UTC+0"), ZoneId.of("UTC+8")))); assertEquals("2022-01-01 19:04:05", Dates.format(Dates.zoneConvert(date, ZoneId.of("UTC+8"), ZoneId.of("UTC+0")))); LocalDateTime originLocalDateTime = LocalDateTime.of(1970, 01, 01, 00, 00, 00, 0); Date originDate = Dates.toDate(originLocalDateTime); // defaultZone: UTC+8 assertEquals("1970-01-01T08:00", Dates.zoneConvert(originLocalDateTime, ZoneId.of("UTC+0"), ZoneId.of("UTC+8")).toString()); assertEquals("1969-12-31T16:00", Dates.zoneConvert(originLocalDateTime, ZoneId.of("UTC+8"), ZoneId.of("UTC+0")).toString()); assertEquals("1970-01-01 08:00:00", Dates.format(Dates.zoneConvert(originDate, ZoneId.of("UTC+0"), ZoneId.of("UTC+8")))); assertEquals("1969-12-31 16:00:00", Dates.format(Dates.zoneConvert(originDate, ZoneId.of("UTC+8"), ZoneId.of("UTC+0")))); SimpleDateFormat simpleDateFormat = new SimpleDateFormat(Dates.DATETIME_PATTERN); simpleDateFormat.setTimeZone(TimeZone.getTimeZone(ZoneId.of("UTC+0"))); assertEquals("1969-12-31 16:00:00", simpleDateFormat.format(originDate)); assertEquals("1970-01-01 08:00:00", Dates.zoneConvert(Dates.toDate("1970-01-01 00:00:00"), ZoneId.of("UTC+0"), ZoneId.of("UTC+8"))); assertEquals("1970-01-01 00:00:00", Dates.zoneConvert(Dates.toDate("1970-01-01 08:00:00"), ZoneId.of("UTC+8"), ZoneId.of("UTC+0"))); } @Test public void test8() { LocalDateTime localDateTime = LocalDateTime.now(); Date date = Dates.toDate(localDateTime); System.out.println(localDateTime.toString()); System.out.println(Dates.format(date).toString()); System.out.println("\n-------toDate"); System.out.println(Dates.toDate(localDateTime)); System.out.println(Dates.toDate(localDateTime)); System.out.println("\n-------toLocalDateTime"); System.out.println(Dates.toLocalDateTime(date)); System.out.println(Dates.toLocalDateTime(date)); System.out.println("\n-------zoneConvert1"); LocalDateTime targetLocalDateTime = Dates.zoneConvert(localDateTime, ZoneId.of("UTC+8"), ZoneId.of("UTC+0")); Date targetDate = Dates.zoneConvert(date, ZoneId.of("UTC+8"), ZoneId.of("UTC+0")); System.out.println(targetLocalDateTime); System.out.println(targetDate); System.out.println("\n-------zoneConvert2"); targetLocalDateTime = Dates.zoneConvert(localDateTime, ZoneId.of("UTC+0"), ZoneId.of("UTC+8")); targetDate = Dates.zoneConvert(date, ZoneId.of("UTC+0"), ZoneId.of("UTC+8")); System.out.println(targetLocalDateTime); System.out.println(targetDate); } @Test public void test0() throws ParseException { LocalDateTime localDateTime = LocalDateTime.of(2022, 01, 02, 03, 05, 05, 123000000); assertEquals("2022-01-02T03:05:05.123", localDateTime.toString()); assertEquals("20220102030505", LocalDateTimeFormat.PATTERN_01.format(localDateTime)); assertEquals("2022-01-02 03:05:05", LocalDateTimeFormat.PATTERN_11.format(localDateTime)); assertEquals("2022/01/02 03:05:05", LocalDateTimeFormat.PATTERN_12.format(localDateTime)); assertEquals("2022-01-02T03:05:05", LocalDateTimeFormat.PATTERN_13.format(localDateTime)); assertEquals("2022/01/02T03:05:05", LocalDateTimeFormat.PATTERN_14.format(localDateTime)); assertEquals("2022-01-02 03:05:05.123", LocalDateTimeFormat.PATTERN_21.format(localDateTime)); assertEquals("2022/01/02 03:05:05.123", LocalDateTimeFormat.PATTERN_22.format(localDateTime)); assertEquals("2022-01-02T03:05:05.123", LocalDateTimeFormat.PATTERN_23.format(localDateTime)); assertEquals("2022/01/02T03:05:05.123", LocalDateTimeFormat.PATTERN_24.format(localDateTime)); assertEquals("{},ISO resolved to 2022-01-02T03:05:05", LocalDateTimeFormat.PATTERN_01.parse("20220102030505").toString()); assertEquals("{},ISO resolved to 2022-01-02T03:05:05", LocalDateTimeFormat.PATTERN_11.parse("2022-01-02 03:05:05").toString()); assertEquals("{},ISO resolved to 2022-01-02T03:05:05", LocalDateTimeFormat.PATTERN_12.parse("2022/01/02 03:05:05").toString()); assertEquals("{},ISO resolved to 2022-01-02T03:05:05", LocalDateTimeFormat.PATTERN_13.parse("2022-01-02T03:05:05").toString()); assertEquals("{},ISO resolved to 2022-01-02T03:05:05", LocalDateTimeFormat.PATTERN_14.parse("2022/01/02T03:05:05").toString()); assertEquals("{},ISO resolved to 2022-01-02T03:05:05.123", LocalDateTimeFormat.PATTERN_21.parse("2022-01-02 03:05:05.123").toString()); assertEquals("{},ISO resolved to 2022-01-02T03:05:05.123", LocalDateTimeFormat.PATTERN_22.parse("2022/01/02 03:05:05.123").toString()); assertEquals("{},ISO resolved to 2022-01-02T03:05:05.123", LocalDateTimeFormat.PATTERN_23.parse("2022-01-02T03:05:05.123").toString()); assertEquals("{},ISO resolved to 2022-01-02T03:05:05.123", LocalDateTimeFormat.PATTERN_24.parse("2022/01/02T03:05:05.123").toString()); assertEquals("2022-01-02T03:05:05", LocalDateTimeFormat.DEFAULT.parse("20220102030505").toString()); assertEquals("2022-01-02T03:05:05", LocalDateTimeFormat.DEFAULT.parse("2022-01-02 03:05:05").toString()); assertEquals("2022-01-02T03:05:05", LocalDateTimeFormat.DEFAULT.parse("2022/01/02 03:05:05").toString()); assertEquals("2022-01-02T03:05:05", LocalDateTimeFormat.DEFAULT.parse("2022-01-02T03:05:05").toString()); assertEquals("2022-01-02T03:05:05", LocalDateTimeFormat.DEFAULT.parse("2022/01/02T03:05:05").toString()); assertEquals("2022-01-02T03:05:05.123", LocalDateTimeFormat.DEFAULT.parse("2022-01-02 03:05:05.123").toString()); assertEquals("2022-01-02T03:05:05.123", LocalDateTimeFormat.DEFAULT.parse("2022/01/02 03:05:05.123").toString()); assertEquals("2022-01-02T03:05:05.123", LocalDateTimeFormat.DEFAULT.parse("2022-01-02T03:05:05.123").toString()); assertEquals("2022-01-02T03:05:05.123", LocalDateTimeFormat.DEFAULT.parse("2022/01/02T03:05:05.123").toString()); assertEquals("2022-01-02T03:05:05.123", LocalDateTimeFormat.DEFAULT.parse("2022-01-02T03:05:05.123Z").toString()); assertEquals("2022-01-02T03:05:05.123", LocalDateTimeFormat.DEFAULT.parse("2022/01/02T03:05:05.123Z").toString()); Date date = Dates.toDate(localDateTime); assertEquals("Sun Jan 02 03:05:05 CST 2022", date.toString()); assertEquals("2022-01-02T03:05:05.123", LocalDateTimeFormat.DEFAULT.parse(String.valueOf(date.getTime())).toString()); assertEquals("2022-01-02T03:05:05", LocalDateTimeFormat.DEFAULT.parse(String.valueOf(date.getTime() / 1000)).toString()); } @Test public void test1() { String dateString = "2022-07-19T13:44:27.873"; LocalDateTime date = LocalDateTime.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE_TIME); LocalDateTimeFormat format = LocalDateTimeFormat.DEFAULT; assertEquals(dateString, date.toString()); assertEquals("20220719134427", LocalDateTimeFormat.PATTERN_01.format(date)); assertEquals("2022-07-19 13:44:27", LocalDateTimeFormat.PATTERN_11.format(date)); assertEquals("2022/07/19 13:44:27", LocalDateTimeFormat.PATTERN_12.format(date)); assertEquals("2022-07-19T13:44:27", LocalDateTimeFormat.PATTERN_13.format(date)); assertEquals("2022/07/19T13:44:27", LocalDateTimeFormat.PATTERN_14.format(date)); assertEquals("2022-07-19 13:44:27.873", LocalDateTimeFormat.PATTERN_21.format(date)); assertEquals("2022/07/19 13:44:27.873", LocalDateTimeFormat.PATTERN_22.format(date)); assertEquals("2022-07-19T13:44:27.873", LocalDateTimeFormat.PATTERN_23.format(date)); assertEquals("2022/07/19T13:44:27.873", LocalDateTimeFormat.PATTERN_24.format(date)); assertEquals("{},ISO resolved to 2022-07-19T13:44:27", LocalDateTimeFormat.PATTERN_01.parse(LocalDateTimeFormat.PATTERN_01.format(date)).toString()); assertEquals("{},ISO resolved to 2022-07-19T13:44:27", LocalDateTimeFormat.PATTERN_11.parse(LocalDateTimeFormat.PATTERN_11.format(date)).toString()); assertEquals("{},ISO resolved to 2022-07-19T13:44:27", LocalDateTimeFormat.PATTERN_12.parse(LocalDateTimeFormat.PATTERN_12.format(date)).toString()); assertEquals("{},ISO resolved to 2022-07-19T13:44:27", LocalDateTimeFormat.PATTERN_13.parse(LocalDateTimeFormat.PATTERN_13.format(date)).toString()); assertEquals("{},ISO resolved to 2022-07-19T13:44:27", LocalDateTimeFormat.PATTERN_14.parse(LocalDateTimeFormat.PATTERN_14.format(date)).toString()); assertEquals("{},ISO resolved to 2022-07-19T13:44:27.873", LocalDateTimeFormat.PATTERN_21.parse(LocalDateTimeFormat.PATTERN_21.format(date)).toString()); assertEquals("{},ISO resolved to 2022-07-19T13:44:27.873", LocalDateTimeFormat.PATTERN_22.parse(LocalDateTimeFormat.PATTERN_22.format(date)).toString()); assertEquals("{},ISO resolved to 2022-07-19T13:44:27.873", LocalDateTimeFormat.PATTERN_23.parse(LocalDateTimeFormat.PATTERN_23.format(date)).toString()); assertEquals("{},ISO resolved to 2022-07-19T13:44:27.873", LocalDateTimeFormat.PATTERN_24.parse(LocalDateTimeFormat.PATTERN_24.format(date)).toString()); assertEquals("2022-07-19T13:44:27", format.parse(LocalDateTimeFormat.PATTERN_01.format(date)).toString()); assertEquals("2022-07-19T13:44:27", format.parse(LocalDateTimeFormat.PATTERN_11.format(date)).toString()); assertEquals("2022-07-19T13:44:27", format.parse(LocalDateTimeFormat.PATTERN_12.format(date)).toString()); assertEquals("2022-07-19T13:44:27", format.parse(LocalDateTimeFormat.PATTERN_13.format(date)).toString()); assertEquals("2022-07-19T13:44:27", format.parse(LocalDateTimeFormat.PATTERN_14.format(date)).toString()); assertEquals("2022-07-19T13:44:27.873", format.parse(LocalDateTimeFormat.PATTERN_21.format(date)).toString()); assertEquals("2022-07-19T13:44:27.873", format.parse(LocalDateTimeFormat.PATTERN_22.format(date)).toString()); assertEquals("2022-07-19T13:44:27.873", format.parse(LocalDateTimeFormat.PATTERN_23.format(date)).toString()); assertEquals("2022-07-19T13:44:27.873", format.parse(LocalDateTimeFormat.PATTERN_24.format(date)).toString()); } @Test public void test2() throws ParseException { LocalDateTimeFormat format = LocalDateTimeFormat.DEFAULT; assertEquals("2022-07-18T00:00", format.parse("20220718").toString()); assertEquals("2022-07-18T00:00", format.parse("2022-07-18").toString()); assertEquals("2022-07-18T00:00", format.parse("2022/07/18").toString()); assertEquals("2022-07-18T15:45:59", format.parse("20220718154559").toString()); assertEquals("2022-07-18T15:45:59", format.parse("2022-07-18 15:45:59").toString()); assertEquals("2022-07-18T15:45:59", format.parse("2022/07/18 15:45:59").toString()); assertEquals("2022-07-18T15:45:59", format.parse("2022-07-18T15:45:59").toString()); assertEquals("2022-07-18T15:45:59", format.parse("2022/07/18T15:45:59").toString()); assertEquals("2022-07-18T15:45:59.414", format.parse("2022-07-18 15:45:59.414").toString()); assertEquals("2022-07-18T15:45:59.414", format.parse("2022/07/18 15:45:59.414").toString()); assertEquals("2022-07-18T15:45:59.414", format.parse("2022-07-18T15:45:59.414").toString()); assertEquals("2022-07-18T15:45:59.414", format.parse("2022/07/18T15:45:59.414").toString()); assertEquals("2022-07-18T15:45:59", format.parse("1658130359").toString()); assertEquals("2022-07-18T15:45:59", format.parse("1658130359000").toString()); assertEquals("2001-09-10T21:59:19", format.parse("1000130359").toString()); assertEquals("2001-09-10T21:59:19", format.parse("1000130359000").toString()); assertEquals("2022-07-18T15:11:11", format.parse("2022-07-18T15:11:11Z").toString()); assertEquals("2022-07-18T15:11:11", format.parse("2022-07-18T15:11:11.Z").toString()); assertEquals("2022-07-18T15:11:11.100", format.parse("2022-07-18T15:11:11.1Z").toString()); assertEquals("2022-07-18T15:11:11.130", format.parse("2022-07-18T15:11:11.13Z").toString()); assertEquals("2022-07-18T15:11:11.133", format.parse("2022-07-18T15:11:11.133Z").toString()); assertEquals("2022-07-18T15:11:11", format.parse("2022/07/18T15:11:11Z").toString()); assertEquals("2022-07-18T15:11:11", format.parse("2022/07/18T15:11:11.Z").toString()); assertEquals("2022-07-18T15:11:11.100", format.parse("2022/07/18T15:11:11.1Z").toString()); assertEquals("2022-07-18T15:11:11.130", format.parse("2022/07/18T15:11:11.13Z").toString()); assertEquals("2022-07-18T15:11:11.133", format.parse("2022/07/18T15:11:11.133Z").toString()); assertThrows(Exception.class, () -> format.parse("2022-07-18T1:1:1Z")); } @Test public void test3() { String string1 = "2022-07-18T15:11:11.133"; assertEquals("2022-07-18T15:11:11.133", LocalDateTime.parse(string1, DateTimeFormatter.ISO_LOCAL_DATE_TIME).toString()); String string2 = DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(LocalDateTime.parse(string1, DateTimeFormatter.ISO_LOCAL_DATE_TIME)); assertEquals(string1, string2); String dateString = "2022-07-18T15:11:11.133Z"; LocalDateTime date = LocalDateTime.ofInstant(Instant.parse(dateString), ZoneOffset.UTC); assertEquals(date.toString(), "2022-07-18T15:11:11.133"); date = LocalDateTime.ofInstant(Instant.parse(dateString), ZoneOffset.ofHours(8)); assertEquals(date.toString(), "2022-07-18T23:11:11.133"); assertThrows( DateTimeParseException.class, () -> LocalDateTime.parse(dateString, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")) ); } @Test public void test4() { String text = "2022/07/18T15:11:11.133Z"; //System.out.println(JavaTimeDateFormat.DEFAULT.parse(text)); //LocalDateTime.parse(text, DateTimeFormatter.ofPattern("yyyy/MM/dd'T'HH:mm:ss.SSSZ")); //LocalDateTime.ofInstant(DateTimeFormatter.ofPattern("yyyy/MM/dd'T'HH:mm:ss.SSSZ").parse(text, Instant::from), ZoneOffset.UTC); /* DateTimeFormatter dateTimeFormatter = new DateTimeFormatterBuilder() .parseCaseInsensitive() .append(DateTimeFormatter.ofPattern("yyyy/MM/dd")) .appendLiteral('T') .append(DateTimeFormatter.ISO_LOCAL_TIME) .appendInstant() .toFormatter(); */ LocalDateTimeFormat format = LocalDateTimeFormat.DEFAULT; /*System.out.println(format.parse("2022/07/18T15:11:11.1Z").toString()); System.out.println(format.parse("2022/07/18T15:11:11Z").toString()); System.out.println(format.parse("2022/07/18T15:11:11.13Z").toString()); System.out.println(format.parse("2022/07/18T15:11:11.133Z").toString());*/ } } ================================================ FILE: src/test/java/cn/ponfee/commons/event/EventBusTest.java ================================================ package cn.ponfee.commons.event; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; public class EventBusTest { public static class OrderEvent { private String message; public OrderEvent(String message) { this.message = message; } public String getMessage() { return message; } } public static class OrderEventListener { @Subscribe public void listen(OrderEvent event) { System.out.println("OrderEventListener receive msg: " + event.getMessage()); } @Subscribe public void listen(String event) { System.out.println("OrderEventListener receive msg: " + event); } } public static void main(String[] args) { /* * 通过EventBus.register(Object object)方法来注册订阅者(subscriber), * 使用EventBus.post(Object event)方法来发布事件。 */ //1.Creates a new EventBus with the given identifier. EventBus eventBus = new EventBus("jackson"); //2.register all subscriber eventBus.register(new OrderEventListener()); //eventBus.register(new HelloListener()); //publish event eventBus.post(new OrderEvent("order-event-message")); eventBus.post("string-event-message"); } } ================================================ FILE: src/test/java/cn/ponfee/commons/exception/ThrowablesTest.java ================================================ package cn.ponfee.commons.exception; import cn.ponfee.commons.exception.Throwables.ThrowingConsumer; import cn.ponfee.commons.exception.Throwables.ThrowingFunction; import cn.ponfee.commons.exception.Throwables.ThrowingRunnable; import cn.ponfee.commons.exception.Throwables.ThrowingSupplier; import cn.ponfee.commons.util.ImageUtils; import org.junit.Test; import java.io.FileInputStream; /** * @author Ponfee */ public class ThrowablesTest { @Test public void test() { ThrowingRunnable.doCaught(ThrowablesTest::get0); System.out.println("---------------\n"); ThrowingRunnable.doCaught(ThrowablesTest::get1); System.out.println("---------------\n"); String caught = ThrowingSupplier.doCaught(ThrowablesTest::get2); System.out.println("---------------" + caught + "\n"); ThrowingConsumer.doCaught(ThrowablesTest::get3, "xxx"); System.out.println("---------------\n"); String yyy = ThrowingFunction.doCaught(ThrowablesTest::get4, "yyy"); System.out.println("---------------" + yyy + "\n"); } public static void get0() { System.out.println("get0"); int i = 1 / 0; } public static void get1() throws Throwable { System.out.println("get1"); ImageUtils.getImageType(new FileInputStream("")); } public static String get2() throws Throwable { System.out.println("get2"); ImageUtils.getImageType(new FileInputStream("")); return ""; } public static void get3(String a) throws Throwable { System.out.println("get3:" + a); ImageUtils.getImageType(new FileInputStream("")); } public static String get4(String a) throws Throwable { System.out.println("get4:" + a); ImageUtils.getImageType(new FileInputStream("")); return a; } } ================================================ FILE: src/test/java/cn/ponfee/commons/innerclass/MyInterface.java ================================================ package cn.ponfee.commons.innerclass; public interface MyInterface { void doSomething(); } ================================================ FILE: src/test/java/cn/ponfee/commons/innerclass/TryUsingAnonymousClass.java ================================================ package cn.ponfee.commons.innerclass; public class TryUsingAnonymousClass { public static void main(String[] args) { new TryUsingAnonymousClass().useMyInterface(); } public void useMyInterface() { final Integer number = 123; System.out.println(number); MyInterface myInterface = new MyInterface() { @Override public void doSomething() { System.out.println(number); } }; myInterface.doSomething(); System.out.println(number); } } ================================================ FILE: src/test/java/cn/ponfee/commons/io/BeforeReadInputStreamTest.java ================================================ package cn.ponfee.commons.io; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import org.apache.commons.io.IOUtils; import org.junit.Assert; import org.junit.Test; import cn.ponfee.commons.util.MavenProjects; public class BeforeReadInputStreamTest { @Test public void test() throws IOException { File f = MavenProjects.getTestJavaFile(BeforeReadInputStreamTest.class); //System.out.println(Files.toString(f)); byte[] fb, bb; /*BeforeReadInputStream binput = new BeforeReadInputStream(new FileInputStream(f), 200); System.out.println(new String(binput.getArray()).replaceAll("\n|\r\n", "")); fb = IOUtils.toByteArray(new FileInputStream(f)); bb = IOUtils.toByteArray(binput); Assert.assertArrayEquals(fb, bb);*/ for (int i = 1, n = (int) f.length() + 500; i < n; i += 7) { try (InputStream input1 = new FileInputStream(f); InputStream input2 = new PrereadInputStream(new FileInputStream(f), i) ){ fb = IOUtils.toByteArray(input1); bb = IOUtils.toByteArray(input2); Assert.assertArrayEquals(fb, bb); } catch (Exception e) { e.printStackTrace(); } } } } ================================================ FILE: src/test/java/cn/ponfee/commons/io/CopyrightTest.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.io; import cn.ponfee.commons.exception.Throwables.*; import cn.ponfee.commons.util.MavenProjects; import cn.ponfee.commons.util.Strings; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.junit.Test; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; import java.util.function.Consumer; import static java.nio.charset.StandardCharsets.UTF_8; /** * 添加copyright * * @author Ponfee */ public class CopyrightTest { private static final String baseDir = MavenProjects.getMainJavaPath(""); private static final String copyright = ThrowingSupplier.doChecked( () -> IOUtils.resourceToString("copy-right.txt", UTF_8, CopyrightTest.class.getClassLoader()) ); @Test public void upsertCopyright() { handleFile(file -> { String text = ThrowingSupplier.doChecked(() -> IOUtils.toString(file.toURI(), UTF_8)); if (!isOwnerCode(text)) { return; } try { if (!text.contains("** \\______ \\____ _____/ ____\\____ ____ Copyright (c) 2017-20")) { Writer writer = new FileWriter(file.getAbsolutePath()); IOUtils.write(copyright, writer); IOUtils.write(text, writer); writer.flush(); writer.close(); return; } if (!text.contains("** \\______ \\____ _____/ ____\\____ ____ Copyright (c) 2017-2023 Ponfee **")) { Writer writer = new FileWriter(file.getAbsolutePath()); IOUtils.write(copyright, writer); IOUtils.write(text.substring(84 * 7 + 1), writer); writer.flush(); writer.close(); return; } } catch (IOException e) { ExceptionUtils.rethrow(e); } }); } @Test public void checkCopyright() { handleFile(file -> { String text = ThrowingSupplier.doChecked(() -> IOUtils.toString(file.toURI(), UTF_8)); if (Strings.count(text, " @author ") == 0) { System.out.println(file.getName()); } else if (isOwnerCode(text)) { // 自己编写的代码,需要加Copyright if (!text.contains(" Copyright (c) 2017-2023 Ponfee ")) { System.out.println(file.getName()); } } else { // 引用他人的代码,不用加Copyright if (text.contains(" Copyright (c) 2017-2023 Ponfee ")) { System.out.println(file.getName()); } } }); } private static void handleFile(Consumer consumer) { FileUtils .listFiles(new File(baseDir).getParentFile(), new String[]{"java"}, true) .forEach(e -> ThrowingRunnable.doChecked(() -> consumer.accept(e))); } private boolean isOwnerCode(String sourceCode) { if (sourceCode.contains("public class " + getClass().getSimpleName() + " {\n")) { return true; } return sourceCode.contains(" * @author Ponfee\n") && Strings.count(sourceCode, " @author ") == 1; } } ================================================ FILE: src/test/java/cn/ponfee/commons/io/FileTransformerTest.java ================================================ package cn.ponfee.commons.io; import org.junit.Test; public class FileTransformerTest { @Test public void test() { //System.out.println(detectBytesCharset(Streams.file2bytes("D:\\test\\2.png"))); //System.out.println(detectBytesCharset(Streams.file2bytes("D:\\test\\lib\\cache\\Cache.java"))); /*FileTransformer transformer = new FileTransformer(MavenProjects.getMainJavaPath(""), "/Users/ponfee/test", "GBK"); transformer.transform(); System.out.println(transformer.getTransformLog());*/ FileTransformer t = new FileTransformer("/Users/ponfee/scm/github/commons-core", "/Users/ponfee/scm/github/test111/commons-core"); t.setReplaceEach(new String[]{"cn.ponfee.commons."}, new String[]{"cn.ponfee.commons."}); t.transform(); } } ================================================ FILE: src/test/java/cn/ponfee/commons/io/FileTypeDetector.java ================================================ package cn.ponfee.commons.io; import cn.ponfee.commons.util.MavenProjects; import com.google.common.collect.ImmutableMap; import org.apache.commons.codec.binary.Hex; import org.apache.commons.lang3.ArrayUtils; import org.apache.tika.Tika; import org.apache.tika.metadata.Metadata; import org.apache.tika.parser.AutoDetectParser; import org.apache.tika.parser.ParseContext; import org.xml.sax.helpers.DefaultHandler; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Map; public class FileTypeDetector { // -----------------------------------------------------------------file type private static final int SUB_PREFIX = 64; public static final Map FILE_TYPE_MAGIC = ImmutableMap. builder() .put("jpg", "FFD8FF") // JPEG (jpg) .put("png", "89504E47") // PNG (png) .put("gif", "47494638") // GIF (gif) .put("tif", "49492A00") // TIFF (tif) .put("bmp", "424D") // Windows Bitmap (bmp) .put("dwg", "41433130") // CAD (dwg) .put("html","68746D6C3E") // HTML (html) .put("rtf", "7B5C727466") // Rich Text Format (rtf) .put("xml", "3C3F786D6C") .put("zip", "504B0304") .put("rar", "52617221") .put("psd", "38425053") // Photoshop (psd) .put("eml", "44656C69766572792D646174653A") // Email [thorough only] (eml) .put("dbx", "CFAD12FEC5FD746F") // Outlook Express (dbx) .put("pst", "2142444E") // Outlook (pst) .put("xls", "D0CF11E0") // MS Word .put("doc", "D0CF11E0") // MS Excel 注意:word 和 excel的文件头一样 .put("mdb", "5374616E64617264204A") // MS Access (mdb) .put("wpd", "FF575043") // WordPerfect (wpd) .put("eps", "252150532D41646F6265") .put("ps", "252150532D41646F6265") .put("pdf", "255044462D312E") // Adobe Acrobat (pdf) .put("qdf", "AC9EBD8F") // Quicken (qdf) .put("pwl", "E3828596") // Windows Password (pwl) .put("wav", "57415645") // Wave (wav) .put("avi", "41564920") .put("ram", "2E7261FD") // Real Audio (ram) .put("rm", "2E524D46") // Real Media (rm) .put("mpg", "000001BA") .put("mov", "6D6F6F76") // Quicktime (mov) .put("asf", "3026B2758E66CF11") // Windows Media (asf) .put("mid", "4D546864") // MIDI (mid) .build(); /** * 探测文件类型 * * @param file * @return * @throws IOException */ public static String detectFileType(File file) throws IOException { return detectFileType(Files.readByteArray(file, SUB_PREFIX)); } public static String detectFileType(byte[] array) { if (array.length > SUB_PREFIX) { array = ArrayUtils.subarray(array, 0, SUB_PREFIX); } String hex = Hex.encodeHexString(array, false); for (Map.Entry entry : FILE_TYPE_MAGIC.entrySet()) { if (hex.startsWith(entry.getValue())) { return entry.getKey(); } } return null; } public static void main(String[] args) throws IOException { File file = MavenProjects.getTestJavaFile(FileTypeDetector.class); // 1 Tika tika1 = new Tika(); String fileType = tika1.detect(file); System.out.println(fileType); // 2 AutoDetectParser parser1 = new AutoDetectParser(); parser1.setParsers(new HashMap<>()); Metadata metadata1 = new Metadata(); //metadata.add(TikaMetadataKeys.RESOURCE_NAME_KEY, file.getName()); try (InputStream stream = new FileInputStream(file)) { parser1.parse(stream, new DefaultHandler(), metadata1, new ParseContext()); System.out.println(metadata1); } catch (Exception e) { e.printStackTrace(); } // 3 Tika tika2 = new Tika(); Metadata metadata2 = new Metadata(); tika2.parse(new FileInputStream(file), metadata2); System.out.println(metadata2); } } ================================================ FILE: src/test/java/cn/ponfee/commons/io/FilesTest.java ================================================ package cn.ponfee.commons.io; import cn.ponfee.commons.extract.DataExtractorBuilder; import cn.ponfee.commons.io.charset.BytesDetector; import cn.ponfee.commons.json.Jsons; import cn.ponfee.commons.spring.SpringContextHolder; import cn.ponfee.commons.tree.TreeNode; import cn.ponfee.commons.util.MavenProjects; import com.google.common.collect.Lists; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.csv.CSVFormat; import org.apache.commons.lang3.StringUtils; import org.junit.Test; import org.openjdk.jol.info.ClassLayout; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.net.MalformedURLException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class FilesTest { @Test public void testx() { System.out.println(ClassLayout.parseInstance(new Object()).toPrintable()); System.out.println("\n-----------------------"); System.out.println(ClassLayout.parseInstance(new Object[10]).toPrintable()); System.out.println("\n-----------------------"); System.out.println(ClassLayout.parseInstance(new long[10]).toPrintable()); System.out.println("\n-----------------------"); System.out.println(CharsetDetector.detect(MavenProjects.getMainJavaFile(TreeNode.class))); } @Test public void test1() throws MalformedURLException { System.out.println("GBK.properties -> "+ CharsetDetector.detect("D:/temp/GBK.properties")); System.out.println("UTF8.txt -> "+ CharsetDetector.detect("D:/temp/UTF8.txt")); System.out.println("UTF8-WITH-BOM.xml -> "+ CharsetDetector.detect("D:/temp/UTF8-WITH-BOM.xml")); System.out.println("UTF8-WITHOUT-BOM.xml -> "+ CharsetDetector.detect("D:/temp/UTF8-WITHOUT-BOM.xml")); System.out.println("UTF16-BIG-ENDIAN-WITH-BOM.xml -> "+ CharsetDetector.detect("D:/temp/UTF16-BIG-ENDIAN-WITH-BOM.xml")); System.out.println("UTF16-BIG-ENDIAN-WITHOUT-BOM.xml -> "+ CharsetDetector.detect("D:/temp/UTF16-BIG-ENDIAN-WITHOUT-BOM.xml")); System.out.println("UTF16-WITH-BOM.xml -> "+ CharsetDetector.detect("D:/temp/UTF16-WITH-BOM.xml")); System.out.println("UTF16-WITHOUT-BOM.xml -> "+ CharsetDetector.detect("D:/temp/UTF16-WITHOUT-BOM.xml")); } @Test public void test0() throws IOException { System.out.println(ByteOrderMarks.has("D:\\temp\\withbom\\csv-gbk-bom.csv")); System.out.println(ByteOrderMarks.has("D:\\temp\\withbom\\csv-utf8-bom.csv")); System.out.println(ByteOrderMarks.has("D:\\temp\\withbom\\csv-utf16le-bom.csv")); System.out.println(ByteOrderMarks.has("D:\\temp\\withbom\\csv-utf16be-bom.csv")); System.out.println(ByteOrderMarks.has("D:\\temp\\withbom\\csv-unicode-ascii-escaped-bom.csv")); System.out.println(ByteOrderMarks.has("D:\\temp\\withbom\\csv-ansi-ascii-bom.csv")); } @Test public void test2() throws IOException { /*System.out.println(WindowsBOM.add("D:\\temp\\withbom\\csv-gbk-bom.csv")); System.out.println(WindowsBOM.add("D:\\temp\\withbom\\csv-utf8-bom.csv")); System.out.println(WindowsBOM.add("D:\\temp\\withbom\\csv-utf16le-bom.csv")); System.out.println(WindowsBOM.add("D:\\temp\\withbom\\csv-utf16be-bom.csv")); System.out.println(WindowsBOM.add("D:\\temp\\withbom\\csv-unicode-ascii-escaped-bom.csv")); System.out.println(WindowsBOM.add("D:\\temp\\withbom\\csv-ansi-ascii-bom.csv"));*/ System.out.println(ByteOrderMarks.add(StandardCharsets.UTF_8, "D:\\temp\\withoutbom\\test-utf8-bom.csv")); System.out.println(ByteOrderMarks.add(StandardCharsets.UTF_16LE, "D:\\temp\\withoutbom\\test-utf16le-bom.csv")); System.out.println(ByteOrderMarks.add(StandardCharsets.UTF_16BE, "D:\\temp\\withoutbom\\test-utf16be-bom.csv")); } @Test public void test3() throws IOException { System.out.println(ByteOrderMarks.remove("D:\\temp\\withbom\\csv-gbk-bom.csv")); System.out.println(ByteOrderMarks.remove("D:\\temp\\withbom\\csv-utf8-bom.csv")); System.out.println(ByteOrderMarks.remove("D:\\temp\\withbom\\csv-utf16le-bom.csv")); System.out.println(ByteOrderMarks.remove("D:\\temp\\withbom\\csv-utf16be-bom.csv")); System.out.println(ByteOrderMarks.remove("D:\\temp\\withbom\\csv-unicode-ascii-escaped-bom.csv")); System.out.println(ByteOrderMarks.remove("D:\\temp\\withbom\\csv-ansi-ascii-bom.csv")); } @Test public void test4() throws IOException { String[] files = { "D:\\temp\\withoutbom\\csv-gbk.csv", "D:\\temp\\withbom\\csv-gbk-bom.csv", "D:\\temp\\withoutbom\\csv-utf8.csv", "D:\\temp\\withbom\\csv-utf8-bom.csv", "D:\\temp\\withoutbom\\csv-utf16le.csv", "D:\\temp\\withbom\\csv-utf16le-bom.csv", "D:\\temp\\withoutbom\\csv-utf16be.csv", "D:\\temp\\withbom\\csv-utf16be-bom.csv", "D:\\temp\\withoutbom\\csv-unicode-ascii-escaped.csv", "D:\\temp\\withbom\\csv-unicode-ascii-escaped-bom.csv", "D:\\temp\\withoutbom\\csv-ansi-ascii.csv", "D:\\temp\\withbom\\csv-ansi-ascii-bom.csv", }; for (String file : files) { Charset charset = CharsetDetector.detect(file); System.out.println("\n============================="+file+" -> "+", "+charset+" "+Files.toString(new File(file)).replaceAll("\r\n|\n", ";")); DataExtractorBuilder builder = DataExtractorBuilder.newBuilder(file); builder.build().extract(100).stream().forEach(row -> System.out.println(Jsons.toJson(row))); } } @Test public void test5() throws IOException { System.out.println(CharsetDetector.detect("D:\\temp\\c24aafd4f3f24c2a86734b20f9a0edd3.Adobe_Fireworks_CS6_XiaZaiBa.exe")); System.out.println(CharsetDetector.detect("D:\\temp\\csv-gbk.csv")); System.out.println(CharsetDetector.detect("D:\\temp\\csv-gbk-bom.csv")); System.out.println(CharsetDetector.detect("D:\\temp\\csv-utf8.csv")); System.out.println(CharsetDetector.detect("D:\\temp\\csv-utf8-bom.csv")); System.out.println(CharsetDetector.detect("D:\\temp\\csv-utf16.csv")); System.out.println(CharsetDetector.detect("D:\\temp\\csv-utf16-bom.csv")); System.out.println(CharsetDetector.detect("D:\\temp\\2.png")); System.out.println(CharsetDetector.detect("D:\\temp\\IMG_2485.JPG")); System.out.println(CharsetDetector.detect("D:\\temp\\ca.pfx")); System.out.println(CharsetDetector.detect("D:\\temp\\signers.xml")); } @Test public void test6() throws IOException { System.out.println(Charset.forName("utf-8") == StandardCharsets.UTF_8); System.out.println(Charset.forName("utf8") == StandardCharsets.UTF_8); System.out.println(Charset.forName("utf-16") == StandardCharsets.UTF_16); } @Test public void test7() throws IOException { String[] files = { "D:\\temp\\withoutbom\\test-utf8-bom.csv", "D:\\temp\\withoutbom\\test-utf16le-bom.csv", "D:\\temp\\withoutbom\\test-utf16be-bom.csv", }; CSVFormat format = CSVFormat.DEFAULT.withDelimiter(',').withQuote('"'); for (String file : files) { Charset charset = CharsetDetector.detect(file); System.out.println("\n============================="+file+" -> "+", "+charset+" "+Files.toString(new File(file)).substring(1, 1000).replaceAll("\r\n|\n", ";")); DataExtractorBuilder builder = DataExtractorBuilder.newBuilder(file).csvFormat(format).startRow(1); builder.build().extract(2).stream().forEach(row -> System.out.println(Jsons.toJson(row))); } } @Test public void test() throws IOException { String file; DataExtractorBuilder builder; CSVFormat format = CSVFormat.DEFAULT.withDelimiter(',').withQuote('"'); /*file = "D:\\temp\\withoutbom\\test-utf8.csv"; builder = DataExtractorBuilder.newBuilder(file).csvFormat(format); builder.build().extract(100).stream().forEach(row -> System.out.println(Jsons.toJson(row)));*/ /*file = "D:\\temp\\withoutbom\\test-utf16le.csv"; builder = DataExtractorBuilder.newBuilder(file).csvFormat(format).charset(StandardCharsets.UTF_16LE); builder.build().extract(100).stream().forEach(row -> System.out.println(Jsons.toJson(row)));*/ file = "D:\\temp\\withoutbom\\test-utf16be.csv"; builder = DataExtractorBuilder.newBuilder(file).csvFormat(format).charset(StandardCharsets.UTF_16BE); builder.build().extract(100).stream().forEach(row -> System.out.println(Jsons.toJson(row))); } @Test public void test8() throws IOException { System.out.println(CharsetDetector.detect("D:\\temp\\withoutbom\\test-utf8.csv")); System.out.println(CharsetDetector.detect("D:\\temp\\withoutbom\\test-utf16le.csv")); System.out.println(CharsetDetector.detect("D:\\temp\\withoutbom\\test-utf16be.csv")); //System.out.println(Files.toString(new File("D:\\temp\\withoutbom\\test-utf16be.csv"), "UTF-16BE")); System.out.println(CharsetDetector.detect("D:\\temp\\withoutbom\\gbk.txt")); } @Test public void testDetect() throws IOException { File filePath = MavenProjects.getMainJavaFile(SpringContextHolder.class); System.out.println("CharsetDetector.detect --> " + CharsetDetector.detect(new FileInputStream(filePath))); System.out.println("EncodingDetector.detect --> " + BytesDetector.detect(Files.readByteArray(new FileInputStream(filePath), 12000))); } @Test public void testDetectFile() { //detectFile(MavenProjects.getMainJavaPath("cn.ponfee.commons")); detectFile(MavenProjects.getTestJavaPath("cn.ponfee.commons.io.file")); } private static void detectFile(String filePath) { Files.listFiles(filePath).traverse(tree -> { if (CollectionUtils.isEmpty(tree.getChildren())) { try { File f = tree.getAttach(); System.out.println(f.getName() + ": CharsetDetector=" + CharsetDetector.detect(new FileInputStream(f)) + ", EncodingDetector=" + BytesDetector.detect(Files.readByteArray(new FileInputStream(f), 1200))); } catch (IOException e) { e.printStackTrace(); } } }); } @Test public void testFormat() { String text = "JPFreq[3][74] = 600;\n" + " JPFreq[3][45] = 599;\n" + " JPFreq[3][3] = 598;\n" + " JPFreq[3][24] = 597;\n" + " JPFreq[3][30] = 596;\n" + " JPFreq[4][76] = 485;\n" + " JPFreq[22][65] = 3;\n" + " JPFreq[42][29] = 2;\n" + " JPFreq[27][66] = 1;\n" + " JPFreq[26][89] = 0;"; List collect = Arrays.stream(text.split(";")) .map(String::trim) .collect(Collectors.toList()); int maxLeft = collect.stream().mapToInt(s -> s.split("=")[0].trim().length()).max().orElse(0); int maxRight = collect.stream().mapToInt(s -> s.split("=")[1].trim().length()).max().orElse(0); for (List line : Lists.partition(collect, 5)) { String s = line.stream().map(e -> { String[] array = e.split("="); return StringUtils.rightPad(array[0].trim(), maxLeft, " ") + " = " + StringUtils.leftPad(array[1].trim(), maxRight, " ")+"; "; }).collect(Collectors.joining()); System.out.println(s); } } public static void main(String[] args) { int[][] GBFreq = new int[94][94]; Arrays.stream(GBFreq).forEach(e -> System.out.println(Arrays.toString(e))); } } ================================================ FILE: src/test/java/cn/ponfee/commons/io/WindowsBomTest.java ================================================ package cn.ponfee.commons.io; import java.io.IOException; import java.nio.charset.StandardCharsets; import cn.ponfee.commons.util.MavenProjects; import org.junit.Test; public class WindowsBomTest { @Test public void testhas() throws IOException { System.out.println(ByteOrderMarks.has(MavenProjects.getTestJavaFile(WindowsBomTest.class))); System.out.println(ByteOrderMarks.has("D:\\temp\\withbom\\csv-utf8-bom.csv")); System.out.println(ByteOrderMarks.has("D:\\temp\\withbom\\csv-utf16le-bom.csv")); System.out.println(ByteOrderMarks.has("D:\\temp\\withbom\\csv-utf16be-bom.csv")); } @Test public void testadd() throws IOException { System.out.println(ByteOrderMarks.add("D:\\temp\\withbom\\csv-utf8-bom.csv")); System.out.println(ByteOrderMarks.add("D:\\temp\\withbom\\csv-utf16le-bom.csv")); System.out.println(ByteOrderMarks.add("D:\\temp\\withbom\\csv-utf16be-bom.csv")); } @Test public void testremove() throws IOException { System.out.println(ByteOrderMarks.remove("D:\\temp\\withbom\\csv-utf8-bom.csv")); System.out.println(ByteOrderMarks.remove("D:\\temp\\withbom\\csv-utf16le-bom.csv")); System.out.println(ByteOrderMarks.remove("D:\\temp\\withbom\\csv-utf16be-bom.csv")); } @Test public void testaddCharset() throws IOException { System.out.println(ByteOrderMarks.add(StandardCharsets.UTF_8, "D:\\temp\\withbom\\csv-utf8-bom.csv")); System.out.println(ByteOrderMarks.add(StandardCharsets.UTF_16LE,"D:\\temp\\withbom\\csv-utf16le-bom.csv")); System.out.println(ByteOrderMarks.add(StandardCharsets.UTF_16BE,"D:\\temp\\withbom\\csv-utf16be-bom.csv")); } @Test public void testremoveCharset() throws IOException { System.out.println(ByteOrderMarks.remove(StandardCharsets.UTF_8,"D:\\temp\\withbom\\csv-utf8-bom.csv")); System.out.println(ByteOrderMarks.remove(StandardCharsets.UTF_16LE,"D:\\temp\\withbom\\csv-utf16le-bom.csv")); System.out.println(ByteOrderMarks.remove(StandardCharsets.UTF_16BE,"D:\\temp\\withbom\\csv-utf16be-bom.csv")); } } ================================================ FILE: src/test/java/cn/ponfee/commons/io/WrappedBufferedReaderTest.java ================================================ package cn.ponfee.commons.io; import cn.ponfee.commons.util.MavenProjects; import java.io.File; public class WrappedBufferedReaderTest { public static void main(String[] args) { try (WrappedBufferedReader reader = new WrappedBufferedReader(MavenProjects.getTestJavaFile(WrappedBufferedReaderTest.class))) { for (String str = reader.readLine(); str != null; str = reader.readLine()) { System.out.println(str); } } catch (Exception e) { e.printStackTrace(); } } } ================================================ FILE: src/test/java/cn/ponfee/commons/io/file/ASCII.txt ================================================ ASCII (American Standard Code for information exchange) is a computer coding system based on Latin alphabet, which is mainly used to display modern English and other Western European languages. Powered by gzu-liyujiang 2020/8/7 ================================================ FILE: src/test/java/cn/ponfee/commons/io/file/Big5.txt ================================================ ڷRABIG5uc餤 Powered by Q{CHΦ 2020~85 ================================================ FILE: src/test/java/cn/ponfee/commons/io/file/EUC-KR.txt ================================================ EUC-KR ѱ KSX 1001 ( KSC 5601) ȴ. ԰ KSX 2901 ( Ī KS C 5861) . KS X 1001 Ʈ ǥ Ѵ. ' Ʈ' 0 xA 1 - 0 xFE Ѵ ' Ʈ' 0 xA 1 - 0 xFE Ѵ ================================================ FILE: src/test/java/cn/ponfee/commons/io/file/GB18030.txt ================================================ Ұй ҐЇ йۤƤޤ ݧҧݧ ܧڧѧ Powered by Fݴԣ 202085 ================================================ FILE: src/test/java/cn/ponfee/commons/io/file/GB2312.txt ================================================ ҰйGB2312ֻּ֧ Powered by ݴԣ 202085 ================================================ FILE: src/test/java/cn/ponfee/commons/io/file/GBK.txt ================================================ Ұй йۤƤޤ ݧҧݧ ܧڧѧ Powered by ݴԣ 202085 ================================================ FILE: src/test/java/cn/ponfee/commons/io/file/KOI8-R.txt ================================================ KOI8-R 8 - KOI-8 . , Unicode , KOI8-R , ISO-8859-5. ================================================ FILE: src/test/java/cn/ponfee/commons/io/file/Shift_JIS.txt ================================================ Shift_JIS͓{̃Rs[^VXeł悭gĂR[h\łBSpƔp̃At@xbgAAЉALƓ{̊邱Ƃł܂B Shift_ƖtĂ܂BJIŠ́ASpuɁA{0 x A 1-0 xDFɒuĂp邽߂łB }CN\tgyIBM̓{Rs[^VXeł́ÃR[h\gĂ܂B̃R[h\CP 932ƌĂ΂B ================================================ FILE: src/test/java/cn/ponfee/commons/io/file/UTF-8-BOM.txt ================================================ KOI8-R представляет собой кодирование 8 - битного текста на славянском языке серии KOI-8 для использования на русском и болгарском языках.   до того, как Unicode не стал популярным, KOI8-R был наиболее широко используемым русским кодом, который даже выше стандарта ISO-8859-5.  我爱中国 我愛中國 中国を愛しています Я люблю китай I love China. Powered by 貴州穿青人李裕江 2020年8月7日 ================================================ FILE: src/test/java/cn/ponfee/commons/io/file/UTF-8.txt ================================================ 我爱中国 中国を愛しています 나 는 중국 을 사랑한다 Я люблю китай Powered by 貴州穿青人李裕江 2020年8月5日 ================================================ FILE: src/test/java/cn/ponfee/commons/jce/DesgitTest.java ================================================ package cn.ponfee.commons.jce; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.util.Arrays; import java.util.concurrent.ThreadLocalRandom; import org.apache.commons.codec.binary.Hex; import org.junit.Assert; import org.junit.Test; import com.google.common.base.Stopwatch; import cn.ponfee.commons.jce.digest.DigestUtils; import cn.ponfee.commons.jce.digest.HmacUtils; import cn.ponfee.commons.jce.implementation.digest.RipeMD160Digest; import cn.ponfee.commons.jce.implementation.digest.SHA1Digest; import cn.ponfee.commons.jce.implementation.rsa.RSAKey; import cn.ponfee.commons.jce.implementation.symmetric.RC4; import cn.ponfee.commons.jce.symmetric.Algorithm; import cn.ponfee.commons.jce.symmetric.SymmetricCryptor; import cn.ponfee.commons.jce.symmetric.SymmetricCryptorBuilder; import cn.ponfee.commons.util.MavenProjects; import cn.ponfee.commons.util.SecureRandoms; public class DesgitTest { @Test public void test() { byte[] data = "1234567890".getBytes(); RipeMD160Digest md = RipeMD160Digest.getInstance(); String actual = Hex.encodeHexString(md.doFinal(data)); if(!"9d752daa3fb4df29837088e1e5a1acf74932e074".equals(actual)) { System.err.println("fail"); } else { System.out.println("success"); } System.out.println(Hex.encodeHexString(md.doFinal(data))); System.out.println(Hex.encodeHexString(md.doFinal(data))); md.update(data); System.out.println(Hex.encodeHexString(md.doFinal())); } @Test public void test2() { System.out.println(Hex.encodeHexString(SHA1Digest.getInstance().doFinal())); System.out.println(DigestUtils.sha1Hex(new byte[] {})); byte[] data = MavenProjects.getMainJavaFileAsBytes(SHA1Digest.class); SHA1Digest sha1 = SHA1Digest.getInstance(); System.out.println(Hex.encodeHexString(sha1.doFinal(data))); System.out.println(DigestUtils.sha1Hex(data)); for (int i = 0; i < 1000; i++) { byte[] data1 = SecureRandoms.nextBytes(ThreadLocalRandom.current().nextInt(65537) + 1); byte[] data2 = SecureRandoms.nextBytes(ThreadLocalRandom.current().nextInt(65537) + 1); byte[] data3 = SecureRandoms.nextBytes(ThreadLocalRandom.current().nextInt(65537) + 1); sha1.reset(); sha1.update(data1); sha1.update(data2); sha1.update(data3); byte[] expect = DigestUtils.digest(DigestAlgorithms.SHA1, data1, data2, data3); if (!Arrays.equals(expect, sha1.doFinal())) { System.err.println("FAIL" + " --> " + data.length); } } } public static final int RSA_F4 = 65537; @Test public void test3() { Stopwatch watch = Stopwatch.createStarted(); RSAKey.generateKey(4096, RSA_F4); System.out.println("generateKey1: " + watch.stop()); watch.reset().start(); RSAKey.generateKey(4096, RSA_F4); System.out.println("generateKey2: " + watch.stop()); } @Test public void test4() { byte[] key = "0123456789123456".getBytes(); byte[] data = MavenProjects.getMainJavaFileAsBytes(RC4.class); RC4 rc4 = new RC4(key); byte[] encrypted1 = rc4.encrypt(data); byte[] encrypted2 = rc4.encrypt(data); Assert.assertArrayEquals(encrypted1, encrypted2); if ( !Arrays.equals(rc4.decrypt(encrypted1), data) && !Arrays.equals(rc4.decrypt(encrypted1), data)) { System.err.println("rc4 crypt fail!"); } else { //System.out.println(new String(rc4.crypt(encrypted))); } SymmetricCryptor rc = SymmetricCryptorBuilder.newBuilder(Algorithm.RC4, key).build(); if ( !Arrays.equals(rc.decrypt(encrypted1), data) && !Arrays.equals(rc.decrypt(encrypted1), data)) { System.err.println("rc4 crypt fail!"); } else { //System.out.println(new String(rc4.crypt(encrypted))); } encrypted1 = rc.encrypt(data); encrypted2 = rc.encrypt(data); if ( !Arrays.equals(rc4.decrypt(encrypted1), data) && !Arrays.equals(rc4.decrypt(encrypted2), data)) { System.err.println("rc4 crypt fail!"); } else { //System.out.println(new String(rc4.decrypt(encrypted))); } } @Test public void test5() throws Exception { System.out.println(DigestUtils.sha224Hex("1".getBytes())); System.out.println(DigestUtils.ripeMD160Hex("1234567890".getBytes())); //System.out.println(ObjectUtils.toString(shortText("http://www.manong5.com/102542001/"))); long start = System.currentTimeMillis(); System.out.println(DigestUtils.sha1Hex(new FileInputStream("E:\\tools\\develop\\linux\\CentOS-6.6-x86_64-bin-DVD1.iso"))); System.out.println((System.currentTimeMillis() - start) / 1000); } @Test public void test6() throws FileNotFoundException { byte[] key = SecureRandoms.nextBytes(16); System.out.println(HmacUtils.sha1Hex(key, new FileInputStream(MavenProjects.getMainJavaFile(HmacUtils.class)))); System.out.println(HmacUtils.sha1Hex(key, new FileInputStream(MavenProjects.getMainJavaFile(HmacUtils.class)))); System.out.println(HmacUtils.ripeMD128Hex(key, "abc".getBytes())); System.out.println(HmacUtils.ripeMD160Hex(key, "abc".getBytes())); System.out.println(HmacUtils.ripeMD256Hex(key, "abc".getBytes())); System.out.println(HmacUtils.ripeMD320Hex(key, "abc".getBytes())); } } ================================================ FILE: src/test/java/cn/ponfee/commons/jce/PBECryptorTest.java ================================================ package cn.ponfee.commons.jce; import cn.ponfee.commons.jce.symmetric.Mode; import cn.ponfee.commons.jce.symmetric.PBECryptor; import cn.ponfee.commons.jce.symmetric.PBECryptor.PBEAlgorithm; import cn.ponfee.commons.jce.symmetric.PBECryptorBuilder; import cn.ponfee.commons.jce.symmetric.Padding; public class PBECryptorTest { public static void main(String[] args) { //PBEAlgorithm alg = PBEAlgorithm.PBEWithMD5AndTripleDES; //PBEAlgorithm alg = PBEAlgorithm.PBEWithMD5AndDES; //PBEAlgorithm alg = PBEAlgorithm.PBEWithSHA1AndRC2_40; PBEAlgorithm alg = PBEAlgorithm.PBEWithSHA1AndDESede; char[] pass = "87654321".toCharArray(); byte[] salt = "12345678".getBytes(); int iterations = 100; // 加密 PBECryptor p = PBECryptorBuilder.newBuilder(alg, pass) .mode(Mode.CBC).padding(Padding.PKCS5Padding) .parameter(salt, iterations) .build(); byte[] encrypted = p.encrypt("abc".getBytes()); // 解密 p = PBECryptorBuilder.newBuilder(alg, p.getPass()) .mode(p.getMode()).padding(p.getPadding()) .parameter(p.getSalt(), p.getIterations()) .build(); byte[] decrypted = p.decrypt(encrypted); System.out.println(new String(decrypted)); } } ================================================ FILE: src/test/java/cn/ponfee/commons/jce/PasswdTest.java ================================================ package cn.ponfee.commons.jce; import java.security.GeneralSecurityException; import org.apache.commons.codec.binary.Hex; import org.junit.Assert; import org.junit.Test; import com.google.common.base.Stopwatch; import cn.ponfee.commons.jce.passwd.BCrypt; import cn.ponfee.commons.jce.passwd.Crypt; import cn.ponfee.commons.jce.passwd.PBKDF2; import cn.ponfee.commons.jce.passwd.SCrypt; import cn.ponfee.commons.util.SecureRandoms; public class PasswdTest { @Test public void testCrypt() { String passwd = "passwd"; String hashed = Crypt.create(HmacAlgorithms.HmacSHA3_256, passwd, 32, Providers.BC); boolean flag = true; System.out.println(hashed); long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { if (!Crypt.check(passwd, hashed)) { flag = false; break; } } System.out.println("cost: " + (System.currentTimeMillis() - start)); if (flag) { System.out.println("success!"); } else { System.err.println("fail!"); } } /** * Tests the basic functionality of the PasswordHash class * @param args ignored * @throws GeneralSecurityException */ @Test public void testPBKDF2_1() { // Print out 10 hashes for (int i = 0; i < 10; i++) { System.out.println(PBKDF2.create(HmacAlgorithms.HmacSHA3_256, "p\r\nassw0Rd!".toCharArray(), 16, 65535, 32)); } System.out.println("============================================\n"); // Test password validation HmacAlgorithms alg = HmacAlgorithms.HmacSHA3_256; System.out.println("Running tests..."); String passwd = "password"; String hashed = PBKDF2.create(alg, passwd); System.out.println(hashed); boolean failure = false; long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { // 20 seconds if (!PBKDF2.check(passwd, hashed)) { failure = true; break; } } System.out.println("cost: " + (System.currentTimeMillis() - start) / 1000); if (failure) { System.err.println("TESTS FAILED!"); } else { System.out.println("TESTS PASSED!"); } } @Test public void testScrypt() { byte[] pwd = "123456".getBytes(); byte[] salt = "0123456789123456".getBytes(); System.out.println("\n=====================PBKDF2============================="); System.out.println("\n=====================scrypt cost============================="); Stopwatch watch = Stopwatch.createStarted(); SCrypt.scrypt(HmacAlgorithms.HmacSHA256, "123".getBytes(), "123".getBytes(), 16384, 8, 8, 64); // 推荐参数 System.out.println("16384, 8, 8, 64 cost: " + watch.stop()); watch.reset().start(); SCrypt.scrypt(HmacAlgorithms.HmacSHA256, "123".getBytes(), "123".getBytes(), 2, 2, 2, 32); // 推荐参数 System.out.println("2, 2, 2, 32 cost: " + watch.stop()); System.out.println("\n=====================scrypt verify============================="); String actual = Hex.encodeHexString(SCrypt.scrypt(HmacAlgorithms.HmacSHA256, pwd, salt, 8, 255, 255, 32)); if (!"e488217f72b6c850f82911e78427a78d8a64aa7d313cdc9ee6989915d7548df4".equals(actual)) { System.err.println("scrypt fail!"); } else { System.out.println("scrypt success!"); } System.out.println("\n=====================Scrypt============================="); String password = "passwd"; String hashed = SCrypt.create(password, 1, 2, 2); System.out.println(hashed); System.out.println("Test begin..."); boolean flag = true; watch.reset().start(); for (int i = 0; i < 100000; i++) { // 20 seconds if (!SCrypt.check(password, hashed)) { flag = false; break; } } if (flag) { System.out.println("Test success!"); } else { System.err.println("Test fail!"); } System.out.println("cost: " + watch.stop()); } @Test public void testBcrypt() { byte[] pwd = "123456".getBytes(); byte[] salt = "0123456789123456".getBytes(); String actual = Hex.encodeHexString(BCrypt.crypt(pwd, salt, 5)); if (!"ddc41d0b514ecedb8ae12c42e8c2f4419e71e15c519ecd4b".equals(actual)) { System.err.println("crypt fail!"); } else { System.out.println("crypt success!"); } System.out.println(); String password = "passwd"; System.out.println(BCrypt.create(password, 11)); System.out.println(); System.out.println("Test begin..."); boolean flag = true; String hashed = BCrypt.create(password, 5); long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { // 45 seconds if (!BCrypt.check(password, hashed)) { flag = false; break; } } System.out.println("cost: " + (System.currentTimeMillis() - start) / 1000); if (flag) { System.out.println("Test success!"); } else { System.err.println("Test fail!"); } } @Test public void testScrypt2() { byte[] pwd = SecureRandoms.nextBytes(20); byte[] salt = SecureRandoms.nextBytes(16); byte[] except = SCrypt.scrypt(HmacAlgorithms.HmacSHA256, pwd, salt, 8, 255, 255, 32); byte[] actual = org.bouncycastle.crypto.generators.SCrypt.generate(pwd, salt, 8, 255, 255, 32); Assert.assertArrayEquals(except, actual); } @Test public void testBcrypt2() { byte[] pwd = SecureRandoms.nextBytes(20); byte[] salt = SecureRandoms.nextBytes(16); byte[] except = BCrypt.crypt(pwd, salt, 5); byte[] actual = org.bouncycastle.crypto.generators.BCrypt.generate(pwd, salt, 5); Assert.assertArrayEquals(except, actual); } @Test public void testPBKDF2_2() { String pwd = "SecureRandoms.nextBytes(20)"; int iterationCount = 20; int dkLen = 32; byte[] salt = SecureRandoms.nextBytes(16); byte[] except = PBKDF2.pbkdf2(HmacAlgorithms.HmacSHA3_256, pwd.toCharArray(), salt, iterationCount, dkLen); byte[] actual = SCrypt.pbkdf2(HmacAlgorithms.HmacSHA3_256, pwd.getBytes(), salt, iterationCount, dkLen); Assert.assertArrayEquals(except, actual); } } ================================================ FILE: src/test/java/cn/ponfee/commons/jce/SCryptTester.java ================================================ // Copyright (C) 2011 - Will Glozer. All rights reserved. package cn.ponfee.commons.jce; import static java.lang.Integer.MAX_VALUE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.util.Base64; import org.junit.Assert; import org.junit.Test; import cn.ponfee.commons.jce.passwd.SCrypt; public class SCryptTester { String passwd = "secret"; @Test public void scrypt() { int N = 8388608; int r = 1; int p = 1; String hashed = SCrypt.create(passwd, N, r, p); String[] parts = hashed.split("\\$"); assertEquals(5, parts.length); assertEquals("s0", parts[1]); Assert.assertEquals(16, Base64.getUrlDecoder().decode(parts[3]).length); assertEquals(32, Base64.getUrlDecoder().decode(parts[4]).length); long params = Long.parseLong(parts[2], 16); // 0xe0801 >> 16 -> 0xe assertEquals(N, (int) Math.pow(2, params >> 16 & 0xffff)); assertEquals(r, params >> 8 & 0xff); assertEquals(p, params >> 0 & 0xff); } @Test public void check() { String hashed = SCrypt.create(passwd, 16384, 8, 1); assertTrue(SCrypt.check(passwd, hashed)); assertFalse(SCrypt.check("s3cr3t", hashed)); } @Test public void format_0_rp_max() { int N = 2; int r = 255; int p = 255; String hashed = SCrypt.create(passwd, N, r, p); assertTrue(SCrypt.check(passwd, hashed)); String[] parts = hashed.split("\\$"); long params = Long.parseLong(parts[2], 16); assertEquals(N, (int) Math.pow(2, params >>> 16 & 0xffff)); assertEquals(r, params >> 8 & 0xff); assertEquals(p, params >> 0 & 0xff); } public static void main(String[] args) { System.out.println(MAX_VALUE / 128 / 255); System.out.println(Long.toString(8388608, 16)); System.out.println(Math.pow(2, 0xe)); } } ================================================ FILE: src/test/java/cn/ponfee/commons/jce/security/DHKeyExchangerTest.java ================================================ package cn.ponfee.commons.jce.security; import javax.crypto.interfaces.DHPrivateKey; import javax.crypto.interfaces.DHPublicKey; import org.apache.commons.lang3.tuple.Pair; import cn.ponfee.commons.jce.Providers; import cn.ponfee.commons.util.MavenProjects; public class DHKeyExchangerTest { public static void main(String[] args) { Providers.set(Providers.BC); Pair partA = DHKeyExchanger.initPartAKey(1024); Pair partB = DHKeyExchanger.initPartBKey(partA.getLeft()); byte[] data = MavenProjects.getMainJavaFileAsBytes(DHKeyExchanger.class); // 乙方加密甲方解密 byte[] encrypted = DHKeyExchanger.encrypt(data, DHKeyExchanger.genSecretKey(partB.getRight(), partA.getLeft())); byte[] decrypted = DHKeyExchanger.decrypt(encrypted, DHKeyExchanger.genSecretKey(partA.getRight(), partB.getLeft())); System.out.println(new String(decrypted)); // 甲方加密乙方解密 encrypted = DHKeyExchanger.encrypt(data, DHKeyExchanger.genSecretKey(partA.getRight(), partB.getLeft())); decrypted = DHKeyExchanger.decrypt(encrypted, DHKeyExchanger.genSecretKey(partB.getRight(), partA.getLeft())); System.out.println(new String(decrypted)); } } ================================================ FILE: src/test/java/cn/ponfee/commons/jce/security/DSASignerTest.java ================================================ package cn.ponfee.commons.jce.security; import java.security.interfaces.DSAPrivateKey; import java.security.interfaces.DSAPublicKey; import org.apache.commons.lang3.tuple.Pair; import cn.ponfee.commons.jce.Providers; public class DSASignerTest { public static void main(String[] args) { Providers.set(Providers.BC); Pair keyPair = DSASigner.initKey(); byte[] data = "123456".getBytes(); byte[] signed = DSASigner.sign(data, keyPair.getRight()); boolean flag = DSASigner.verify(data, keyPair.getLeft(), signed); System.out.println(flag); } } ================================================ FILE: src/test/java/cn/ponfee/commons/jce/security/ECDHKeyExchangerTest.java ================================================ package cn.ponfee.commons.jce.security; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import org.apache.commons.lang3.tuple.Pair; import cn.ponfee.commons.jce.Providers; public class ECDHKeyExchangerTest { public static void main(String[] args) { Providers.set(Providers.BC); Pair partA = ECDHKeyExchanger.initPartAKey(192); Pair partB = ECDHKeyExchanger.initPartBKey(partA.getLeft()); byte[] data = "123456".getBytes(); // 乙方加密甲方解密 byte[] encrypted = ECDHKeyExchanger.encrypt(data, ECDHKeyExchanger.genSecretKey(partB.getRight(), partA.getLeft())); byte[] decrypted = ECDHKeyExchanger.decrypt(encrypted, ECDHKeyExchanger.genSecretKey(partA.getRight(), partB.getLeft())); System.out.println(new String(decrypted)); // 甲方加密乙方解密 encrypted = ECDHKeyExchanger.encrypt(data, ECDHKeyExchanger.genSecretKey(partA.getRight(), partB.getLeft())); decrypted = ECDHKeyExchanger.decrypt(encrypted, ECDHKeyExchanger.genSecretKey(partB.getRight(), partA.getLeft())); System.out.println(new String(decrypted)); } } ================================================ FILE: src/test/java/cn/ponfee/commons/jce/security/ECDSASignerTest.java ================================================ package cn.ponfee.commons.jce.security; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import org.apache.commons.lang3.tuple.Pair; public class ECDSASignerTest { public static void main(String[] args) { Pair keyPair = ECDSASigner.generateKeyPair(571); byte[] data = "123456".getBytes(); byte[] signed = ECDSASigner.signSha512(data, keyPair.getRight()); System.out.println(signed.length); System.out.println(ECDSASigner.verifySha512(data, signed, keyPair.getLeft())); /*byte[] encrypted = encrypt(data, getPublicKey(keyMap)); byte[] decrypted = decrypt(encrypted, getPrivateKey(keyMap)); System.out.println(new String(decrypted));*/ } } ================================================ FILE: src/test/java/cn/ponfee/commons/jce/security/RSAPrivateKeysTest.java ================================================ package cn.ponfee.commons.jce.security; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import org.junit.Test; import cn.ponfee.commons.jce.security.RSACryptor.RSAKeyPair; public class RSAPrivateKeysTest { @Test public void test1() { RSAKeyPair keyPair = RSACryptor.generateKeyPair(2018); RSAPrivateKey privKey = keyPair.getPrivateKey(); System.out.println("\n================================================toPkcs1"); System.out.println(RSAPrivateKeys.toPkcs1(privKey)); System.out.println("\n================================================toPkcs1Pem"); System.out.println(RSAPrivateKeys.toPkcs1Pem(privKey)); System.out.println("\n================================================toPkcs8"); System.out.println(RSAPrivateKeys.toPkcs8(privKey)); System.out.println("\n================================================toEncryptedPkcs8"); System.out.println(RSAPrivateKeys.toEncryptedPkcs8(privKey, "123456")); System.out.println("\n================================================toEncryptedPkcs8Pem"); System.out.println(RSAPrivateKeys.toEncryptedPkcs8Pem(privKey, "123456")); } @Test public void test2() { RSAKeyPair keyPair = RSACryptor.generateKeyPair(2018); RSAPublicKey pubKey = keyPair.getPublicKey(); System.out.println("\n================================================toPkcs1"); System.out.println(RSAPublicKeys.toPkcs1(pubKey)); System.out.println("\n================================================toPkcs8"); System.out.println(RSAPublicKeys.toPkcs8(pubKey)); System.out.println("\n================================================toPkcs8Pem"); System.out.println(RSAPublicKeys.toPkcs8Pem(pubKey)); } } ================================================ FILE: src/test/java/cn/ponfee/commons/json/BooleanPojoTest.java ================================================ package cn.ponfee.commons.json; import cn.ponfee.commons.model.Result; import com.alibaba.fastjson.JSON; /** * @author Ponfee */ public class BooleanPojoTest { private boolean success1; private boolean isSuccess2; private Boolean success3; private Boolean isSuccess4; public boolean isSuccess1() { return success1; } public void setSuccess1(boolean success1) { this.success1 = success1; } public boolean isSuccess2() { return isSuccess2; } public void setSuccess2(boolean success2) { isSuccess2 = success2; } public Boolean getSuccess3() { return success3; } public void setSuccess3(Boolean success3) { this.success3 = success3; } public Boolean getSuccess4() { return isSuccess4; } public void setSuccess4(Boolean success4) { isSuccess4 = success4; } public static void main(String[] args) { // 一空 BooleanPojoTest pojo1 = new BooleanPojoTest(); pojo1.success1 = true; pojo1.success3 = false; System.out.println(Jsons.toJson(pojo1)); System.out.println(JSON.toJSONString(pojo1)); System.out.println("\n-------------"); BooleanPojoTest pojo2 = new BooleanPojoTest(); pojo2.success1 = false; pojo2.isSuccess4 = false; System.out.println(Jsons.toJson(pojo2)); System.out.println(JSON.toJSONString(pojo2)); System.out.println("\n-------------"); BooleanPojoTest pojo3 = new BooleanPojoTest(); pojo3.success1 = true; System.out.println(Jsons.toJson(pojo3)); System.out.println(JSON.toJSONString(pojo3)); System.out.println("\n-------------"); BooleanPojoTest pojo4 = new BooleanPojoTest(); pojo4.success1 = true; pojo4.success3 = false; pojo4.isSuccess4 = false; System.out.println(Jsons.toJson(pojo4)); System.out.println(JSON.toJSONString(pojo4)); System.out.println("\n-------------"); System.out.println(Jsons.toJson(Result.success())); System.out.println(JSON.toJSONString(Result.success())); } } ================================================ FILE: src/test/java/cn/ponfee/commons/json/FastJsonUtils.java ================================================ package cn.ponfee.commons.json; import java.io.IOException; import java.lang.reflect.Type; import java.text.ParseException; import java.util.Date; import java.util.Random; import org.apache.commons.lang3.time.DateParser; import org.apache.commons.lang3.time.FastDateFormat; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.annotation.JSONField; import com.alibaba.fastjson.parser.DefaultJSONParser; import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer; import com.alibaba.fastjson.serializer.JSONSerializer; import com.alibaba.fastjson.serializer.ObjectSerializer; public class FastJsonUtils { /*private static final SerializeConfig mapping = new SerializeConfig(); static { mapping.put(Date.class, new CustomDateFormatSerializer()); }*/ private static final String[] DATA_PATTERS = { "yyyy-MM-dd HH:mm:ss,SSS", "yyyy-MM-dd HH:mm:ss" }; public static class CustomDateFormatSerializer implements ObjectSerializer { @Override public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException { if (object == null) { serializer.out.writeNull(); return; } serializer.write(FastDateFormat.getInstance(DATA_PATTERS[new Random().nextInt(2)]).format((Date) object)); } } public static class CustomDateFormatDeserializer implements ObjectDeserializer { private static final Date DEFAULT_DATE = new Date(0L); private static final DateParser FORMAT1 = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss"); private static final DateParser FORMAT2 = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss,SSS"); @Override @SuppressWarnings("unchecked") public Date deserialze(DefaultJSONParser parser, Type type, Object fieldName) { String dateString = parser.getLexer().stringVal(); try { return (dateString.length() == 19 ? FORMAT1 : FORMAT2).parse(dateString); } catch (ParseException e) { return DEFAULT_DATE; } } @Override public int getFastMatchToken() { return 0; } } public static class DateBean { @JSONField(serializeUsing = CustomDateFormatSerializer.class, deserializeUsing = CustomDateFormatDeserializer.class) private Date date; public DateBean() {} public DateBean(Date date) { this.date = date; } public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } @Override public String toString() { return "DateBean [date=" + FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss SSS").format(date) + "]"; } } public static void main(String[] args) { DateBean bean = new DateBean(new Date(System.currentTimeMillis())); for (int i = 0; i < 100; i++) { String json = JSON.toJSONString(bean); bean = JSON.parseObject(json, DateBean.class); System.out.println("json: " + json + ", bean: " + bean); } } } ================================================ FILE: src/test/java/cn/ponfee/commons/json/JacksonIgnore.java ================================================ package cn.ponfee.commons.json; import java.util.Map; import com.fasterxml.jackson.annotation.JsonFilter; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; import cn.ponfee.commons.model.Result; public class JacksonIgnore { public static void main(String[] args) throws Exception { SimpleBeanPropertyFilter fieldFilter = SimpleBeanPropertyFilter.serializeAllExcept("code", "b", "c"); SimpleFilterProvider filterProvider = new SimpleFilterProvider().addFilter("fieldFilter", fieldFilter); ObjectMapper mapper = new ObjectMapper(); mapper.setFilterProvider(filterProvider).addMixIn(Map.class, FieldFilterMixIn.class); System.out.println(mapper.writeValueAsString(Result.success())); } @JsonFilter("fieldFilter") interface FieldFilterMixIn { } } ================================================ FILE: src/test/java/cn/ponfee/commons/json/JsonsTest.java ================================================ package cn.ponfee.commons.json; import cn.ponfee.commons.collect.Maps; import cn.ponfee.commons.model.Result; import cn.ponfee.commons.date.Dates; import cn.ponfee.commons.util.ObjectUtils; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig; import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer; import com.alibaba.fastjson.serializer.ObjectSerializer; import com.alibaba.fastjson.serializer.SerializeConfig; import com.alibaba.fastjson.serializer.SimplePropertyPreFilter; import com.alibaba.fastjson.spi.Module; import com.fasterxml.jackson.core.type.TypeReference; import lombok.Data; import org.javamoney.moneta.Money; import org.junit.Assert; import org.junit.Test; import javax.money.Monetary; import java.io.Serializable; import java.util.Arrays; import java.util.Date; import java.util.Map; @SuppressWarnings("unchecked") public class JsonsTest { @Test public void testFastjsonMoney() { Module module = new Module() { @Override public ObjectDeserializer createDeserializer(ParserConfig config, Class type) { if (type == Money.class) { return new FastjsonMoney(); } return null; } @Override public ObjectSerializer createSerializer(SerializeConfig config, Class type) { if (type == Money.class) { return new FastjsonMoney(); } return null; } }; SerializeConfig serializeConfig = new SerializeConfig(); ParserConfig parserConfig = new ParserConfig(); serializeConfig.register(module); parserConfig.register(module); Money money = Money.ofMinor(Monetary.getCurrency("CNY"), 999); String json = JSON.toJSONString(money, serializeConfig); System.out.println(json); Object parse = JSON.parseObject(json, Money.class, parserConfig); System.out.println(parse.getClass()); System.out.println(parse); } @Test public void test1() { String json = Jsons.NORMAL.string(Maps.toMap("a", "abc", "b", 1)); System.out.println(json); Map map = Jsons.NORMAL.parse(json, Map.class); System.out.println("parse(json, target): " + map); map = Jsons.NORMAL.parse(json, new TypeReference>() {}); System.out.println("parse(json, TypeReference): " + map); } @Test public void test2() { Map map = Maps.toMap("a", "xx", "b", 1, "c", 1.2D, "d", null); System.out.println(Jsons.toJson(map)); Result result = Result.success("xx"); System.out.println(Jsons.toJson(result)); } @Test public void test3() { Map map = Maps.toMap("a", "xx", "b", 1, "c", 1.2D, "d", null); System.out.println(JSON.toJSONString(map)); System.out.println(JSON.toJSONString(map, FastjsonPropertyFilter.include("a", "b"))); System.out.println(JSON.toJSONString(map, FastjsonPropertyFilter.exclude("a", "b"))); Result result = Result.success("xx"); System.out.println(JSON.toJSONString(result)); System.out.println(JSON.toJSONString(result, FastjsonPropertyFilter.include("msg"))); System.out.println(JSON.toJSONString(result, FastjsonPropertyFilter.exclude("msg"))); } @Test public void test4() { Map map = Maps.toMap("a", "xx", "b", 1, "c", 1.2D, "d", null); System.out.println(JSON.toJSONString(map)); System.out.println(JSON.toJSONString(map, new SimplePropertyPreFilter("a", "b"))); Result result = Result.success("xx"); System.out.println(JSON.toJSONString(result)); System.out.println(JSON.toJSONString(result, new SimplePropertyPreFilter("msg"))); } @Test public void test5() { System.out.println(Jsons.fromJson(Jsons.toJson(new StringBuilder("111111111")), StringBuilder.class)); System.out.println(Jsons.fromJson(Jsons.toJson(new StringPlain()), StringPlain.class)); System.out.println(JSON.parseObject(JSON.toJSONString(new StringBuilder("111111111")), StringBuilder.class)); System.out.println(JSON.parseObject(JSON.toJSONString(new StringPlain()), StringPlain.class)); System.out.println(JSON.parse(JSON.toJSONString(Arrays.asList(1, 2, 3, 4)))); } @Test public void test6() { String datestring = "2022-01-01 01:01:01"; Person person1 = new Person(); person1.setName("tom"); person1.setBirthday(Dates.toDate(datestring)); person1.setBalance(Money.ofMinor(Monetary.getCurrency("CNY"), 999)); String personString = Jsons.toJson(person1); Assert.assertEquals("{\"name\":\"tom\",\"birthday\":\"2022-01-01 01:01:01\",\"balance\":{\"currency\":\"CNY\",\"number\":999}}", personString); Person person2 = Jsons.fromJson("{\"name\":\"tom\",\"birthday\":\"2022-01-01 01:01:01\",\"balance\":{\"currency\":\"CNY\",\"number\":999},\"birthday2\":null,\"balance2\":null}", Person.class); Assert.assertEquals(person1.getBirthday(), person2.getBirthday()); Assert.assertEquals(person1.getBalance(), person2.getBalance()); Person person3 = Jsons.fromJson("{\"name\":\"tom\",\"birthday\":\"2022-01-01T01:01:01.0Z\",\"balance\":{\"currency\":\"CNY\",\"number\":999}}", Person.class); Assert.assertEquals(person1.getBirthday(), person3.getBirthday()); Assert.assertEquals(person1.getBalance(), person3.getBalance()); Person person4 = new Person(); person4.setName("person4"); String json4 = Jsons.toJson(person4); Assert.assertEquals(json4, "{\"name\":\"person4\"}"); person4 = Jsons.fromJson(json4, Person.class); Assert.assertEquals(person4.getName(), "person4"); Assert.assertNull(person4.getBalance()); } @Test public void test7() { Money money1 = Money.ofMinor(Monetary.getCurrency("CNY"), 999); String moneyStr = Jsons.toJson(money1); System.out.println(moneyStr); Assert.assertEquals("{\"currency\":\"CNY\",\"number\":999}", moneyStr); Money money2 = Jsons.fromJson(moneyStr, Money.class); Assert.assertEquals(money1, money2); } public static class StringPlain implements Serializable { private static final long serialVersionUID = 1L; private StringBuilder sb = new StringBuilder("xxxxxxxxxx"); public StringBuilder getSb() { return sb; } public void setSb(StringBuilder sb) { this.sb = sb; } @Override public String toString() { return ObjectUtils.toString(this); } } @Data public static class Person implements java.io.Serializable { private String name; private Date birthday; private Money balance; private Date birthday2; private Money balance2; } } ================================================ FILE: src/test/java/cn/ponfee/commons/loadbalance/AbstractLoadBalance.java ================================================ package cn.ponfee.commons.loadbalance; /** * server load balance algorithm * * @author Ponfee */ public abstract class AbstractLoadBalance { public abstract String select(); } ================================================ FILE: src/test/java/cn/ponfee/commons/loadbalance/HashedLoadBalance.java ================================================ package cn.ponfee.commons.loadbalance; import java.util.ArrayList; import java.util.List; import java.util.Map; import cn.ponfee.commons.math.Maths; /** * 源地址哈希法 * * @author Ponfee */ public class HashedLoadBalance extends AbstractLoadBalance { private final List servers; public HashedLoadBalance(Map serverMap) { this.servers = new ArrayList<>(serverMap.keySet()); } @Override public String select() { throw new UnsupportedOperationException(); } public String select(String invokeIp) { return servers.get(Maths.abs(invokeIp.hashCode()) % servers.size()); } } ================================================ FILE: src/test/java/cn/ponfee/commons/loadbalance/LeastActiveLoadBalance.java ================================================ package cn.ponfee.commons.loadbalance; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; /** * 最少活跃数 * * @author Ponfee */ public class LeastActiveLoadBalance extends AbstractLoadBalance { private final Map serverMap; private final List> servers; public LeastActiveLoadBalance(Map serverMap) { this.serverMap = serverMap; this.servers = new ArrayList<>(serverMap.entrySet()); this.servers.sort(Comparator.comparing(e -> e.getValue().get())); //this.servers.sort(Comparator.comparing(Entry::getValue)); //this.servers.sort(Comparator.comparing(e -> e.getValue().get())); //this.servers.sort((o1, o2) -> o1.getValue().compareTo(o2.getValue())); //Collections.sort(servers, Comparator.comparing(Entry::getValue)); } @Override public String select() { return servers.get(0).getKey(); } /** * 调用前活跃数加1 * * @param server */ public void begin(String server) { serverMap.get(server).incrementAndGet(); } /** * 调用后活跃数减1 * * @param server */ public void end(String server) { serverMap.get(server).decrementAndGet(); } } ================================================ FILE: src/test/java/cn/ponfee/commons/loadbalance/RandomLoadBalance.java ================================================ package cn.ponfee.commons.loadbalance; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ThreadLocalRandom; /** * 随机法 * * @author Ponfee */ public class RandomLoadBalance extends AbstractLoadBalance { private final List servers; public RandomLoadBalance(Map serverMap) { this.servers = new ArrayList<>(serverMap.keySet()); } @Override public String select() { return servers.get(ThreadLocalRandom.current().nextInt(servers.size())); } } ================================================ FILE: src/test/java/cn/ponfee/commons/loadbalance/RoundRobinLoadBalance.java ================================================ package cn.ponfee.commons.loadbalance; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; /** * 轮询法 * * @author Ponfee */ public class RoundRobinLoadBalance extends AbstractLoadBalance { private final AtomicLong pos = new AtomicLong(0); private final List servers; public RoundRobinLoadBalance(Map serverMap) { this.servers = new ArrayList<>(serverMap.keySet()); } @Override public String select() { return servers.get((int) (pos.getAndIncrement() % servers.size())); } } ================================================ FILE: src/test/java/cn/ponfee/commons/loadbalance/WeightRandomLoadBalance.java ================================================ package cn.ponfee.commons.loadbalance; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ThreadLocalRandom; /** * 加权随机法 * * @author Ponfee */ public class WeightRandomLoadBalance extends AbstractLoadBalance { private final List servers; public WeightRandomLoadBalance(Map serverMap) { this.servers = new ArrayList<>(); for (Entry entry : serverMap.entrySet()) { for (int n = entry.getValue(), i = 0; i < n; i++) { this.servers.add(entry.getKey()); } } Collections.shuffle(this.servers); } @Override public String select() { return servers.get(ThreadLocalRandom.current().nextInt(servers.size())); } } ================================================ FILE: src/test/java/cn/ponfee/commons/loadbalance/WeightRoundRobinLoadBalance.java ================================================ package cn.ponfee.commons.loadbalance; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.atomic.AtomicLong; /** * 加权轮询法 * * @author Ponfee */ public class WeightRoundRobinLoadBalance extends AbstractLoadBalance { private final AtomicLong pos = new AtomicLong(0); private final List servers; public WeightRoundRobinLoadBalance(Map serverMap) { this.servers = new ArrayList<>(); for (Entry entry : serverMap.entrySet()) { for (int n = entry.getValue(), i = 0; i < n; i++) { this.servers.add(entry.getKey()); } } Collections.shuffle(this.servers); } @Override public String select() { return servers.get((int) (pos.getAndIncrement() % servers.size())); } } ================================================ FILE: src/test/java/cn/ponfee/commons/loadbalance/package-info.java ================================================ /** * server load balance * * @author Ponfee */ package cn.ponfee.commons.loadbalance; ================================================ FILE: src/test/java/cn/ponfee/commons/log/JclLogger.java ================================================ package cn.ponfee.commons.log; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * */ public class JclLogger { private static Log logger = LogFactory.getLog(JclLogger.class); public static void main(String[] args) { logger.error("JclLogger"); } } ================================================ FILE: src/test/java/cn/ponfee/commons/log/JulLogger.java ================================================ package cn.ponfee.commons.log; import java.util.logging.Level; import java.util.logging.Logger; import org.slf4j.bridge.SLF4JBridgeHandler; /** * */ public class JulLogger { private static Logger logger = Logger.getLogger(JulLogger.class.getSimpleName()); public static void main(String[] args) { SLF4JBridgeHandler.removeHandlersForRootLogger(); SLF4JBridgeHandler.install(); logger.log(Level.SEVERE, "JulLogger"); } } ================================================ FILE: src/test/java/cn/ponfee/commons/log/Log4jLogger.java ================================================ package cn.ponfee.commons.log; import org.apache.log4j.Logger; /** * */ public class Log4jLogger { private static Logger logger = Logger.getLogger(Log4jLogger.class); public static void main(String[] args) { logger.error("Log4jLogger"); } } ================================================ FILE: src/test/java/cn/ponfee/commons/log/Slf4jLogger.java ================================================ package cn.ponfee.commons.log; import java.io.IOException; import java.net.URL; import java.util.Enumeration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * */ public class Slf4jLogger { private static Logger logger = LoggerFactory.getLogger(Slf4jLogger.class); public static void main(String[] args) throws IOException { logger.error("Slf4jLogger"); //SystemClassloader==APPClassloader // 加载-classpath(-cp)参数的指定的jar包 Enumeration r = ClassLoader.getSystemResources("org/slf4j/impl/StaticLoggerBinder.class"); while (r.hasMoreElements()) { URL url = (URL) r.nextElement(); System.out.println(url.getPath()); } // ExtClassloader // JAVA_HOME/jre/lib/ext/ // -Xbootclasspath URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs(); for (int i = 0; i < urls.length; i++) { System.out.println(urls[i].toExternalForm()); } } } ================================================ FILE: src/test/java/cn/ponfee/commons/model/ParamsTest.java ================================================ package cn.ponfee.commons.model; import org.junit.Test; /** * * * @author Ponfee */ public class ParamsTest { @Test public void test1() { PageParameter params = new PageParameter(); params.setSort("name, test asc"); params.validateSort("name", "test"); } } ================================================ FILE: src/test/java/cn/ponfee/commons/mybatis/SQLMapperTest.java ================================================ package cn.ponfee.commons.mybatis; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import cn.ponfee.commons.data.DataSourceNaming; public class SQLMapperTest { @DataSourceNaming("'primary'") public void selectScroll() { String sql = ""; AtomicInteger count = new AtomicInteger(0); new SqlMapper(null).selectScroll(sql, new HashMap<>(), Map.class, (param, list) -> { param.put("id", list.get(list.size() - 1).get("id")); count.addAndGet(list.size()); return param; }); System.out.println("================="+count.get()); } } ================================================ FILE: src/test/java/cn/ponfee/commons/reflect/ClassA.java ================================================ package cn.ponfee.commons.reflect; /** * * * @author Ponfee */ public abstract class ClassA { public void test1(T arg) { System.out.println(arg); } public T test2() { return (T)""; } public static class ClassB extends ClassA { } public static void main(String[] args) throws Exception { System.out.println(GenericUtils.getMethodArgActualType(ClassB.class, ClassB.class.getMethod("test1", Object.class), 0)); System.out.println(GenericUtils.getMethodReturnActualType(ClassB.class, ClassB.class.getMethod("test1", Object.class))); System.out.println(); System.out.println(GenericUtils.getMethodArgActualType(ClassB.class, ClassB.class.getMethod("test1", String.class), 0)); System.out.println(GenericUtils.getMethodReturnActualType(ClassB.class, ClassB.class.getMethod("test1", String.class))); } } ================================================ FILE: src/test/java/cn/ponfee/commons/reflect/FieldsTest.java ================================================ package cn.ponfee.commons.reflect; import cn.ponfee.commons.model.BaseEntity; /** * * * @author Ponfee */ public class FieldsTest { public static class ClassA extends BaseEntity.Creator { private static final long serialVersionUID = -5617457253295566886L; } public static void main(String[] args) { ClassA a = new ClassA(); System.out.println(a.getId()); Fields.put(a, "id", 999L); System.out.println(a.getId()); } } ================================================ FILE: src/test/java/cn/ponfee/commons/reflect/GenericExtendsTest.java ================================================ package cn.ponfee.commons.reflect; import java.util.List; import cn.ponfee.commons.model.BaseEntity; public class GenericExtendsTest { public static class ClassA { } public static abstract class ClassB extends ClassA implements List { } public static interface InterfaceC extends List, java.io.Serializable { } public static void main(String[] args) { System.out.println(GenericUtils.getActualTypeVariableMapping(ClassB.class)); System.out.println(GenericUtils.getGenericTypes(ClassB.class)); System.out.println(GenericUtils.getGenericTypes(BaseEntity.Updater.class)); } } ================================================ FILE: src/test/java/cn/ponfee/commons/reflect/GenericTest.java ================================================ package cn.ponfee.commons.reflect; import java.lang.reflect.Field; import java.lang.reflect.GenericDeclaration; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.TypeVariable; import java.util.List; import org.junit.Test; import cn.ponfee.commons.model.BaseEntity; import cn.ponfee.commons.util.ObjectUtils; import cn.ponfee.commons.ws.adapter.MarshalJsonAdapter; import cn.ponfee.commons.ws.adapter.ResultListAdapter; import cn.ponfee.commons.ws.adapter.ResultListMapNormalAdapter; /** * * * @author Ponfee */ public class GenericTest { @Test public void test0() { Field creator = ClassUtils.getField(BeanClass.class, "creator"); Field id = ClassUtils.getField(BeanClass.class, "id"); System.out.println("\n1--------------------------------"); System.out.println(creator.getType()); System.out.println(creator.getGenericType().getClass()); System.out.println("\n2--------------------------------"); System.out.println(id.getType()); System.out.println(id.getGenericType().getClass()); System.out.println("\n3--------------------------------"); System.out.println(GenericUtils.getActualTypeArgument(id)); System.out.println(GenericUtils.getActualTypeArgument(creator)); } @Test public void test1() throws Exception { Method method = B.class.getDeclaredMethod("setList", List.class); System.out.println("====" + GenericUtils.getActualArgTypeArgument(method, 0, 0)); java.lang.reflect.Type type = method.getGenericParameterTypes()[0]; System.out.println(type.getClass()); // class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl System.out.println(type.getTypeName()); // java.util.List System.out.println("\n1--------------------------------"); ParameterizedType ptype = (ParameterizedType) type; System.out.println(ptype.getRawType()); // interface java.util.List System.out.println(ptype.getActualTypeArguments()[0]); System.out.println(ptype.getOwnerType()); // null System.out.println("\n2--------------------------------"); Field field = B.class.getDeclaredField("list"); System.out.println("====" + GenericUtils.getActualTypeArgument(field, 0)); type = field.getGenericType(); System.out.println(type.getTypeName()); // java.util.List System.out.println(type.getClass()); // class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl System.out.println("\n3--------------------------------"); method = GenericUtils.class.getDeclaredMethod("getActualTypeArgument", Class.class, int.class); System.out.println(ObjectUtils.toString(method.getParameterTypes())); System.out.println(ObjectUtils.toString(method.getGenericParameterTypes())); System.out.println(ObjectUtils.toString(method.getTypeParameters())); System.out.println(ObjectUtils.toString(GenericUtils.getActualArgTypeArgument(method, 0, 0))); } @Test public void test3() throws Exception { //System.out.println(GenericUtils.getActualTypeArgument(ClassUtils.getField(B.class, "f1"))); System.out.println(GenericUtils.getActualTypeArgument(B.class)); System.out.println(GenericUtils.getActualTypeArgument(String.class)); System.out.println(GenericUtils.getActualTypeArgument(BeanClass.class)); } @Test public void test4() throws Exception { System.out.println(GenericUtils.getFieldActualType(BeanClass.class, ClassUtils.getField(BeanClass.class, "id"))); System.out.println(GenericUtils.getFieldActualType(BeanClass.class, ClassUtils.getField(BeanClass.class, "creator"))); System.out.println(GenericUtils.getFieldActualType(BeanClass2.class, ClassUtils.getField(BeanClass2.class, "creator"))); } @Test public void test5() throws Exception { TypeVariable type = (TypeVariable) ClassUtils.getField(BeanClass.class, "creator").getGenericType(); GenericDeclaration gd = type.getGenericDeclaration(); System.out.println(gd); for (TypeVariable t : gd.getTypeParameters()) { System.out.println(type == t); } } @Test public void test6() throws Exception { /*// ResultListAdapter System.out.println(ResultListAdapter.class.getTypeParameters()); System.out.println("\n========================================="); // XmlAdapter>, Result>> Type ggs = ResultListAdapter.class.getGenericSuperclass(); ParameterizedType pt = (ParameterizedType) ggs; System.out.println(pt.getTypeName()); // <=> toString() System.out.println(pt.getRawType()); // XmlAdapter System.out.println(pt.getOwnerType()); // 内部类的“父类”,如 Map就是 Map.Entry的拥有者 System.out.println(Arrays.toString(((Class) pt.getRawType()).getTypeParameters())); System.out.println(Arrays.toString(pt.getActualTypeArguments()));*/ System.out.println(GenericUtils.getActualTypeVariableMapping(ResultListMapNormalAdapter.class)); System.out.println(GenericUtils.getActualTypeVariableMapping(ResultListAdapter.class)); System.out.println(GenericUtils.getActualTypeVariableMapping(MarshalJsonAdapter.class)); System.out.println(B.class.toGenericString()); System.out.println(B.class.getDeclaredMethod("setList", List.class).toGenericString()); System.out.println(B.class.getConstructor().toGenericString()); System.out.println(B.class.toString()); System.out.println(B.class.getDeclaredMethod("setList", List.class).toString()); System.out.println(B.class.getConstructor().toString()); } // ------------------------------------------------------------- public static class BeanClass extends BaseEntity.Creator { private static final long serialVersionUID = 1L; } public static class BeanClass2 extends BaseEntity.Creator> { private static final long serialVersionUID = 1L; } public static class A { private int i; private String s; public int getI() { return i; } public void setI(int i) { this.i = i; } public String getS() { return s; } public void setS(String s) { this.s = s; } } public static class B { private List list; private List f1; public List getList() { return list; } public void setList(List list) { this.list = list; } public List getF1() { return f1; } public void setF1(List f1) { this.f1 = f1; } } } ================================================ FILE: src/test/java/cn/ponfee/commons/reflect/GenericTest2.java ================================================ package cn.ponfee.commons.reflect; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; /** * Hello world! * */ public class GenericTest2 { public static abstract class QueryParams { private String type; public String getType() { return type; } public void setType(String type) { this.type = type; } } public static class LimitedQueryParams extends QueryParams { private int limit; public int getLimit() { return limit; } public void setLimit(int limit) { this.limit = limit; } } public static abstract class QueryExecutor { public abstract String query(Q query); } public static class DatabaseQueryExecutor extends QueryExecutor { @Override public String query(LimitedQueryParams query) { return query.toString(); } } public static class MysqlQueryExecutor extends DatabaseQueryExecutor { } public static void main(String[] args) throws Exception { // class cn.ponfee.commons.reflect.GenericTest2$QueryExecutor System.out.println(QueryExecutor.class.getMethod("query", QueryParams.class).getDeclaringClass()); // class cn.ponfee.commons.reflect.GenericTest2$DatabaseQueryExecutor System.out.println(DatabaseQueryExecutor.class.getMethod("query", QueryParams.class).getDeclaringClass()); // class cn.ponfee.commons.reflect.GenericTest2$DatabaseQueryExecutor System.out.println(DatabaseQueryExecutor.class.getMethod("query", LimitedQueryParams.class).getDeclaringClass()); // class cn.ponfee.commons.reflect.GenericTest2$DatabaseQueryExecutor System.out.println(MysqlQueryExecutor.class.getMethod("query", QueryParams.class).getDeclaringClass()); // class cn.ponfee.commons.reflect.GenericTest2$DatabaseQueryExecutor System.out.println(MysqlQueryExecutor.class.getMethod("query", LimitedQueryParams.class).getDeclaringClass()); // --------------------------------------------------------------------- System.out.println("\n\n\n"); // class java.lang.Object System.out.println(GenericUtils.getMethodArgActualType( MysqlQueryExecutor.class, QueryExecutor.class.getMethod("query", QueryParams.class), 0) ); // class cn.ponfee.commons.reflect.GenericTest2$LimitedQueryParams System.out.println(GenericUtils.getMethodArgActualType( DatabaseQueryExecutor.class, QueryExecutor.class.getMethod("query", QueryParams.class), 0) ); System.out.println("\n\n\n"); printMethods(QueryExecutor.class); printMethods(DatabaseQueryExecutor.class); printMethods(MysqlQueryExecutor.class); } public static void printMethods(Class clazz) { System.out.println("\n\n-------------------------" + clazz); Method[] methods = clazz.getMethods(); for (Method method : methods) { if ( Modifier.isAbstract(method.getModifiers()) || Modifier.isPrivate(method.getModifiers()) || method.getDeclaringClass() == Object.class || !"query".equals(method.getName()) ) { continue; } Class[] params = method.getParameterTypes(); if (params.length == 1 && QueryParams.class.isAssignableFrom(params[0])) { System.out.println(method.toGenericString()); System.out.println(Arrays.toString(method.getParameters())); System.out.println(Arrays.toString(method.getTypeParameters())); } } } } ================================================ FILE: src/test/java/cn/ponfee/commons/serial/JacksonObjectMapperTest.java ================================================ package cn.ponfee.commons.serial; import java.io.Serializable; import java.text.SimpleDateFormat; import java.util.Date; import javax.validation.constraints.Email; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; import org.hibernate.validator.constraints.Length; import org.junit.Test; import org.springframework.format.annotation.DateTimeFormat; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import cn.ponfee.commons.json.JacksonDate; import cn.ponfee.commons.date.JavaUtilDateFormat; public class JacksonObjectMapperTest { static int round = 9999999; @Test public void test1() throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); Date date = new Date(); System.out.println(mapper.writeValueAsString(date)); for (int i = 0; i < round; i++) { mapper.writeValueAsString(date); } } @Test public void test2() throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); mapper.setDateFormat(new JavaUtilDateFormat("yyyy-MM-dd HH:mm:ss")); Date date = new Date(); System.out.println(mapper.writeValueAsString(date)); for (int i = 0; i < round; i++) { mapper.writeValueAsString(date); } } @Test public void test3() throws Exception { String json = "{\"id\": 0,\"title\": \"\",\"content\": \"xxx\",\"email\": \"ponfee.cn@gmail.com\",\"createDate\": \"20140202\",\"updateDate\": \"2019-10-18 16:02:52\"}"; ObjectMapper mapper = new ObjectMapper(); mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); JavaUtilDateFormat format = new JavaUtilDateFormat("yyyy-MM-dd HH:mm:ss"); SimpleModule module = new SimpleModule(); JacksonDate jacksonDate = new JacksonDate(format); module.addSerializer(java.util.Date.class, jacksonDate.serializer()); module.addDeserializer(java.util.Date.class, jacksonDate.deserializer()); mapper.registerModule(module); //mapper.setConfig(mapper.getDeserializationConfig().with(format)); Article a = mapper.readValue(json, Article.class); System.out.println(a.getCreateDate()); System.out.println(a.getUpdateDate()); } public static class Article implements Serializable { private static final long serialVersionUID = 1L; /** 编号 */ private int id; /** 标题 */ @NotNull(message = "标题不能为空") private String title; /** 内容 */ @Length(min = 10, max = 100, message = "内容不能少于10个字符") @NotBlank(message = "内容不能为空") private String content; @NotEmpty(message = "邮箱不能为空") @Email(regexp = "^\\w+((-\\w+)|(\\.\\w+))*\\@[A-Za-z0-9]+((\\.|-)[A-Za-z0-9]+)*\\.[A-Za-z0-9]+$", message = "邮箱格式错误") private String email; @DateTimeFormat(pattern = "yyyy-MM-dd") // 字符串转日期(需引入joda-time) @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS", timezone = "GMT+8") // 日期转字符串(覆盖spring-mvc配置) private Date createDate; private Date updateDate = new Date(); // 日期转字符串(使用spring-mvc配置) public Article() {} public Article(int id, String title, String content) { super(); this.id = id; this.title = title; this.content = content; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public Date getCreateDate() { return createDate; } public void setCreateDate(Date createDate) { this.createDate = createDate; } public Date getUpdateDate() { return updateDate; } public void setUpdateDate(Date updateDate) { this.updateDate = updateDate; } @Override public String toString() { return "Article [id=" + id + ", title=" + title + ", content=" + content + "]"; } } } ================================================ FILE: src/test/java/cn/ponfee/commons/serial/PersonProtobuf.java ================================================ // Generated by the protocol buffer compiler. DO NOT EDIT! // source: person.proto package cn.ponfee.commons.serial; public final class PersonProtobuf { private PersonProtobuf() {} public static void registerAllExtensions( com.google.protobuf.ExtensionRegistryLite registry) { } public static void registerAllExtensions( com.google.protobuf.ExtensionRegistry registry) { registerAllExtensions( (com.google.protobuf.ExtensionRegistryLite) registry); } /** * Protobuf enum {@code PhoneType} */ public enum PhoneType implements com.google.protobuf.ProtocolMessageEnum { /** * MOBILE = 0; */ MOBILE(0), /** * HOME = 1; */ HOME(1), /** * WORK = 2; */ WORK(2), UNRECOGNIZED(-1), ; /** * MOBILE = 0; */ public static final int MOBILE_VALUE = 0; /** * HOME = 1; */ public static final int HOME_VALUE = 1; /** * WORK = 2; */ public static final int WORK_VALUE = 2; public final int getNumber() { if (this == UNRECOGNIZED) { throw new java.lang.IllegalArgumentException( "Can't get the number of an unknown enum value."); } return value; } /** * @deprecated Use {@link #forNumber(int)} instead. */ @java.lang.Deprecated public static PhoneType valueOf(int value) { return forNumber(value); } public static PhoneType forNumber(int value) { switch (value) { case 0: return MOBILE; case 1: return HOME; case 2: return WORK; default: return null; } } public static com.google.protobuf.Internal.EnumLiteMap internalGetValueMap() { return internalValueMap; } private static final com.google.protobuf.Internal.EnumLiteMap< PhoneType> internalValueMap = new com.google.protobuf.Internal.EnumLiteMap() { public PhoneType findValueByNumber(int number) { return PhoneType.forNumber(number); } }; public final com.google.protobuf.Descriptors.EnumValueDescriptor getValueDescriptor() { return getDescriptor().getValues().get(ordinal()); } public final com.google.protobuf.Descriptors.EnumDescriptor getDescriptorForType() { return getDescriptor(); } public static final com.google.protobuf.Descriptors.EnumDescriptor getDescriptor() { return cn.ponfee.commons.serial.PersonProtobuf.getDescriptor().getEnumTypes().get(0); } private static final PhoneType[] VALUES = values(); public static PhoneType valueOf( com.google.protobuf.Descriptors.EnumValueDescriptor desc) { if (desc.getType() != getDescriptor()) { throw new java.lang.IllegalArgumentException( "EnumValueDescriptor is not for this type."); } if (desc.getIndex() == -1) { return UNRECOGNIZED; } return VALUES[desc.getIndex()]; } private final int value; private PhoneType(int value) { this.value = value; } // @@protoc_insertion_point(enum_scope:PhoneType) } public interface PersonOrBuilder extends // @@protoc_insertion_point(interface_extends:Person) com.google.protobuf.MessageOrBuilder { /** * int32 id = 1; */ int getId(); /** * string name = 2; */ java.lang.String getName(); /** * string name = 2; */ com.google.protobuf.ByteString getNameBytes(); /** * int32 age = 3; */ int getAge(); /** * .Addr addr = 4; */ boolean hasAddr(); /** * .Addr addr = 4; */ cn.ponfee.commons.serial.PersonProtobuf.Addr getAddr(); /** * .Addr addr = 4; */ cn.ponfee.commons.serial.PersonProtobuf.AddrOrBuilder getAddrOrBuilder(); /** * repeated .Phone phone = 5; */ java.util.List getPhoneList(); /** * repeated .Phone phone = 5; */ cn.ponfee.commons.serial.PersonProtobuf.Phone getPhone(int index); /** * repeated .Phone phone = 5; */ int getPhoneCount(); /** * repeated .Phone phone = 5; */ java.util.List getPhoneOrBuilderList(); /** * repeated .Phone phone = 5; */ cn.ponfee.commons.serial.PersonProtobuf.PhoneOrBuilder getPhoneOrBuilder( int index); } /** * Protobuf type {@code Person} */ public static final class Person extends com.google.protobuf.GeneratedMessageV3 implements // @@protoc_insertion_point(message_implements:Person) PersonOrBuilder { private static final long serialVersionUID = 0L; // Use Person.newBuilder() to construct. private Person(com.google.protobuf.GeneratedMessageV3.Builder builder) { super(builder); } private Person() { name_ = ""; phone_ = java.util.Collections.emptyList(); } @java.lang.Override public final com.google.protobuf.UnknownFieldSet getUnknownFields() { return this.unknownFields; } private Person( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { this(); if (extensionRegistry == null) { throw new java.lang.NullPointerException(); } int mutable_bitField0_ = 0; com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder(); try { boolean done = false; while (!done) { int tag = input.readTag(); switch (tag) { case 0: done = true; break; case 8: { id_ = input.readInt32(); break; } case 18: { java.lang.String s = input.readStringRequireUtf8(); name_ = s; break; } case 24: { age_ = input.readInt32(); break; } case 34: { cn.ponfee.commons.serial.PersonProtobuf.Addr.Builder subBuilder = null; if (addr_ != null) { subBuilder = addr_.toBuilder(); } addr_ = input.readMessage(cn.ponfee.commons.serial.PersonProtobuf.Addr.parser(), extensionRegistry); if (subBuilder != null) { subBuilder.mergeFrom(addr_); addr_ = subBuilder.buildPartial(); } break; } case 42: { if (!((mutable_bitField0_ & 0x00000010) != 0)) { phone_ = new java.util.ArrayList(); mutable_bitField0_ |= 0x00000010; } phone_.add( input.readMessage(cn.ponfee.commons.serial.PersonProtobuf.Phone.parser(), extensionRegistry)); break; } default: { if (!parseUnknownField( input, unknownFields, extensionRegistry, tag)) { done = true; } break; } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { throw e.setUnfinishedMessage(this); } catch (java.io.IOException e) { throw new com.google.protobuf.InvalidProtocolBufferException( e).setUnfinishedMessage(this); } finally { if (((mutable_bitField0_ & 0x00000010) != 0)) { phone_ = java.util.Collections.unmodifiableList(phone_); } this.unknownFields = unknownFields.build(); makeExtensionsImmutable(); } } public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { return cn.ponfee.commons.serial.PersonProtobuf.internal_static_Person_descriptor; } @java.lang.Override protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internalGetFieldAccessorTable() { return cn.ponfee.commons.serial.PersonProtobuf.internal_static_Person_fieldAccessorTable .ensureFieldAccessorsInitialized( cn.ponfee.commons.serial.PersonProtobuf.Person.class, cn.ponfee.commons.serial.PersonProtobuf.Person.Builder.class); } private int bitField0_; public static final int ID_FIELD_NUMBER = 1; private int id_; /** * int32 id = 1; */ public int getId() { return id_; } public static final int NAME_FIELD_NUMBER = 2; private volatile java.lang.Object name_; /** * string name = 2; */ public java.lang.String getName() { java.lang.Object ref = name_; if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); name_ = s; return s; } } /** * string name = 2; */ public com.google.protobuf.ByteString getNameBytes() { java.lang.Object ref = name_; if (ref instanceof java.lang.String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); name_ = b; return b; } else { return (com.google.protobuf.ByteString) ref; } } public static final int AGE_FIELD_NUMBER = 3; private int age_; /** * int32 age = 3; */ public int getAge() { return age_; } public static final int ADDR_FIELD_NUMBER = 4; private cn.ponfee.commons.serial.PersonProtobuf.Addr addr_; /** * .Addr addr = 4; */ public boolean hasAddr() { return addr_ != null; } /** * .Addr addr = 4; */ public cn.ponfee.commons.serial.PersonProtobuf.Addr getAddr() { return addr_ == null ? cn.ponfee.commons.serial.PersonProtobuf.Addr.getDefaultInstance() : addr_; } /** * .Addr addr = 4; */ public cn.ponfee.commons.serial.PersonProtobuf.AddrOrBuilder getAddrOrBuilder() { return getAddr(); } public static final int PHONE_FIELD_NUMBER = 5; private java.util.List phone_; /** * repeated .Phone phone = 5; */ public java.util.List getPhoneList() { return phone_; } /** * repeated .Phone phone = 5; */ public java.util.List getPhoneOrBuilderList() { return phone_; } /** * repeated .Phone phone = 5; */ public int getPhoneCount() { return phone_.size(); } /** * repeated .Phone phone = 5; */ public cn.ponfee.commons.serial.PersonProtobuf.Phone getPhone(int index) { return phone_.get(index); } /** * repeated .Phone phone = 5; */ public cn.ponfee.commons.serial.PersonProtobuf.PhoneOrBuilder getPhoneOrBuilder( int index) { return phone_.get(index); } private byte memoizedIsInitialized = -1; @java.lang.Override public final boolean isInitialized() { byte isInitialized = memoizedIsInitialized; if (isInitialized == 1) return true; if (isInitialized == 0) return false; memoizedIsInitialized = 1; return true; } @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { if (id_ != 0) { output.writeInt32(1, id_); } if (!getNameBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 2, name_); } if (age_ != 0) { output.writeInt32(3, age_); } if (addr_ != null) { output.writeMessage(4, getAddr()); } for (int i = 0; i < phone_.size(); i++) { output.writeMessage(5, phone_.get(i)); } unknownFields.writeTo(output); } @java.lang.Override public int getSerializedSize() { int size = memoizedSize; if (size != -1) return size; size = 0; if (id_ != 0) { size += com.google.protobuf.CodedOutputStream .computeInt32Size(1, id_); } if (!getNameBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, name_); } if (age_ != 0) { size += com.google.protobuf.CodedOutputStream .computeInt32Size(3, age_); } if (addr_ != null) { size += com.google.protobuf.CodedOutputStream .computeMessageSize(4, getAddr()); } for (int i = 0; i < phone_.size(); i++) { size += com.google.protobuf.CodedOutputStream .computeMessageSize(5, phone_.get(i)); } size += unknownFields.getSerializedSize(); memoizedSize = size; return size; } @java.lang.Override public boolean equals(final java.lang.Object obj) { if (obj == this) { return true; } if (!(obj instanceof cn.ponfee.commons.serial.PersonProtobuf.Person)) { return super.equals(obj); } cn.ponfee.commons.serial.PersonProtobuf.Person other = (cn.ponfee.commons.serial.PersonProtobuf.Person) obj; if (getId() != other.getId()) return false; if (!getName() .equals(other.getName())) return false; if (getAge() != other.getAge()) return false; if (hasAddr() != other.hasAddr()) return false; if (hasAddr()) { if (!getAddr() .equals(other.getAddr())) return false; } if (!getPhoneList() .equals(other.getPhoneList())) return false; if (!unknownFields.equals(other.unknownFields)) return false; return true; } @java.lang.Override public int hashCode() { if (memoizedHashCode != 0) { return memoizedHashCode; } int hash = 41; hash = (19 * hash) + getDescriptor().hashCode(); hash = (37 * hash) + ID_FIELD_NUMBER; hash = (53 * hash) + getId(); hash = (37 * hash) + NAME_FIELD_NUMBER; hash = (53 * hash) + getName().hashCode(); hash = (37 * hash) + AGE_FIELD_NUMBER; hash = (53 * hash) + getAge(); if (hasAddr()) { hash = (37 * hash) + ADDR_FIELD_NUMBER; hash = (53 * hash) + getAddr().hashCode(); } if (getPhoneCount() > 0) { hash = (37 * hash) + PHONE_FIELD_NUMBER; hash = (53 * hash) + getPhoneList().hashCode(); } hash = (29 * hash) + unknownFields.hashCode(); memoizedHashCode = hash; return hash; } public static cn.ponfee.commons.serial.PersonProtobuf.Person parseFrom( java.nio.ByteBuffer data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } public static cn.ponfee.commons.serial.PersonProtobuf.Person parseFrom( java.nio.ByteBuffer data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } public static cn.ponfee.commons.serial.PersonProtobuf.Person parseFrom( com.google.protobuf.ByteString data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } public static cn.ponfee.commons.serial.PersonProtobuf.Person parseFrom( com.google.protobuf.ByteString data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } public static cn.ponfee.commons.serial.PersonProtobuf.Person parseFrom(byte[] data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } public static cn.ponfee.commons.serial.PersonProtobuf.Person parseFrom( byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } public static cn.ponfee.commons.serial.PersonProtobuf.Person parseFrom(java.io.InputStream input) throws java.io.IOException { return com.google.protobuf.GeneratedMessageV3 .parseWithIOException(PARSER, input); } public static cn.ponfee.commons.serial.PersonProtobuf.Person parseFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { return com.google.protobuf.GeneratedMessageV3 .parseWithIOException(PARSER, input, extensionRegistry); } public static cn.ponfee.commons.serial.PersonProtobuf.Person parseDelimitedFrom(java.io.InputStream input) throws java.io.IOException { return com.google.protobuf.GeneratedMessageV3 .parseDelimitedWithIOException(PARSER, input); } public static cn.ponfee.commons.serial.PersonProtobuf.Person parseDelimitedFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { return com.google.protobuf.GeneratedMessageV3 .parseDelimitedWithIOException(PARSER, input, extensionRegistry); } public static cn.ponfee.commons.serial.PersonProtobuf.Person parseFrom( com.google.protobuf.CodedInputStream input) throws java.io.IOException { return com.google.protobuf.GeneratedMessageV3 .parseWithIOException(PARSER, input); } public static cn.ponfee.commons.serial.PersonProtobuf.Person parseFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { return com.google.protobuf.GeneratedMessageV3 .parseWithIOException(PARSER, input, extensionRegistry); } @java.lang.Override public Builder newBuilderForType() { return newBuilder(); } public static Builder newBuilder() { return DEFAULT_INSTANCE.toBuilder(); } public static Builder newBuilder(cn.ponfee.commons.serial.PersonProtobuf.Person prototype) { return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); } @java.lang.Override public Builder toBuilder() { return this == DEFAULT_INSTANCE ? new Builder() : new Builder().mergeFrom(this); } @java.lang.Override protected Builder newBuilderForType( com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { Builder builder = new Builder(parent); return builder; } /** * Protobuf type {@code Person} */ public static final class Builder extends com.google.protobuf.GeneratedMessageV3.Builder implements // @@protoc_insertion_point(builder_implements:Person) cn.ponfee.commons.serial.PersonProtobuf.PersonOrBuilder { public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { return cn.ponfee.commons.serial.PersonProtobuf.internal_static_Person_descriptor; } @java.lang.Override protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internalGetFieldAccessorTable() { return cn.ponfee.commons.serial.PersonProtobuf.internal_static_Person_fieldAccessorTable .ensureFieldAccessorsInitialized( cn.ponfee.commons.serial.PersonProtobuf.Person.class, cn.ponfee.commons.serial.PersonProtobuf.Person.Builder.class); } // Construct using cn.ponfee.commons.serial.PersonProtobuf.Person.newBuilder() private Builder() { maybeForceBuilderInitialization(); } private Builder( com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { super(parent); maybeForceBuilderInitialization(); } private void maybeForceBuilderInitialization() { if (com.google.protobuf.GeneratedMessageV3 .alwaysUseFieldBuilders) { getPhoneFieldBuilder(); } } @java.lang.Override public Builder clear() { super.clear(); id_ = 0; name_ = ""; age_ = 0; if (addrBuilder_ == null) { addr_ = null; } else { addr_ = null; addrBuilder_ = null; } if (phoneBuilder_ == null) { phone_ = java.util.Collections.emptyList(); bitField0_ = (bitField0_ & ~0x00000010); } else { phoneBuilder_.clear(); } return this; } @java.lang.Override public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { return cn.ponfee.commons.serial.PersonProtobuf.internal_static_Person_descriptor; } @java.lang.Override public cn.ponfee.commons.serial.PersonProtobuf.Person getDefaultInstanceForType() { return cn.ponfee.commons.serial.PersonProtobuf.Person.getDefaultInstance(); } @java.lang.Override public cn.ponfee.commons.serial.PersonProtobuf.Person build() { cn.ponfee.commons.serial.PersonProtobuf.Person result = buildPartial(); if (!result.isInitialized()) { throw newUninitializedMessageException(result); } return result; } @java.lang.Override public cn.ponfee.commons.serial.PersonProtobuf.Person buildPartial() { cn.ponfee.commons.serial.PersonProtobuf.Person result = new cn.ponfee.commons.serial.PersonProtobuf.Person(this); int from_bitField0_ = bitField0_; int to_bitField0_ = 0; result.id_ = id_; result.name_ = name_; result.age_ = age_; if (addrBuilder_ == null) { result.addr_ = addr_; } else { result.addr_ = addrBuilder_.build(); } if (phoneBuilder_ == null) { if (((bitField0_ & 0x00000010) != 0)) { phone_ = java.util.Collections.unmodifiableList(phone_); bitField0_ = (bitField0_ & ~0x00000010); } result.phone_ = phone_; } else { result.phone_ = phoneBuilder_.build(); } result.bitField0_ = to_bitField0_; onBuilt(); return result; } @java.lang.Override public Builder clone() { return super.clone(); } @java.lang.Override public Builder setField( com.google.protobuf.Descriptors.FieldDescriptor field, java.lang.Object value) { return super.setField(field, value); } @java.lang.Override public Builder clearField( com.google.protobuf.Descriptors.FieldDescriptor field) { return super.clearField(field); } @java.lang.Override public Builder clearOneof( com.google.protobuf.Descriptors.OneofDescriptor oneof) { return super.clearOneof(oneof); } @java.lang.Override public Builder setRepeatedField( com.google.protobuf.Descriptors.FieldDescriptor field, int index, java.lang.Object value) { return super.setRepeatedField(field, index, value); } @java.lang.Override public Builder addRepeatedField( com.google.protobuf.Descriptors.FieldDescriptor field, java.lang.Object value) { return super.addRepeatedField(field, value); } @java.lang.Override public Builder mergeFrom(com.google.protobuf.Message other) { if (other instanceof cn.ponfee.commons.serial.PersonProtobuf.Person) { return mergeFrom((cn.ponfee.commons.serial.PersonProtobuf.Person)other); } else { super.mergeFrom(other); return this; } } public Builder mergeFrom(cn.ponfee.commons.serial.PersonProtobuf.Person other) { if (other == cn.ponfee.commons.serial.PersonProtobuf.Person.getDefaultInstance()) return this; if (other.getId() != 0) { setId(other.getId()); } if (!other.getName().isEmpty()) { name_ = other.name_; onChanged(); } if (other.getAge() != 0) { setAge(other.getAge()); } if (other.hasAddr()) { mergeAddr(other.getAddr()); } if (phoneBuilder_ == null) { if (!other.phone_.isEmpty()) { if (phone_.isEmpty()) { phone_ = other.phone_; bitField0_ = (bitField0_ & ~0x00000010); } else { ensurePhoneIsMutable(); phone_.addAll(other.phone_); } onChanged(); } } else { if (!other.phone_.isEmpty()) { if (phoneBuilder_.isEmpty()) { phoneBuilder_.dispose(); phoneBuilder_ = null; phone_ = other.phone_; bitField0_ = (bitField0_ & ~0x00000010); phoneBuilder_ = com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? getPhoneFieldBuilder() : null; } else { phoneBuilder_.addAllMessages(other.phone_); } } } this.mergeUnknownFields(other.unknownFields); onChanged(); return this; } @java.lang.Override public final boolean isInitialized() { return true; } @java.lang.Override public Builder mergeFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { cn.ponfee.commons.serial.PersonProtobuf.Person parsedMessage = null; try { parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); } catch (com.google.protobuf.InvalidProtocolBufferException e) { parsedMessage = (cn.ponfee.commons.serial.PersonProtobuf.Person) e.getUnfinishedMessage(); throw e.unwrapIOException(); } finally { if (parsedMessage != null) { mergeFrom(parsedMessage); } } return this; } private int bitField0_; private int id_ ; /** * int32 id = 1; */ public int getId() { return id_; } /** * int32 id = 1; */ public Builder setId(int value) { id_ = value; onChanged(); return this; } /** * int32 id = 1; */ public Builder clearId() { id_ = 0; onChanged(); return this; } private java.lang.Object name_ = ""; /** * string name = 2; */ public java.lang.String getName() { java.lang.Object ref = name_; if (!(ref instanceof java.lang.String)) { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); name_ = s; return s; } else { return (java.lang.String) ref; } } /** * string name = 2; */ public com.google.protobuf.ByteString getNameBytes() { java.lang.Object ref = name_; if (ref instanceof String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); name_ = b; return b; } else { return (com.google.protobuf.ByteString) ref; } } /** * string name = 2; */ public Builder setName( java.lang.String value) { if (value == null) { throw new NullPointerException(); } name_ = value; onChanged(); return this; } /** * string name = 2; */ public Builder clearName() { name_ = getDefaultInstance().getName(); onChanged(); return this; } /** * string name = 2; */ public Builder setNameBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } checkByteStringIsUtf8(value); name_ = value; onChanged(); return this; } private int age_ ; /** * int32 age = 3; */ public int getAge() { return age_; } /** * int32 age = 3; */ public Builder setAge(int value) { age_ = value; onChanged(); return this; } /** * int32 age = 3; */ public Builder clearAge() { age_ = 0; onChanged(); return this; } private cn.ponfee.commons.serial.PersonProtobuf.Addr addr_; private com.google.protobuf.SingleFieldBuilderV3< cn.ponfee.commons.serial.PersonProtobuf.Addr, cn.ponfee.commons.serial.PersonProtobuf.Addr.Builder, cn.ponfee.commons.serial.PersonProtobuf.AddrOrBuilder> addrBuilder_; /** * .Addr addr = 4; */ public boolean hasAddr() { return addrBuilder_ != null || addr_ != null; } /** * .Addr addr = 4; */ public cn.ponfee.commons.serial.PersonProtobuf.Addr getAddr() { if (addrBuilder_ == null) { return addr_ == null ? cn.ponfee.commons.serial.PersonProtobuf.Addr.getDefaultInstance() : addr_; } else { return addrBuilder_.getMessage(); } } /** * .Addr addr = 4; */ public Builder setAddr(cn.ponfee.commons.serial.PersonProtobuf.Addr value) { if (addrBuilder_ == null) { if (value == null) { throw new NullPointerException(); } addr_ = value; onChanged(); } else { addrBuilder_.setMessage(value); } return this; } /** * .Addr addr = 4; */ public Builder setAddr( cn.ponfee.commons.serial.PersonProtobuf.Addr.Builder builderForValue) { if (addrBuilder_ == null) { addr_ = builderForValue.build(); onChanged(); } else { addrBuilder_.setMessage(builderForValue.build()); } return this; } /** * .Addr addr = 4; */ public Builder mergeAddr(cn.ponfee.commons.serial.PersonProtobuf.Addr value) { if (addrBuilder_ == null) { if (addr_ != null) { addr_ = cn.ponfee.commons.serial.PersonProtobuf.Addr.newBuilder(addr_).mergeFrom(value).buildPartial(); } else { addr_ = value; } onChanged(); } else { addrBuilder_.mergeFrom(value); } return this; } /** * .Addr addr = 4; */ public Builder clearAddr() { if (addrBuilder_ == null) { addr_ = null; onChanged(); } else { addr_ = null; addrBuilder_ = null; } return this; } /** * .Addr addr = 4; */ public cn.ponfee.commons.serial.PersonProtobuf.Addr.Builder getAddrBuilder() { onChanged(); return getAddrFieldBuilder().getBuilder(); } /** * .Addr addr = 4; */ public cn.ponfee.commons.serial.PersonProtobuf.AddrOrBuilder getAddrOrBuilder() { if (addrBuilder_ != null) { return addrBuilder_.getMessageOrBuilder(); } else { return addr_ == null ? cn.ponfee.commons.serial.PersonProtobuf.Addr.getDefaultInstance() : addr_; } } /** * .Addr addr = 4; */ private com.google.protobuf.SingleFieldBuilderV3< cn.ponfee.commons.serial.PersonProtobuf.Addr, cn.ponfee.commons.serial.PersonProtobuf.Addr.Builder, cn.ponfee.commons.serial.PersonProtobuf.AddrOrBuilder> getAddrFieldBuilder() { if (addrBuilder_ == null) { addrBuilder_ = new com.google.protobuf.SingleFieldBuilderV3< cn.ponfee.commons.serial.PersonProtobuf.Addr, cn.ponfee.commons.serial.PersonProtobuf.Addr.Builder, cn.ponfee.commons.serial.PersonProtobuf.AddrOrBuilder>( getAddr(), getParentForChildren(), isClean()); addr_ = null; } return addrBuilder_; } private java.util.List phone_ = java.util.Collections.emptyList(); private void ensurePhoneIsMutable() { if (!((bitField0_ & 0x00000010) != 0)) { phone_ = new java.util.ArrayList(phone_); bitField0_ |= 0x00000010; } } private com.google.protobuf.RepeatedFieldBuilderV3< cn.ponfee.commons.serial.PersonProtobuf.Phone, cn.ponfee.commons.serial.PersonProtobuf.Phone.Builder, cn.ponfee.commons.serial.PersonProtobuf.PhoneOrBuilder> phoneBuilder_; /** * repeated .Phone phone = 5; */ public java.util.List getPhoneList() { if (phoneBuilder_ == null) { return java.util.Collections.unmodifiableList(phone_); } else { return phoneBuilder_.getMessageList(); } } /** * repeated .Phone phone = 5; */ public int getPhoneCount() { if (phoneBuilder_ == null) { return phone_.size(); } else { return phoneBuilder_.getCount(); } } /** * repeated .Phone phone = 5; */ public cn.ponfee.commons.serial.PersonProtobuf.Phone getPhone(int index) { if (phoneBuilder_ == null) { return phone_.get(index); } else { return phoneBuilder_.getMessage(index); } } /** * repeated .Phone phone = 5; */ public Builder setPhone( int index, cn.ponfee.commons.serial.PersonProtobuf.Phone value) { if (phoneBuilder_ == null) { if (value == null) { throw new NullPointerException(); } ensurePhoneIsMutable(); phone_.set(index, value); onChanged(); } else { phoneBuilder_.setMessage(index, value); } return this; } /** * repeated .Phone phone = 5; */ public Builder setPhone( int index, cn.ponfee.commons.serial.PersonProtobuf.Phone.Builder builderForValue) { if (phoneBuilder_ == null) { ensurePhoneIsMutable(); phone_.set(index, builderForValue.build()); onChanged(); } else { phoneBuilder_.setMessage(index, builderForValue.build()); } return this; } /** * repeated .Phone phone = 5; */ public Builder addPhone(cn.ponfee.commons.serial.PersonProtobuf.Phone value) { if (phoneBuilder_ == null) { if (value == null) { throw new NullPointerException(); } ensurePhoneIsMutable(); phone_.add(value); onChanged(); } else { phoneBuilder_.addMessage(value); } return this; } /** * repeated .Phone phone = 5; */ public Builder addPhone( int index, cn.ponfee.commons.serial.PersonProtobuf.Phone value) { if (phoneBuilder_ == null) { if (value == null) { throw new NullPointerException(); } ensurePhoneIsMutable(); phone_.add(index, value); onChanged(); } else { phoneBuilder_.addMessage(index, value); } return this; } /** * repeated .Phone phone = 5; */ public Builder addPhone( cn.ponfee.commons.serial.PersonProtobuf.Phone.Builder builderForValue) { if (phoneBuilder_ == null) { ensurePhoneIsMutable(); phone_.add(builderForValue.build()); onChanged(); } else { phoneBuilder_.addMessage(builderForValue.build()); } return this; } /** * repeated .Phone phone = 5; */ public Builder addPhone( int index, cn.ponfee.commons.serial.PersonProtobuf.Phone.Builder builderForValue) { if (phoneBuilder_ == null) { ensurePhoneIsMutable(); phone_.add(index, builderForValue.build()); onChanged(); } else { phoneBuilder_.addMessage(index, builderForValue.build()); } return this; } /** * repeated .Phone phone = 5; */ public Builder addAllPhone( java.lang.Iterable values) { if (phoneBuilder_ == null) { ensurePhoneIsMutable(); com.google.protobuf.AbstractMessageLite.Builder.addAll( values, phone_); onChanged(); } else { phoneBuilder_.addAllMessages(values); } return this; } /** * repeated .Phone phone = 5; */ public Builder clearPhone() { if (phoneBuilder_ == null) { phone_ = java.util.Collections.emptyList(); bitField0_ = (bitField0_ & ~0x00000010); onChanged(); } else { phoneBuilder_.clear(); } return this; } /** * repeated .Phone phone = 5; */ public Builder removePhone(int index) { if (phoneBuilder_ == null) { ensurePhoneIsMutable(); phone_.remove(index); onChanged(); } else { phoneBuilder_.remove(index); } return this; } /** * repeated .Phone phone = 5; */ public cn.ponfee.commons.serial.PersonProtobuf.Phone.Builder getPhoneBuilder( int index) { return getPhoneFieldBuilder().getBuilder(index); } /** * repeated .Phone phone = 5; */ public cn.ponfee.commons.serial.PersonProtobuf.PhoneOrBuilder getPhoneOrBuilder( int index) { if (phoneBuilder_ == null) { return phone_.get(index); } else { return phoneBuilder_.getMessageOrBuilder(index); } } /** * repeated .Phone phone = 5; */ public java.util.List getPhoneOrBuilderList() { if (phoneBuilder_ != null) { return phoneBuilder_.getMessageOrBuilderList(); } else { return java.util.Collections.unmodifiableList(phone_); } } /** * repeated .Phone phone = 5; */ public cn.ponfee.commons.serial.PersonProtobuf.Phone.Builder addPhoneBuilder() { return getPhoneFieldBuilder().addBuilder( cn.ponfee.commons.serial.PersonProtobuf.Phone.getDefaultInstance()); } /** * repeated .Phone phone = 5; */ public cn.ponfee.commons.serial.PersonProtobuf.Phone.Builder addPhoneBuilder( int index) { return getPhoneFieldBuilder().addBuilder( index, cn.ponfee.commons.serial.PersonProtobuf.Phone.getDefaultInstance()); } /** * repeated .Phone phone = 5; */ public java.util.List getPhoneBuilderList() { return getPhoneFieldBuilder().getBuilderList(); } private com.google.protobuf.RepeatedFieldBuilderV3< cn.ponfee.commons.serial.PersonProtobuf.Phone, cn.ponfee.commons.serial.PersonProtobuf.Phone.Builder, cn.ponfee.commons.serial.PersonProtobuf.PhoneOrBuilder> getPhoneFieldBuilder() { if (phoneBuilder_ == null) { phoneBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< cn.ponfee.commons.serial.PersonProtobuf.Phone, cn.ponfee.commons.serial.PersonProtobuf.Phone.Builder, cn.ponfee.commons.serial.PersonProtobuf.PhoneOrBuilder>( phone_, ((bitField0_ & 0x00000010) != 0), getParentForChildren(), isClean()); phone_ = null; } return phoneBuilder_; } @java.lang.Override public final Builder setUnknownFields( final com.google.protobuf.UnknownFieldSet unknownFields) { return super.setUnknownFields(unknownFields); } @java.lang.Override public final Builder mergeUnknownFields( final com.google.protobuf.UnknownFieldSet unknownFields) { return super.mergeUnknownFields(unknownFields); } // @@protoc_insertion_point(builder_scope:Person) } // @@protoc_insertion_point(class_scope:Person) private static final cn.ponfee.commons.serial.PersonProtobuf.Person DEFAULT_INSTANCE; static { DEFAULT_INSTANCE = new cn.ponfee.commons.serial.PersonProtobuf.Person(); } public static cn.ponfee.commons.serial.PersonProtobuf.Person getDefaultInstance() { return DEFAULT_INSTANCE; } private static final com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { @java.lang.Override public Person parsePartialFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return new Person(input, extensionRegistry); } }; public static com.google.protobuf.Parser parser() { return PARSER; } @java.lang.Override public com.google.protobuf.Parser getParserForType() { return PARSER; } @java.lang.Override public cn.ponfee.commons.serial.PersonProtobuf.Person getDefaultInstanceForType() { return DEFAULT_INSTANCE; } } public interface AddrOrBuilder extends // @@protoc_insertion_point(interface_extends:Addr) com.google.protobuf.MessageOrBuilder { /** * string contry = 1; */ java.lang.String getContry(); /** * string contry = 1; */ com.google.protobuf.ByteString getContryBytes(); /** * string city = 2; */ java.lang.String getCity(); /** * string city = 2; */ com.google.protobuf.ByteString getCityBytes(); } /** * Protobuf type {@code Addr} */ public static final class Addr extends com.google.protobuf.GeneratedMessageV3 implements // @@protoc_insertion_point(message_implements:Addr) AddrOrBuilder { private static final long serialVersionUID = 0L; // Use Addr.newBuilder() to construct. private Addr(com.google.protobuf.GeneratedMessageV3.Builder builder) { super(builder); } private Addr() { contry_ = ""; city_ = ""; } @java.lang.Override public final com.google.protobuf.UnknownFieldSet getUnknownFields() { return this.unknownFields; } private Addr( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { this(); if (extensionRegistry == null) { throw new java.lang.NullPointerException(); } int mutable_bitField0_ = 0; com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder(); try { boolean done = false; while (!done) { int tag = input.readTag(); switch (tag) { case 0: done = true; break; case 10: { java.lang.String s = input.readStringRequireUtf8(); contry_ = s; break; } case 18: { java.lang.String s = input.readStringRequireUtf8(); city_ = s; break; } default: { if (!parseUnknownField( input, unknownFields, extensionRegistry, tag)) { done = true; } break; } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { throw e.setUnfinishedMessage(this); } catch (java.io.IOException e) { throw new com.google.protobuf.InvalidProtocolBufferException( e).setUnfinishedMessage(this); } finally { this.unknownFields = unknownFields.build(); makeExtensionsImmutable(); } } public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { return cn.ponfee.commons.serial.PersonProtobuf.internal_static_Addr_descriptor; } @java.lang.Override protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internalGetFieldAccessorTable() { return cn.ponfee.commons.serial.PersonProtobuf.internal_static_Addr_fieldAccessorTable .ensureFieldAccessorsInitialized( cn.ponfee.commons.serial.PersonProtobuf.Addr.class, cn.ponfee.commons.serial.PersonProtobuf.Addr.Builder.class); } public static final int CONTRY_FIELD_NUMBER = 1; private volatile java.lang.Object contry_; /** * string contry = 1; */ public java.lang.String getContry() { java.lang.Object ref = contry_; if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); contry_ = s; return s; } } /** * string contry = 1; */ public com.google.protobuf.ByteString getContryBytes() { java.lang.Object ref = contry_; if (ref instanceof java.lang.String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); contry_ = b; return b; } else { return (com.google.protobuf.ByteString) ref; } } public static final int CITY_FIELD_NUMBER = 2; private volatile java.lang.Object city_; /** * string city = 2; */ public java.lang.String getCity() { java.lang.Object ref = city_; if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); city_ = s; return s; } } /** * string city = 2; */ public com.google.protobuf.ByteString getCityBytes() { java.lang.Object ref = city_; if (ref instanceof java.lang.String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); city_ = b; return b; } else { return (com.google.protobuf.ByteString) ref; } } private byte memoizedIsInitialized = -1; @java.lang.Override public final boolean isInitialized() { byte isInitialized = memoizedIsInitialized; if (isInitialized == 1) return true; if (isInitialized == 0) return false; memoizedIsInitialized = 1; return true; } @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { if (!getContryBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 1, contry_); } if (!getCityBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 2, city_); } unknownFields.writeTo(output); } @java.lang.Override public int getSerializedSize() { int size = memoizedSize; if (size != -1) return size; size = 0; if (!getContryBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, contry_); } if (!getCityBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, city_); } size += unknownFields.getSerializedSize(); memoizedSize = size; return size; } @java.lang.Override public boolean equals(final java.lang.Object obj) { if (obj == this) { return true; } if (!(obj instanceof cn.ponfee.commons.serial.PersonProtobuf.Addr)) { return super.equals(obj); } cn.ponfee.commons.serial.PersonProtobuf.Addr other = (cn.ponfee.commons.serial.PersonProtobuf.Addr) obj; if (!getContry() .equals(other.getContry())) return false; if (!getCity() .equals(other.getCity())) return false; if (!unknownFields.equals(other.unknownFields)) return false; return true; } @java.lang.Override public int hashCode() { if (memoizedHashCode != 0) { return memoizedHashCode; } int hash = 41; hash = (19 * hash) + getDescriptor().hashCode(); hash = (37 * hash) + CONTRY_FIELD_NUMBER; hash = (53 * hash) + getContry().hashCode(); hash = (37 * hash) + CITY_FIELD_NUMBER; hash = (53 * hash) + getCity().hashCode(); hash = (29 * hash) + unknownFields.hashCode(); memoizedHashCode = hash; return hash; } public static cn.ponfee.commons.serial.PersonProtobuf.Addr parseFrom( java.nio.ByteBuffer data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } public static cn.ponfee.commons.serial.PersonProtobuf.Addr parseFrom( java.nio.ByteBuffer data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } public static cn.ponfee.commons.serial.PersonProtobuf.Addr parseFrom( com.google.protobuf.ByteString data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } public static cn.ponfee.commons.serial.PersonProtobuf.Addr parseFrom( com.google.protobuf.ByteString data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } public static cn.ponfee.commons.serial.PersonProtobuf.Addr parseFrom(byte[] data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } public static cn.ponfee.commons.serial.PersonProtobuf.Addr parseFrom( byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } public static cn.ponfee.commons.serial.PersonProtobuf.Addr parseFrom(java.io.InputStream input) throws java.io.IOException { return com.google.protobuf.GeneratedMessageV3 .parseWithIOException(PARSER, input); } public static cn.ponfee.commons.serial.PersonProtobuf.Addr parseFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { return com.google.protobuf.GeneratedMessageV3 .parseWithIOException(PARSER, input, extensionRegistry); } public static cn.ponfee.commons.serial.PersonProtobuf.Addr parseDelimitedFrom(java.io.InputStream input) throws java.io.IOException { return com.google.protobuf.GeneratedMessageV3 .parseDelimitedWithIOException(PARSER, input); } public static cn.ponfee.commons.serial.PersonProtobuf.Addr parseDelimitedFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { return com.google.protobuf.GeneratedMessageV3 .parseDelimitedWithIOException(PARSER, input, extensionRegistry); } public static cn.ponfee.commons.serial.PersonProtobuf.Addr parseFrom( com.google.protobuf.CodedInputStream input) throws java.io.IOException { return com.google.protobuf.GeneratedMessageV3 .parseWithIOException(PARSER, input); } public static cn.ponfee.commons.serial.PersonProtobuf.Addr parseFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { return com.google.protobuf.GeneratedMessageV3 .parseWithIOException(PARSER, input, extensionRegistry); } @java.lang.Override public Builder newBuilderForType() { return newBuilder(); } public static Builder newBuilder() { return DEFAULT_INSTANCE.toBuilder(); } public static Builder newBuilder(cn.ponfee.commons.serial.PersonProtobuf.Addr prototype) { return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); } @java.lang.Override public Builder toBuilder() { return this == DEFAULT_INSTANCE ? new Builder() : new Builder().mergeFrom(this); } @java.lang.Override protected Builder newBuilderForType( com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { Builder builder = new Builder(parent); return builder; } /** * Protobuf type {@code Addr} */ public static final class Builder extends com.google.protobuf.GeneratedMessageV3.Builder implements // @@protoc_insertion_point(builder_implements:Addr) cn.ponfee.commons.serial.PersonProtobuf.AddrOrBuilder { public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { return cn.ponfee.commons.serial.PersonProtobuf.internal_static_Addr_descriptor; } @java.lang.Override protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internalGetFieldAccessorTable() { return cn.ponfee.commons.serial.PersonProtobuf.internal_static_Addr_fieldAccessorTable .ensureFieldAccessorsInitialized( cn.ponfee.commons.serial.PersonProtobuf.Addr.class, cn.ponfee.commons.serial.PersonProtobuf.Addr.Builder.class); } // Construct using cn.ponfee.commons.serial.PersonProtobuf.Addr.newBuilder() private Builder() { maybeForceBuilderInitialization(); } private Builder( com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { super(parent); maybeForceBuilderInitialization(); } private void maybeForceBuilderInitialization() { if (com.google.protobuf.GeneratedMessageV3 .alwaysUseFieldBuilders) { } } @java.lang.Override public Builder clear() { super.clear(); contry_ = ""; city_ = ""; return this; } @java.lang.Override public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { return cn.ponfee.commons.serial.PersonProtobuf.internal_static_Addr_descriptor; } @java.lang.Override public cn.ponfee.commons.serial.PersonProtobuf.Addr getDefaultInstanceForType() { return cn.ponfee.commons.serial.PersonProtobuf.Addr.getDefaultInstance(); } @java.lang.Override public cn.ponfee.commons.serial.PersonProtobuf.Addr build() { cn.ponfee.commons.serial.PersonProtobuf.Addr result = buildPartial(); if (!result.isInitialized()) { throw newUninitializedMessageException(result); } return result; } @java.lang.Override public cn.ponfee.commons.serial.PersonProtobuf.Addr buildPartial() { cn.ponfee.commons.serial.PersonProtobuf.Addr result = new cn.ponfee.commons.serial.PersonProtobuf.Addr(this); result.contry_ = contry_; result.city_ = city_; onBuilt(); return result; } @java.lang.Override public Builder clone() { return super.clone(); } @java.lang.Override public Builder setField( com.google.protobuf.Descriptors.FieldDescriptor field, java.lang.Object value) { return super.setField(field, value); } @java.lang.Override public Builder clearField( com.google.protobuf.Descriptors.FieldDescriptor field) { return super.clearField(field); } @java.lang.Override public Builder clearOneof( com.google.protobuf.Descriptors.OneofDescriptor oneof) { return super.clearOneof(oneof); } @java.lang.Override public Builder setRepeatedField( com.google.protobuf.Descriptors.FieldDescriptor field, int index, java.lang.Object value) { return super.setRepeatedField(field, index, value); } @java.lang.Override public Builder addRepeatedField( com.google.protobuf.Descriptors.FieldDescriptor field, java.lang.Object value) { return super.addRepeatedField(field, value); } @java.lang.Override public Builder mergeFrom(com.google.protobuf.Message other) { if (other instanceof cn.ponfee.commons.serial.PersonProtobuf.Addr) { return mergeFrom((cn.ponfee.commons.serial.PersonProtobuf.Addr)other); } else { super.mergeFrom(other); return this; } } public Builder mergeFrom(cn.ponfee.commons.serial.PersonProtobuf.Addr other) { if (other == cn.ponfee.commons.serial.PersonProtobuf.Addr.getDefaultInstance()) return this; if (!other.getContry().isEmpty()) { contry_ = other.contry_; onChanged(); } if (!other.getCity().isEmpty()) { city_ = other.city_; onChanged(); } this.mergeUnknownFields(other.unknownFields); onChanged(); return this; } @java.lang.Override public final boolean isInitialized() { return true; } @java.lang.Override public Builder mergeFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { cn.ponfee.commons.serial.PersonProtobuf.Addr parsedMessage = null; try { parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); } catch (com.google.protobuf.InvalidProtocolBufferException e) { parsedMessage = (cn.ponfee.commons.serial.PersonProtobuf.Addr) e.getUnfinishedMessage(); throw e.unwrapIOException(); } finally { if (parsedMessage != null) { mergeFrom(parsedMessage); } } return this; } private java.lang.Object contry_ = ""; /** * string contry = 1; */ public java.lang.String getContry() { java.lang.Object ref = contry_; if (!(ref instanceof java.lang.String)) { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); contry_ = s; return s; } else { return (java.lang.String) ref; } } /** * string contry = 1; */ public com.google.protobuf.ByteString getContryBytes() { java.lang.Object ref = contry_; if (ref instanceof String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); contry_ = b; return b; } else { return (com.google.protobuf.ByteString) ref; } } /** * string contry = 1; */ public Builder setContry( java.lang.String value) { if (value == null) { throw new NullPointerException(); } contry_ = value; onChanged(); return this; } /** * string contry = 1; */ public Builder clearContry() { contry_ = getDefaultInstance().getContry(); onChanged(); return this; } /** * string contry = 1; */ public Builder setContryBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } checkByteStringIsUtf8(value); contry_ = value; onChanged(); return this; } private java.lang.Object city_ = ""; /** * string city = 2; */ public java.lang.String getCity() { java.lang.Object ref = city_; if (!(ref instanceof java.lang.String)) { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); city_ = s; return s; } else { return (java.lang.String) ref; } } /** * string city = 2; */ public com.google.protobuf.ByteString getCityBytes() { java.lang.Object ref = city_; if (ref instanceof String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); city_ = b; return b; } else { return (com.google.protobuf.ByteString) ref; } } /** * string city = 2; */ public Builder setCity( java.lang.String value) { if (value == null) { throw new NullPointerException(); } city_ = value; onChanged(); return this; } /** * string city = 2; */ public Builder clearCity() { city_ = getDefaultInstance().getCity(); onChanged(); return this; } /** * string city = 2; */ public Builder setCityBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } checkByteStringIsUtf8(value); city_ = value; onChanged(); return this; } @java.lang.Override public final Builder setUnknownFields( final com.google.protobuf.UnknownFieldSet unknownFields) { return super.setUnknownFields(unknownFields); } @java.lang.Override public final Builder mergeUnknownFields( final com.google.protobuf.UnknownFieldSet unknownFields) { return super.mergeUnknownFields(unknownFields); } // @@protoc_insertion_point(builder_scope:Addr) } // @@protoc_insertion_point(class_scope:Addr) private static final cn.ponfee.commons.serial.PersonProtobuf.Addr DEFAULT_INSTANCE; static { DEFAULT_INSTANCE = new cn.ponfee.commons.serial.PersonProtobuf.Addr(); } public static cn.ponfee.commons.serial.PersonProtobuf.Addr getDefaultInstance() { return DEFAULT_INSTANCE; } private static final com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { @java.lang.Override public Addr parsePartialFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return new Addr(input, extensionRegistry); } }; public static com.google.protobuf.Parser parser() { return PARSER; } @java.lang.Override public com.google.protobuf.Parser getParserForType() { return PARSER; } @java.lang.Override public cn.ponfee.commons.serial.PersonProtobuf.Addr getDefaultInstanceForType() { return DEFAULT_INSTANCE; } } public interface PhoneOrBuilder extends // @@protoc_insertion_point(interface_extends:Phone) com.google.protobuf.MessageOrBuilder { /** * string number = 1; */ java.lang.String getNumber(); /** * string number = 1; */ com.google.protobuf.ByteString getNumberBytes(); /** * .PhoneType type = 2; */ int getTypeValue(); /** * .PhoneType type = 2; */ cn.ponfee.commons.serial.PersonProtobuf.PhoneType getType(); } /** * Protobuf type {@code Phone} */ public static final class Phone extends com.google.protobuf.GeneratedMessageV3 implements // @@protoc_insertion_point(message_implements:Phone) PhoneOrBuilder { private static final long serialVersionUID = 0L; // Use Phone.newBuilder() to construct. private Phone(com.google.protobuf.GeneratedMessageV3.Builder builder) { super(builder); } private Phone() { number_ = ""; type_ = 0; } @java.lang.Override public final com.google.protobuf.UnknownFieldSet getUnknownFields() { return this.unknownFields; } private Phone( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { this(); if (extensionRegistry == null) { throw new java.lang.NullPointerException(); } int mutable_bitField0_ = 0; com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder(); try { boolean done = false; while (!done) { int tag = input.readTag(); switch (tag) { case 0: done = true; break; case 10: { java.lang.String s = input.readStringRequireUtf8(); number_ = s; break; } case 16: { int rawValue = input.readEnum(); type_ = rawValue; break; } default: { if (!parseUnknownField( input, unknownFields, extensionRegistry, tag)) { done = true; } break; } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { throw e.setUnfinishedMessage(this); } catch (java.io.IOException e) { throw new com.google.protobuf.InvalidProtocolBufferException( e).setUnfinishedMessage(this); } finally { this.unknownFields = unknownFields.build(); makeExtensionsImmutable(); } } public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { return cn.ponfee.commons.serial.PersonProtobuf.internal_static_Phone_descriptor; } @java.lang.Override protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internalGetFieldAccessorTable() { return cn.ponfee.commons.serial.PersonProtobuf.internal_static_Phone_fieldAccessorTable .ensureFieldAccessorsInitialized( cn.ponfee.commons.serial.PersonProtobuf.Phone.class, cn.ponfee.commons.serial.PersonProtobuf.Phone.Builder.class); } public static final int NUMBER_FIELD_NUMBER = 1; private volatile java.lang.Object number_; /** * string number = 1; */ public java.lang.String getNumber() { java.lang.Object ref = number_; if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); number_ = s; return s; } } /** * string number = 1; */ public com.google.protobuf.ByteString getNumberBytes() { java.lang.Object ref = number_; if (ref instanceof java.lang.String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); number_ = b; return b; } else { return (com.google.protobuf.ByteString) ref; } } public static final int TYPE_FIELD_NUMBER = 2; private int type_; /** * .PhoneType type = 2; */ public int getTypeValue() { return type_; } /** * .PhoneType type = 2; */ public cn.ponfee.commons.serial.PersonProtobuf.PhoneType getType() { @SuppressWarnings("deprecation") cn.ponfee.commons.serial.PersonProtobuf.PhoneType result = cn.ponfee.commons.serial.PersonProtobuf.PhoneType.valueOf(type_); return result == null ? cn.ponfee.commons.serial.PersonProtobuf.PhoneType.UNRECOGNIZED : result; } private byte memoizedIsInitialized = -1; @java.lang.Override public final boolean isInitialized() { byte isInitialized = memoizedIsInitialized; if (isInitialized == 1) return true; if (isInitialized == 0) return false; memoizedIsInitialized = 1; return true; } @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { if (!getNumberBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 1, number_); } if (type_ != cn.ponfee.commons.serial.PersonProtobuf.PhoneType.MOBILE.getNumber()) { output.writeEnum(2, type_); } unknownFields.writeTo(output); } @java.lang.Override public int getSerializedSize() { int size = memoizedSize; if (size != -1) return size; size = 0; if (!getNumberBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, number_); } if (type_ != cn.ponfee.commons.serial.PersonProtobuf.PhoneType.MOBILE.getNumber()) { size += com.google.protobuf.CodedOutputStream .computeEnumSize(2, type_); } size += unknownFields.getSerializedSize(); memoizedSize = size; return size; } @java.lang.Override public boolean equals(final java.lang.Object obj) { if (obj == this) { return true; } if (!(obj instanceof cn.ponfee.commons.serial.PersonProtobuf.Phone)) { return super.equals(obj); } cn.ponfee.commons.serial.PersonProtobuf.Phone other = (cn.ponfee.commons.serial.PersonProtobuf.Phone) obj; if (!getNumber() .equals(other.getNumber())) return false; if (type_ != other.type_) return false; if (!unknownFields.equals(other.unknownFields)) return false; return true; } @java.lang.Override public int hashCode() { if (memoizedHashCode != 0) { return memoizedHashCode; } int hash = 41; hash = (19 * hash) + getDescriptor().hashCode(); hash = (37 * hash) + NUMBER_FIELD_NUMBER; hash = (53 * hash) + getNumber().hashCode(); hash = (37 * hash) + TYPE_FIELD_NUMBER; hash = (53 * hash) + type_; hash = (29 * hash) + unknownFields.hashCode(); memoizedHashCode = hash; return hash; } public static cn.ponfee.commons.serial.PersonProtobuf.Phone parseFrom( java.nio.ByteBuffer data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } public static cn.ponfee.commons.serial.PersonProtobuf.Phone parseFrom( java.nio.ByteBuffer data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } public static cn.ponfee.commons.serial.PersonProtobuf.Phone parseFrom( com.google.protobuf.ByteString data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } public static cn.ponfee.commons.serial.PersonProtobuf.Phone parseFrom( com.google.protobuf.ByteString data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } public static cn.ponfee.commons.serial.PersonProtobuf.Phone parseFrom(byte[] data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } public static cn.ponfee.commons.serial.PersonProtobuf.Phone parseFrom( byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } public static cn.ponfee.commons.serial.PersonProtobuf.Phone parseFrom(java.io.InputStream input) throws java.io.IOException { return com.google.protobuf.GeneratedMessageV3 .parseWithIOException(PARSER, input); } public static cn.ponfee.commons.serial.PersonProtobuf.Phone parseFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { return com.google.protobuf.GeneratedMessageV3 .parseWithIOException(PARSER, input, extensionRegistry); } public static cn.ponfee.commons.serial.PersonProtobuf.Phone parseDelimitedFrom(java.io.InputStream input) throws java.io.IOException { return com.google.protobuf.GeneratedMessageV3 .parseDelimitedWithIOException(PARSER, input); } public static cn.ponfee.commons.serial.PersonProtobuf.Phone parseDelimitedFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { return com.google.protobuf.GeneratedMessageV3 .parseDelimitedWithIOException(PARSER, input, extensionRegistry); } public static cn.ponfee.commons.serial.PersonProtobuf.Phone parseFrom( com.google.protobuf.CodedInputStream input) throws java.io.IOException { return com.google.protobuf.GeneratedMessageV3 .parseWithIOException(PARSER, input); } public static cn.ponfee.commons.serial.PersonProtobuf.Phone parseFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { return com.google.protobuf.GeneratedMessageV3 .parseWithIOException(PARSER, input, extensionRegistry); } @java.lang.Override public Builder newBuilderForType() { return newBuilder(); } public static Builder newBuilder() { return DEFAULT_INSTANCE.toBuilder(); } public static Builder newBuilder(cn.ponfee.commons.serial.PersonProtobuf.Phone prototype) { return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); } @java.lang.Override public Builder toBuilder() { return this == DEFAULT_INSTANCE ? new Builder() : new Builder().mergeFrom(this); } @java.lang.Override protected Builder newBuilderForType( com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { Builder builder = new Builder(parent); return builder; } /** * Protobuf type {@code Phone} */ public static final class Builder extends com.google.protobuf.GeneratedMessageV3.Builder implements // @@protoc_insertion_point(builder_implements:Phone) cn.ponfee.commons.serial.PersonProtobuf.PhoneOrBuilder { public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { return cn.ponfee.commons.serial.PersonProtobuf.internal_static_Phone_descriptor; } @java.lang.Override protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internalGetFieldAccessorTable() { return cn.ponfee.commons.serial.PersonProtobuf.internal_static_Phone_fieldAccessorTable .ensureFieldAccessorsInitialized( cn.ponfee.commons.serial.PersonProtobuf.Phone.class, cn.ponfee.commons.serial.PersonProtobuf.Phone.Builder.class); } // Construct using cn.ponfee.commons.serial.PersonProtobuf.Phone.newBuilder() private Builder() { maybeForceBuilderInitialization(); } private Builder( com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { super(parent); maybeForceBuilderInitialization(); } private void maybeForceBuilderInitialization() { if (com.google.protobuf.GeneratedMessageV3 .alwaysUseFieldBuilders) { } } @java.lang.Override public Builder clear() { super.clear(); number_ = ""; type_ = 0; return this; } @java.lang.Override public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { return cn.ponfee.commons.serial.PersonProtobuf.internal_static_Phone_descriptor; } @java.lang.Override public cn.ponfee.commons.serial.PersonProtobuf.Phone getDefaultInstanceForType() { return cn.ponfee.commons.serial.PersonProtobuf.Phone.getDefaultInstance(); } @java.lang.Override public cn.ponfee.commons.serial.PersonProtobuf.Phone build() { cn.ponfee.commons.serial.PersonProtobuf.Phone result = buildPartial(); if (!result.isInitialized()) { throw newUninitializedMessageException(result); } return result; } @java.lang.Override public cn.ponfee.commons.serial.PersonProtobuf.Phone buildPartial() { cn.ponfee.commons.serial.PersonProtobuf.Phone result = new cn.ponfee.commons.serial.PersonProtobuf.Phone(this); result.number_ = number_; result.type_ = type_; onBuilt(); return result; } @java.lang.Override public Builder clone() { return super.clone(); } @java.lang.Override public Builder setField( com.google.protobuf.Descriptors.FieldDescriptor field, java.lang.Object value) { return super.setField(field, value); } @java.lang.Override public Builder clearField( com.google.protobuf.Descriptors.FieldDescriptor field) { return super.clearField(field); } @java.lang.Override public Builder clearOneof( com.google.protobuf.Descriptors.OneofDescriptor oneof) { return super.clearOneof(oneof); } @java.lang.Override public Builder setRepeatedField( com.google.protobuf.Descriptors.FieldDescriptor field, int index, java.lang.Object value) { return super.setRepeatedField(field, index, value); } @java.lang.Override public Builder addRepeatedField( com.google.protobuf.Descriptors.FieldDescriptor field, java.lang.Object value) { return super.addRepeatedField(field, value); } @java.lang.Override public Builder mergeFrom(com.google.protobuf.Message other) { if (other instanceof cn.ponfee.commons.serial.PersonProtobuf.Phone) { return mergeFrom((cn.ponfee.commons.serial.PersonProtobuf.Phone)other); } else { super.mergeFrom(other); return this; } } public Builder mergeFrom(cn.ponfee.commons.serial.PersonProtobuf.Phone other) { if (other == cn.ponfee.commons.serial.PersonProtobuf.Phone.getDefaultInstance()) return this; if (!other.getNumber().isEmpty()) { number_ = other.number_; onChanged(); } if (other.type_ != 0) { setTypeValue(other.getTypeValue()); } this.mergeUnknownFields(other.unknownFields); onChanged(); return this; } @java.lang.Override public final boolean isInitialized() { return true; } @java.lang.Override public Builder mergeFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { cn.ponfee.commons.serial.PersonProtobuf.Phone parsedMessage = null; try { parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); } catch (com.google.protobuf.InvalidProtocolBufferException e) { parsedMessage = (cn.ponfee.commons.serial.PersonProtobuf.Phone) e.getUnfinishedMessage(); throw e.unwrapIOException(); } finally { if (parsedMessage != null) { mergeFrom(parsedMessage); } } return this; } private java.lang.Object number_ = ""; /** * string number = 1; */ public java.lang.String getNumber() { java.lang.Object ref = number_; if (!(ref instanceof java.lang.String)) { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); number_ = s; return s; } else { return (java.lang.String) ref; } } /** * string number = 1; */ public com.google.protobuf.ByteString getNumberBytes() { java.lang.Object ref = number_; if (ref instanceof String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); number_ = b; return b; } else { return (com.google.protobuf.ByteString) ref; } } /** * string number = 1; */ public Builder setNumber( java.lang.String value) { if (value == null) { throw new NullPointerException(); } number_ = value; onChanged(); return this; } /** * string number = 1; */ public Builder clearNumber() { number_ = getDefaultInstance().getNumber(); onChanged(); return this; } /** * string number = 1; */ public Builder setNumberBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } checkByteStringIsUtf8(value); number_ = value; onChanged(); return this; } private int type_ = 0; /** * .PhoneType type = 2; */ public int getTypeValue() { return type_; } /** * .PhoneType type = 2; */ public Builder setTypeValue(int value) { type_ = value; onChanged(); return this; } /** * .PhoneType type = 2; */ public cn.ponfee.commons.serial.PersonProtobuf.PhoneType getType() { @SuppressWarnings("deprecation") cn.ponfee.commons.serial.PersonProtobuf.PhoneType result = cn.ponfee.commons.serial.PersonProtobuf.PhoneType.valueOf(type_); return result == null ? cn.ponfee.commons.serial.PersonProtobuf.PhoneType.UNRECOGNIZED : result; } /** * .PhoneType type = 2; */ public Builder setType(cn.ponfee.commons.serial.PersonProtobuf.PhoneType value) { if (value == null) { throw new NullPointerException(); } type_ = value.getNumber(); onChanged(); return this; } /** * .PhoneType type = 2; */ public Builder clearType() { type_ = 0; onChanged(); return this; } @java.lang.Override public final Builder setUnknownFields( final com.google.protobuf.UnknownFieldSet unknownFields) { return super.setUnknownFields(unknownFields); } @java.lang.Override public final Builder mergeUnknownFields( final com.google.protobuf.UnknownFieldSet unknownFields) { return super.mergeUnknownFields(unknownFields); } // @@protoc_insertion_point(builder_scope:Phone) } // @@protoc_insertion_point(class_scope:Phone) private static final cn.ponfee.commons.serial.PersonProtobuf.Phone DEFAULT_INSTANCE; static { DEFAULT_INSTANCE = new cn.ponfee.commons.serial.PersonProtobuf.Phone(); } public static cn.ponfee.commons.serial.PersonProtobuf.Phone getDefaultInstance() { return DEFAULT_INSTANCE; } private static final com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { @java.lang.Override public Phone parsePartialFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return new Phone(input, extensionRegistry); } }; public static com.google.protobuf.Parser parser() { return PARSER; } @java.lang.Override public com.google.protobuf.Parser getParserForType() { return PARSER; } @java.lang.Override public cn.ponfee.commons.serial.PersonProtobuf.Phone getDefaultInstanceForType() { return DEFAULT_INSTANCE; } } private static final com.google.protobuf.Descriptors.Descriptor internal_static_Person_descriptor; private static final com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internal_static_Person_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_Addr_descriptor; private static final com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internal_static_Addr_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_Phone_descriptor; private static final com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internal_static_Phone_fieldAccessorTable; public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { return descriptor; } private static com.google.protobuf.Descriptors.FileDescriptor descriptor; static { java.lang.String[] descriptorData = { "\n\014person.proto\"[\n\006Person\022\n\n\002id\030\001 \001(\005\022\014\n\004" + "name\030\002 \001(\t\022\013\n\003age\030\003 \001(\005\022\023\n\004addr\030\004 \001(\0132\005." + "Addr\022\025\n\005phone\030\005 \003(\0132\006.Phone\"$\n\004Addr\022\016\n\006c" + "ontry\030\001 \001(\t\022\014\n\004city\030\002 \001(\t\"1\n\005Phone\022\016\n\006nu" + "mber\030\001 \001(\t\022\030\n\004type\030\002 \001(\0162\n.PhoneType*+\n\t" + "PhoneType\022\n\n\006MOBILE\020\000\022\010\n\004HOME\020\001\022\010\n\004WORK\020" + "\002B,\n\032cn.ponfee.commons.serialB\016PersonP" + "rotobufb\006proto3" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor. InternalDescriptorAssigner() { public com.google.protobuf.ExtensionRegistry assignDescriptors( com.google.protobuf.Descriptors.FileDescriptor root) { descriptor = root; return null; } }; com.google.protobuf.Descriptors.FileDescriptor .internalBuildGeneratedFileFrom(descriptorData, new com.google.protobuf.Descriptors.FileDescriptor[] { }, assigner); internal_static_Person_descriptor = getDescriptor().getMessageTypes().get(0); internal_static_Person_fieldAccessorTable = new com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( internal_static_Person_descriptor, new java.lang.String[] { "Id", "Name", "Age", "Addr", "Phone", }); internal_static_Addr_descriptor = getDescriptor().getMessageTypes().get(1); internal_static_Addr_fieldAccessorTable = new com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( internal_static_Addr_descriptor, new java.lang.String[] { "Contry", "City", }); internal_static_Phone_descriptor = getDescriptor().getMessageTypes().get(2); internal_static_Phone_fieldAccessorTable = new com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( internal_static_Phone_descriptor, new java.lang.String[] { "Number", "Type", }); } // @@protoc_insertion_point(outer_class_scope) } ================================================ FILE: src/test/java/cn/ponfee/commons/serial/ProtobufClient.java ================================================ package cn.ponfee.commons.serial; import java.io.IOException; import java.net.Socket; import cn.ponfee.commons.serial.PersonProtobuf.Addr; import cn.ponfee.commons.serial.PersonProtobuf.Person; import cn.ponfee.commons.serial.PersonProtobuf.Phone; import cn.ponfee.commons.serial.PersonProtobuf.PhoneType; /** * * @author */ public class ProtobufClient { public static void main(String[] args) throws IOException { Socket socket = new Socket("127.0.0.1", 3030); Person person = Person.newBuilder() .setId(1).setAge(12).setName("ccf") .setAddr(Addr.newBuilder().setContry("china").setCity("shenzhen").build()) .addPhone(Phone.newBuilder().setNumber("13418467597").setType(PhoneType.MOBILE).build()) .addPhone(Phone.newBuilder().setNumber("0755-41245647").setType(PhoneType.HOME).build()) .build(); byte[] messageBody = person.toByteArray(); int headerLen = 1; byte[] message = new byte[headerLen + messageBody.length]; message[0] = (byte) messageBody.length; System.arraycopy(messageBody, 0, message, 1, messageBody.length); System.out.println("msg len:" + message.length); socket.getOutputStream().write(message); socket.close(); } } ================================================ FILE: src/test/java/cn/ponfee/commons/serial/ProtobufServer.java ================================================ package cn.ponfee.commons.serial; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import cn.ponfee.commons.json.Jsons; import cn.ponfee.commons.serial.PersonProtobuf.Person; public class ProtobufServer { public static void main(String[] args) throws IOException { try (ServerSocket serverSock = new ServerSocket(3030)) { while (true) { Socket sock = serverSock.accept(); byte[] msg = new byte[256]; sock.getInputStream().read(msg); int msgBodyLen = msg[0]; System.out.println("msg body len:" + msgBodyLen); byte[] msgbody = new byte[msgBodyLen]; System.arraycopy(msg, 1, msgbody, 0, msgBodyLen); Person person = Person.parseFrom(msgbody); System.out.println("toString:============================="); System.out.println(person); //System.out.println("toJson:============================="); //System.out.println(Jsons.toJson(person)); } } } } ================================================ FILE: src/test/java/cn/ponfee/commons/serial/SerializerTester.java ================================================ package cn.ponfee.commons.serial; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import org.junit.Before; import org.junit.Test; import test.TestBean; import cn.ponfee.commons.io.Files; import cn.ponfee.commons.io.GzipProcessor; import cn.ponfee.commons.util.Bytes; import cn.ponfee.commons.util.MavenProjects; public class SerializerTester { private String text; @Before public void setUp() { text = MavenProjects.getTestJavaFileAsString(this.getClass()); } @Test public void testKryo() { Serializer serializer = null; boolean isCompress; byte[] data = null; TestBean b = null; long start = System.currentTimeMillis(); System.out.println("\nkryo start ======================================================="); System.out.println("--------------------no gzip---------------------------------"); isCompress = false; serializer = KryoSerializer.INSTANCE; data = serializer.serialize(new TestBean(1312321111, 222243222L, text), isCompress); System.out.println("序例化后不压缩的数据大小:" + data.length); b = serializer.deserialize(data, TestBean.class, isCompress); System.out.println("反序例化后的对象:" + b); System.out.println("--------------------use gzip---------------------------------"); isCompress = true; serializer = KryoSerializer.INSTANCE; data = serializer.serialize(new TestBean(1312321111, 222243222L, text), isCompress); System.out.println("序例化后压缩的数据大小:" + data.length); b = serializer.deserialize(data, TestBean.class, isCompress); System.out.println("反序例化后的对象:" + b); System.out.println("kryo end =================================================耗时" + (System.currentTimeMillis() - start) + "\n"); } @Test public void testJdk() { Serializer serializer = null; boolean isCompress; byte[] data = null; TestBean b = null; long start = System.currentTimeMillis(); System.out.println("\njdk start ======================================================="); System.out.println("--------------------no gzip---------------------------------"); isCompress = false; serializer = new JdkSerializer(); data = serializer.serialize(new TestBean(1312321111, 222243222L, text), isCompress); System.out.println("序例化后不压缩的数据大小:" + data.length); b = serializer.deserialize(data, TestBean.class, isCompress); System.out.println("反序例化后的对象:" + b); System.out.println("--------------------use gzip---------------------------------"); isCompress = true; serializer = new JdkSerializer(); data = serializer.serialize(new TestBean(1312321111, 222243222L, text), isCompress); System.out.println("序例化后压缩的数据大小:" + data.length); b = serializer.deserialize(data, TestBean.class, isCompress); System.out.println("反序例化后的对象:" + b); System.out.println("jdk end ===============================================耗时" + (System.currentTimeMillis() - start) + "\n"); } @Test public void testJson() { Serializer serializer = null; boolean isCompress; byte[] data = null; TestBean b = null; long start = System.currentTimeMillis(); System.out.println("\njson start ======================================================="); System.out.println("--------------------no gzip---------------------------------"); isCompress = false; serializer = new JsonSerializer(); data = serializer.serialize(new TestBean(1312321111, 222243222L, text), isCompress); System.out.println("序例化后不压缩的数据大小:" + data.length); b = serializer.deserialize(data, TestBean.class, isCompress); System.out.println("反序例化后的对象:" + b); System.out.println("--------------------use gzip---------------------------------"); isCompress = true; serializer = new JsonSerializer(); data = serializer.serialize(new TestBean(1312321111, 222243222L, text), isCompress); System.out.println("序例化后压缩的数据大小:" + data.length); b = serializer.deserialize(data, TestBean.class, isCompress); System.out.println("反序例化后的对象:" + b); System.out.println("json end ==================================================耗时" + (System.currentTimeMillis() - start) + "\n"); } @Test public void testHessian() { Serializer serializer = null; boolean isCompress; byte[] data = null; TestBean b = null; long start = System.currentTimeMillis(); System.out.println("\nhessian start ======================================================="); System.out.println("--------------------no gzip---------------------------------"); isCompress = false; serializer = new HessianSerializer(); data = serializer.serialize(new TestBean(1312321111, 222243222L, text), isCompress); System.out.println("序例化后不压缩的数据大小:" + data.length); b = serializer.deserialize(data, TestBean.class, isCompress); System.out.println("反序例化后的对象:" + b); System.out.println("--------------------use gzip---------------------------------"); isCompress = true; serializer = new HessianSerializer(); data = serializer.serialize(new TestBean(1312321111, 222243222L, text), isCompress); System.out.println("序例化后压缩的数据大小:" + data.length); b = serializer.deserialize(data, TestBean.class, isCompress); System.out.println("反序例化后的对象:" + b); System.out.println("hessian end =================================================耗时" + (System.currentTimeMillis() - start) + "\n"); } @Test public void testFst() { Serializer serializer = null; boolean isCompress; byte[] data = null; TestBean b = null; long start = System.currentTimeMillis(); System.out.println("\nfst start ======================================================="); System.out.println("--------------------no gzip---------------------------------"); isCompress = false; serializer = new FstSerializer(); data = serializer.serialize(new TestBean(1312321111, 222243222L, text), isCompress); System.out.println("序例化后不压缩的数据大小:" + data.length); b = serializer.deserialize(data, TestBean.class, isCompress); System.out.println("反序例化后的对象:" + b); System.out.println("--------------------use gzip---------------------------------"); isCompress = true; serializer = new FstSerializer(); data = serializer.serialize(new TestBean(1312321111, 222243222L, text), isCompress); System.out.println("序例化后压缩的数据大小:" + data.length); b = serializer.deserialize(data, TestBean.class, isCompress); System.out.println("反序例化后的对象:" + b); System.out.println("fst end =================================================耗时" + (System.currentTimeMillis() - start) + "\n"); } @Test public void testString() { Serializer serializer = null; boolean isCompress; byte[] data = null; String b = null; long start = System.currentTimeMillis(); System.out.println("\nstring start ======================================================="); System.out.println("--------------------no gzip---------------------------------"); isCompress = false; serializer = new StringSerializer(); data = serializer.serialize(text, isCompress); System.out.println("序例化后不压缩的数据大小:" + data.length); b = serializer.deserialize(data, String.class, isCompress); System.out.println("反序例化后的对象:" + b); System.out.println("--------------------use gzip---------------------------------"); isCompress = true; serializer = new StringSerializer(); data = serializer.serialize(text, isCompress); System.out.println("序例化后压缩的数据大小:" + data.length); b = serializer.deserialize(data, String.class, isCompress); System.out.println("反序例化后的对象:" + b); System.out.println("string end ===============================================耗时" + (System.currentTimeMillis() - start) + "\n"); } @Test public void testProtostuff() { Serializer serializer = null; boolean isCompress; byte[] data = null; TestBean b = null; long start = System.currentTimeMillis(); System.out.println("\nProtostuff start ======================================================="); System.out.println("--------------------no gzip---------------------------------"); isCompress = false; serializer = new ProtostuffSerializer(); data = serializer.serialize(new TestBean(1312321111, 222243222L, text), isCompress); System.out.println("序例化后不压缩的数据大小:" + data.length); b = serializer.deserialize(data, TestBean.class, isCompress); System.out.println("反序例化后的对象:" + b); System.out.println("--------------------use gzip---------------------------------"); isCompress = true; serializer = new ProtostuffSerializer(); data = serializer.serialize(new TestBean(1312321111, 222243222L, text), isCompress); System.out.println("序例化后压缩的数据大小:" + data.length); b = serializer.deserialize(data, TestBean.class, isCompress); System.out.println("反序例化后的对象:" + b); System.out.println("Protostuff end =================================================耗时" + (System.currentTimeMillis() - start) + "\n"); } @Test public void testCompress() { long start = System.currentTimeMillis(); System.out.println("\ncompress start ======================================================="); System.out.println("压缩前数据:" + text); byte[] data = text.getBytes(); System.out.println("压缩前的数据大小:" + data.length); data = GzipProcessor.compress(data); System.out.println("压缩后的数据大小:" + data.length); data = GzipProcessor.decompress(data); System.out.println("解压缩后数据:" + new String(data)); System.out.println("compress end =======================================================耗时" + (System.currentTimeMillis() - start) + "\n"); } //@Test public void testDeserializer() throws IOException { String filepath = MavenProjects.getTestResourcesPath("test.txt"); String data = Files.toString(new File(filepath)); System.out.println(data); ByteArrayOutputStream out = new ByteArrayOutputStream(); StringBuilder b = new StringBuilder(); for (int i = 0; i < data.length();) { char w1 = data.charAt(i++); b.append(w1); if (w1 == '\\') { char w2 = data.charAt(i++); b.append(w2); if (w2 == 'x') { char w3 = data.charAt(i++); b.append(w3); char w4 = data.charAt(i++); b.append(w4); out.write(Integer.parseInt(new String(new char[] { w3, w4 }), 16)); System.out.println(new String(new char[] { '0', w2, w3, w4 })); } out.write(w2); } out.write(w1); } byte[] bytes = out.toByteArray(); System.out.println(Bytes.dumpHex(bytes)); System.out.println(b.toString()); JdkSerializer serializer = new JdkSerializer(); System.out.println(serializer.deserialize(bytes, String.class, false)); } } ================================================ FILE: src/test/java/cn/ponfee/commons/serial/person.proto ================================================ // protoc --proto_path=E:/commons-code/commons-core/src/test/java/code/ponfee/commons/serial --java_out=E:/commons-code/commons-core/src/test/java E:/commons-code/commons-core/src/test/java/code/ponfee/commons/serial/person.proto syntax = "proto3"; option java_package = "cn.ponfee.commons.serial"; option java_outer_classname = "PersonProtobuf"; message Person { int32 id = 1; string name = 2; int32 age = 3; Addr addr = 4; repeated Phone phone = 5; } message Addr { string contry = 1; string city = 2; } enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } message Phone { string number = 1; PhoneType type = 2; } ================================================ FILE: src/test/java/cn/ponfee/commons/util/BitSetTest.java ================================================ package cn.ponfee.commons.util; import java.util.ArrayList; import java.util.BitSet; import java.util.List; import java.util.Random; public class BitSetTest { public static void main(String[] args) { Random random = new Random(); List list = new ArrayList<>(); int size = 10000000; BitSet bitSet = new BitSet(); for (int i = 0; i < size; i++) { int randomResult = random.nextInt(size); list.add(randomResult); bitSet.set(randomResult); } System.out.println("0~1亿不在上述随机数中有" + bitSet.size()); for (int i = 0; i < size; i++) { if (!bitSet.get(i)) { System.out.println(i); } } } } ================================================ FILE: src/test/java/cn/ponfee/commons/util/BloomFilterTest.java ================================================ package cn.ponfee.commons.util; import java.util.ArrayList; import java.util.BitSet; import java.util.HashSet; import java.util.List; import java.util.Random; import com.google.common.hash.BloomFilter; import com.google.common.hash.Funnels; public class BloomFilterTest { static int sizeOfNumberSet = Integer.MAX_VALUE >> 10; static Random generator = new Random(); public static void main(String[] args) { int error = 0; HashSet hashSet = new HashSet(); BloomFilter filter = BloomFilter.create(Funnels.integerFunnel(), sizeOfNumberSet, 0.000001); for(int i = 0; i < sizeOfNumberSet; i++) { int number = generator.nextInt(); if(filter.mightContain(number) != hashSet.contains(number)) { error++; } filter.put(number); hashSet.add(number); } System.out.println("Error count: " + error + ", error rate = " + String.format("%f", (float)error/(float)sizeOfNumberSet)); } } ================================================ FILE: src/test/java/cn/ponfee/commons/util/ELParserTest.java ================================================ package cn.ponfee.commons.util; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.junit.Test; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import com.google.common.collect.ImmutableMap; import cn.ponfee.commons.json.Jsons; import cn.ponfee.commons.parser.ELParser; public class ELParserTest { private static final Pattern PARAMS_PATTERN = Pattern.compile("\\$\\{\\s*([a-zA-Z0-9_\\-\\.]+)\\s*\\}"); private static final Pattern DATETM_PATTERN = Pattern.compile("\\$\\[\\s*([a-zA-Z0-9_,\\-\\+\\.\\(\\)\\s]+)\\s*\\]"); private static final Pattern SPEL_PATTERN = Pattern.compile("\\{\\{\\s*([^\\{\\}]+)\\s*\\}\\}"); @Test public void test1() { String text = "dfsa |${abc}| a |${12}| |$[ yyyyMM \n]| xx |$[start_year( yyyyMMddHHmmss, -1y)]| aa |$[now( timestamp\n, -1y)]"; Matcher matcher = PARAMS_PATTERN.matcher(text); while (matcher.find()) { System.out.println(matcher.group(1)); } } @Test public void test2() { String text = "dfsa |${abc}| a |${12}| |$[ yyyyMM \n]| xx |$[start_year( yyyyMMddHHmmss, -1y)]| aa |$[now( timestamp\n, -1y)]"; Matcher matcher = DATETM_PATTERN.matcher(text); while (matcher.find()) { System.out.println(matcher.group(1)); } } @Test public void test3() { String text = "{{#key1}},{{#key2}}"; Matcher matcher = SPEL_PATTERN.matcher(text); while (matcher.find()) { System.out.println(matcher.group(1)); } } @Test public void test4() { StandardEvaluationContext context = new StandardEvaluationContext(new String[] { "a", "b" }); ExpressionParser parser = new SpelExpressionParser(); String name = parser.parseExpression("#root[0]").getValue(context, String.class); System.out.println(name); name = parser.parseExpression("\"asfdsaf\"").getValue(context, String.class); System.out.println(name); } @Test public void test5() { System.out.println(ELParser.parse("dfsa |${abc}| a |${12}| |$[ yyyyMM \n]| xx |$[start_year( yyyyMMddHHmmss, -1y)]| aa |$[now( timestamp\n, -1y)]", ImmutableMap.of("abc", 123, "test.1", "xxx"))); String params = "{\"from\":0,\"size\":5000,\"query\":{\"bool\":{\"must\":[{\"range\":{\"statEndTime\":{\"from\":$[start_year(timestamp)],\"to\":$[end_day(timestamp)],\"include_lower\":true,\"include_upper\":true}}},{\"terms\":{\"companyName\":[\"xx\",\"yy\"]}}]}}}"; System.out.println(ELParser.parse(params)); System.out.println(Jsons.toJson(ImmutableMap.of("key", "val"))); System.out.println(ELParser.parse("{{#key1}},{{#key2}}", ImmutableMap.of("key1", "val1", "key2", "val2"))); } } ================================================ FILE: src/test/java/cn/ponfee/commons/util/EscapeRegexTest.java ================================================ package cn.ponfee.commons.util; import org.apache.commons.lang3.StringUtils; import org.junit.Assert; import org.junit.Test; public class EscapeRegexTest { static String s = "t\\\\\\\\e\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\st.a.bc"; int round = 9999999; @Test public void test1() { System.out.println(Strings.escapeRegex(s)); Assert.assertEquals(Strings.escapeRegex(s), escapeExprSpecialWord(s)); for (int i = 0; i < round; i++) { Strings.escapeRegex(s); } } @Test public void test2() { System.out.println(Strings.escapeRegex(s)); Assert.assertEquals(Strings.escapeRegex(s), escapeExprSpecialWord(s)); for (int i = 0; i < round; i++) { escapeExprSpecialWord(s); } } static String[] fbsArr = { "\\", "$", "(", ")", "*", "+", ".", "[", "]", "?", "^", "{", "}", "|" }; /** * 转义正则特殊字符 ($()*+.[]?\^{},|) * * @param keyword * @return */ public static String escapeExprSpecialWord(String keyword) { if (StringUtils.isNotBlank(keyword)) { for (String key : fbsArr) { if (keyword.contains(key)) { keyword = keyword.replace(key, "\\" + key); } } } return keyword; } } ================================================ FILE: src/test/java/cn/ponfee/commons/util/FibonacciTest.java ================================================ package cn.ponfee.commons.util; import java.math.BigDecimal; import java.math.RoundingMode; public class FibonacciTest { public static int fibonacci(int i) { return (i == 1 || i == 2) ? 1 : fibonacci(i - 1) + fibonacci(i - 2); } public static void main(String[] args) { BigDecimal previous = new BigDecimal(1), following = new BigDecimal(1), temp; for (int i = 1; i < 1000; i++) { System.out.println(previous.divide(following, 1000, RoundingMode.HALF_UP).toString()); temp = following; following = following.add(previous); previous = temp; } } } ================================================ FILE: src/test/java/cn/ponfee/commons/util/ForEachTest.java ================================================ package cn.ponfee.commons.util; import java.util.Arrays; import java.util.Iterator; import java.util.List; public class ForEachTest { public static void main(String[] args) { //foreach(); forEachRemaining(); } private static void foreach() { List list = Arrays.asList(1, 2, 3, 4, 5, 6); list.forEach(System.out::println); System.out.println("==================================="); list.forEach(System.out::println); } private static void forEachRemaining() { Iterator iter = Arrays.asList(1, 2, 3, 4, 5, 6).iterator(); iter.forEachRemaining(System.out::println); System.out.println("==================================="); iter.forEachRemaining(System.out::println); } } ================================================ FILE: src/test/java/cn/ponfee/commons/util/IdcardResolverTest.java ================================================ package cn.ponfee.commons.util; import cn.ponfee.commons.date.Dates; import org.junit.Test; /** * * * @author Ponfee */ public class IdcardResolverTest { @Test public void test1() { for (int i = 0; i < 100; i++) { System.out.println(IdcardResolver.generate()); } } @Test public void test2() { long start = Dates.toDate("1950-01-01 00:00:00").getTime(); for (int i = 0; i < 100; i++) { System.out.println(Dates.format(Dates.random(start, System.currentTimeMillis()), "yyyy-MM-dd HH:mm:ss.SSS")); } } } ================================================ FILE: src/test/java/cn/ponfee/commons/util/MathsTest.java ================================================ package cn.ponfee.commons.util; import cn.ponfee.commons.math.Maths; import cn.ponfee.commons.math.Numbers; import org.junit.Test; import org.testng.Assert; import java.util.Random; /** * * * @author Ponfee */ public class MathsTest { @Test public void test1() { int a1 = 100; int b1 = Integer.MAX_VALUE - a1 + 1; System.out.println("\n==============正+正"); Assert.assertTrue((a1 + b1) < 0); // 溢出 Assert.assertEquals(Maths.plus(a1, b1), Integer.MAX_VALUE); int a2 = -100; int b2 = Integer.MIN_VALUE - a2 - 1; System.out.println("\n==============负+负"); Assert.assertTrue((a2 + b2)>0); // 溢出 Assert.assertEquals(Maths.plus(a2, b2), Integer.MIN_VALUE); System.out.println("\n==============正-负"); int a3 = Integer.MAX_VALUE - 100; int b3 = -1000; Assert.assertTrue((a3 - b3) < 0); // 溢出 Assert.assertEquals(Maths.minus(a3, b3), Integer.MAX_VALUE); System.out.println("\n==============负-正"); int a4 = Integer.MIN_VALUE + 10; int b4 = 1000; Assert.assertTrue((a4 - b4) > 0); // 溢出 Assert.assertEquals(Maths.minus(a4, b4), Integer.MIN_VALUE); } @Test public void test2() { Random ran = new Random(SecureRandoms.nextLong()); for (int i = 0; i < 6000; i++) { double num = Math.random() + ran.nextInt(Integer.MAX_VALUE); double a = Math.sqrt(num), b = /*Maths.sqrtNewton(num)*/ Maths.sqrtBinary(num); //String s = "0011110100010000000000000000000000000000000000000000000000000000"; // 0011110110010000000000000000000000000000000000000000000000000000 if (a != b) { //System.out.println(Bytes.toBinary(Bytes.toBytes(Math.abs(a - b)))); System.err.println(Numbers.format(a, 60) + ", " + Numbers.format(b, 60) + ", " + Numbers.format(Math.abs(a - b), 60)); } } } @Test public void test3() { Assert.assertTrue(Maths.sqrtBinary(4) == Math.sqrt(4)); Assert.assertTrue(Maths.sqrtBinary(0.01) == Math.sqrt(0.01)); Assert.assertTrue(Maths.sqrtBinary(5) == Math.sqrt(5)); Assert.assertFalse(Maths.sqrtBinary(9997.9997D) == Math.sqrt(9997.9997D)); } private static final int ROUND = 9999999; @Test public void test31() { for (int i = 2; i < ROUND; i++) { Math.sqrt(i); } } @Test public void test32() { for (int i = 2; i < ROUND; i++) { Maths.sqrtBinary(i); } } @Test public void test33() { for (int i = 2; i < ROUND; i++) { Maths.sqrtNewton(i); } } @Test public void test34() { System.out.println(Maths.sqrtNewton(0)); System.out.println(Maths.sqrtNewton(1)); System.out.println(Maths.sqrtNewton(0.01)); System.out.println(Maths.sqrtNewton(4.0)); } @Test public void test4() { System.out.println(find(98)); Random ran = new Random(); for (int i = 1; i < 100; i++) { int number = ran.nextInt(Integer.MAX_VALUE / 4); int a = find(number); /*System.out.println(number); System.out.println(a); System.out.println(calculate(a)); System.out.println(calculate(a - 1));*/ } } // ------------------------------------------------------------------------------------------ public static boolean isBorderline(int value, int start, int end) { return value == start || value == end; } // 2-99 public static int find(int value) { int start = 1, end = value, number, less = -1, grater = -1; while (!isBorderline(number = (start + end) / 2, start, end)) { //System.out.println("-------" + temp); int a = calculate(number); if (a == value) { return number; } else if (a > value) { grater = number; end = Math.max(number - 1, start); } else { less = number; start = Math.min(number + 1, end); } } System.out.println(String.format("[less=%d,start=%d,end=%d,grater=%d]", less, start, end, grater)); if (grater == -1) { return -1; } if (less == -1) { return calculate(start - 1) < value ? start : -1; } for (int i = grater - 1; i >= less; i--) { if (i == less) { return i + 1; } else if (calculate(i) < value) { return i + 1; } } return -1; } public static int calculate(int n) { if (n < 1) { throw new IllegalArgumentException(); } int result = 0; do { result += n; } while (--n > 0); return result; } } ================================================ FILE: src/test/java/cn/ponfee/commons/util/MoneyTest.java ================================================ package cn.ponfee.commons.util; import cn.ponfee.commons.serial.JdkSerializer; import cn.ponfee.commons.serial.Serializer; import org.junit.Assert; import org.junit.Test; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Currency; import java.util.Locale; /** * @author Ponfee */ public class MoneyTest { @Test public void testx() { Assert.assertEquals("MXV", new String(new char[]{0x4d, 0x58, 0x56})); } @Test public void test0() { System.out.println(Arrays.toString(Strings.hexadecimal(CurrencyEnum.CZK.currencySymbol()))); System.out.println(Bytes.encodeHex(CurrencyEnum.CZK.currencySymbol().substring(1,2).getBytes(StandardCharsets.UTF_8))); System.out.println(); for (char x = 'A'; x <= 'Z'; x++) { for (char y = 'A'; y <= 'Z'; y++) { for (char z = 'A'; z <= 'Z'; z++) { try { Currency instance = Currency.getInstance(new String(new char[]{x, y, z})); if (CurrencyEnum.ofCurrencyCode(instance.getCurrencyCode()) == null && CurrencyEnum.ofNumericCode(String.format("%03d", instance.getNumericCode())) == null) { String text = String.format( "/** %s [%s] */\n%s(new char[]{%s}),", instance.getDisplayName(Locale.CHINA), instance.getSymbol(Locale.CHINA), instance.getCurrencyCode(), Strings.join(Arrays.asList(Strings.hexadecimal(instance.getSymbol(Locale.CHINA))), ", ") ); System.out.println(text); System.out.println(); } } catch (Exception e) { } } } } } /* @Test public void testa() { for (CurrencyEnum1 c1 : CurrencyEnum1.values()) { CurrencyEnum c0 = CurrencyEnum.ofCurrencyCode(c1.currencyCode()); if (c0 == null) { System.err.println("not found: "+c1.currencyCode()); continue; } if (c1.currencySymbol().equals(c0.currencySymbol())) { continue; } System.out.println(c1.currencyCode()+", "+c0.currencySymbol()+", "+c1.currencySymbol()); } } */ @Test public void test1() { System.out.println(CurrencyEnum.INR.currencySymbol()); System.out.println(CurrencyEnum.UAH.currencySymbol()); System.out.println(CurrencyEnum.CHF.currencySymbol()); System.out.println(CurrencyEnum.AZN.currencySymbol()); System.out.println(CurrencyEnum.GHS.currencySymbol()); System.out.println(CurrencyEnum.TRY.currencySymbol()); Money money = new Money(CurrencyEnum.USD.currency(), 53243234); System.out.println(money); System.out.println("----------------------"); System.out.println(CurrencyEnum.JPY.currencyCode()); System.out.println(CurrencyEnum.JPY.numericCode()); System.out.println(CurrencyEnum.JPY.currencySymbol()); System.out.println("----------------------"); System.out.println(CurrencyEnum.JPY.currency().getCurrencyCode()); System.out.println(CurrencyEnum.JPY.currency().getNumericCode()); System.out.println(CurrencyEnum.JPY.currency().getSymbol()); System.out.println(CurrencyEnum.JPY.currency().getSymbol(Locale.CHINA)); } @Test public void test2() { System.out.println(CurrencyEnum.CNY.currency().getNumericCode()); for (CurrencyEnum c : CurrencyEnum.values()) { System.out.println(c.currency().getSymbol(Locale.CHINA)); if (!c.numericCode().equals(String.format("%03d", c.currency().getNumericCode()))) { System.out.println(c.numericCode() + " != " + c.currency().getNumericCode()); } } } @Test public void test3() { Money money = new Money(CurrencyEnum.USD.currency(), 53243234); //Serializer ser = KryoSerializer.INSTANCE; Serializer ser = new JdkSerializer(); byte[] data = ser.serialize(money); System.out.println(ser.deserialize(data, Money.class)); } } ================================================ FILE: src/test/java/cn/ponfee/commons/util/ObjectUtilsTest.java ================================================ package cn.ponfee.commons.util; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.IntStream; import cn.ponfee.commons.concurrent.ThreadPoolTestUtils; import com.google.common.base.Stopwatch; import org.junit.Test; import cn.ponfee.commons.model.Result; import cn.ponfee.commons.reflect.ClassUtils; import org.openjdk.jol.info.ClassLayout; public class ObjectUtilsTest { static int round = 100000000; private static final Map map = new HashMap<>(); @Test public void test0() { System.out.println(ObjectUtils.newInstance(int.class)); System.out.println(ObjectUtils.newInstance(Integer.class)); System.out.println(ObjectUtils.newInstance(String.class)); System.out.println(ObjectUtils.newInstance(Map.class)); System.out.println(ObjectUtils.newInstance(List.class)); System.out.println("\n-----------------------"); System.out.println(ClassLayout.parseInstance(new Object()).toPrintable()); System.out.println("\n-----------------------"); System.out.println(ClassLayout.parseInstance(new Object[10]).toPrintable()); System.out.println("\n-----------------------"); System.out.println(ClassLayout.parseInstance(new long[10]).toPrintable()); } @Test public void test1() { for (int i = 0; i < round; i++) { ObjectUtils.newInstance(Result.class); } } @Test public void test2() { for (int i = 0; i < round; i++) { ClassUtils.newInstance(Result.class); } } @Test public void test3() throws Exception { for (int i = 0; i < round; i++) { Result.class.getConstructor().newInstance(); } } @Test public void test4() { Byte b1 = 127; Byte b2 = 127; Byte b3 = new Byte((byte) 127); System.out.println(b1 == b2); System.out.println(b3 == b2); } @Test public void test5() { Byte b1 = 127; Byte b2 = 127; Byte b3 = new Byte((byte) 127); System.out.println(b1 == b2); System.out.println(b3 == b2); } @Test public void test6() { // XXX: if Executor is CALLER_RUN_EXECUTOR and threadNumber>=33 then will be dead loop execute(32, () -> { get("123"); }, 5, ThreadPoolTestUtils.CALLER_RUN_SCHEDULER); } @Test public void test7() { // XXX: if Executor is CALLER_RUN_EXECUTOR and threadNumber>=33 then will be dead loop execute(32, () -> { Singleton.getInstance(); }, 5, ThreadPoolTestUtils.CALLER_RUN_SCHEDULER); } public static Object get(String key) { Object val = map.get(key); if (val == null) { synchronized (map) { if ((val = map.get(key)) == null) { map.put(key, val = new Object()); System.err.println("================"); } } } return val; } public static class Singleton { private static Singleton instance = null; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { System.err.println("================"); instance = new Singleton(); } } } return instance; } } class Helper { } class Foo { /** * If perThreadInstance.get() returns a non-null value, this thread * has done synchronization needed to see initialization * of helper */ private final ThreadLocal perThreadInstance = new ThreadLocal(); private Helper helper = null; public Helper getHelper() { if (perThreadInstance.get() == null) createHelper(); return helper; } private final void createHelper() { synchronized (this) { if (helper == null) helper = new Helper(); } // Any non-null value would do as the argument here perThreadInstance.set(perThreadInstance); } } /** * Exec async, usual use in test case * * @param parallelism the parallelism * @param command the command * @param execSeconds the execSeconds * @param executor the executor */ public static void execute(int parallelism, Runnable command, int execSeconds, Executor executor) { Stopwatch watch = Stopwatch.createStarted(); AtomicBoolean flag = new AtomicBoolean(true); // CALLER_RUNS: caller run will be dead loop // caller thread will be loop exec command, can't to run the after code{flag.set(false)} // threadNumber > 32 CompletableFuture[] futures = IntStream .range(0, parallelism) .mapToObj(i -> (Runnable) () -> { while (flag.get() && !Thread.currentThread().isInterrupted()) { command.run(); } }) .map(runnable -> CompletableFuture.runAsync(runnable, executor)) .toArray(CompletableFuture[]::new); try { // parent thread sleep Thread.sleep(execSeconds * 1000L); flag.set(false); CompletableFuture.allOf(futures).join(); } catch (InterruptedException e) { flag.set(false); Thread.currentThread().interrupt(); throw new RuntimeException(e); } finally { System.out.println("multi thread exec async duration: " + watch.stop()); } } } ================================================ FILE: src/test/java/cn/ponfee/commons/util/ProxyTest.java ================================================ package cn.ponfee.commons.util; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.junit.Test; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import java.util.Arrays; /** * @author Ponfee */ public class ProxyTest { @Test public void test() { User user = LazyLoader.of(User.class, ProxyTest::findById, 1); System.out.println("------222"); System.out.println("bean1: " + System.identityHashCode(user) + ", " + user.getClass()); System.out.println("---------user.getId()"); System.out.println(user.getId()); System.out.println(user.getName()); System.out.println(user.getSex()); System.out.println(user.toString()); } public static User findById(Integer id) { System.out.println("-------bbb"); return new User(1, "bob", "男"); } @Data @NoArgsConstructor @AllArgsConstructor public static class User { private Integer id; private String name; private String sex; } //--------------------------------------------------------------------------------------- @Test public void test2() { TargetBean bean2 = (TargetBean) createProxyBean(new TargetBean()); System.out.println("bean1: " + System.identityHashCode(bean2) + ", " + bean2.getClass()); bean2.say(); } /** * 创建代理类 * * @param targetBean 源Bean * @return 代理Bean */ private Object createProxyBean(final Object targetBean) { System.out.println("targetBean: " + System.identityHashCode(targetBean) + ", " + targetBean.getClass()); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(targetBean.getClass()); enhancer.setUseCache(true); enhancer.setInterceptDuringConstruction(false); enhancer.setCallback((MethodInterceptor) (proxy, method, args, methodProxy) -> { System.out.println("proxy: " + System.identityHashCode(proxy) + ", " + proxy.getClass()); System.out.println("method: " + method.toGenericString()); System.out.println("args: " + Arrays.toString(args)); System.out.println("methodProxy: " + System.identityHashCode(methodProxy) + ", " + methodProxy.getClass()); System.err.println("代理前"); //System.out.println("method.invoke(bean1, args): " + method.invoke(bean1, args)); // bean1为代理类,此处会死循环 //System.out.println("method.invoke(targetBean, args): " + method.invoke(targetBean, args)); Object result = methodProxy.invokeSuper(proxy, args); // 调用父类方法 System.err.println("代理后"); return result; }); Object targetProxyBean = enhancer.create(); return targetProxyBean; } public static class TargetBean { public TargetBean() { } public void say() { System.err.println("Hello"); } } } ================================================ FILE: src/test/java/cn/ponfee/commons/util/RegexUtilsTest.java ================================================ package cn.ponfee.commons.util; import org.junit.Test; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class RegexUtilsTest { @Test public void testIsIPv4() { assertTrue(RegexUtils.isIpv4("127.0.0.1")); assertTrue(RegexUtils.isIpv4("0.0.0.0")); assertTrue(RegexUtils.isIpv4("255.255.255.255")); } @Test public void testIsIPv6() { assertTrue(RegexUtils.isIpv6("::1")); assertFalse(RegexUtils.isIpv6("1200::AB00:1234::2552:7777:1313")); assertTrue(RegexUtils.isIpv6("1200:0000:AB00:1234:0000:2552:7777:1313")); assertTrue(RegexUtils.isIpv6("21DA:D3:0:2F3B:2AA:FF:FE28:9C5A")); } } ================================================ FILE: src/test/java/cn/ponfee/commons/util/SqlUtilsTest.java ================================================ package cn.ponfee.commons.util; import static org.junit.Assert.assertEquals; import org.junit.Test; public class SqlUtilsTest { @Test public void trimSql() { assertEquals("", SqlUtils.trim(" ; ; ; ;; ")); assertEquals("select * from t lImit 10", SqlUtils.trim(" select * from t lImit 10")); assertEquals("select * from t lImit 10", SqlUtils.trim("select * from t lImit 10 ")); assertEquals("select * from t lImit 10", SqlUtils.trim("select * from t lImit 10; ")); assertEquals("select * from t lImit 10", SqlUtils.trim("select * from t lImit 10 ;")); assertEquals("select * from t lImit 10", SqlUtils.trim("select * from t lImit 10 ;; ; ;; ; ")); } @Test public void limitMsql() { assertEquals("select * from t lImit 10", SqlUtils.limitMysql("select * from t lImit 10", 100)); assertEquals("select * from t lImit 1000,10", SqlUtils.limitMysql("select * from t lImit 1000,10", 100)); assertEquals("select * from t LIMIT 100", SqlUtils.limitMysql("select * from t lImit 118", 100)); assertEquals("select * from t LIMIT 1951,100", SqlUtils.limitMysql("select * from t liMiT 1951 , 210", 100)); assertEquals("select * from t LIMIT 100", SqlUtils.limitMysql("select * from t ", 100)); } @Test public void limitPgsql() { assertEquals("select * from t lImit 12 ", SqlUtils.limitPgsql("select * from t lImit 12 ", 100)); assertEquals("select * from t LIMIT 11 OFFSET 9999 ",SqlUtils.limitPgsql("select * from t LIMIT 11 OFFSET 9999 ", 100)); assertEquals("select * from t LIMIT 100", SqlUtils.limitPgsql("select * from t lImit 118", 100)); assertEquals("select * from t LIMIT 100 OFFSET 210", SqlUtils.limitPgsql("select * from t liMiT 1951 ofFseT 210", 100)); assertEquals("select * from t LIMIT 100", SqlUtils.limitPgsql("select * from t ", 100)); } @Test public void limitOracle() { assertEquals("select * from t WHERE ROWNUM<100", SqlUtils.limitOracle("select * from t", 100)); assertEquals("select * from where rownum<=10", SqlUtils.limitOracle("select * from where rownum<=10", 100)); assertEquals("select * from where rownum<=10 and 1=1", SqlUtils.limitOracle("select * from where rownum<=10 and 1=1", 100)); assertEquals("select * from where 1=1 aNd rownum<=10", SqlUtils.limitOracle("select * from where 1=1 aNd rownum<=10", 100)); assertEquals("select * from WHERE ROWNUM<100", SqlUtils.limitOracle("select * from rownum<1000", 100)); assertEquals("select * from WHERE ROWNUM<100 AND 1=1", SqlUtils.limitOracle("select * from rownum<=999 AND 1=1", 100)); assertEquals("select * from where ROWNUM<100", SqlUtils.limitOracle("select * from where rownum<=12346", 100)); assertEquals("select * from (select * from where 1=1) t WHERE ROWNUM<100", SqlUtils.limitOracle("select * from (select * from where 1=1) t", 100)); assertEquals("select * from (select * from where 1=1) t where 1=1 AND ROWNUM<100", SqlUtils.limitOracle("select * from (select * from where 1=1) t where 1=1", 100)); assertEquals("select * from (select * from where 1=1) t where 1=1 AND ROWNUM<100 AND 2=2", SqlUtils.limitOracle("select * from (select * from where 1=1) t where 1=1 AND rownuM<1000 AND 2=2", 100)); } @Test public void limitSqlServer() { assertEquals("select TOP 100 * from t ", SqlUtils.limitMssql("select * from t ", 100)); assertEquals("select TOP 100 * from t ", SqlUtils.limitMssql("select TOP 1000 * from t ", 100)); assertEquals("select * from (select TOP 100 * from a) b", SqlUtils.limitMssql("select * from (select TOP 1000 * from a) b", 100)); assertEquals("select TOP 100 * from (select TOP 1000 * from a) b",SqlUtils.limitMssql("select TOP 101 * from (select TOP 1000 * from a) b", 100)); } @Test public void limitHive() { assertEquals("select * from t lImit 10", SqlUtils.limitHive("select * from t lImit 10", 100)); assertEquals("select * from t LIMIT 100", SqlUtils.limitHive("select * from t lImit 118", 100)); assertEquals("select * from t LIMIT 100", SqlUtils.limitHive("select * from t ", 100)); assertEquals("select * from t lImit 1000,10 LIMIT 100", SqlUtils.limitHive("select * from t lImit 1000,10", 100)); assertEquals("select * from t liMiT 1951 , 210 LIMIT 100", SqlUtils.limitHive("select * from t liMiT 1951 , 210", 100)); } } ================================================ FILE: src/test/java/cn/ponfee/commons/util/StreamForkerTest.java ================================================ package cn.ponfee.commons.util; import java.util.stream.Stream; import cn.ponfee.commons.collect.StreamForker; public class StreamForkerTest { public static void main(String[] args) throws Exception { Stream stream = Stream.of(1, 2, 3, 4, 4, 5, 5); StreamForker.Results results = new StreamForker<>(stream) .fork(1, s -> s.max(Integer::compareTo)) // 直接聚合 //.fork(2, s -> s.distinct().collect(Collectors.reducing(Integer::sum))) // 先收集再聚合 //.fork(2, s -> s.distinct().collect(Collectors.summingInt(Integer::intValue))) // 先收集再聚合 .fork(2, s -> s.distinct().reduce(0, Integer::sum)) .getResults(); System.out.println(results.get(1) + ""); System.out.println(results.get(2) + ""); } } ================================================ FILE: src/test/java/cn/ponfee/commons/util/StringsTest.java ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ package cn.ponfee.commons.util; import org.junit.Assert; import org.junit.Test; /** * Strings test * * @author Ponfee */ public class StringsTest { @Test public void testWildcardMatch() { Assert.assertFalse(Strings.isMatch("aa", "a")); Assert.assertTrue(Strings.isMatch("aa", "aa")); Assert.assertFalse(Strings.isMatch("aaa", "aa")); Assert.assertTrue(Strings.isMatch("aa", "*")); Assert.assertTrue(Strings.isMatch("aa", "a*")); Assert.assertTrue(Strings.isMatch("ab", "?*")); Assert.assertFalse(Strings.isMatch("aab", "c*a*b")); } @Test public void testToSeparatedName() { Assert.assertEquals("test.to.separated.name", Strings.toSeparatedName("testToSeparatedName", '.')); Assert.assertEquals("testToSeparatedName", Strings.toCamelcaseName("test.to.separated.name", '.')); Assert.assertEquals("test-to-separated-name", Strings.toSeparatedName("testToSeparatedName", '-')); Assert.assertEquals("testToSeparatedName", Strings.toCamelcaseName("test-to-separated-name", '-')); } } ================================================ FILE: src/test/java/cn/ponfee/commons/util/TestSerialize.java ================================================ package cn.ponfee.commons.util; import cn.ponfee.commons.reflect.ClassUtils; import cn.ponfee.commons.reflect.Fields; import cn.ponfee.commons.serial.WrappedSerializer; import java.lang.reflect.Field; import java.util.Date; /** * * @author Ponfee */ public class TestSerialize { public static enum E { A, B } public TestSerialize(byte a, Byte b, int c, Integer d, float f, Float g, E h, Date i) { super(); this.a = a; this.b = b; this.c = c; this.d = d; this.f = f; this.g = g; this.h = h; this.i = i; } private byte a; private Byte b; private int c; private Integer d; private float f; private Float g; private E h; private Date i; public byte getA() { return a; } public void setA(byte a) { this.a = a; } public Byte getB() { return b; } public void setB(Byte b) { this.b = b; } public int getC() { return c; } public void setC(int c) { this.c = c; } public Integer getD() { return d; } public void setD(Integer d) { this.d = d; } public float getF() { return f; } public void setF(float f) { this.f = f; } public Float getG() { return g; } public void setG(Float g) { this.g = g; } public E getH() { return h; } public void setH(E h) { this.h = h; } public Date getI() { return i; } public void setI(Date i) { this.i = i; } @Override public String toString() { return "TestSerialize [a=" + a + ", b=" + b + ", c=" + c + ", d=" + d + ", f=" + f + ", g=" + g + ", h=" + h + ", i=" + i + "]"; } public static void main(String[] args) { WrappedSerializer serializer = WrappedSerializer.WRAPPED_KRYO_SERIALIZER; //byte[] data = serializer.serialize(new Integer(1)); byte[] data = serializer.serialize((Integer)null); System.out.println(serializer.deserialize(data, int.class)); TestSerialize source = new TestSerialize((byte) 12, (Byte) (byte) 23, 34, 45, 56f, 67f, E.A, new Date()); System.out.println(source); TestSerialize target = new TestSerialize((byte) 0, null, 1, 2, 3, null, E.B, null); System.out.println(target); for (Field field : ClassUtils.listFields(TestSerialize.class)) { byte[] value = serializer.serialize(Fields.get(source, field)); Fields.put(target, field, serializer.deserialize(value, field.getType())); } System.out.println(target); } } ================================================ FILE: src/test/java/test/Cat.java ================================================ package test; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.Arrays; // java test.Cat -n < Cat.java | java test.Cat -n public class Cat { public static void main(String[] args) throws IOException { //是否显示行号,使用参数 -n 启用 boolean showNumber = args.length > 0 && Arrays.asList(args).contains("-n"); int num = 0; BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); String line = reader.readLine(); while (line != null) { if (showNumber) { System.out.printf("%1$8s %2$s%n", num++, line); } else { System.out.println(line); } line = reader.readLine(); } } } ================================================ FILE: src/test/java/test/CsvWrappedCharTest.java ================================================ package test; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import com.google.common.io.Files; import com.google.common.io.LineProcessor; import cn.ponfee.commons.io.WrappedBufferedWriter; public class CsvWrappedCharTest { public static void main(String[] args) throws IOException { String file = "D:\\download\\出库报表.csv"; WrappedBufferedWriter writer = new WrappedBufferedWriter(new File("d:\\出库报表-修正.csv"), StandardCharsets.UTF_8); Files.asCharSource(new File(file), StandardCharsets.UTF_8).readLines(new LineProcessor() { @Override public boolean processLine(String line) throws IOException { String[] array = new String[8]; String[] s = line.split(","); array[0] = s[0]; array[1] = s[1]; array[2] = s[2]; array[3] = s[3]; array[7] = s[s.length - 1]; array[6] = s[s.length - 2]; array[5] = s[s.length - 3]; array[4] = StringUtils.join(s, ",", 4, s.length - 3); String str = Arrays.stream(array).collect(Collectors.joining(",", "\"", "\"")); System.out.println(str); writer.write(str); writer.write(cn.ponfee.commons.io.Files.UNIX_LINE_SEPARATOR); return StringUtils.isNotBlank(line); } @Override public String getResult() { return null; } }); writer.close(); } } ================================================ FILE: src/test/java/test/CustomClassLoader.java ================================================ package test; import java.io.File; import java.io.IOException; import java.util.Arrays; import com.google.common.io.Files; public class CustomClassLoader extends ClassLoader { private final byte[] classBytes; public CustomClassLoader() { this.classBytes = null; } public CustomClassLoader(File classFile) throws IOException { this.classBytes = Files.toByteArray(classFile); } protected Class findClass(String name) throws ClassNotFoundException { return defineClass(name, classBytes, 0, classBytes.length); } public static void main(String[] args) throws Exception { CustomClassLoader classLoader1 = new CustomClassLoader(); CustomClassLoader classLoader2 = new CustomClassLoader(new File("D:\\test\\ToolProvider.class")); System.out.println(Arrays.toString(classLoader1.loadClass("javax.tools.ToolProvider").getDeclaredMethods())); System.out.println("\n============================================"); Class objClass = classLoader2.findClass("javax.tools.ToolProvider"); System.out.println(Arrays.toString(objClass.getDeclaredMethods())); objClass.getDeclaredMethod("say").invoke(objClass.newInstance()); cn.ponfee.commons.io.Files.touch(new File("D:\\test\\a\\b\\ToolProvider1.class")); } } ================================================ FILE: src/test/java/test/GuavaCacheRefreshTest.java ================================================ package test; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import static com.google.common.base.Preconditions.checkNotNull; /** * @author Ponfee */ public class GuavaCacheRefreshTest { public class SkuCache { private String skuId; private String skuCode; private Long realQuantity; public String getSkuId() { return skuId; } public String getSkuCode() { return skuCode; } public Long getRealQuantity() { return realQuantity; } public void setSkuId(String skuId) { this.skuId = skuId; } public void setSkuCode(String skuCode) { this.skuCode = skuCode; } public void setRealQuantity(Long realQuantity) { this.realQuantity = realQuantity; } } AtomicInteger loadTimes = new AtomicInteger(0); AtomicInteger count = new AtomicInteger(0); @Test public void testCacheUse() throws Exception { LoadingCache loadingCache = CacheBuilder.newBuilder().refreshAfterWrite(1000, TimeUnit.MILLISECONDS) //Prevent data reloading from failing, but the value of memory remains the same .expireAfterWrite(1500, TimeUnit.MILLISECONDS).build(new CacheLoader() { @Override public SkuCache load(String key) { System.out.println("============================load " + key); return load0(key); } @Override public ListenableFuture reload(String key, SkuCache oldValue) throws Exception { checkNotNull(key); checkNotNull(oldValue); System.out.println("============================reload " + key); return Futures.immediateFuture(load0(key)); } private SkuCache load0(String key) { SkuCache skuCache = new SkuCache(); skuCache.setSkuCode(key + "---" + (loadTimes.incrementAndGet())); skuCache.setSkuId(key); skuCache.setRealQuantity(100L); return skuCache; } }); int count = 5; Thread[] threads = new Thread[count]; for (int i = 0; i < count; i++) { threads[i] = new Thread(() -> { try { getValue(loadingCache); } catch (Exception e) { e.printStackTrace(); } }); } for (Thread t : threads) { t.start(); } for (Thread t : threads) { t.join(); } System.out.println("finish"); } private void getValue(LoadingCache loadingCache) throws Exception { for (int i = 0; i < 10; i++) { Thread.sleep(300l); System.out.println(loadingCache.get("sku").toString() + " - " + count.incrementAndGet()); } } } ================================================ FILE: src/test/java/test/Test1.java ================================================ package test; import java.io.ByteArrayOutputStream; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; import cn.ponfee.commons.base.tuple.Tuple2; import cn.ponfee.commons.collect.ArrayHashKey; import cn.ponfee.commons.concurrent.ThreadPoolTestUtils; import cn.ponfee.commons.util.CurrencyEnum; import cn.ponfee.commons.util.Money; import com.alibaba.fastjson.JSON; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.io.IOUtils; import org.junit.Test; import com.google.common.base.Stopwatch; import com.google.common.collect.Lists; import cn.ponfee.commons.jce.CryptoProvider; import cn.ponfee.commons.resource.ResourceLoaderFacade; import cn.ponfee.commons.util.Bytes; import cn.ponfee.commons.date.Dates; import cn.ponfee.commons.util.MavenProjects; /** * * @author Ponfee */ public class Test1 { public static void main(String[] args) throws InterruptedException, ExecutionException, JsonProcessingException { Money money = Money.of(CurrencyEnum.CNY.currency(), 100); String json = JSON.toJSONString(money); System.out.println(json); System.out.println(new ObjectMapper().writeValueAsString(money)); Money money1 = JSON.parseObject(json, Money.class); Money money2 = new ObjectMapper().readValue(json, Money.class); System.out.println(money1.equals(money2)); System.out.println(new ObjectMapper().writeValueAsString(money1)); String nids = Arrays.stream(new Integer[]{1,2,3}).map(e -> String.valueOf(e)).collect(Collectors.joining(",")); System.out.println(nids); String[] arr = {"a", "b"}; System.out.println(Arrays.toString(Arrays.copyOfRange(arr, 1, 1))); System.out.println(null==null); Tuple2 a = Tuple2.of(String.class, ArrayHashKey.of(new Class[]{IntStream.class, Object.class})); Tuple2 b = Tuple2.of(String.class, ArrayHashKey.of(new Class[]{IntStream.class, Object.class})); System.out.println(a.equals(b)); System.out.println(a.hashCode() == b.hashCode()); System.out.println((-1L ^ (-1L << 10)) == (~(-1L << 10))); System.out.println(String.format("%02d", 1)); Date d1 = Dates.toDate("2019-05-10 10:23:34"); Date d2 = Dates.toDate("2019-05-11 08:23:34"); System.out.println(Dates.daysBetween(Dates.startOfDay(d1), Dates.endOfDay(d2))); System.out.println(List.class.isInstance(null)); Stopwatch watch = Stopwatch.createStarted(); CompletableFuture future1 = new CompletableFuture<>(); new Thread(()->{ try { Thread.sleep(2000); future1.complete("test1"); // 完成,会通知CompletableFuture.get() } catch (InterruptedException e) { e.printStackTrace(); } }).start(); System.out.println("==================="); System.out.println(future1.get() + " cost: " + watch.stop().toString()); System.out.println("==================="); System.out.println(); watch.reset().start(); CompletableFuture future2 = CompletableFuture.completedFuture("test2"); System.out.println("==================="); System.out.println(future2.get() + " cost: " + watch.stop().toString()); System.out.println("==================="); } @Test public void test() throws InterruptedException, ExecutionException { System.out.println("开始..."); CompletableFuture fu = CompletableFuture.supplyAsync(new Supplier() { @Override public String get() { try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("返回 zhang"); return "zhang"; } }).thenCombine(CompletableFuture.supplyAsync(new Supplier() { @Override public String get() { try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("返回 phil"); return "phil"; } }), new BiFunction() { @Override public Object apply(String s1, String s2) { String s = s1 + " , " + s2; System.out.println("apply:" + s); return s; } }).whenCompleteAsync(new BiConsumer() { @Override public void accept(Object o, Throwable throwable) { System.out.println("accept:" + o.toString()); } }); System.out.println(fu.get()); } @Test public void test2() throws InterruptedException { Stream.of(1,2,3,4,5).map(i -> CompletableFuture.runAsync(()-> { try { Thread.sleep(ThreadLocalRandom.current().nextInt(2000)); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(i); })) .count(); Thread.sleep(5000); } @Test public void test3() throws InterruptedException { System.out.println(ThreadPoolTestUtils.INFINITY_QUEUE_EXECUTOR); Lists.newArrayList(1,2,3) .parallelStream().parallel().sequential() // 并行或串行由最后一个设置决定 ; // ClassName::staticMethodName // ClassName::new // TypeName[]::new int[]::new <=> (x -> new int[x]) // ClassName::instanceMethodName // instance::instanceMethodName // super::instanceMethodName // this::instanceMethodName Lists.newArrayList(1,2,3).stream().filter(this::test).forEach(System.out::println); } public boolean test(Integer i) { return i > 2; } @Test public void test5() throws Exception { byte[] data = MavenProjects.getTestJavaFileAsBytes(this.getClass()); System.out.println((data.length + 15) / 16); System.out.println(Integer.toHexString((data.length + 15) / 16)); String lineNumberFormat = "%0" + Integer.toHexString((data.length + 15) / 16).length() + "x: "; System.out.println(lineNumberFormat); System.out.println(Bytes.dumpHex(data)); ByteArrayOutputStream out = new ByteArrayOutputStream(); /*org.apache.commons.io.HexDump.dump(MavenProjects.getTestJavaFileAsByteArray(this.getClass()), 0, out, 0); System.out.println(new String(out.toByteArray()));*/ /*new sun.misc.HexDumpEncoder().encode(MavenProjects.getTestJavaFileAsByteArray(this.getClass()), out); System.out.println(new String(out.toByteArray()));*/ //System.out.println(Bytes.hexDump(Bytes.fromChar(Numbers.CHAR_ZERO))); } @Test public void test6() throws Exception { //System.out.println(IOUtils.toString(ResourceLoaderFacade.getResource("/gbkxxx.txt", CryptoProvider.class).getStream(), "GBK")); System.out.println(IOUtils.toString(ResourceLoaderFacade.getResource("cert/gbkyyy.txt", CryptoProvider.class).getStream(), "GBK")); } } ================================================ FILE: src/test/java/test/Test2.java ================================================ package test; import cn.ponfee.commons.base.tuple.Tuple; import cn.ponfee.commons.base.tuple.Tuple1; import cn.ponfee.commons.base.tuple.Tuple2; import cn.ponfee.commons.collect.Collects; import cn.ponfee.commons.collect.ValueSortedMap; import cn.ponfee.commons.io.HumanReadables; import cn.ponfee.commons.json.Jsons; import cn.ponfee.commons.math.Maths; import cn.ponfee.commons.math.Numbers; import cn.ponfee.commons.model.Page; import cn.ponfee.commons.reflect.BeanCopiers; import cn.ponfee.commons.util.Bytes; import cn.ponfee.commons.date.Dates; import cn.ponfee.commons.util.Snowflake; import cn.ponfee.commons.util.SecureRandoms; import com.google.common.base.Stopwatch; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.RandomStringUtils; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; import org.springframework.objenesis.ObjenesisHelper; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.*; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ThreadLocalRandom; /** * * @author Ponfee */ public class Test2 { @Test public void test0() { Map map = new HashMap<>(); map.put(1, null); map.put("a", ""); System.out.println(Jsons.toJson(map)); System.out.println(Jsons.ALL.string(map)); } @Test public void test1() { Map map = new HashMap<>(); for (int i = 0; i < 10; i++) { map.put(RandomStringUtils.randomAlphabetic(1), ThreadLocalRandom.current().nextInt(100)); } map.put("b", 8); System.out.println(map); TreeMap tree = ValueSortedMap.nullsFirst(map, Comparator.comparing(v->v)); System.out.println(tree); TreeMap tree2 = new TreeMap<>(Comparator.comparing(k -> map.get(k))); tree2.putAll(map); System.out.println(tree2); } @SuppressWarnings("rawtypes") @Test public void test2() { Page> source = new Page<>(); Page target = new Page<>(); BeanCopiers.copy(source, target); System.out.println(source.copy()); } @Test public void test3() { ForkJoinPool.commonPool().shutdownNow(); byte b = 127; System.out.println(Integer.toBinaryString( b )); System.out.println(Integer.toBinaryString((b & 0xFF) )); System.out.println(Integer.toBinaryString((b & 0xFF) | 0x100)); System.out.println(Integer.toBinaryString((b & 0xFF) + 0x100)); } @Test public void test4() { System.out.println(Dates.format(Dates.ofUnixTimestamp(-2000000000))); } @Test @Ignore public void test5() throws IOException { Files.delete(Paths.get("D:\\test\\framework")); } @Test public void test6() throws IOException { System.out.println(ObjenesisHelper.newInstance(HashMap.class)); System.out.println(Double.isFinite(Math.PI)); System.out.println(Double.isInfinite(Double.POSITIVE_INFINITY)); System.out.println(Double.isNaN(0.0/0.0)); System.out.println(Double.POSITIVE_INFINITY == Double.POSITIVE_INFINITY); } @Test public void test10() throws IOException { Map map = new HashMap<>(); System.out.println(map.replace("xx", 1)); System.out.println(map); System.out.println(map.put("a", 1)); System.out.println(map); System.out.println(map.put("a", 2)); System.out.println(map); } @Test @Ignore public void test11() throws IOException { File source = new File("D:\\test\\test1\\CentOS-6.6-x86_64-bin-DVD1.iso"); File target = new File("D:\\test\\test1\\CentOS-6.6-x86_64-bin-DVD2.iso"); Stopwatch watch = Stopwatch.createStarted(); Files.move(source.toPath(), target.toPath()); System.out.println("move cost: " + watch.stop()); watch.reset().start(); source = target; target = new File("D:\\test\\test1\\CentOS-6.6-x86_64-bin-DVD3.iso"); boolean f = source.renameTo(target); System.out.println(f); System.out.println("move cost: " + watch.stop()); } @Test public void test13() throws IOException { System.out.println(HumanReadables.BINARY.parse("-1,023.56 GiB")); System.out.println(HumanReadables.BINARY.human(-1099039181373L).length()); long size = 1, max = 0; for (int i = 0; i < 100; i++) { String s = HumanReadables.BINARY.human(size); System.out.println(s); max = Math.max(max, s.length()); size *= 7; //Assert.assertEquals(s, HumanReadables.BINARY.human(size).replace("i", "")); //System.out.println(HumanReadables.BINARY.parse(s, false)); } System.out.println("======="+max); } @Test public void test14() throws IOException { System.out.println(HumanReadables.BINARY.human(1047552)); System.out.println(HumanReadables.BINARY.human(123456789123456L)); System.out.println(HumanReadables.BINARY.human(999_949_999_999_999_999L)); System.out.println(HumanReadables.BINARY.human(-12345678)); System.out.println(HumanReadables.BINARY.human(0)); System.out.println(HumanReadables.BINARY.human(-0)); System.out.println(HumanReadables.BINARY.human(5)); System.out.println(HumanReadables.BINARY.human(-5)); System.out.println(HumanReadables.BINARY.human(98745612)); System.out.println("\n==============================="); System.out.println(HumanReadables.BINARY.parse("1,023KB")); System.out.println(HumanReadables.BINARY.parse("1047552")); System.out.println(HumanReadables.BINARY.parse("112.28TB")); System.out.println(HumanReadables.BINARY.parse("888.13PB")); System.out.println(HumanReadables.BINARY.parse("-11.77MB")); System.out.println(HumanReadables.BINARY.parse("0B")); System.out.println(HumanReadables.BINARY.parse("5B")); System.out.println(HumanReadables.BINARY.parse("-5B")); System.out.println(HumanReadables.BINARY.parse("94.17MB")); System.out.println(HumanReadables.BINARY.parse("-1KB")); System.out.println(HumanReadables.BINARY.parse("0B")); System.out.println(HumanReadables.BINARY.parse("-0B")); System.out.println(HumanReadables.BINARY.parse("123B")); System.out.println(HumanReadables.BINARY.parse("-123B")); System.out.println(HumanReadables.BINARY.parse("6MB")); } @Test public void test15() throws IOException { System.out.println(HumanReadables.BINARY.parse("888.13 PiB")); System.out.println(HumanReadables.BINARY.parse("888.13PiB", true)); System.out.println(HumanReadables.BINARY.parse("888.13PB")); //System.out.println(HumanReadables.BINARY.parse("888.13PB", true)); } @Test public void test16() throws IOException { System.out.println(HumanReadables.BINARY.parse("888.13PiB", true)); //System.out.println(HumanReadables.BINARY.parse("888.13PB", true)); } @Test public void test18() throws IOException { System.out.println(Long.MAX_VALUE); System.out.println(HumanReadables.SI.human(Long.MAX_VALUE)); System.out.println(HumanReadables.BINARY.human(Long.MAX_VALUE)); System.out.println(HumanReadables.SI.parse("9.223372036854776 EB", true)); System.out.println(HumanReadables.BINARY.parse("8EiB", true)); } @Test public void test19() throws IOException { System.out.println(HumanReadables.SI.human(Long.MIN_VALUE)); System.out.println(HumanReadables.BINARY.human(Long.MIN_VALUE)); System.out.println(HumanReadables.SI.parse("-9.22EB", true)); System.out.println(HumanReadables.BINARY.parse("-8EiB", true)); System.out.println(Long.MIN_VALUE); } @Test public void test20() throws IOException { System.out.println(HumanReadables.SI.parse("888.13P", false)); System.out.println(HumanReadables.SI.parse("888.13P", true)); //System.out.println(HumanReadables.BINARY.parse("888.13Pi", false)); //System.out.println(HumanReadables.BINARY.parse("888.13Pi", true)); System.out.println(HumanReadables.BINARY.parse("888.13P", false)); //System.out.println(HumanReadables.BINARY.parse("888.13P", true)); } @Test public void test21() throws IOException { System.out.println(HumanReadables.SI.parse("888.13", false)); System.out.println(HumanReadables.SI.parse("888.13", true)); System.out.println(HumanReadables.SI.parse("888.13B", false)); System.out.println(HumanReadables.SI.parse("888.13B", true)); System.out.println(HumanReadables.SI.parse("888.13PB", false)); System.out.println(HumanReadables.SI.parse("888.13PB", true)); //System.out.println(HumanReadables.SI.parse("888.13PiB", false)); //System.out.println(HumanReadables.SI.parse("888.13PiB", true)); System.out.println(HumanReadables.SI.parse("888.13P", false)); System.out.println(HumanReadables.SI.parse("888.13P", true)); } @Test public void test22() throws IOException { System.out.println(HumanReadables.BINARY.parse("888.13", false)); System.out.println(HumanReadables.BINARY.parse("888.13", true)); System.out.println(HumanReadables.BINARY.parse("888.13B", false)); System.out.println(HumanReadables.BINARY.parse("888.13B", true)); System.out.println(HumanReadables.BINARY.parse("888.13PB", false)); //System.out.println(HumanReadables.BINARY.parse("888.13PB", true)); System.out.println(HumanReadables.BINARY.parse("888.13PiB", false)); System.out.println(HumanReadables.BINARY.parse("888.13PiB", true)); System.out.println(HumanReadables.BINARY.parse("888.13P", false)); //System.out.println(HumanReadables.BINARY.parse("888.13P", true)); } @Test public void test23() throws IOException { //System.out.println(HumanReadables.BINARY.parse("888.13AB", false)); //System.out.println(HumanReadables.BINARY.parse("888.13BA", true)); //System.out.println(HumanReadables.SI.parse("888.13AB", false)); //System.out.println(HumanReadables.SI.parse("888.13BA", true)); } @Test public void test24() throws IOException { System.out.println(FileUtils.byteCountToDisplaySize(1047552)); System.out.println(FileUtils.byteCountToDisplaySize(123456789123456L)); System.out.println(FileUtils.byteCountToDisplaySize(999_949_999_999_999_999L)); System.out.println(FileUtils.byteCountToDisplaySize(-12345678)); System.out.println(FileUtils.byteCountToDisplaySize(0)); System.out.println(FileUtils.byteCountToDisplaySize(-0)); System.out.println(FileUtils.byteCountToDisplaySize(5)); System.out.println(FileUtils.byteCountToDisplaySize(-5)); System.out.println(FileUtils.byteCountToDisplaySize(98745612)); } @Test public void test25() throws IOException { System.out.println(HumanReadables.BINARY.human(1099382778757L)); System.out.println(HumanReadables.BINARY.parse("-1,023.88GiB ", false)); System.out.println(HumanReadables.BINARY.parse("-1,023.88 GiB ", false)); System.out.println(HumanReadables.BINARY.parse("-1,023.88 B", false)); //System.out.println(HumanReadables.BINARY.parse("-1,023.88 Gi B", false)); //System.out.println(HumanReadables.BINARY.parse("-1,023.88 G iB", false)); //System.out.println(HumanReadables.BINARY.parse("-B", false)); //System.out.println(HumanReadables.BINARY.parse(".B", false)); //System.out.println(HumanReadables.BINARY.parse("TB", false)); //System.out.println(HumanReadables.BINARY.parse("xTB", false)); } @Test public void test26() throws IOException { System.out.println(Bytes.toBinary(Bytes.toBytes(new Snowflake(10).nextId()))); String format = "yyyy-MM-dd HH:mm:ss.SSS"; System.out.println(Long.toBinaryString(Long.MAX_VALUE)); System.out.println(Long.toBinaryString(System.currentTimeMillis())); System.out.println(Bytes.toBinary(Bytes.toBytes(Long.MAX_VALUE))); System.out.println(Dates.format(new Date(0B0000000000000000000000011111111111111111111111111111111111111111L), format)); System.out.println(Dates.format(new Date(0B0000000000000000000011111111111111111111111111111111111111111111L), format)); System.out.println(Long.toBinaryString(Long.MAX_VALUE).length()); System.out.println("====\n"); System.out.println("0 -> " + Maths.bitsMask(0) + " -> " + Bytes.toBinary(Bytes.toBytes(Maths.bitsMask(0)))); System.out.println("1 -> " + Maths.bitsMask(1) + " -> " + Bytes.toBinary(Bytes.toBytes(Maths.bitsMask(1)))); System.out.println("2 -> " + Maths.bitsMask(2) + " -> " + Bytes.toBinary(Bytes.toBytes(Maths.bitsMask(2)))); System.out.println("10 -> " + Maths.bitsMask(10) + " -> " + Bytes.toBinary(Bytes.toBytes(Maths.bitsMask(10)))); System.out.println("20 -> " + Maths.bitsMask(20) + " -> " + Bytes.toBinary(Bytes.toBytes(Maths.bitsMask(20)))); System.out.println("63 -> " + Maths.bitsMask(63) + " -> " + Bytes.toBinary(Bytes.toBytes(Maths.bitsMask(63)))); System.out.println("64 -> " + Maths.bitsMask(64) + " -> " + Bytes.toBinary(Bytes.toBytes(Maths.bitsMask(64)))); System.out.println("====\n"); System.out.println(Dates.format(new Date(17592186044415L), format)); int bits = 41; System.out.println((1L << bits) - 1); Assert.assertEquals((1L << bits) - 1, -1L ^ (-1L << bits)); Assert.assertEquals((1L << bits) - 1, Long.MAX_VALUE >>> (63 - bits)); System.out.println(Long.toBinaryString((1L << bits) - 1)); System.out.println(Long.toBinaryString(-1L ^ (-1L << bits))); System.out.println(Long.toBinaryString(Long.MAX_VALUE >>> (63 - 41))); } @Test public void test27() throws IOException { System.out.println(10); System.out.println(0B010); System.out.println(0010); System.out.println(010); System.out.println(0X010); System.out.println(0E10); } @Test public void test28() throws IOException { try { Long.parseLong("321.0"); } catch (NumberFormatException e) { e.printStackTrace(); } } @Test public void test29() throws IOException { System.out.println("321.0".indexOf('.')); System.out.println(Numbers.toInt("321.0")); } @Test() public void test30() { String str = null; Long num = null; System.out.println(str == null ? num : (Long) Long.parseLong(str)); System.out.println(str == null ? null : Long.parseLong(str)); NullPointerException ex = null; try { System.out.println(str == null ? num : Long.parseLong(str)); } catch (NullPointerException e) { ex = e; } Assert.assertNotNull(ex); } @Test public void tes31() { System.out.println(Character.toUpperCase('y')); System.out.println(Character.toUpperCase('Y')); System.out.println(Arrays.toString(Numbers.slice(0, 2))); System.out.println(Arrays.toString(Numbers.slice(2, 3))); System.out.println(Arrays.toString(Numbers.slice(5, 100))); System.out.println(Arrays.toString(Numbers.slice(3, 1))); System.out.println(Arrays.toString(Numbers.slice(9, 3))); System.out.println(Arrays.toString(Numbers.slice(10, 3))); System.out.println(Arrays.toString(Numbers.slice(11, 3))); System.out.println(Arrays.toString(Numbers.slice(12, 3))); System.out.println("-----\n\n"); System.out.println(Numbers.partition( 0, 2)); System.out.println(Numbers.partition( 2, 3)); System.out.println(Numbers.partition( 3, 1)); System.out.println(Numbers.partition( 9, 3)); System.out.println(Numbers.partition(10, 3)); System.out.println(Numbers.partition(11, 3)); System.out.println(Numbers.partition(12, 3)); } @Test public void tes32() { System.out.println(String.format("Holder(%s)", "null")); List x = Arrays.asList(1, 2, 3); List y = Arrays.asList(4, 5, 6); System.out.println(Collects.cartesian(x, y, (a, b) -> a * b)); System.out.println(SecureRandoms.random(512)); System.out.println(Bytes.toBigInteger(SecureRandoms.nextBytes(10))); } @Test public void tes33() { List> list = new ArrayList<>(); /*list.add(Tuple1.of(new Object()));*/ list.add(Tuple1.of(SecureRandoms.nextInt(10))); list.add(Tuple1.of(SecureRandoms.nextInt(10))); list.add(Tuple1.of(SecureRandoms.nextInt(10))); list.add(Tuple1.of(SecureRandoms.nextInt(10))); list.add(Tuple1.of(SecureRandoms.nextInt(10))); System.out.println(list); list.sort(Comparator.naturalOrder()); System.out.println(list); Assert.assertTrue(Object.class.isInstance(new Object())); Assert.assertTrue(Object.class.isInstance(new Object[0])); Assert.assertTrue(Object.class.isInstance(1)); Assert.assertTrue(Integer.class.isInstance(1)); Assert.assertFalse(Integer.class.isInstance(1L)); Assert.assertTrue(Number.class.isInstance(1L)); Assert.assertTrue(Number.class.isInstance(1)); List list1 = new ArrayList<>(); list1.add(Tuple1.of(new Object())); list1.add(Tuple1.of(1)); list1.add(Tuple1.of("z")); list1.add(Tuple2.of("x", 4)); list1.add(Tuple1.of(null)); list1.add(Tuple1.of(new RuntimeException())); list1.add(null); list1.add(Tuple1.of("b")); list1.add(Tuple1.of(null)); list1.add(Tuple1.of(2)); list1.add(Tuple1.of(null)); System.out.println(list1); list1.sort(Comparator.nullsLast(Comparator.naturalOrder())); System.out.println(list1); System.out.println(); List list2 = new ArrayList<>(); list2.add(Tuple1.of(SecureRandoms.nextInt(100))); list2.add(Tuple1.of("a")); list2.add(Tuple1.of(SecureRandoms.nextInt(100))); list2.add(Tuple1.of('x')); list2.add(Tuple1.of(SecureRandoms.nextInt(100))); list2.add(Tuple1.of(null)); list2.add(Tuple1.of(SecureRandoms.nextInt(100))); list2.add(Tuple1.of("x")); list2.add(Tuple1.of(SecureRandoms.nextInt(100))); list2.add(Tuple1.of('z')); list2.add(Tuple1.of(SecureRandoms.nextInt(100))); list2.add(Tuple1.of(SecureRandoms.nextInt(100))); list2.add(Tuple1.of('c')); list2.add(Tuple1.of(new Object())); list2.add(Tuple1.of(new Object())); list2.add(Tuple1.of('a')); list2.add(Tuple1.of(new Object())); list2.add(Tuple1.of(SecureRandoms.nextInt(100))); list2.add(Tuple1.of(SecureRandoms.nextInt(100))); list2.add(Tuple1.of("y")); list2.add(Tuple1.of(null)); list2.add(Tuple1.of(SecureRandoms.nextInt(100))); list2.add(Tuple1.of('a')); list2.add(Tuple1.of(SecureRandoms.nextInt(10))); System.out.println("un-sorted: "+list2); list2.sort(Comparator.nullsLast(Comparator.naturalOrder())); System.out.println("do-sorted: "+list2); } } ================================================ FILE: src/test/java/test/Test3.java ================================================ package test; import cn.ponfee.commons.collect.FilterableIterator; import cn.ponfee.commons.exception.UnimplementedException; import cn.ponfee.commons.json.Jsons; import cn.ponfee.commons.reflect.Fields; import org.junit.Assert; import org.openjdk.jol.info.ClassLayout; public class Test3 { public static abstract class Animal implements java.io.Serializable { private static final long serialVersionUID = 3890678647435825868L; private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } public static class Dog extends Animal { private static final long serialVersionUID = -2829034009272727923L; private String ext1; public String getExt1() { return ext1; } public void setExt1(String ext1) { this.ext1 = ext1; } } public static interface A { static A fromByteArray(byte[] arg) { throw new UnimplementedException(); } } public static class B implements A { public B() { System.out.println("Create B."); } static B fromByteArray(byte[] arg) { return new B(); } } public static class C implements A { public C() { System.out.println("Create C."); } static C fromByteArray(byte[] arg) { return new C(); } } public static void main(String[] args) throws Exception { Assert.assertTrue(null == null); System.out.println(Double.doubleToLongBits(0.1D)); System.out.println(Double.doubleToLongBits(0.0D)); System.out.println(); for (Integer s : FilterableIterator.of(e -> e != null && e % 2 == 1, null, 3, 4, 5)) { System.out.println("|" + s + "|"); } System.out.println(); System.out.println(0 >> 1); System.out.println(1 >> 1); System.out.println(4 >> 1); System.out.println(System.getProperty("user.home")); B.fromByteArray(new byte[]{}); Class clazz1 = B.class; clazz1.getDeclaredMethod("fromByteArray", byte[].class).invoke(null, new byte[]{}); Dog dog = new Dog(); dog.setName("xxx"); dog.setAge(10); dog.setExt1("extxxx"); System.out.println(Jsons.toJson(dog)); Fields.put(dog, "ext1", "extyyy"); System.out.println(Jsons.toJson(dog)); Jsons.toJson(dog); System.out.println("---------------------------------------------"); System.out.println(ClassLayout.parseClass(B.class).toPrintable()); } } ================================================ FILE: src/test/java/test/TestBean.java ================================================ package test; import cn.ponfee.commons.constrain.Constraint; import java.io.Serializable; public class TestBean implements Serializable { static final int MAXIMUM_CAPACITY = 1 << 30; private static final long serialVersionUID = 1716190333294826147L; @Constraint(tense = Constraint.Tense.FUTURE) private int i; private Long l; private String s; private String str; public TestBean() {} public TestBean(int i, Long l, String s) { super(); this.i = i; this.l = l; this.s = s; } public int getI() { return i; } public void setI(int i) { this.i = i; } public Long getL() { return l; } public void setL(Long l) { this.l = l; } public String getS() { return s; } @Override public String toString() { return "Bean [i=" + i + ", l=" + l + ", s=" + s + "]"; } public void setS(String s) { this.s = s; } public String getStr() { return str; } public void setStr(String str) { this.str = str; } static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; } public static void main(String[] args) { System.out.println(tableSizeFor(9)); System.out.println(MAXIMUM_CAPACITY); } } ================================================ FILE: src/test/java/test/TestSynthetic.java ================================================ package test; import java.lang.reflect.Method; import junit.framework.TestCase; /** * * * @author Ponfee */ public class TestSynthetic extends TestCase { public void testSynthetic() { try { new User().age = 1; System.out.println(new User().age); Method[] methods = User.class.getDeclaredMethods(); for (Method method : methods) { System.out.println(method.toString() + ", " + method.isSynthetic()); } } catch (SecurityException e) { e.printStackTrace(); } } class User { private int age; private String name; private User() {} private User(int age, String name) { this.age = age; this.name = name; } private int getAge() { return age; } private void setAge(int age) { this.age = age; } private String getName() { return name; } private void setName(String name) { this.name = name; } } } ================================================ FILE: src/test/java/test/ThrowEggsTest.java ================================================ package test; // 动态规划,https://blog.csdn.net/qq_32782339/article/details/100836449 public class ThrowEggsTest { public int countMinSetp(int egg, int num) { if (egg < 1 || num < 1) return 0; int[][] f = new int[egg + 1][num + 1];//代表egg个鸡蛋,从num楼层冷下来所需的最小的次数 for (int i = 1; i <= egg; i++) { for (int j = 1; j <= num; j++) f[i][j] = j;//初始化,最坏的步数 } for (int n = 2; n <= egg; n++) { for (int m = 1; m <= num; m++) { for (int k = 1; k < m; k++) { //这里的DP的递推公式为f[n][m] = 1+max(f[n-1][k-1],f[n][m-k]) k属于[1,m-1] //从1-m层中随机抽出来一层k //如果第一个鸡蛋在k层碎了,那么我们将测试1~k-1层,就可以找出来,也即1+f[1][k-1] //如果第一个鸡蛋在k层没有碎,那么我们将测试k+1~m也即m-k层, // 这里也是重点!!!! // 现在我们手里有2个鸡蛋,要测试m-k层,那么我想问,此时和你手里有2个鸡蛋要测试1~m-k层有什么区别? // 没有区别!是的在已知k层不碎的情况下,测试k+1~m层的方法和测试1~m-k没区别,所以可以写成 1+f[n][m-k] 其中1表示为 在k层的那一次测试 f[n][m] = Math.min(f[n][m], 1 + Math.max(f[n - 1][k - 1], f[n][m - k])); } } } return f[egg][num]; } public static void main(String[] args) { ThrowEggsTest e = new ThrowEggsTest(); System.out.println(e.countMinSetp(2, 100)); } } ================================================ FILE: src/test/java/test/concurrent/AsnycBatchProcessorTest.java ================================================ package test.concurrent; import cn.ponfee.commons.concurrent.AsyncBatchProcessor; import org.junit.Assert; import org.junit.Test; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; public class AsnycBatchProcessorTest { @Test public void test1() throws InterruptedException { System.out.println(20 + (20 >>> 1)); AtomicInteger summer = new AtomicInteger(); AtomicBoolean end = new AtomicBoolean(false); final AsyncBatchProcessor processor = new AsyncBatchProcessor<>((list, isEnd) -> { summer.addAndGet(list.size()); System.out.println(list.size() + "==" + Thread.currentThread().getId() + "-" + Thread.currentThread().getName() + ", " + isEnd); if (isEnd) { end.set(true); } //System.out.println(1 / 0); // submit方式不会打印异常 }, 100, 200, 10); AtomicBoolean flag = new AtomicBoolean(true); AtomicInteger increment = new AtomicInteger(0); int n = 13; Thread[] threads = new Thread[n]; for (int i = 0; i < n; i++) { Thread thread = new Thread(() -> { while (flag.get()) { try { processor.put(increment.incrementAndGet()); Thread.sleep(3 + ThreadLocalRandom.current().nextInt(7)); } catch (InterruptedException e) { e.printStackTrace(); } } }); thread.setDaemon(true); thread.start(); threads[i] = thread; } Thread.sleep(10000); flag.set(false); processor.stopAndAwait(); for (Thread thread : threads) { thread.join(); } while (!end.get()) { // noop-loop } System.out.println(increment.get()); System.out.println(summer.get()); Assert.assertEquals(increment.get(), summer.get()); System.out.println("end"); } @Test public void test2() throws InterruptedException { AtomicInteger summer = new AtomicInteger(); AtomicBoolean end = new AtomicBoolean(false); final AsyncBatchProcessor processor = new AsyncBatchProcessor<>((list, isEnd) -> { summer.addAndGet(list.size()); System.out.println(list.size() + "==" + Thread.currentThread().getId() + "-" + Thread.currentThread().getName() + ", " + isEnd); if (isEnd) { end.set(true); } //System.out.println(1 / 0); // submit方式不会打印异常 }, 200, 50, 10); AtomicBoolean flag = new AtomicBoolean(true); AtomicInteger increment = new AtomicInteger(0); int n = 20; Thread[] threads = new Thread[n]; for (int i = 0; i < n; i++) { Thread thread = new Thread(() -> { while (flag.get()) { try { processor.put(increment.incrementAndGet()); Thread.sleep(3 + ThreadLocalRandom.current().nextInt(7)); } catch (InterruptedException e) { e.printStackTrace(); } } }); thread.setDaemon(true); thread.start(); threads[i] = thread; } Thread.sleep(10000); flag.set(false); processor.stopAndAwait(); for (Thread thread : threads) { thread.join(); } while (!end.get()) { // noop-loop } System.out.println(increment.get()); System.out.println(summer.get()); Assert.assertEquals(increment.get(), summer.get()); System.out.println("end"); } } ================================================ FILE: src/test/java/test/concurrent/ForkJoinPoolTest1.java ================================================ package test.concurrent; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinTask; import java.util.concurrent.RecursiveAction; //RecursiveAction为ForkJoinTask的抽象子类,没有返回值的任务 class PrintTask extends RecursiveAction { private static final long serialVersionUID = 3840971532382813847L; // 每个"小任务"最多只打印50个数 private static final int MAX = 50; private int start; private int end; PrintTask(int start, int end) { this.start = start; this.end = end; } @Override protected void compute() { // 当end-start的值小于MAX时候,开始打印 if ((end - start) < MAX) { for (int i = start; i < end; i++) { System.out.println(Thread.currentThread().getName() + "的i值:" + i); } } else { // 将大任务分解成两个小任务 int middle = (start + end) / 2; PrintTask left = new PrintTask(start, middle); PrintTask right = new PrintTask(middle, end); // 并行执行两个小任务 left.fork(); right.fork(); left.join(); right.join(); } } } public class ForkJoinPoolTest1 { /** * @param args * @throws Exception */ public static void main(String[] args) throws Exception { // 创建包含Runtime.getRuntime().availableProcessors()返回值作为个数的并行线程的ForkJoinPool ForkJoinPool forkJoinPool = ForkJoinPool.commonPool(); ForkJoinTask future = forkJoinPool.submit(new PrintTask(0, 200)); future.get(); /*forkJoinPool.execute(new PrintTask(0, 200)); forkJoinPool.awaitTermination(20, TimeUnit.SECONDS);*/ forkJoinPool.shutdown(); // 关闭线程池 } } ================================================ FILE: src/test/java/test/concurrent/ForkJoinPoolTest2.java ================================================ package test.concurrent; import java.util.Random; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.Future; import java.util.concurrent.RecursiveTask; class CalTask extends RecursiveTask { private static final long serialVersionUID = 4854215345100228419L; // 每个“小任务”只最多只累加20个数 private static final int THRESHOLD = 20; private int arr[]; private int start; private int end; // 累加从start到end的数组元素 public CalTask(int[] arr, int start, int end) { this.arr = arr; this.start = start; this.end = end; } @Override protected Integer compute() { int sum = 0; // 当end与start之间的差小于THRESHOLD时,开始进行实际累加 if (end - start < THRESHOLD) { for (int i = start; i < end; i++) { sum += arr[i]; } return sum; } else { // 如果当end与start之间的差大于THRESHOLD时,即要打印的数超过20个 // 将大任务分解成两个小任务。 int middle = (start + end) / 2; CalTask left = new CalTask(arr, start, middle); CalTask right = new CalTask(arr, middle, end); // 并行执行两个“小任务” left.fork(); right.fork(); // 把两个“小任务”累加的结果合并起来 return left.join() + right.join(); } } } public class ForkJoinPoolTest2 { public static void main(String[] args) throws Exception { int[] arr = new int[1000000]; Random rand = new Random(); int total = 0; // 初始化100个数字元素 for (int i = 0, len = arr.length; i < len; i++) { int tmp = rand.nextInt(20); // 对数组元素赋值,并将数组元素的值添加到total总和中。 total += (arr[i] = tmp); } System.out.println(total); ForkJoinPool pool = new ForkJoinPool(); // 提交可分解的CalTask任务 Future future = pool.submit(new CalTask(arr, 0, arr.length)); System.out.println(future.get()); // 关闭线程池 pool.shutdown(); } } ================================================ FILE: src/test/java/test/concurrent/InheritableThreadLocalTest.java ================================================ package test.concurrent; public class InheritableThreadLocalTest { private static InheritableThreadLocal ITL = new InheritableThreadLocal() { protected StringBuffer initialValue() { return new StringBuffer("hello"); } }; public static void main(String[] args) { System.out.println(Thread.currentThread().getName() + " : " + ITL.get()); new Thread(new Runnable() { public void run() { System.out.println(Thread.currentThread().getName() + " : " + ITL.get()); new Thread(new Runnable() { public void run() { System.out.println(Thread.currentThread().getName() + " : " + ITL.get()); ITL.get().append(", wqf"); System.out.println(Thread.currentThread().getName() + " : " + ITL.get()); } }).start(); try { Thread.sleep(1000); } catch (InterruptedException ex) { ex.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " : " + ITL.get()); } }).start(); try { Thread.sleep(1000); } catch (InterruptedException ex) { ex.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " : " + ITL.get()); } } ================================================ FILE: src/test/java/test/concurrent/InheritableThreadLocalTest2.java ================================================ package test.concurrent; import com.alibaba.ttl.TransmittableThreadLocal; public class InheritableThreadLocalTest2 { //private static InheritableThreadLocal ITL = new InheritableThreadLocal() { private static TransmittableThreadLocal ITL = new TransmittableThreadLocal() { protected String initialValue() { return "hello"; } }; public static void main(String[] args) { System.out.println(Thread.currentThread().getName() + " : " + ITL.get()); new Thread(new Runnable() { public void run() { System.out.println(Thread.currentThread().getName() + " : " + ITL.get()); new Thread(new Runnable() { public void run() { System.out.println(Thread.currentThread().getName() + " : " + ITL.get()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " : " + ITL.get()); } }).start(); try { Thread.sleep(1000); } catch (InterruptedException ex) { ex.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " : " + ITL.get()); } }).start(); try { Thread.sleep(1000); } catch (InterruptedException ex) { ex.printStackTrace(); } ITL.set("word"); // 创建子线程的时候拷贝父线程的threadLocalMap数据到子线程,之后与父线程没有关系了 System.out.println(Thread.currentThread().getName() + " : " + ITL.get()); } } ================================================ FILE: src/test/java/test/concurrent/ReadWriteLock.java ================================================ package test.concurrent; public class ReadWriteLock { private int readThreadCounter = 0; // 正在读取的线程数(0个或多个) private int waitingWriteCounter = 0; // 等待写入的线程数(0个或多个) private int writeThreadCounter = 0; // 正在写入的线程数(0个或1个) private boolean writable = true; // 是否对写入优先(默认为是) private ReadWriteLock() {} public static ReadWriteLock create() { return new ReadWriteLock(); } // 读取加锁 public synchronized void readLock() throws InterruptedException { // 若存在正在写入的线程,或当写入优先时存在等待写入的线程,则将当前线程设置为等待状态 while (writeThreadCounter > 0 || (writable && waitingWriteCounter > 0)) { wait(); } // 使正在读取的线程数加一 readThreadCounter++; } // 读取解锁 public synchronized void readUnlock() { // 使正在读取的线程数减一 readThreadCounter--; // 读取结束,对写入优先 writable = true; // 通知所有处于 wait 状态的线程 notifyAll(); } // 写入加锁 public synchronized void writeLock() throws InterruptedException { // 使等待写入的线程数加一 waitingWriteCounter++; try { // 若存在正在读取的线程,或存在正在写入的线程,则将当前线程设置为等待状态 while (readThreadCounter > 0 || writeThreadCounter > 0) { wait(); } } finally { // 使等待写入的线程数减一 waitingWriteCounter--; } // 使正在写入的线程数加一 writeThreadCounter++; } // 写入解锁 public synchronized void writeUnlock() { // 使正在写入的线程数减一 writeThreadCounter--; // 写入结束,对读取优先 writable = false; // 通知所有处于等待状态的线程 notifyAll(); } } ================================================ FILE: src/test/java/test/concurrent/TestThread.java ================================================ package test.concurrent; import java.util.Date; import java.util.Map; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; public class TestThread { private static final Date ORIGIN_DATE = toDate("1950-01-01 00:00:00", "yyyy-MM-dd HH:mm:ss"); private static final long ORIGIN_DATE_TIME = ORIGIN_DATE.getTime(); private static final Lock LOCK = new ReentrantLock(); private static volatile ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private static volatile Map lastTwoBatch = new ConcurrentHashMap<>(); public static void loop(int num) { Lock readLock = readWriteLock.readLock(); for (int i = 0; i < num; i++) { readLock.lock(); lastTwoBatch.put(randomBirthday(), true); readLock.unlock(); } String lastButOne = null; LOCK.lock(); try { if (lastTwoBatch.size() > 2) { Map temp = lastTwoBatch; lastTwoBatch = new ConcurrentHashMap<>(); Lock writeLock = readWriteLock.writeLock(); readWriteLock = new ReentrantReadWriteLock(); writeLock.lock(); TreeSet set = new TreeSet<>(temp.keySet()); temp.clear(); writeLock.unlock(); String lastOne = set.pollLast(); lastButOne = set.pollLast(); lastTwoBatch.put(lastOne, true); lastTwoBatch.put(lastButOne, true); set.clear(); } } finally { LOCK.unlock(); } } public static Date toDate(String dateStr, String pattern) { return DateTimeFormat.forPattern(pattern).parseDateTime(dateStr).toDate(); } private static String randomBirthday() { long diffSeconds = (new Date().getTime() - ORIGIN_DATE_TIME) / 1000; long date = new DateTime(ORIGIN_DATE_TIME).plusSeconds((int) (Math.random() * diffSeconds)).getMillis(); return new DateTime(date).toString("yyyy-MM-dd HH:mm:ss"); // 2017-04-26 16:59:29 } public static void main(String[] args) throws InterruptedException { long start = System.currentTimeMillis(); int num = 5000; CountDownLatch latch = new CountDownLatch(num); for (int i = 0; i < num; i++) { new Thread(() -> { loop(10); latch.countDown(); }).start(); } latch.await(); System.out.println(lastTwoBatch); System.out.println(System.currentTimeMillis() - start); } } ================================================ FILE: src/test/java/test/concurrent/TheadPoolExecTester.java ================================================ package test.concurrent; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class TheadPoolExecTester { /*private static final ExecutorService EXECUTOR = new ThreadPoolExecutor(2, 10, 300, TimeUnit.SECONDS, new SynchronousQueue(), new ThreadPoolExecutor.CallerRunsPolicy());*/ private static final ExecutorService EXECUTOR = new ThreadPoolExecutor(2, 10, 300, TimeUnit.SECONDS, new LinkedBlockingQueue<>(300), new ThreadPoolExecutor.CallerRunsPolicy()); public static void main(String[] args) { System.out.println("main thread "+Thread.currentThread().getId()); for (int i = 0; i < 200; i++) { EXECUTOR.submit(new Caller()); } EXECUTOR.shutdown(); } private static final class Caller implements Callable { @Override public Boolean call() throws Exception { System.out.println(Thread.currentThread().getId() + "----start"); Thread.sleep(1000+new Random().nextInt(10000)); System.out.println(Thread.currentThread().getId() + "----end"); return true; } } } ================================================ FILE: src/test/java/test/concurrent/TtlTest.java ================================================ package test.concurrent; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import com.alibaba.ttl.TransmittableThreadLocal; import com.alibaba.ttl.TtlRunnable; /** */ public class TtlTest { static ExecutorService executorService = Executors.newFixedThreadPool(1); public static void main(String[] args) { //子线程每次new 所以会复制线程的InheritableThreadLocal,结果正确 //withoutThreadPool(10); //因线程池复用线程,不会每次new 所以不会更新父线程InheritableThreadLocal 的值,导致结果错误 withThreadPool(10); } public static void withoutThreadPool(int c) { for (int i = 0; i < c; i++) { Integer var1 = (int) (Math.random() * 100); Integer var2 = (int) (Math.random() * 100); MyContextHolder.set(var1); threadRun(var1, var2); } } public static void withThreadPool(int c) { for (int i = 0; i < c; i++) { Integer var1 = (int) (Math.random() * 100); Integer var2 = (int) (Math.random() * 100); MyContextHolder.set(var1); threadPoolExecute(var1, var2); } } public static void threadRun(Integer var1, Integer var2) { new Thread(() -> assert1(var1, var2)).start(); } public static void threadPoolExecute(Integer var1, Integer var2) { executorService.execute(TtlRunnable.get(() -> assert1(var1, var2))); // XXX TtlRunnable } public static void assert1(Integer var1, Integer var2) { System.out.println(MyContextHolder.get() * var2 == var1 * var2); } public static class MyContextHolder { //private static ThreadLocal stringThreadLocal = new InheritableThreadLocal<>(); private static ThreadLocal stringThreadLocal = new TransmittableThreadLocal<>(); // XXX TransmittableThreadLocal public static void set(Integer data) { stringThreadLocal.set(data); } public static Integer get() { return stringThreadLocal.get(); } } } ================================================ FILE: src/test/java/test/constraint/TestConstraint.java ================================================ package test.constraint; import java.util.Date; import cn.ponfee.commons.constrain.Constraint; import cn.ponfee.commons.constrain.Constraint.Tense; import cn.ponfee.commons.constrain.FieldValidator; public class TestConstraint { @Constraint(maxLen=0) private String s1; @Constraint(minLen=1) private String s2; @Constraint(tense=Tense.FUTURE) private Date date = new Date(); @Constraint(datePattern="yyyy-MM-dd", tense=Tense.FUTURE) private String d = "2016-05-01"; public static void main(String[] args) { TestConstraint t = new TestConstraint(); t.s1 = ""; t.s2 = "1"; t.date = new Date(System.currentTimeMillis()+5000000); try { FieldValidator.newInstance().constrain(t); } catch (Exception e) { e.printStackTrace(); } } } ================================================ FILE: src/test/java/test/disruptor/InParkingDataEvent.java ================================================ package test.disruptor; public class InParkingDataEvent { private String carLicense; public void setCarLicense(String carLicense) { this.carLicense = carLicense; } public String getCarLicense() { return carLicense; } } ================================================ FILE: src/test/java/test/disruptor/Main.java ================================================ package test.disruptor; import java.util.concurrent.CountDownLatch; import org.apache.commons.lang3.RandomStringUtils; import com.lmax.disruptor.YieldingWaitStrategy; import com.lmax.disruptor.dsl.Disruptor; import com.lmax.disruptor.dsl.EventHandlerGroup; import com.lmax.disruptor.dsl.ProducerType; import cn.ponfee.commons.concurrent.NamedThreadFactory; /** * 测试 P1生产消息,C1,C2消费消息,C1和C2会共享所有的event元素! C3依赖C1,C2处理结果 */ @SuppressWarnings({ "unchecked", "deprecation" }) public class Main { private static final int COUNT = 50; private static final int RING_BUFFER_SIZE = 1; public static void main(String[] args) throws InterruptedException { CountDownLatch latch = new CountDownLatch(COUNT); //构造缓冲区与事件生成 Disruptor disruptor = new Disruptor<>(() -> new InParkingDataEvent(), RING_BUFFER_SIZE, NamedThreadFactory.builder().prefix("disruptor").build(), ProducerType.SINGLE, new YieldingWaitStrategy()); //使用disruptor创建消费者组C1,C2 EventHandlerGroup handlerGroup = disruptor.handleEventsWith(new ParkingDataToKafkaHandler(), new ParkingDataInDbHandler()); //声明在C1,C2完事之后执行SMS消息发送操作 也就是流程走到C3 handlerGroup.then(new ParkingDataSmsHandler(latch)); //启动 disruptor.start(); long beginTime = System.currentTimeMillis(); //生产 for (int i = 0; i < COUNT; i++) { /*RingBuffer ringBuffer = disruptor.getRingBuffer(); long seq = ringBuffer.next(); try { ringBuffer.get(seq).setCarLicense("[粤B·" + RandomStringUtils.randomAlphanumeric(5).toUpperCase() + "]"); } finally { ringBuffer.publish(seq); }*/ disruptor.publishEvent((inParkingEvent, sequence) -> { inParkingEvent.setCarLicense("[粤B·" + RandomStringUtils.randomAlphanumeric(5).toUpperCase() + "]"); System.out.println("["+Thread.currentThread().getId() + ", " + Thread.currentThread().getName() + "] in parking " + inParkingEvent.getCarLicense()); }); } latch.await(); // 等待全部处理结束 System.out.println("已通行" + COUNT + "车辆,总耗时:" + (System.currentTimeMillis() - beginTime)); disruptor.shutdown(); } } ================================================ FILE: src/test/java/test/disruptor/ParkingDataInDbHandler.java ================================================ package test.disruptor; import java.util.concurrent.ThreadLocalRandom; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.WorkHandler; public class ParkingDataInDbHandler implements EventHandler, WorkHandler { @Override public void onEvent(InParkingDataEvent event) throws Exception { long start = System.currentTimeMillis(); Thread.sleep(ThreadLocalRandom.current().nextInt(400)); long threadId = Thread.currentThread().getId(); String threadName = Thread.currentThread().getName(); String carLicense = event.getCarLicense(); System.out.println(String.format("[%s, %s] save %s into db %s", threadId, threadName, carLicense, System.currentTimeMillis()-start)); } @Override public void onEvent(InParkingDataEvent event, long sequence, boolean endOfBatch) throws Exception { // TODO Auto-generated method stub this.onEvent(event); } } ================================================ FILE: src/test/java/test/disruptor/ParkingDataSmsHandler.java ================================================ package test.disruptor; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ThreadLocalRandom; import com.lmax.disruptor.EventHandler; public class ParkingDataSmsHandler implements EventHandler { private final CountDownLatch latch; public ParkingDataSmsHandler(CountDownLatch latch) { this.latch = latch; } @Override public void onEvent(InParkingDataEvent event, long sequence, boolean endOfBatch) throws Exception { long start = System.currentTimeMillis(); Thread.sleep(ThreadLocalRandom.current().nextInt(400) + 100); long threadId = Thread.currentThread().getId(); String threadName = Thread.currentThread().getName(); String carLicense = event.getCarLicense(); System.out.println(String.format("[%s, %s] send %s in plaza sms to user %s", threadId, threadName, carLicense, System.currentTimeMillis()-start)); latch.countDown(); } } ================================================ FILE: src/test/java/test/disruptor/ParkingDataToKafkaHandler.java ================================================ package test.disruptor; import java.util.concurrent.ThreadLocalRandom; import com.lmax.disruptor.EventHandler; public class ParkingDataToKafkaHandler implements EventHandler { @Override public void onEvent(InParkingDataEvent event, long sequence, boolean endOfBatch) throws Exception { long start = System.currentTimeMillis(); Thread.sleep(ThreadLocalRandom.current().nextInt(300)); long threadId = Thread.currentThread().getId(); String threadName = Thread.currentThread().getName(); String carLicense = event.getCarLicense(); System.out.println(String.format("[%s, %s] send %s in plaza messsage to kafka %s", threadId, threadName, carLicense, (System.currentTimeMillis()-start))); } } ================================================ FILE: src/test/java/test/disruptor/Sequence.java ================================================ package test.disruptor; import com.lmax.disruptor.util.Util; /* * Copyright 2012 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import sun.misc.Unsafe; /** *

    Concurrent sequence class used for tracking the progress of * the ring buffer and event processors. Support a number * of concurrent operations including CAS and order writes. * *

    Also attempts to be more efficient with regards to false * sharing by adding padding around the volatile field. */ @SuppressWarnings("restriction") public class Sequence extends RhsPadding { static final long INITIAL_VALUE = -1L; private static final Unsafe UNSAFE; private static final long VALUE_OFFSET; static { UNSAFE = Util.getUnsafe(); try { VALUE_OFFSET = UNSAFE.objectFieldOffset(Value.class.getDeclaredField("value")); } catch (final Exception e) { throw new RuntimeException(e); } } /** * Create a sequence initialised to -1. */ public Sequence() { this(INITIAL_VALUE); } /** * Create a sequence with a specified initial value. * * @param initialValue The initial value for this sequence. */ public Sequence(final long initialValue) { UNSAFE.putOrderedLong(this, VALUE_OFFSET, initialValue); } /** * Perform a volatile read of this sequence's value. * * @return The current value of the sequence. */ public long get() { return value; } /** * Perform an ordered write of this sequence. The intent is * a Store/Store barrier between this write and any previous * store. * * @param value The new value for the sequence. */ public void set(final long value) { UNSAFE.putOrderedLong(this, VALUE_OFFSET, value); } /** * Performs a volatile write of this sequence. The intent is * a Store/Store barrier between this write and any previous * write and a Store/Load barrier between this write and any * subsequent volatile read. * * @param value The new value for the sequence. */ public void setVolatile(final long value) { UNSAFE.putLongVolatile(this, VALUE_OFFSET, value); } /** * Perform a compare and set operation on the sequence. * * @param expectedValue The expected current value. * @param newValue The value to update to. * @return true if the operation succeeds, false otherwise. */ public boolean compareAndSet(final long expectedValue, final long newValue) { return UNSAFE.compareAndSwapLong(this, VALUE_OFFSET, expectedValue, newValue); } /** * Atomically increment the sequence by one. * * @return The value after the increment */ public long incrementAndGet() { return addAndGet(1L); } /** * Atomically add the supplied value. * * @param increment The value to add to the sequence. * @return The value after the increment. */ public long addAndGet(final long increment) { long currentValue; long newValue; do { currentValue = get(); newValue = currentValue + increment; } while (!compareAndSet(currentValue, newValue)); return newValue; } @Override public String toString() { return Long.toString(get()); } } class LhsPadding { protected final long p1 = 0, p2 = 0, p3 = 0, p4 = 0, p5 = 0, p6 = 0, p7 = 0; } class Value extends LhsPadding { protected volatile long value; } class RhsPadding extends Value { protected final long p9 = 0, p10 = 0, p11 = 0, p12 = 0, p13 = 0, p14 = 0, p15 = 0; } ================================================ FILE: src/test/java/test/elasticsearch/CateEsDaoImpl.java ================================================ //package test.elasticsearch; // //public class CateEsDaoImpl extends EsDbUtils implements CateEsDao { // @Override // public List queryListByFilterQuery(EsQueryObj esQueryObj) throws Exception { // return (List)queryObjectListByFilterQuery(esQueryObj,CateVenderData.class); // } // // @Override // public List queryListByFilterQueryWithAgg(EsQueryObj esQueryObj) throws Exception { // return (List)queryObjectListByFilterQueryWithAgg(esQueryObj,CateVenderData.class); // } //} // // //public class RealTimeEsDaoImpl extends EsDbUtils implements RealTimeEsDao { // @Override // public List queryListByFilterQuery(EsQueryObj esQueryObj) throws Exception { // return (List)queryObjectListByFilterQuery(esQueryObj,OdpOperatorSum.class); // } // // @Override // public List queryListByFilterQueryWithAgg(EsQueryObj esQueryObj) throws Exception { // return (List)queryObjectListByFilterQueryWithAgg(esQueryObj,OdpOperatorSum.class); // } //} ================================================ FILE: src/test/java/test/elasticsearch/EsClientFactory.java ================================================ //package test.elasticsearch; // //import java.net.InetAddress; //import java.net.UnknownHostException; // //import org.apache.commons.logging.Log; //import org.apache.commons.logging.LogFactory; //import org.elasticsearch.client.transport.TransportClient; //import org.elasticsearch.common.settings.Settings; //import org.elasticsearch.common.transport.InetSocketTransportAddress; //import org.elasticsearch.transport.client.PreBuiltTransportClient; // //import com.google.common.base.Preconditions; // //public class EsClientFactory { // private static final Log logger = LogFactory.getLog(EsClientFactory.class); // // // 是否扫描集群 // private static boolean sniff = false; // // ES 集群名称 // private static String clusterName; // // IP地址 // private static String[] ips; // // 端口 // private static int esPort; // // private TransportClient esClient;//ES 客户端对象 // // public EsClientFactory(String clusterName,String[] ips,int esPort) { // this.clusterName=clusterName; // this.ips=ips; // this.esPort=esPort; // init(); // } // // /** // * ES 客户端连接初始化 // * // * @return ES客户端对象 // */ // private void init() { // Preconditions.checkNotNull(clusterName, "es 服务clusterName未配置"); // Preconditions.checkNotNull(ips, "es 服务ip未配置"); // Preconditions.checkArgument(esPort > 0, "es 服务服务port未配置"); // //设置集群的名字 // Settings settings = Settings.builder() // .put("cluster.name", clusterName) // .put("client.transport.sniff", sniff) // .build(); // //创建集群client并添加集群节点地址 // esClient = new PreBuiltTransportClient(settings); // for (String ip : ips) { // try { // esClient.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(ip), esPort)); // } catch (UnknownHostException e) { // } // } // } // // public TransportClient getEsClient() { // return esClient; // } // // public boolean isSniff() { // return sniff; // } // // public String getClusterName() { // return clusterName; // } // // // public String[] getIps() { // return ips; // } // // // public int getEsPort() { // return esPort; // } // //} ================================================ FILE: src/test/java/test/elasticsearch/EsDbUtils.java ================================================ //package test.elasticsearch; // //import java.lang.reflect.Field; //import java.math.BigDecimal; //import java.util.ArrayList; //import java.util.Date; //import java.util.HashMap; //import java.util.List; //import java.util.Map; // //import org.apache.commons.lang3.StringUtils; //import org.apache.poi.ss.usermodel.DateUtil; //import org.elasticsearch.action.search.SearchRequestBuilder; //import org.elasticsearch.action.search.SearchResponse; //import org.elasticsearch.action.search.SearchType; //import org.elasticsearch.index.query.QueryBuilders; //import org.elasticsearch.search.SearchHit; //import org.elasticsearch.search.aggregations.Aggregation; //import org.elasticsearch.search.aggregations.AggregationBuilders; //import org.elasticsearch.search.aggregations.bucket.terms.Terms; //import org.elasticsearch.search.aggregations.bucket.terms.TermsBuilder; //import org.elasticsearch.search.aggregations.metrics.avg.Avg; //import org.elasticsearch.search.aggregations.metrics.sum.Sum; //import org.elasticsearch.search.sort.SortOrder; //import org.slf4j.Logger; //import org.slf4j.LoggerFactory; // //import com.fasterxml.jackson.dataformat.yaml.snakeyaml.introspector.PropertyUtils; //import com.google.gson.Gson; // //public class EsDbUtils { // private static final Logger logger = LoggerFactory.getLogger(EsDbUtils.class); // private static final int FromIndex = 0; // private static final int MinSize = 100; // private static final int MaxSize = 100000; // private static final int GroupMinSize = 100; // private static final int GroupMaxSize = 500; // private EsClientFactory esClientFactory;//ES 客户端工厂类 // // /** // * 根据过滤条件查询出数据列表.需要传递索引和表名 // * @param esQueryObj ES查询对象 // * @param targetClass ES结果需要转换的类型 // * @return // */ // public List queryObjectListByFilterQuery(EsQueryObj esQueryObj,Class targetClass) throws Exception { // validationEsQuery(esQueryObj); // List esRecords = new ArrayList(); // long startCountTime = System.currentTimeMillis(); // //创建ES查询Request对象 // SearchRequestBuilder esSearch= esClientFactory.getEsClient().prepareSearch(esQueryObj.getIndexName()); // esSearch.setTypes(esQueryObj.getTypeName()) // .setSearchType(SearchType.QUERY_THEN_FETCH) // .setFrom((esQueryObj.getFromIndex() > 0) ? esQueryObj.getFromIndex() : FromIndex) // .setSize((0 entry : esQueryObj.getSortField().entrySet()) { // esSearch.addSort(entry.getKey(), entry.getValue()); // } // } // //执行查询 // SearchResponse response =esSearch .execute().actionGet(); // for (SearchHit hit : response.getHits()) { // Object t = mapResult(hit.sourceAsMap(), targetClass); // esRecords.add(t); // } // logger.info("queryObjectListByFilterQuery search " + response.getHits().getTotalHits() + " data ,esQueryObj=" + new Gson().toJson(esQueryObj)+"-----------------------------------------! use " + (System.currentTimeMillis() - startCountTime) + " ms."); // return esRecords; // } // // /** // * 根据过滤条件和分组键SUM/AVG键获取分组结果(目前分组结果不支持LIMIT操作) // * @param esQueryObj ES查询对象 // * @param targetClass ES结果需要转换的类型 // * @return // * @throws Exception // */ // public List queryObjectListByFilterQueryWithAgg(EsQueryObj esQueryObj,Class targetClass) throws Exception { // validationEsGroupQuery(esQueryObj); // List esRecords = new ArrayList(); // long startCountTime = System.currentTimeMillis(); // if( esQueryObj.getSumFields()==null){ // esQueryObj.setSumFields(new ArrayList()); // } // if(esQueryObj.getAvgFields()==null){ // esQueryObj.setAvgFields(new ArrayList()); // } // TermsBuilder agg = getEsAgg(esQueryObj); // //创建ES查询Request对象 // SearchRequestBuilder esSearch= esClientFactory.getEsClient().prepareSearch(esQueryObj.getIndexName()); // esSearch.setTypes(esQueryObj.getTypeName()) // .setSearchType(SearchType.QUERY_THEN_FETCH) // .addAggregation(agg); // //添加查询条件 // if(esQueryObj.getAndFilterBuilder()!=null){ // esSearch.setQuery(QueryBuilders.filteredQuery(QueryBuilders.matchAllQuery(), esQueryObj.getAndFilterBuilder())); // } // //添加多级排序 // if(esQueryObj.getSortField()!=null) { // for (Map.Entry entry : esQueryObj.getSortField().entrySet()) { // esSearch.addSort(entry.getKey(), entry.getValue()); // } // } // //执行查询 // SearchResponse response =esSearch .execute().actionGet(); // List> aggMaps= getAggMap(response,esQueryObj.getGroupFields(), esQueryObj.getSumFields(), esQueryObj.getAvgFields()); // for(Map aggMap : aggMaps){ // Object t = mapResult(aggMap, targetClass); // esRecords.add(t); // } // logger.info("queryObjectListByFilterQuery search " + response.getHits().getTotalHits() + " data,esQueryObj=" + new Gson().toJson(esQueryObj)+"-----------------------------------------! use " + (System.currentTimeMillis() - startCountTime) + " ms."); // return esRecords; // } // /** // * 根据分组键和SUM/AVG键组合AGG条件 // * @param esQueryObj // * @return // */ // private TermsBuilder getEsAgg(EsQueryObj esQueryObj) throws Exception{ // List groupFields= esQueryObj.getGroupFields(); // List sumFields= esQueryObj.getSumFields(); // List avgFields= esQueryObj.getAvgFields(); // int groupSize=esQueryObj.getGroupSize(); // Map groupSortMap=esQueryObj.getGroupSortField(); // TermsBuilder termsBuilder = AggregationBuilders.terms(groupFields.get(0)).field(groupFields.get(0)); // if (groupFields.size() == 1) { // //设置排序后最后一层的结果数目 // termsBuilder.size((0 termsOrders=new ArrayList(); // for (Map.Entry entry : groupSortMap.entrySet()) { // if(entry.getValue().equals(SortOrder.ASC)){ // termsOrders.add(Terms.Order.aggregation(entry.getKey(),true)); // }else{ // termsOrders.add(Terms.Order.aggregation(entry.getKey(), false)); // } // } // termsBuilder.order(Terms.Order.compound(termsOrders)); // } // for (String avgField : avgFields) { // termsBuilder.subAggregation(AggregationBuilders.avg(avgField).field(avgField)); // } // for (String sumField : sumFields) { // termsBuilder.subAggregation(AggregationBuilders.sum(sumField).field(sumField)); // } // } else { // termsBuilder.subAggregation(getChildTermsBuilder(groupFields, 1, sumFields, avgFields,groupSize,groupSortMap)); // //设置最外层分组量 // termsBuilder.size(GroupMaxSize); // } // return termsBuilder; // } // // /** // * 通过递归的方式获取bucket agg分组语句 // * @param groupFields // * @param i // * @param sumFields // * @param avgFields // * @return // */ // private TermsBuilder getChildTermsBuilder(List groupFields,int i,List sumFields, List avgFields,int groupSize,Map groupSortMap){ // if(i+1==groupFields.size()){ // TermsBuilder termsBuilderLast = AggregationBuilders.terms(groupFields.get(i)).field(groupFields.get(i)); // //设置排序后最后一层的结果数目 // termsBuilderLast.size((0 entry : groupSortMap.entrySet()) { // if(entry.getValue().equals(SortOrder.ASC)){ // termsBuilderLast.order(Terms.Order.aggregation(entry.getKey(),true)); // }else{ // termsBuilderLast.order(Terms.Order.aggregation(entry.getKey(),false)); // } // // } // } // for (String avgField : avgFields) { // termsBuilderLast.subAggregation(AggregationBuilders.avg(avgField).field(avgField)); // } // for (String sumField : sumFields) { // termsBuilderLast.subAggregation(AggregationBuilders.sum(sumField).field(sumField)); // } // return termsBuilderLast; // } // else{ // TermsBuilder termsBuilder= AggregationBuilders.terms(groupFields.get(i)).field(groupFields.get(i)); // //设置最外层分组量 // termsBuilder.size(GroupMaxSize); // return termsBuilder.subAggregation(getChildTermsBuilder(groupFields,i+1,sumFields,avgFields,groupSize,groupSortMap)); // } // } // // /** // * 根据汇总键和SUM/AVG键,组合返回的查询值为MAP格式 // * @param response // * @param groupFields // * @param sumFields // * @param avgFields // * @return // */ // private List> getAggMap(SearchResponse response,List groupFields,List sumFields, List avgFields){ // // List> aggMaps = new ArrayList>(); // //首先获取最外层的AGG结果 // Terms tempAggregation = response.getAggregations().get(groupFields.get(0)); // //只有一个分组键不用进行递归 // if(groupFields.size()==1){ // for(Terms.Bucket tempBk:tempAggregation.getBuckets()){ // Map tempMap = new HashMap(); // tempMap.put(tempAggregation.getName(), tempBk.getKey()); // for (Map.Entry entry : tempBk.getAggregations().getAsMap().entrySet()) { // String key = entry.getKey(); // if (sumFields.contains(key)) { // Sum aggSum = (Sum) entry.getValue(); // double value = aggSum.getValue(); // tempMap.put(key, value); // } // if (avgFields.contains(key)) { // Avg aggAvg = (Avg) entry.getValue(); // double value = aggAvg.getValue(); // tempMap.put(key, value); // } // } // aggMaps.add(tempMap); // } // } // else { // for (Terms.Bucket bk : tempAggregation.getBuckets()) { // //每个最外层的分组键生成一个键值对MAP // Map nkMap = new HashMap(); // nkMap.put(tempAggregation.getName(), bk.getKey()); // //通过递归的方式填充键值对MAP并加到最终的列表中 // getChildAggMap(bk, 1, groupFields, sumFields, avgFields, nkMap, aggMaps); // } // } // return aggMaps; // } // // /** // * 深层递归所有的AGG返回值,组合成最终的MAP列表 // * @param bk 每次递归的单个Bucket // * @param i 控制分组键列表到了哪一层 // * @param groupFields 分组键列表 // * @param sumFields SUM键列表 // * @param avgFields AVG键列表 // * @param nkMap 键值对MAP // * @param aggMaps 最终结果的中间值 // * @return // */ // private List> getChildAggMap(Terms.Bucket bk,int i,List groupFields,List sumFields, List avgFields,Map nkMap,List> aggMaps){ // // if(i==groupFields.size()-1){ // Terms tempAggregation = bk.getAggregations().get(groupFields.get(i)); // for(Terms.Bucket tempBk:tempAggregation.getBuckets()){ // Map tempMap = new HashMap(); // tempMap.putAll(nkMap); // tempMap.put(tempAggregation.getName(), tempBk.getKey()); // for (Map.Entry entry : tempBk.getAggregations().getAsMap().entrySet()) { // String key = entry.getKey(); // if (sumFields.contains(key)) { // Sum aggSum = (Sum) entry.getValue(); // double value = aggSum.getValue(); // tempMap.put(key, value); // } // if (avgFields.contains(key)) { // Avg aggAvg = (Avg) entry.getValue(); // double value = aggAvg.getValue(); // tempMap.put(key, value); // } // } // aggMaps.add(tempMap); // } // return aggMaps; // } else{ // Terms tempAggregation = bk.getAggregations().get(groupFields.get(i)); // for(Terms.Bucket tempBk:tempAggregation.getBuckets()){ // nkMap.put(tempAggregation.getName(), tempBk.getKey()); // getChildAggMap(tempBk, i + 1, groupFields, sumFields, avgFields, nkMap, aggMaps); // } // return aggMaps; // } // } // // /** // * 将ES结果映射到指定对象 // * @param resultMap // * @param cls // * @return // * @throws Exception // */ // public T mapResult(Map resultMap, Class cls) throws Exception{ // T result = cls.newInstance(); // Field[] fields = cls.getDeclaredFields(); // for (Field field : fields) { // Object object = resultMap.get(field.getName()); // if (object != null) { // //根据几种基本类型做转换 // if (field.getType().equals(Long.class) || field.getType().equals(long.class)) { // if(object.toString().indexOf('.')>0){ // PropertyUtils.setProperty(result, field.getName(), Long.parseLong(object.toString().substring(0, object.toString().indexOf('.')))); // }else{ // PropertyUtils.setProperty(result, field.getName(), Long.parseLong(object.toString())); // } // }else if (field.getType().equals(long.class) || field.getType().equals(long.class)) { // if(object.toString().indexOf('.')>0){ // PropertyUtils.setProperty(result, field.getName(), Long.parseLong(object.toString().substring(0, object.toString().indexOf('.')))); // }else{ // PropertyUtils.setProperty(result, field.getName(), Long.parseLong(object.toString())); // } // }else if (field.getType().equals(Integer.class) || field.getType().equals(Integer.class)) { // if(object.toString().indexOf('.')>0){ // PropertyUtils.setProperty(result, field.getName(), Integer.parseInt(object.toString().substring(0, object.toString().indexOf('.')))); // }else{ // PropertyUtils.setProperty(result, field.getName(), Integer.parseInt(object.toString())); // } // }else if (field.getType().equals(int.class) || field.getType().equals(int.class)) { // if(object.toString().indexOf('.')>0){ // PropertyUtils.setProperty(result, field.getName(), Integer.parseInt(object.toString().substring(0, object.toString().indexOf('.')))); // }else{ // PropertyUtils.setProperty(result, field.getName(), Integer.parseInt(object.toString())); // } // // }else if (field.getType().equals(BigDecimal.class) || field.getType().equals(BigDecimal.class)) { // PropertyUtils.setProperty(result, field.getName(), BigDecimal.valueOf(Double.parseDouble(object.toString()))); // }else if (field.getType().equals(Double.class) || field.getType().equals(Double.class)) { // PropertyUtils.setProperty(result, field.getName(), Double.parseDouble(object.toString())); // }else if (field.getType().equals(double.class) || field.getType().equals(double.class)) { // PropertyUtils.setProperty(result, field.getName(), Double.parseDouble(object.toString())); // }else if (field.getType().equals(Date.class) || field.getType().equals(Date.class)) { // PropertyUtils.setProperty(result, field.getName(), DateUtil.createDate(object.toString())); // }else if (field.getType().equals(String.class) || field.getType().equals(String.class)) { // PropertyUtils.setProperty(result, field.getName(), object); // } // } // } // return result; // } // // /** // * 验证ES查询对象 // * @param esQueryObj // * @throws Exception // */ // private void validationEsQuery(EsQueryObj esQueryObj) throws Exception{ // if(StringUtils.isEmpty(esQueryObj.getIndexName())&&StringUtils.isEmpty(esQueryObj.getTypeName())){ // throw new Exception("please check indexName and typeName"); // } // } // /** // * 验证ES查询分组对象 // * @param esQueryObj // * @throws Exception // */ // private void validationEsGroupQuery(EsQueryObj esQueryObj) throws Exception{ // if(StringUtils.isEmpty(esQueryObj.getIndexName())&&StringUtils.isEmpty(esQueryObj.getTypeName())){ // throw new Exception("please check indexName and typeName"); // } // boolean groupOrderStatus=true; // st:for (Map.Entry entry : esQueryObj.getGroupSortField().entrySet()) { // if(!esQueryObj.getSumFields().contains(entry.getKey())&&!esQueryObj.getAvgFields().contains(entry.getKey())){ // groupOrderStatus=false; // break st; // } // } // if(!groupOrderStatus){ // throw new Exception("please check groupSortField"); // } // if (esQueryObj.getGroupFields().isEmpty() || esQueryObj.getGroupFields().size() <= 0 ||(esQueryObj.getSumFields().isEmpty()&&esQueryObj.getAvgFields().isEmpty())) { // throw new Exception("please check groupFields and sumFields and avgFields"); // } // } // public EsClientFactory getEsClientFactory() { // return esClientFactory; // } // // public void setEsClientFactory(EsClientFactory esClientFactory) { // this.esClientFactory = esClientFactory; // } //} ================================================ FILE: src/test/java/test/elasticsearch/EsQueryObj.java ================================================ //package test.elasticsearch; // //import java.util.List; //import java.util.Map; // //import org.elasticsearch.index.query.AndFilterBuilder; //import org.elasticsearch.index.query.BoolQueryBuilder; //import org.elasticsearch.search.sort.SortOrder; // //public class EsQueryObj { // /** // * ES索引名 // */ // String indexName; // /** // * ES TYPE名(表名) // */ // String typeName; // /** // * 查询条件组合,类似于 "boolQuery().must( QueryBuilders.termQuery("opTime", "2016-03-30")).must(QueryBuilders.termQuery("operErpID", "xingkai"))" // */ // BoolQueryBuilder bq; // /** // * 查询条件组合,类似于 "boolQuery().must( QueryBuilders.termQuery("opTime", "2016-03-30")).must(QueryBuilders.termQuery("operErpID", "xingkai"))" // */ // AndFilterBuilder andFilterBuilder; // /** // * 分组键值列表,类似于group by 之后的字段 // */ // List groupFields; // /** // * 分组SUM键值列表 // */ // List sumFields; // /** // * 分组AVG键值列表 // */ // List avgFields; // /** // * 排序字段键值对,可以添加多个,类似于("opTime","DESC") // */ // Map sortField; // /** // * 分组后排序字段键值对,可以添加多个,类似于("opTime","DESC"),注意此处最后PUT的键最先排序,排序键必须在sumFields或avgFields(只针对最后一层分组) // */ // Map groupSortField; // /** // * 分组后返回数据数量,默认为100,最大不能超过500(只针对最后一层分组) // */ // int groupSize; // /** // * 取值的起始位置,默认为0 // */ // int fromIndex; // /** // * 返回数据数量,默认为100,最大不能超过100000 // */ // int size; // // public String getIndexName() { // return indexName; // } // // public void setIndexName(String indexName) { // this.indexName = indexName; // } // // public String getTypeName() { // return typeName; // } // // public void setTypeName(String typeName) { // this.typeName = typeName; // } // // public BoolQueryBuilder getBq() { // return bq; // } // // public void setBq(BoolQueryBuilder bq) { // this.bq = bq; // } // // public AndFilterBuilder getAndFilterBuilder() { // return andFilterBuilder; // } // // public void setAndFilterBuilder(AndFilterBuilder andFilterBuilder) { // this.andFilterBuilder = andFilterBuilder; // } // // public List getGroupFields() { // return groupFields; // } // // public void setGroupFields(List groupFields) { // this.groupFields = groupFields; // } // // public List getSumFields() { // return sumFields; // } // // public void setSumFields(List sumFields) { // this.sumFields = sumFields; // } // // public List getAvgFields() { // return avgFields; // } // // public void setAvgFields(List avgFields) { // this.avgFields = avgFields; // } // // public Map getSortField() { // return sortField; // } // // public void setSortField(Map sortField) { // this.sortField = sortField; // } // // public int getFromIndex() { // return fromIndex; // } // // public void setFromIndex(int fromIndex) { // this.fromIndex = fromIndex; // } // // public int getSize() { // return size; // } // // public void setSize(int size) { // this.size = size; // } // // public Map getGroupSortField() { // return groupSortField; // } // // public void setGroupSortField(Map groupSortField) { // this.groupSortField = groupSortField; // } // // public int getGroupSize() { // return groupSize; // } // // public void setGroupSize(int groupSize) { // this.groupSize = groupSize; // } //} ================================================ FILE: src/test/java/test/export/ConsoleExportTest.java ================================================ package test.export; import cn.ponfee.commons.export.AbstractDataExporter; import cn.ponfee.commons.export.ConsoleExporter; import cn.ponfee.commons.export.Table; import org.junit.Test; import java.util.Arrays; import java.util.List; import java.util.UUID; public class ConsoleExportTest { private int multiple = 20; @Test public void test() { //AbstractDataExporter export = new ConsoleExporter(System.out, 30, true); AbstractDataExporter export = new ConsoleExporter(System.out, 36, false); Table table1 = new Table(new String[]{"name1", "age", "gender", "birthday", "x"}); List data1 = Arrays.asList( new Object[]{"alalicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealiceice", 20, "male", "2000-05-30", 0}, new Object[]{"bob", 20, "female", "2001-05-30", 1}, new Object[]{UUID.randomUUID().toString(), 25, "male", "2000-04-30", 2} ); table1.setCaption("test"); table1.addRowsAndEnd(data1); export.setName("报表1").build(table1); } } ================================================ FILE: src/test/java/test/export/ExportTester.java ================================================ package test.export; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import java.util.Map; import cn.ponfee.commons.tree.PlainNode; import cn.ponfee.commons.util.MavenProjects; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.RandomStringUtils; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.junit.Test; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import cn.ponfee.commons.export.AbstractDataExporter; import cn.ponfee.commons.export.CellStyleOptions; import cn.ponfee.commons.export.CsvStringExporter; import cn.ponfee.commons.export.ExcelExporter; import cn.ponfee.commons.export.HtmlExporter; import cn.ponfee.commons.export.Table; import cn.ponfee.commons.export.Thead; import cn.ponfee.commons.io.ByteOrderMarks; import cn.ponfee.commons.json.Jsons; import cn.ponfee.commons.model.Result; import cn.ponfee.commons.model.ResultCode; import cn.ponfee.commons.tree.BaseNode; import cn.ponfee.commons.util.Captchas; public class ExportTester { public static final String baseDir = MavenProjects.getProjectBaseDir()+"/test/"; static { try { FileUtils.forceMkdir(new File(baseDir)); } catch (IOException e) { e.printStackTrace(); } } private int multiple = 20; public @Test void testHtml1() throws IOException { AbstractDataExporter html = new HtmlExporter(); AbstractDataExporter csv = new CsvStringExporter(); List> list = new ArrayList<>(); list.add(new PlainNode<>(1, 0, new Thead("区域"))); list.add(new PlainNode<>(2, 0, new Thead("分公司"))); list.add(new PlainNode<>(3, 0, new Thead("昨天"))); list.add(new PlainNode<>(4, 3, new Thead("项目数"))); list.add(new PlainNode<>(5, 3, new Thead("项目应收(元)"))); list.add(new PlainNode<>(6, 3, new Thead("成交套数"))); list.add(new PlainNode<>(7, 3, new Thead("套均收入(元)"))); list.add(new PlainNode<>(8, 3, new Thead("团购项目数"))); list.add(new PlainNode<>(9, 3, new Thead("导客项目数"))); list.add(new PlainNode<>(10, 3, new Thead("代收项目数"))); list.add(new PlainNode<>(11, 3, new Thead("线上项目数"))); list.add(new PlainNode<>(12, 0, new Thead("本月"))); list.add(new PlainNode<>(13, 12,new Thead("应收(万)"))); list.add(new PlainNode<>(14, 12,new Thead("实收(万)"))); list.add(new PlainNode<>(15, 12,new Thead("成交套数"))); list.add(new PlainNode<>(16, 12,new Thead("套均收入(元)"))); list.add(new PlainNode<>(17, 12,new Thead("团购项目应收(万)"))); list.add(new PlainNode<>(18, 12,new Thead("团购项目成交套数"))); list.add(new PlainNode<>(19, 12,new Thead("团购项目经服成交套数"))); list.add(new PlainNode<>(20, 12,new Thead("团购项目套均收入(元)"))); list.add(new PlainNode<>(21, 12,new Thead("团购项目经服成交应收(万)"))); list.add(new PlainNode<>(22, 12,new Thead("团购项目中介应付外佣(万)"))); list.add(new PlainNode<>(23, 12,new Thead("团购项目经服成交套数占比"))); list.add(new PlainNode<>(24, 12,new Thead("团购项目中介分佣比例"))); list.add(new PlainNode<>(25, 12,new Thead("导客项目应收(万)"))); list.add(new PlainNode<>(26, 12,new Thead("导客项目成交套数"))); list.add(new PlainNode<>(27, 12,new Thead("导客项目套均收入(元)"))); list.add(new PlainNode<>(28, 12,new Thead("导客项目中介应付外佣(万)"))); list.add(new PlainNode<>(29, 12,new Thead("导客项目中介分佣比例"))); list.add(new PlainNode<>(30, 12,new Thead("代收项目应收(万)"))); list.add(new PlainNode<>(31, 12,new Thead("代收项目成交套数"))); list.add(new PlainNode<>(32, 12,new Thead("线上项目应收(万)"))); list.add(new PlainNode<>(33, 12,new Thead("线上项目成交套数"))); list.add(new PlainNode<>(34, 12,new Thead("月指标(万)"))); list.add(new PlainNode<>(35, 12,new Thead("指标完成率"))); Table table = new Table(list); System.out.println(Jsons.toJson(table.getThead())); table.setCaption("abc"); table.addRowsAndEnd(Lists.newArrayList(new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} )); table.setTfoot(new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}); table.setComment("comment1;comment2;comment3;comment4;comment5;comment6;"); html.build(table); csv.build(table); table = new Table(list); table.setCaption("123"); table.toEnd(); html.build(table); table = new Table(list); table.toEnd(); table.setCaption("bnm"); html.build(table); IOUtils.write((String) html.setName("报表").export(), new FileOutputStream(baseDir+"testHtml1.html"), "UTF-8"); ByteOrderMarks.add(baseDir+"testHtml1.html"); IOUtils.write((String) csv.export(), new FileOutputStream(baseDir+"testHtml1.csv"), "UTF-8"); ByteOrderMarks.add(baseDir+"testHtml1.csv"); html.close(); csv.close(); } @Test public void testHtml2() throws FileNotFoundException, IOException { AbstractDataExporter html = new HtmlExporter(); html.build(new Table("a,b,c,d,e".split(",")).toEnd()); IOUtils.write((String) html.export(), new FileOutputStream(baseDir+"testHtml2.html"), "UTF-8"); ByteOrderMarks.add(baseDir+"testHtml2.html"); html.close(); } @Test public void testExcel() throws IOException { ExcelExporter excel = new ExcelExporter(); List> list = new ArrayList<>(); list.add(new PlainNode<>(1, 0, new Thead("区域"))); list.add(new PlainNode<>(2, 0, new Thead("分公司"))); list.add(new PlainNode<>(3, 0, new Thead("昨天"))); list.add(new PlainNode<>(4, 3, new Thead("项目数"))); list.add(new PlainNode<>(5, 3, new Thead("项目应收(元)"))); list.add(new PlainNode<>(6, 3, new Thead("成交套数"))); list.add(new PlainNode<>(7, 3, new Thead("套均收入(元)"))); list.add(new PlainNode<>(8, 3, new Thead("团购项目数"))); list.add(new PlainNode<>(9, 3, new Thead("导客项目数"))); list.add(new PlainNode<>(10, 3, new Thead("代收项目数"))); list.add(new PlainNode<>(11, 3, new Thead("线上项目数"))); list.add(new PlainNode<>(12, 0, new Thead("本月"))); list.add(new PlainNode<>(13, 12, new Thead("应收(万)"))); list.add(new PlainNode<>(14, 12, new Thead("实收(万)"))); list.add(new PlainNode<>(15, 12, new Thead("成交套数"))); list.add(new PlainNode<>(16, 12, new Thead("套均收入(元)"))); list.add(new PlainNode<>(17, 12, new Thead("团购项目应收(万)"))); list.add(new PlainNode<>(18, 12, new Thead("团购项目成交套数"))); list.add(new PlainNode<>(19, 12, new Thead("团购项目经服成交套数"))); list.add(new PlainNode<>(20, 12, new Thead("团购项目套均收入(元)"))); list.add(new PlainNode<>(21, 12, new Thead("团购项目经服成交应收(万)"))); list.add(new PlainNode<>(22, 12, new Thead("团购项目中介应付外佣(万)"))); list.add(new PlainNode<>(23, 12, new Thead("团购项目经服成交套数占比"))); list.add(new PlainNode<>(24, 12, new Thead("团购项目中介分佣比例"))); list.add(new PlainNode<>(25, 12, new Thead("导客项目应收(万)"))); list.add(new PlainNode<>(26, 12, new Thead("导客项目成交套数"))); list.add(new PlainNode<>(27, 12, new Thead("导客项目套均收入(元)"))); list.add(new PlainNode<>(28, 12, new Thead("导客项目中介应付外佣(万)"))); list.add(new PlainNode<>(29, 12, new Thead("导客项目中介分佣比例"))); list.add(new PlainNode<>(30, 12, new Thead("代收项目应收(万)"))); list.add(new PlainNode<>(31, 12, new Thead("代收项目成交套数"))); list.add(new PlainNode<>(32, 12, new Thead("线上项目应收(万)"))); list.add(new PlainNode<>(33, 12, new Thead("线上项目成交套数"))); list.add(new PlainNode<>(34, 12, new Thead("月指标(万)"))); list.add(new PlainNode<>(35, 12, new Thead("指标完成率"))); List data1 = new ArrayList<>(); for (int i = 0; i < 2*multiple; i++) { data1.add(new Object[] { "1234563.918%", "2017-02-03", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "1" }); } List data2 = new ArrayList<>(); for (int i = 0; i < 5*multiple; i++) { data2.add(new Object[] { "1234563.918%", "2017-02-03", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "1" }); } List data3 = new ArrayList<>(); for (int i = 0; i < 3*multiple; i++) { data3.add(new Object[] { "1234563.918%", "2017-02-03", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "1" }); } Object[] tfoot = new Object[] {"1", "2", "3", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd","abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "abd", "1" }; Map options = ImmutableMap.of(CellStyleOptions.HIGHLIGHT, ImmutableMap.of("cells", Lists.newArrayList(Lists.newArrayList(1,1),Lists.newArrayList(2,2)), "color", "#FF3030")); long start = System.currentTimeMillis(); System.out.println("========================================start"); Table table1 = new Table<>(list); table1.setCaption("test1"); table1.addRowsAndEnd(data1); table1.setTfoot(tfoot); table1.setOptions(options); excel.setName("报表1").build(table1); // ------------------------------------------ Table table2 = new Table(list); table2.setCaption("test2"); table2.addRowsAndEnd(data2); table2.setTfoot(tfoot); table2.setOptions(options); excel.setName("报表2").build(table2); // ------------------------------------------ Table table3 = new Table(list); table3.setCaption("test3"); table3.addRowsAndEnd(data3); table3.setTfoot(tfoot); table3.setOptions(options); excel.setName("报表1").build(table3); // ------------------------------------------ excel.setName("图表"); ByteArrayOutputStream baos = new ByteArrayOutputStream(); Captchas.generate(200, baos, RandomStringUtils.randomAlphanumeric(10)); excel.insertImage(baos.toByteArray()); baos = new ByteArrayOutputStream(); Captchas.generate(200, baos, RandomStringUtils.randomAlphanumeric(10)); excel.insertImage(baos.toByteArray()); OutputStream out = new FileOutputStream(baseDir+"abc1.xlsx"); excel.write(out); out.close(); excel.close(); System.out.println("========================================excel: " + (System.currentTimeMillis() - start)); start = System.currentTimeMillis(); // -------------------------csv CsvStringExporter csv = new CsvStringExporter(); table1 = new Table<>(list); table1.setCaption("test1"); table1.addRowsAndEnd(data1); table1.setTfoot(tfoot); table1.setOptions(options); csv.build(table1); IOUtils.write(csv.export().toString(), new FileOutputStream(baseDir+"testExcel.csv"), "UTF-8"); ByteOrderMarks.add(new File(baseDir+"testExcel.csv")); csv.close(); System.out.println("========================================csv: " + (System.currentTimeMillis() - start)); start = System.currentTimeMillis(); HtmlExporter html = new HtmlExporter(); table1 = new Table(list); table1.setCaption("test1"); table1.addRowsAndEnd(data1); table1.setTfoot(tfoot); table1.setOptions(options); html.build(table1); html.setName("test"); IOUtils.write((String) html.export(), new FileOutputStream(baseDir+"testExcel.html"), "UTF-8"); ByteOrderMarks.add(baseDir+"testExcel.html"); html.close(); System.out.println("========================================html: " + (System.currentTimeMillis() - start)); } @Test public void testExcel2() throws IOException { AbstractDataExporter excel = new ExcelExporter(); Table table = new Table("a,b,c,d,e".split(",")); table.setCaption("title"); List data = new ArrayList<>(); data.add(new Object[] { "11111111111111111111111111111111111111111", "2", "3", "4", "5" }); table.addRowsAndEnd(data); excel.setName("21321"); excel.build(table); IOUtils.write((byte[]) excel.export(), new FileOutputStream(baseDir+"testExcel2.xlsx")); excel.close(); } @Test public void testExcel5() throws IOException { AbstractDataExporter excel = new ExcelExporter(); Table> table = new Table<>( "a,b".split(","), o -> new Object[] { o.getCode(), o.getMsg() } ); table.setCaption("title"); table.addRow(Result.success()); table.addRow(Result.failure(ResultCode.BAD_REQUEST)); table.toEnd(); excel.setName("21321"); excel.build(table); IOUtils.write((byte[]) excel.export(), new FileOutputStream(baseDir+"11111xxxx.xlsx")); excel.close(); } @Test public void testExcel3() throws IOException { XSSFWorkbook wb = new XSSFWorkbook(); FileOutputStream out = new FileOutputStream(baseDir+"test_empty.xlsx"); wb.createSheet(); wb.write(out); wb.close(); out.close(); } } ================================================ FILE: src/test/java/test/export/ExportTester2.java ================================================ package test.export; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.io.IOUtils; import org.junit.Test; import com.google.common.base.Stopwatch; import cn.ponfee.commons.concurrent.ThreadPoolExecutors; import cn.ponfee.commons.export.AbstractDataExporter; import cn.ponfee.commons.export.CsvFileExporter; import cn.ponfee.commons.export.ExcelExporter; import cn.ponfee.commons.export.SplitCsvFileExporter; import cn.ponfee.commons.export.SplitExcelExporter; import cn.ponfee.commons.export.Table; public class ExportTester2 { static final ExecutorService EXECUTOR = ThreadPoolExecutors.builder() .corePoolSize(4) .maximumPoolSize(16) .workQueue(new SynchronousQueue<>()) .keepAliveTimeSeconds(120) .build(); @Test // 19.5 public void testExcel1() throws IOException { AbstractDataExporter excel = new ExcelExporter(); Table table = new Table("a,b,c,d,e".split(",")); table.setCaption("title"); int n = 10; AtomicInteger count = new AtomicInteger(0); Stopwatch watch = Stopwatch.createStarted(); for (int j = 0; j < n; j++) { EXECUTOR.submit(()-> { List data = new ArrayList<>(); for (int i = 0; i < 100000; i++) { data.add(new Object[] { "1", "2", "3", "4", "5" }); } table.addRows(data); if (count.incrementAndGet() == n) { table.toEnd(); } }); } System.out.println("***************"+watch.stop()); watch.reset().start(); excel.setName("21321"); excel.build(table); IOUtils.write((byte[]) excel.export(), new FileOutputStream(ExportTester.baseDir+"test11.xlsx")); excel.close(); System.out.println(watch.stop()); } @Test // 18.5 public void testExcel2() throws IOException { AbstractDataExporter excel = new ExcelExporter(); Table table = new Table("a,b,c,d,e".split(",")); table.setCaption("title"); int n = 10; AtomicInteger count = new AtomicInteger(0); Stopwatch watch = Stopwatch.createStarted(); for (int j = 0; j < n; j++) { EXECUTOR.submit(()-> { for (int i = 0; i < 100000; i++) { table.addRow(new Object[] { "1", "2", "3", "4", "5" }); } if (count.incrementAndGet() == n) { table.toEnd(); } }); } System.out.println("================"+watch.stop()); watch.reset().start(); excel.setName("21321"); excel.build(table); IOUtils.write((byte[]) excel.export(), new FileOutputStream(ExportTester.baseDir+"test22.xlsx")); excel.close(); System.out.println(watch.stop()); } @Test // 11 public void testCsv1() throws IOException { CsvFileExporter excel = new CsvFileExporter(ExportTester.baseDir+"test.csv", true); Table table = new Table("中,文,b,o,m".split(",")); table.setCaption("title"); int n = 100; AtomicInteger count = new AtomicInteger(0); Stopwatch watch = Stopwatch.createStarted(); for (int j = 0; j < n; j++) { EXECUTOR.submit(()-> { for (int i = 0; i < 100000; i++) { table.addRow(new Object[] { "1", "2", "3", "4", "5" }); } if (count.incrementAndGet() == n) { table.toEnd(); } }); } System.out.println("================"+watch.stop()); watch.reset().start(); excel.setName("21321"); excel.build(table); excel.close(); System.out.println(watch.stop()); } @Test // 1.485 min public void testSplitExcel() throws IOException { Table table = new Table("a,b,c,d,e".split(",")); table.setCaption("title"); int n = 100; AtomicInteger count = new AtomicInteger(0); Stopwatch watch = Stopwatch.createStarted(); for (int j = 0; j < n; j++) { EXECUTOR.submit(()-> { for (int i = 0; i < 100000; i++) { table.addRow(new Object[] { "1111111111111111111111111111", "2", "3", "4", "5" }); } if (count.incrementAndGet() == n) { table.toEnd(); } }); } SplitExcelExporter excel = new SplitExcelExporter(65537,ExportTester.baseDir+"test_excel_", EXECUTOR); excel.setName("21321"); System.out.println("================"+watch.stop()); watch.reset().start(); excel.build(table); excel.close(); System.out.println(watch.stop()); } @Test // 1.485 min public void testSplitCsv() throws IOException { Table table = new Table("中国,人,c,d,e".split(",")); table.setCaption("title"); int n = 10; AtomicInteger count = new AtomicInteger(0); Stopwatch watch = Stopwatch.createStarted(); for (int j = 0; j < n; j++) { EXECUTOR.submit(()-> { for (int i = 0; i < 100000; i++) { table.addRow(new Object[] { "1", "2", "3", "4", "5" }); } if (count.incrementAndGet() == n) { table.toEnd(); } }); } SplitCsvFileExporter csv = new SplitCsvFileExporter(65537,ExportTester.baseDir+"test_csv_", true, EXECUTOR); System.out.println("================"+watch.stop()); watch.reset().start(); csv.build(table); csv.close(); System.out.println(watch.stop()); } } ================================================ FILE: src/test/java/test/extract/ExampleEventUserModel.java ================================================ package test.extract; import java.io.InputStream; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import org.apache.poi.ooxml.util.SAXHelper; import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.openxml4j.opc.PackageAccess; import org.apache.poi.xssf.eventusermodel.XSSFReader; import org.apache.poi.xssf.model.SharedStrings; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.DefaultHandler; public class ExampleEventUserModel { public void processWorkebook(String filename) throws Exception { try (OPCPackage pkg = OPCPackage.open(filename, PackageAccess.READ)) { XSSFReader r = new XSSFReader(pkg); SharedStrings sst = r.getSharedStringsTable(); XMLReader parser = SAXHelper.newXMLReader(); parser.setContentHandler(new SheetHandler(sst)); // custome handler for (Iterator iter = r.getSheetsData(); iter.hasNext();) { InputStream sheet = iter.next(); InputSource sheetSource = new InputSource(sheet); parser.parse(sheetSource); sheet.close(); } } } /** * See org.xml.sax.helpers.DefaultHandler javadocs 重写 startElement characters endElements方法  */ private static class SheetHandler extends DefaultHandler { private SharedStrings sst; private String lastContents; private boolean nextIsString; //是否为string格式标识 private final LruCache lruCache = new LruCache<>(60); /*private int sheetIndex = -1; private int curRow = 0; private int curCol = 0; private List rowlist = new ArrayList(); */ /** * 缓存 * @author Administrator * * @param * @param */ private static class LruCache extends LinkedHashMap { private final int maxEntries; public LruCache(final int maxEntries) { super(maxEntries + 1, 1.0f, true); this.maxEntries = maxEntries; } @Override protected boolean removeEldestEntry(final Map.Entry eldest) { return super.size() > maxEntries; } } private SheetHandler(SharedStrings sst) { this.sst = sst; } /** * 该方法自动被调用,每读一行调用一次,在方法中写自己的业务逻辑即可 * @param sheetIndex 工作簿序号 * @param curRow 处理到第几行 * @param rowList 当前数据行的数据集合 */ /* public void optRow(int sheetIndex, int curRow, List rowList) { String temp = ""; for(String str : rowList) { temp += str + "_"; } this.rowlist.clear(); this.curRow++; this.curCol=0; System.out.println(temp); } */ @Override public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException { // c => cell 代表单元格 if (name.equals("c")) { // Print the cell reference //获取单元格的位置,如A1,B1 System.out.print(attributes.getValue("r") + " - "); // Figure out if the value is an index in the SST 如果下一个元素是 SST 的索引,则将nextIsString标记为true //单元格类型 String cellType = attributes.getValue("t"); //cellType值 s:字符串 b:布尔 e:错误处理 if (cellType != null && cellType.equals("s")) { //标识为true 交给后续endElement处理 nextIsString = true; } else { nextIsString = false; } } // Clear contents cache lastContents = ""; } /** * 得到单元格对应的索引值或是内容值 * 如果单元格类型是字符串、INLINESTR、数字、日期,lastIndex则是索引值 * 如果单元格类型是布尔值、错误、公式,lastIndex则是内容值 */ @Override public void characters(char[] ch, int start, int length) throws SAXException { lastContents += new String(ch, start, length); } @Override public void endElement(String uri, String localName, String name) throws SAXException { // Process the last contents as required. // Do now, as characters() may be called more than once if (nextIsString) { int idx = Integer.parseInt(lastContents); lastContents = lruCache.get(idx); //如果内容为空 或者Cache中存在相同key 不保存到Cache中 if (lastContents == null && !lruCache.containsKey(idx)) { lastContents = sst.getItemAt(idx).getString(); lruCache.put(idx, lastContents); } nextIsString = false; } // v => contents of a cell // Output after we've seen the string contents if (name.equals("v")) { System.out.println(lastContents); //rowlist.add(curCol++,lastContents); } else { //如果标签名称为 row , 已到行尾 if (name.equals("row")) { //optRow(sheetIndex, curRow, rowlist); System.out.println(lruCache); lruCache.clear(); } } } } public static void main(String[] args) throws Exception { new ExampleEventUserModel().processWorkebook("d:/abc1.xlsx"); } } ================================================ FILE: src/test/java/test/extract/ExcelExtractorTest.java ================================================ package test.extract; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Arrays; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; import cn.ponfee.commons.extract.DataExtractor; import cn.ponfee.commons.extract.DataExtractorBuilder; /** * 性能:Path > File > Input * @author Ponfee */ public class ExcelExtractorTest { @Test public void testXLS1() throws FileNotFoundException, IOException { DataExtractor et = DataExtractorBuilder.newBuilder("e:\\writeTest2.xls") .streaming(true) .headers(new String[] { "a", "b", "c", "d", "e", "f" }).build(); et.extract((n, d) -> { System.out.println(Arrays.toString((String[])d)); }); } @Test public void testXLS2() throws FileNotFoundException, IOException { DataExtractor et = DataExtractorBuilder.newBuilder("e:\\writeTest2.xls") .streaming(false) .headers(new String[] { "a", "b", "c", "d", "e", "f" }).build(); et.extract((n, d) -> { System.out.println(Arrays.toString((String[])d)); }); } @Test public void testXLSX1() throws FileNotFoundException, IOException { DataExtractor et = DataExtractorBuilder.newBuilder("e:\\mergeTest.xlsx") .streaming(false) .headers(new String[] { "a", "b", "c", "d", "e", "f" }).build(); et.extract((n, d) -> { System.out.println(Arrays.toString((String[])d)); }); } @Test public void testXLSX2() throws FileNotFoundException, IOException { DataExtractor et = DataExtractorBuilder.newBuilder("e:\\mergeTest.xlsx") .streaming(true) .headers(new String[] { "a", "b", "c", "d", "e", "f" }).build(); et.extract((n, d) -> { System.out.println(Arrays.toString((String[])d)); }); } @Test public void testFile() throws FileNotFoundException, IOException { DataExtractor et = DataExtractorBuilder.newBuilder(new File("D:\\test\\test_excel_14.xlsx")) /*.headers(new String[] { "a", "b", "c", "d", "e" })*/.build(); et.extract((n, d) -> { System.out.println(Arrays.toString((String[])d)); }); } @Test public void testInput() throws FileNotFoundException, IOException { DataExtractor et = DataExtractorBuilder.newBuilder(new FileInputStream("D:\\test\\test_excel_14.xlsx"), "test_excel_16.xlsx", null) .headers(new String[] { "a", "b", "c", "d", "e" }).build(); et.extract((n, d) -> { if (n == 0) { System.out.println(Arrays.toString((String[])d)); } if (n == 1) { System.out.println(Arrays.toString((String[])d)); } System.out.println(Arrays.toString((String[])d)); }); } @Test public void test1() throws FileNotFoundException, IOException { DataExtractor et = DataExtractorBuilder.newBuilder("e:\\data_expert_temp.xls") .headers(new String[] { "a", "b", "c", "d", "e" }).build(); et.extract((n, d) -> { System.out.println(Arrays.toString((String[])d)); }); } @Test public void test2() throws FileNotFoundException, IOException { DataExtractor et = DataExtractorBuilder.newBuilder("E:\\test.xlsx") .headers(new String[] { "a", "b", "c", "d", "e" }).build(); et.extract((n, d) -> { System.out.println(String.join("|",(String[])d)); }); } @Test public void test3() throws FileNotFoundException, IOException { DataExtractor et = DataExtractorBuilder.newBuilder("src/test/java/test/extract/advices_export.xls") .headers(new String[] { "a", "b", "c", "d"}).build(); et.extract((n, d) -> { System.out.println(String.join("|",(String[])d)); }); } @Test public void testCsvPath() throws FileNotFoundException, IOException { DataExtractor et = DataExtractorBuilder.newBuilder("E:\\test.csv") .headers(new String[] { "a", "b", "c", "d", "e" }).build(); et.extract((n, d) -> { if (n < 10) System.out.println(Arrays.toString((String[])d)); }); } @Test public void testCsv1() throws FileNotFoundException, IOException { DataExtractor et = DataExtractorBuilder.newBuilder("E:\\test.csv") .headers(new String[] { "a", "b", "c", "d", "e" }).build(); et.extract((n, d) -> { System.out.println(Arrays.toString((String[])d)); }); } @Test public void testCsv2() throws FileNotFoundException, IOException { DataExtractor et = DataExtractorBuilder.newBuilder("E:\\test.csv") .headers(new String[] { "a", "b", "c", "d", "e" }).build(); et.extract(1).forEach(e -> System.out.println(Arrays.toString(e))); } // ------------------------------------------------------ @Test public void test6() throws FileNotFoundException, IOException { //test("E:\\test20.xlsx", false); // 9.1 s //test("E:\\test100.xlsx", true); // 7.8 //test("E:\\writeTest.xls", false); // 2.6 test("E:\\writeTest.xls", true); // 2.0 } private void test(String filename, boolean streaming) throws FileNotFoundException, IOException { DataExtractor et = DataExtractorBuilder.newBuilder(filename) .streaming(streaming).headers(new String[] { "a", "b", "c", "d", "e" }).build(); AtomicInteger count = new AtomicInteger(); et.extract((n, d) -> { if (n < 10) { System.out.println(Arrays.toString((String[])d)); } count.incrementAndGet(); }); System.out.println(count.get()); } } ================================================ FILE: src/test/java/test/extract/TestHSSFStreaming.java ================================================ package test.extract; import java.util.Iterator; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.junit.Test; import cn.ponfee.commons.concurrent.ThreadPoolExecutors; import cn.ponfee.commons.extract.streaming.xls.HSSFStreamingReader; import cn.ponfee.commons.extract.streaming.xls.HSSFStreamingRow; import cn.ponfee.commons.extract.streaming.xls.HSSFStreamingSheet; import cn.ponfee.commons.extract.streaming.xls.HSSFStreamingWorkbook; public class TestHSSFStreaming { static ExecutorService exec = Executors.newFixedThreadPool(4); @Test public void test1() throws InterruptedException { //String file = "e:/data_expert.xls"; HSSFStreamingWorkbook wb = HSSFStreamingReader.create(40, 0).open("src/test/java/test/extract/writeTest2.xls", exec); HSSFStreamingSheet sheet = (HSSFStreamingSheet) wb.getSheetAt(0); for (Row row : sheet) { System.out.print(row.getRowNum() + ", " + ((HSSFStreamingRow) row).getRowOrder() + ": "); for (Cell cell : row) { System.out.print(cell == null ? "null, " : cell.getStringCellValue() + ", "); } System.out.println(); } System.out.println(); for (Iterator iter = wb.iterator(); iter.hasNext();) { HSSFStreamingSheet sst = (HSSFStreamingSheet) iter.next(); System.out.println("SheetIndex: "+sst.getSheetIndex()+",SheetName: "+sst.getSheetName()+",cheRowCount: "+sheet.getCacheRowCount()); } } } ================================================ FILE: src/test/java/test/extract/XLSEventTest.java ================================================ package test.extract; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import org.apache.poi.hssf.eventusermodel.HSSFEventFactory; import org.apache.poi.hssf.eventusermodel.HSSFListener; import org.apache.poi.hssf.eventusermodel.HSSFRequest; import org.apache.poi.hssf.record.BOFRecord; import org.apache.poi.hssf.record.BoundSheetRecord; import org.apache.poi.hssf.record.ExtSSTRecord; import org.apache.poi.hssf.record.LabelSSTRecord; import org.apache.poi.hssf.record.NumberRecord; import org.apache.poi.hssf.record.Record; import org.apache.poi.hssf.record.RowRecord; import org.apache.poi.hssf.record.SSTRecord; import org.apache.poi.poifs.filesystem.POIFSFileSystem; /** * * @author Ponfee */ /** * This example shows how to use the event API for reading a file. */ public class XLSEventTest implements HSSFListener { private SSTRecord sstrec; /** * This method listens for incoming records and handles them as required. * @param record The record that was found while reading. */ @Override public void processRecord(Record record) { switch (record.getSid()) { // the BOFRecord can represent either the beginning of a sheet or the workbook case BOFRecord.sid: BOFRecord bof = (BOFRecord) record; if (bof.getType() == BOFRecord.TYPE_WORKBOOK) { System.out.println("Encountered workbook"); // assigned to the class level member } else if (bof.getType() == BOFRecord.TYPE_WORKSHEET) { System.out.println("Encountered sheet reference"); } else { System.out.println("others "+bof); } break; case BoundSheetRecord.sid: BoundSheetRecord bsr = (BoundSheetRecord) record; System.out.println("New sheet named: " + bsr.getSheetname()); break; case RowRecord.sid: RowRecord rowrec = (RowRecord) record; System.out.println("Row found "+rowrec.getRowNumber()+", first column at " + rowrec.getFirstCol() + " last column at " + rowrec.getLastCol()); break; // SSTRecords store a array of unique strings used in Excel. case SSTRecord.sid: sstrec = (SSTRecord) record; for (int k = 0; k < sstrec.getNumUniqueStrings(); k++) { System.out.println("String table value " + k + " = " + sstrec.getString(k)); } break; case NumberRecord.sid: NumberRecord numrec = (NumberRecord) record; System.out.println("Cell found with value " + numrec.getValue() + " at row " + numrec.getRow() + " and column " + numrec.getColumn()); break; case LabelSSTRecord.sid: LabelSSTRecord lrec = (LabelSSTRecord) record; System.out.println("String cell found with value " + sstrec.getString(lrec.getSSTIndex())+ " at row " + lrec.getRow() + " and column " + lrec.getColumn()); break; case ExtSSTRecord.sid: ExtSSTRecord extsstrec = (ExtSSTRecord) record; for (int k = 0; k < extsstrec.getRecordSize(); k++) { } default: //System.out.println("==others"+record.getClass()+"---"+record.getSid()); // discard; } } /** * Read an excel file and spit out what we find. * * @param args Expect one argument that is the file to read. * @throws IOException When there is an error processing the file. */ public static void main(String[] args) throws IOException { // create a new file input stream with the input file specified // at the command line FileInputStream fin = new FileInputStream("src/test/java/test/extract/advices_export.xls"); // create a new org.apache.poi.poifs.filesystem.Filesystem POIFSFileSystem poifs = new POIFSFileSystem(fin); // get the Workbook (excel part) stream in a InputStream InputStream din = poifs.createDocumentInputStream("Workbook"); // construct out HSSFRequest object HSSFRequest req = new HSSFRequest(); // lazy listen for ALL records with the listener shown above req.addListenerForAllRecords(new XLSEventTest()); // create our event factory HSSFEventFactory factory = new HSSFEventFactory(); // process our events based on the document input stream factory.processEvents(req, din); // once all the events are processed close our file input stream poifs.close(); fin.close(); // and our document input stream (don't want to leak these!) din.close(); System.out.println("done."); } } ================================================ FILE: src/test/java/test/extract/XLSX2CSV.java ================================================ package test.extract; /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import javax.xml.parsers.ParserConfigurationException; import org.apache.poi.ooxml.util.SAXHelper; import org.apache.poi.openxml4j.exceptions.OpenXML4JException; import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.openxml4j.opc.PackageAccess; import org.apache.poi.ss.usermodel.DataFormatter; import org.apache.poi.ss.util.CellAddress; import org.apache.poi.ss.util.CellReference; import org.apache.poi.xssf.eventusermodel.ReadOnlySharedStringsTable; import org.apache.poi.xssf.eventusermodel.XSSFReader; import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler; import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler.SheetContentsHandler; import org.apache.poi.xssf.extractor.XSSFEventBasedExcelExtractor; import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.model.Styles; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.usermodel.XSSFComment; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; /** * A rudimentary XLSX -> CSV processor modeled on the * POI sample program XLS2CSVmra from the package * org.apache.poi.hssf.eventusermodel.examples. * As with the HSSF version, this tries to spot missing * rows and cells, and output empty entries for them. *

    * Data sheets are read using a SAX parser to keep the * memory footprint relatively small, so this should be * able to read enormous workbooks. The styles table and * the shared-string table must be kept in memory. The * standard POI styles table class is used, but a custom * (read-only) class is used for the shared string table * because the standard POI SharedStringsTable grows very * quickly with the number of unique strings. *

    * For a more advanced implementation of SAX event parsing * of XLSX files, see {@link XSSFEventBasedExcelExtractor} * and {@link XSSFSheetXMLHandler}. Note that for many cases, * it may be possible to simply use those with a custom * {@link SheetContentsHandler} and no SAX code needed of * your own! */ public class XLSX2CSV { /** * Uses the XSSF Event SAX helpers to do most of the work * of parsing the Sheet XML, and outputs the contents * as a (basic) CSV. */ private class SheetToCSV implements SheetContentsHandler { private boolean firstCellOfRow; private int currentRow = -1; private int currentCol = -1; private void outputMissingRows(int number) { for (int i=0; i CSV examples * * @param pkg The XLSX package to process * @param output The PrintStream to output the CSV to * @param minColumns The minimum number of columns to output, or -1 for no minimum */ public XLSX2CSV(OPCPackage pkg, PrintStream output, int minColumns) { this.xlsxPackage = pkg; this.output = output; this.minColumns = minColumns; } /** * Parses and shows the content of one sheet * using the specified styles and shared-strings tables. * * @param styles The table of styles that may be referenced by cells in the sheet * @param strings The table of strings that may be referenced by cells in the sheet * @param sheetInputStream The stream to read the sheet-data from. * @exception java.io.IOException An IO exception from the parser, * possibly from a byte stream or character stream * supplied by the application. * @throws SAXException if parsing the XML data fails. */ public void processSheet( Styles styles, SharedStrings strings, SheetContentsHandler sheetHandler, InputStream sheetInputStream) throws IOException, SAXException { DataFormatter formatter = new DataFormatter(); InputSource sheetSource = new InputSource(sheetInputStream); try { XMLReader sheetParser = SAXHelper.newXMLReader(); ContentHandler handler = new XSSFSheetXMLHandler( styles, null, strings, sheetHandler, formatter, false); sheetParser.setContentHandler(handler); sheetParser.parse(sheetSource); } catch(ParserConfigurationException e) { throw new RuntimeException("SAX parser appears to be broken - " + e.getMessage()); } } /** * Initiates the processing of the XLS workbook file to CSV. * * @throws IOException If reading the data from the package fails. * @throws SAXException if parsing the XML data fails. */ public void process() throws IOException, OpenXML4JException, SAXException { ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable(this.xlsxPackage); XSSFReader xssfReader = new XSSFReader(this.xlsxPackage); StylesTable styles = xssfReader.getStylesTable(); XSSFReader.SheetIterator iter = (XSSFReader.SheetIterator) xssfReader.getSheetsData(); int index = 0; while (iter.hasNext()) { try (InputStream stream = iter.next()) { String sheetName = iter.getSheetName(); this.output.println(); this.output.println(sheetName + " [index=" + index + "]:"); processSheet(styles, strings, new SheetToCSV(), stream); } ++index; } } public static void main(String[] args) throws Exception { File xlsxFile = new File("d:/test/test_excel_3.xlsx"); if (!xlsxFile.exists()) { System.err.println("Not found or not a file: " + xlsxFile.getPath()); return; } int minColumns = -1; if (args.length >= 2) minColumns = Integer.parseInt(args[1]); // The package open is instantaneous, as it should be. try (OPCPackage p = OPCPackage.open(xlsxFile.getPath(), PackageAccess.READ)) { XLSX2CSV xlsx2csv = new XLSX2CSV(p, System.out, minColumns); xlsx2csv.process(); } } } ================================================ FILE: src/test/java/test/extract/XLSXEventTest.java ================================================ package test.extract; import java.io.InputStream; import java.util.Iterator; import org.apache.poi.ooxml.util.SAXHelper; import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.xssf.eventusermodel.XSSFReader; import org.apache.poi.xssf.model.SharedStrings; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.DefaultHandler; import javax.xml.parsers.ParserConfigurationException; public class XLSXEventTest { public void processOneSheet(String filename) throws Exception { OPCPackage pkg = OPCPackage.open(filename); XSSFReader r = new XSSFReader( pkg ); SharedStrings sst = r.getSharedStringsTable(); XMLReader parser = fetchSheetParser(sst); // To look up the Sheet Name / Sheet Order / rID, // you need to process the core Workbook stream. // Normally it's of the form rId# or rSheet# InputStream sheet2 = r.getSheet("rId1"); InputSource sheetSource = new InputSource(sheet2); parser.parse(sheetSource); sheet2.close(); } public void processAllSheets(String filename) throws Exception { OPCPackage pkg = OPCPackage.open(filename); XSSFReader r = new XSSFReader( pkg ); SharedStrings sst = r.getSharedStringsTable(); XMLReader parser = fetchSheetParser(sst); Iterator sheets = r.getSheetsData(); while(sheets.hasNext()) { System.out.println("Processing new sheet:\n"); InputStream sheet = sheets.next(); InputSource sheetSource = new InputSource(sheet); parser.parse(sheetSource); sheet.close(); System.out.println(""); } } public XMLReader fetchSheetParser(SharedStrings sst) throws SAXException, ParserConfigurationException { XMLReader parser = SAXHelper.newXMLReader(); ContentHandler handler = new SheetHandler(sst); parser.setContentHandler(handler); return parser; } /** * See org.xml.sax.helpers.DefaultHandler javadocs */ private static class SheetHandler extends DefaultHandler { private SharedStrings sst; private String lastContents; private boolean nextIsString; private SheetHandler(SharedStrings sst) { this.sst = sst; } public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException { // c => cell if(name.equals("c")) { // Print the cell reference System.out.print(attributes.getValue("r") + " - "); // Figure out if the value is an index in the SST String cellType = attributes.getValue("t"); if(cellType != null && cellType.equals("s")) { nextIsString = true; } else { nextIsString = false; } } // Clear contents cache lastContents = ""; } public void endElement(String uri, String localName, String name) throws SAXException { // Process the last contents as required. // Do now, as characters() may be called more than once if(nextIsString) { int idx = Integer.parseInt(lastContents); lastContents = sst.getItemAt(idx).getString(); nextIsString = false; } // v => contents of a cell // Output after we've seen the string contents if(name.equals("v")) { System.out.println(lastContents); } } public void characters(char[] ch, int start, int length) { lastContents += new String(ch, start, length); } } public static void main(String[] args) throws Exception { XLSXEventTest example = new XLSXEventTest(); //example.processOneSheet("d:/test/test_excel_3.xlsx"); example.processAllSheets("d:/test/test_excel_3.xlsx"); } } ================================================ FILE: src/test/java/test/http/HttpClientUtils.java ================================================ package test.http; import java.io.IOException; import java.net.SocketException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.apache.http.HttpStatus; import org.apache.http.NameValuePair; import org.apache.http.NoHttpResponseException; import org.apache.http.client.HttpRequestRetryHandler; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.HttpContext; import org.apache.http.util.EntityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import cn.ponfee.commons.json.Jsons; public class HttpClientUtils { private static Logger logger = LoggerFactory.getLogger(HttpClientUtils.class); private final static Charset UTF8 = Charset.forName("UTF-8"); public static final CloseableHttpClient HTTP_CLIENT; static { // 初始化线程池 RequestConfig params = RequestConfig.custom().setConnectTimeout(3000).setConnectionRequestTimeout(1000) .setSocketTimeout(4000).setExpectContinueEnabled(true).build(); PoolingHttpClientConnectionManager pccm = new PoolingHttpClientConnectionManager(); pccm.setMaxTotal(300); // 连接池最大并发连接数 pccm.setDefaultMaxPerRoute(50); // 单路由最大并发数 HttpRequestRetryHandler retryHandler = new HttpRequestRetryHandler() { @Override public boolean retryRequest(IOException exception, int executionCount, HttpContext context) { if (executionCount > 1) { return false; // 重试1次,从1开始 } else if (exception instanceof NoHttpResponseException) { logger.info("[NoHttpResponseException has retry request:" + context.toString() + "][executionCount:" + executionCount + "]"); return true; } else if (exception instanceof SocketException) { logger.info("[SocketException has retry request:" + context.toString() + "][executionCount:" + executionCount + "]"); return true; } else { return false; } } }; HTTP_CLIENT = HttpClients.custom().setConnectionManager(pccm).setDefaultRequestConfig(params) .setRetryHandler(retryHandler).build(); } public static String post(String url, Map params, Integer connReqTimeout, Integer connTimeout, Integer socketTimeout) { List nvps = new ArrayList(); if (params != null && !params.isEmpty()) { for (Entry entry : params.entrySet()) { nvps.add(new BasicNameValuePair(entry.getKey(), String.valueOf(entry.getValue()))); } } logger.info("post-req:url:{},param:{}", url, Jsons.NORMAL.string(params)); HttpPost post = new HttpPost(url); RequestConfig.Builder builder = RequestConfig.custom(); if (connReqTimeout != null && connReqTimeout > 0) { builder.setConnectionRequestTimeout(connReqTimeout); } if (connTimeout != null && connTimeout > 0) { builder.setConnectTimeout(connTimeout); } if (socketTimeout != null && socketTimeout > 0) { builder.setSocketTimeout(socketTimeout); } post.setConfig(builder.build()); post.setEntity(new UrlEncodedFormEntity(nvps, UTF8)); CloseableHttpResponse response = null; try { response = HTTP_CLIENT.execute(post); if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { return EntityUtils.toString(response.getEntity(), UTF8); } else { return null; } } catch (IOException e) { logger.error("[HttpClientUtils][invoke][method:" + post.getMethod() + " URI:" + post.getURI() + "] is request exception", e); return null; } finally { if (response != null) { try { response.close(); } catch (IOException e) { logger.error("[HttpClientUtils][invoke][method:" + post.getMethod() + " URI:" + post.getURI() + "] is closed exception", e); } } } } } ================================================ FILE: src/test/java/test/http/HttpParamsTest.java ================================================ package test.http; import java.util.LinkedHashMap; import java.util.Map; import org.junit.Test; import cn.ponfee.commons.http.ContentType; import cn.ponfee.commons.http.Http; import cn.ponfee.commons.http.HttpParams; import cn.ponfee.commons.util.ObjectUtils; public class HttpParamsTest { @Test public void test1() { String str = "service=http%3A%2F%2Flocalhost%2Fcas-client%2F&test=fds中文a"; System.out.println("\n=============parseParams=============="); System.out.println(ObjectUtils.toString(HttpParams.parseParams(str, "UTF-8"))); System.out.println("\n=============parseUrlParams=============="); str = "http://localhost:8080/test?service=http%3A%2F%2Flocalhost%2Fcas-client%2F&test=fds中文a"; System.out.println(ObjectUtils.toString(HttpParams.parseUrlParams(str))); System.out.println("\n=============buildParams=============="); Map map = new LinkedHashMap(); map.put("a", new String[] { "1" }); map.put("b", new String[] { "2" }); map.put("merReserved", new String[] { "{a=1&b=2}" }); String queryString = HttpParams.buildParams(map, "utf-8"); System.out.println(queryString); System.out.println(ObjectUtils.toString(HttpParams.parseParams(queryString, "utf-8"))); System.out.println("\n=============buildUrlPath=============="); System.out.println(HttpParams.buildUrlPath("/index.html", "utf-8", map)); System.out.println("\n=============buildForm=============="); System.out.println(HttpParams.buildForm("http://localhost:8080", map)); } @Test public void test2() { String url = "http://10.118.58.74:8000/open/api/test?a=1=32=14=12=4=3214=2&abcdef&" + Math.random(); System.out.println(ObjectUtils.toString(HttpParams.parseUrlParams(url, "UTF-8"))); } @Test public void test3() { System.out.println(HttpParams.buildUrlPath("url", "UTF-8", "a", "1","b","2")); } @Test public void test4() { String soap = "\n" + " \n" + " \n" + " 119.139.199.75\n" + " \n" + " \n" + ""; //soap = HttpParams.buildSoap("getCountryCityByIp", "http://WebXml.com.cn/", ImmutableMap.of("theIpAddress", "119.139.199.75")); System.out.println(soap); String resp = Http.post("http://www.webxml.com.cn/WebServices/IpAddressSearchWebService.asmx").contentType(ContentType.TEXT_XML).data(soap).request(); System.out.println(resp); } @Test public void test5() { String soap = "\n" + " \n" + " \n" + " 119.139.199.75\n" + " \n" + " \n" + ""; //soap = HttpParams.buildSoap("testListJavaBean", "http://service.screen.ddt.sf.com/", ImmutableMap.of("arg0", "1")); System.out.println(soap); String resp = Http.post("http://localhost:8009/ws/testScreen?wsdl").contentType(ContentType.TEXT_XML).data(soap).request(); System.out.println(resp); } } ================================================ FILE: src/test/java/test/http/HttpPostTester.java ================================================ package test.http; import java.io.IOException; import java.net.SocketException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.NoHttpResponseException; import org.apache.http.StatusLine; import org.apache.http.client.HttpClient; import org.apache.http.client.HttpRequestRetryHandler; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.HttpContext; import org.apache.http.util.EntityUtils; public class HttpPostTester { public static String post(String reqURL, Map params) throws Exception { HttpPost httpPost = new HttpPost(reqURL); if (params != null) { List nvps = new ArrayList<>(); Set> paramEntrys = params.entrySet(); for (Entry entry : paramEntrys) { nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue())); } httpPost.setEntity(new UrlEncodedFormEntity(nvps, "utf-8")); } httpPost.setHeader("User-Agent", "datagrand/datareport/java sdk v1.0.0"); httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded"); // 设置默认时间 RequestConfig config = RequestConfig.custom().setConnectTimeout(3000) .setConnectionRequestTimeout(1000).setSocketTimeout(4000) .setExpectContinueEnabled(true).build(); PoolingHttpClientConnectionManager pccm = new PoolingHttpClientConnectionManager(); pccm.setMaxTotal(300); // 连接池最大并发连接数 pccm.setDefaultMaxPerRoute(50); // 单路由最大并发数 HttpRequestRetryHandler retryHandler = new HttpRequestRetryHandler() { public boolean retryRequest(IOException exception, int executionCount, HttpContext context) { if (executionCount > 3) { return false; } if (exception instanceof NoHttpResponseException) { System.out.println("[NoHttpResponseException has retry request:" + context.toString() + "][executionCount:" + executionCount + "]"); return true; } else if (exception instanceof SocketException) { System.out.println("[SocketException has retry request:" + context.toString() + "][executionCount:" + executionCount + "]"); return true; } return false; } }; HttpClient httpClient = HttpClients.custom().setConnectionManager(pccm) .setDefaultRequestConfig(config) .setRetryHandler(retryHandler).build(); HttpResponse response = httpClient.execute(httpPost); StatusLine status = response.getStatusLine(); if (status.getStatusCode() >= HttpStatus.SC_MULTIPLE_CHOICES) { System.out.printf("Did not receive successful HTTP response: status code = {}, status message = {}", status.getStatusCode(), status.getReasonPhrase()); httpPost.abort(); } String responseContent = ""; HttpEntity entity = response.getEntity(); if (entity != null) { responseContent = EntityUtils.toString(entity, "utf-8"); EntityUtils.consume(entity); } else { System.out.printf("Http entity is null! request url is {},response status is {}", reqURL, response.getStatusLine()); } return responseContent; } public static void main(String[] args) { Map params = new HashMap(); params.put("appid", "12345"); params.put("title", "abc"); params.put("textid", "123456778"); params.put("text", "abcdefg"); String res; try { res = post("http://commentapi.datagrand.com/bad_comment/meituan", params); System.out.println(res); } catch (Exception e) { e.printStackTrace(); } } } ================================================ FILE: src/test/java/test/http/HttpTester.java ================================================ package test.http; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; import javax.net.ssl.SSLSocketFactory; import org.apache.commons.io.IOUtils; import org.junit.Test; import cn.ponfee.commons.http.ContentType; import cn.ponfee.commons.http.Http; import cn.ponfee.commons.jce.security.KeyStoreResolver; import cn.ponfee.commons.jce.security.KeyStoreResolver.KeyStoreType; import cn.ponfee.commons.resource.Resource; import cn.ponfee.commons.resource.ResourceLoaderFacade; import cn.ponfee.commons.util.Bytes; public class HttpTester { //private static final String URL = "http://192.168.1.49:8080/web/"; private static final String URL = "http://192.168.1.120:8100/"; @Test public void testHttps() { InputStream keyInput = Object.class.getResourceAsStream("d:/cert.p12"); KeyStoreResolver resolver = new KeyStoreResolver(KeyStoreType.PKCS12, keyInput, "1253089901"); SSLSocketFactory sockFact = resolver.getSSLContext("1253089901").getSocketFactory(); String url = "https://api.mch.weixin.qq.com/secapi/pay/refund"; String data = ""; String resp = Http.post(url).setSSLSocketFactory(sockFact).data(data).request(); System.out.println(resp); } @Test public void upload() throws IOException { String url = URL + "account/v1/user/photoupdate.json"; Map params = new HashMap<>(); params.put("time", "1478859839449"); params.put("deviceid", "991182d512da8f4615f7a4eddb878512"); params.put("platform", "H5"); params.put("nickName", "厚大司考等哈说11"); params.put("authToken", "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ2aHg0amVkekRsNGUrZElOOWh0RnhIaG5vcnV0UmpyV1YySmFRdWVWODRvPSIsImV4cCI6MTQ4MTQ1MTQ2OCwicmZoIjoxNDc4OTQ1ODY4fQ.kg17ETWHUFbdJVlJnEUgYPC-34PxYnp9eCVvJt3X4ZfM8-FmM112M799Q8vTRyTnG637pfJJfU2PcrB18Xf1MQ"); String resp = Http.post(url).addPart("photo", "photo.jpg", IOUtils.toByteArray(new FileInputStream("D:\\photo.jpg"))).addParam(params).request(); //String resp = Http.post(url).part("photo", "photo.jpg", new Byte[]{123,34}).params(params).request(); System.out.println(resp); } @Test public void testParams() { String url = URL + "account/v1/user/info.json"; Map params = new HashMap<>(); params.put("time", "1478859839449"); params.put("deviceid", "991182d512da8f4615f7a4eddb878512"); params.put("platform", "H5"); params.put("nickName", "厚大司考等哈说11"); params.put("authToken", "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ2aHg0amVkekRsNGUrZElOOWh0RnhIaG5vcnV0UmpyV1YySmFRdWVWODRvPSIsImV4cCI6MTQ4MTQ1MTQ2OCwicmZoIjoxNDc4OTQ1ODY4fQ.kg17ETWHUFbdJVlJnEUgYPC-34PxYnp9eCVvJt3X4ZfM8-FmM112M799Q8vTRyTnG637pfJJfU2PcrB18Xf1MQ"); String resp = Http.post(url).addParam(params).request(); System.out.println(resp); } @Test public void testData() throws IOException { Resource resource = ResourceLoaderFacade.getResource("qq.coupon/createConfig.json", "UTF-8"); String json = IOUtils.toString(resource.getStream(), Charset.forName("UTF-8")); Http http = Http.post("http://3bc98c2a.ngrok.io/pay-center-testdemo/test/json"); String s = http.contentType(ContentType.APPLICATION_JSON, "utf-8").data(json).request(); System.out.println("======================================================\n" + s); http = Http.post("http://3bc98c2a.ngrok.io/pay-center-testdemo/test/post"); s = http.data("a=1&b=2").request(); System.out.println("======================================================\n" + s); } @Test public void test2() throws IOException { String url = "http://10.118.58.74:8080/express/risk/outeridentify.html"; String resp = Http.post(url).addPart("file", "import.txt", IOUtils.toByteArray(new FileInputStream("D:\\import.txt"))).request(); System.out.println(resp); } @Test // 创建个性大屏 public void test11() throws IOException { String url = "http://10.118.40.20:8080/customizedScreen/addScreenInfo"; String resp = Http.post(url) .addParam("activityId", "") .addParam("screenId", "7") .addParam("activityName", "test_ponfee4") .addParam("activityStartDate", "2019-03-08") .addParam("activityEndDate", "2019-03-13") .addParam("showTop", "3") .addParam("monthlyCard", "2017112915,9999999999") .addParam("expressProductsStr", "SE0004,顺丰特惠") .addParam("keyword", "kw") .addParam("expressAddress", "北京市_北京市") .addParam("page_src", "2019-03-08\\4b480f74-6752-495a-ae76-04ef0e2d436f-1061493_7.jpg") .addParam("edit_src", "edit_src") .addHeader("Cookie", "JSESSIONID=1j8qfy7sh1jf61n5fgc25zwjo4") .request(); System.out.println(resp); } @Test // 创建仓储大屏 public void test12() throws IOException { String url = "http://10.118.40.20:8080/battleRoom/saveActiveInfo2"; String resp = Http.post(url) .addParam("activeName", "test3423432") .addParam("activeReady", "2019-02-24") .addParam("activeDate", "2019-02-28") .addParam("activeDay", "2") .addParam("activeNum", "12") .addParam("act_warehouseCode", "DV1,DV2") .addParam("active_warahouseName", "监视器仓库1,监视器仓库2") .addParam("active_sku", "67445276542155002,77445276542155003") .addParam("warehouseCodes", "DV1,DV2") .addParam("skus", "67445276542155002,77445276542155003") .addHeader("Cookie", "JSESSIONID=1j8qfy7sh1jf61n5fgc25zwjo4") .request(); System.out.println(resp); } @Test // 创建快递大屏 public void test13() throws IOException { String url = "http://10.118.40.20:8080/battleRoomExpress/saveActiveInfo"; String resp = Http.post(url) .addParam("activeName", "test3423432") .addParam("activeReady", "2019-02-24") .addParam("activeDate", "2019-02-28") .addParam("activeDay", "2") .addParam("activeState", "0") .addParam("activeNum", "12") .addParam("id", "0") .addParam("customerCodes", "2017112815,2017112915,9999999999,3333333333,5555555555,0203002395") .addHeader("Cookie", "JSESSIONID=1j8qfy7sh1jf61n5fgc25zwjo4") .request(); System.out.println(resp); } public static void main(String[] args) throws FileNotFoundException { //System.out.println(Bytes.hexDump(Http.get("http://www.apachelounge.com/download/VC14/binaries/httpd-2.4.25-win64-VC14.zip").download())); //Http http = Http.get("http://www.baidu.com"); //http.download(new FileOutputStream("d:/baidu.com.txt")); //System.out.println(http.getStatus()); Http http = Http.get("http://www.stockstar.com"); System.out.println(Bytes.dumpHex(http.download())); System.out.println(http.getRespHeaders()); System.out.println(http.getStatus()); //Http.get("http://www.baidu.com").download("d:/baidu.html"); //System.out.println(Http.get("http://localhost:8081/audit/getImg").data(ImmutableMap.of("imgPath", "imgPath")).request()); //String[] params = new String[]{"{\"analyze_type\":\"mine_all_cust\",\"date_type\":4,\"class_name\":\"\"}", "{\"analyze_type\":\"mine_all_cust\",\"date_type\":4,\"class_name\":\"衬衫\"}"}; //Http.post("http://10.118.58.156:8080/market/custgroup/kanban/count/recommend").data(ImmutableMap.of("conditions[]", params)).request(); /*@SuppressWarnings("unchecked") Map resp = Http.post("http://10.118.58.74:8080/uploaded/file") .addParam("param1", "test1213") .addPart("uploadFile", "abc.pdf", new File("d:/test/abc.pdf")) .addPart("uploadFile", "word.pdf", new File("d:/test/word.pdf")) .contentType("multipart/form-data", "UTF-8") // //.contentType("application/json", "UTF-8") // @RequestBody //.contentType("application/x-www-form-urlencoded", "UTF-8") // form data .accept("application/json") // @ResponseBody //.setSSLSocketFactory(factory) // trust certs store .request(Map.class); System.out.println(resp);*/ } } ================================================ FILE: src/test/java/test/http/NewApiTester.java ================================================ package test.http; import java.io.IOException; import java.util.HashMap; import java.util.Map; import org.junit.Test; import cn.ponfee.commons.http.Http; public class NewApiTester { private static final String TOKEN = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJtVU9SMk5XVzJsUXVNd1VxUlRuWjR3PT0iLCJleHAiOjE0OTEwMTY1NTUsInJmaCI6MTQ4ODUxMDk1NX0.mjeVWE3vEfI89r65DtoqRGFkUSx-KeQL0SE5liUrwSIG3mh7ptovcDkpjq6oqJNnMrul3vOHNWhMjBbwt0lFAQ"; private static final String URL = "http://192.168.1.122:8100"; @Test public void testChildrenAdd() { String url = URL + "/account/v1/contact/childrenadd.json"; Map params = new HashMap<>(); params.put("time", "1478859839449"); params.put("deviceid", "991182d512da8f4615f7a4eddb878512"); params.put("platform", "H5"); params.put("authToken", TOKEN); params.put("realName", "abcdef"); params.put("certNo", "430121198901227354"); String resp = Http.post(url).addParam(params).request(); System.out.println(resp); } @Test public void testChildrenUpd() { String url = URL + "/account/v1/contact/childrenupd.json"; Map params = new HashMap<>(); params.put("time", "431121198910163461"); params.put("deviceid", "991182d512da8f4615f7a4eddb878512"); params.put("platform", "H5"); params.put("authToken", TOKEN); params.put("realName", "ccccccc"); params.put("certNo", "430121198901227354"); params.put("id", "5"); String resp = Http.post(url).addParam(params).request(); System.out.println(resp); } @Test public void testChildrenDel() { String url = URL + "/account/v1/contact/childrendel.json"; Map params = new HashMap<>(); params.put("time", "1478859839449"); params.put("deviceid", "991182d512da8f4615f7a4eddb878512"); params.put("platform", "H5"); params.put("authToken", TOKEN); params.put("id", "6"); String resp = Http.post(url).addParam(params).request(); System.out.println(resp); } @Test public void testChildrenlist() { String url = URL + "/account/v1/contact/childrenlist.json"; Map params = new HashMap<>(); params.put("time", "1478859839449"); params.put("deviceid", "991182d512da8f4615f7a4eddb878512"); params.put("platform", "H5"); params.put("authToken", TOKEN); String resp = Http.post(url).addParam(params).request(); System.out.println(resp); } } ================================================ FILE: src/test/java/test/http/OldApiTester.java ================================================ package test.http; import java.io.IOException; import java.net.URLEncoder; import java.security.MessageDigest; import java.util.HashMap; import java.util.Map; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; import org.junit.Test; import cn.ponfee.commons.http.Http; import cn.ponfee.commons.http.HttpParams; import cn.ponfee.commons.jce.digest.DigestUtils; public class OldApiTester { private static final String URL = "http://192.168.1.49:8080/service-webapp"; private static final String SECRET = "abc"; private static final String KEY = "cde"; @Test public void testOldInf() { Map headers = new HashMap<>(); String time = String.valueOf(System.currentTimeMillis()); headers.put("source", "IOS"); headers.put("time", time); headers.put("auth", DigestUtils.md5Hex(SECRET + time)); Map params = new HashMap<>(); params.put("abc", "123"); params.put("sign", buildSign(params, KEY)); String resp = Http.post(URL + "/city/getStartCityList.srv").addParam(params).addHeader(headers).request(); System.out.println(resp); } @Test public void testcarrychildrenaddorupd() { Map headers = new HashMap<>(); String userId = "87"; String time = String.valueOf(System.currentTimeMillis()); headers.put("source", "IOS"); headers.put("time", time); headers.put("auth", DigestUtils.md5Hex(SECRET + time)); headers.put("accToken", buildAcctoken(userId)); Map params = new HashMap<>(); params.put("userId", userId); params.put("certNo", "430122198210130031"); params.put("realName", "realNamecde"); params.put("sign", buildSign(params, KEY)); String resp = Http.post(URL + "/user/carrychildrenaddorupd.srv").addParam(params).addHeader(headers).request(); System.out.println(resp); } @Test public void testcarrychildrendel() { Map headers = new HashMap<>(); String userId = "87"; String time = String.valueOf(System.currentTimeMillis()); headers.put("source", "IOS"); headers.put("time", time); headers.put("auth", DigestUtils.md5Hex(SECRET + time)); headers.put("accToken", buildAcctoken(userId)); Map params = new HashMap<>(); params.put("userId", userId); params.put("id", "2"); params.put("sign", buildSign(params, KEY)); String resp = Http.post(URL + "/user/carrychildrendel.srv").addParam(params).addHeader(headers).request(); System.out.println(resp); } @Test public void testcarrychildrenlist() { Map headers = new HashMap<>(); String userId = "87"; String time = String.valueOf(System.currentTimeMillis()); headers.put("source", "IOS"); headers.put("time", time); headers.put("auth", DigestUtils.md5Hex(SECRET + time)); headers.put("accToken", buildAcctoken(userId)); Map params = new HashMap<>(); params.put("userId", userId); params.put("sign", buildSign(params, KEY)); String resp = Http.post(URL + "/user/carrychildrenlist.srv").addParam(params).addHeader(headers).request(); System.out.println(resp); } @Test public void testgetUserInfo() { Map headers = new HashMap<>(); String userId = "1"; String time = String.valueOf(System.currentTimeMillis()); headers.put("source", "IOS"); headers.put("time", time); headers.put("auth", DigestUtils.md5Hex(SECRET + time)); headers.put("accToken", buildAcctoken(userId)); Map params = new HashMap<>(); params.put("userId", userId); params.put("sign", buildSign(params, KEY)); String resp = Http.post(URL + "/user/getUserInfo.srv").addParam(params).addHeader(headers).request(); System.out.println(resp); } @Test public void testBanner() { Map headers = new HashMap<>(); String time = String.valueOf(System.currentTimeMillis()); headers.put("source", "MOBILE"); headers.put("time", time); headers.put("auth", DigestUtils.md5Hex(SECRET + time)); Map params = new HashMap<>(); params.put("module", "huodong"); params.put("clientIp", "127.0.0.1"); params.put("sign", buildSign(params, KEY)); String resp = Http.post(URL + "/sys/activeAdvert.srv").addParam(params).addHeader(headers).request(); System.out.println(resp); } private String buildSign(Map params, String key) { String signing = HttpParams.buildSigning(params, new String[] { "sign" }); if (signing.length() > 0) signing += "&"; signing += key; return DigestUtils.md5Hex(signing.getBytes()).toUpperCase(); } // 加密 private static String buildAcctoken(String str) { try { byte[] bkey = GetKeyBytes("acd"); SecretKey deskey = new SecretKeySpec(bkey, "DESede"); // 加密 Cipher c1 = Cipher.getInstance("DESede"); c1.init(Cipher.ENCRYPT_MODE, deskey); byte[] bytes = c1.doFinal(URLEncoder.encode(str, "utf-8").getBytes()); return Base64.encodeBase64String(bytes); } catch (Exception e) { e.printStackTrace(); return null; } } private static byte[] GetKeyBytes(String strKey) throws Exception { if (null == strKey || strKey.length() < 1) { throw new Exception("key is null or empty!"); } MessageDigest alg = MessageDigest.getInstance("MD5"); alg.update(strKey.getBytes()); byte[] bkey = alg.digest(); int start = bkey.length; byte[] bkey24 = new byte[24]; for (int i = 0; i < start; i++) { bkey24[i] = bkey[i]; } for (int i = start; i < 24; i++) { // 为了与.net16位key兼容 bkey24[i] = bkey[i - start]; } return bkey24; } } ================================================ FILE: src/test/java/test/http/TestHttpUploadFile.java ================================================ package test.http; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.List; import java.util.Map; import org.junit.Test; import cn.ponfee.commons.http.ContentType; import cn.ponfee.commons.http.Http; import cn.ponfee.commons.io.Files; @SuppressWarnings("unchecked") public class TestHttpUploadFile { @Test public void test0() { List resp = Http.post("http://10.118.58.74:8080/battleRoom/shouSkuInfo2") .accept(ContentType.APPLICATION_JSON) // @ResponseBody .request(List.class); System.out.println(resp); } @Test public void test1() { Map resp = Http.post("http://10.118.58.74:8080/battleRoom/importsku") .addPart("skuFile", "importsku.xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", new File("d:/大屏批量配置-data-2.xlsx")) .contentType(ContentType.MULTIPART_FORM_DATA, "UTF-8") // .accept(ContentType.APPLICATION_JSON) // @ResponseBody .request(Map.class); System.out.println(resp); } @Test public void test2() { Http.post("http://10.118.58.74:8080/battleRoom/exportsku").addParam("fileId","ad").download("d:/abc2dd.xlsx"); } public static void main(String[] args) throws FileNotFoundException { InputStream in = new FileInputStream("d:/abc2dd.csv"); try (InputStream input = in; BufferedInputStream buffInput = new BufferedInputStream(input); OutputStream output = new FileOutputStream("D:/1111.csv"); BufferedOutputStream buffOutput = new BufferedOutputStream(output) ) { byte[] buffer = new byte[8192]; for (int len; (len = buffInput.read(buffer)) != Files.EOF;) { buffOutput.write(buffer, 0, len); } } catch (IOException e) { } } } ================================================ FILE: src/test/java/test/http/TestJsoup.java ================================================ package test.http; import java.util.List; import java.util.Map; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import org.junit.Test; import cn.ponfee.commons.http.Http; /** * * @author Ponfee */ public class TestJsoup { private static final String COOKIE = ""; // ========================================================see @Test(timeout = 999999999) public void viewSeeme() { viewSee(0); } @Test(timeout = 999999999) public void deleteSeeme() { // 谁看过我 deleteSee(0); } @Test(timeout = 999999999) public void viewMesee() { viewSee(1); } @Test(timeout = 999999999) public void deleteMesee() { // 我看过谁 deleteSee(1); } // ========================================================send @Test(timeout = 999999999) public void viewSendme() { // 查看收件箱 viewSend(1); } @Test(timeout = 999999999) public void deleteSendme() { // 删除收件箱 delSend(1); } @Test(timeout = 999999999) public void viewMesend() { // 查看发件箱 viewSend(4); } @Test(timeout = 999999999) public void deleteMesend() { // 删除发件箱 delSend(4); } // ================================================================== private void viewSee(int type) { Http page = Http.get("http://profile.zhenai.com/v2/visit/ajax.do").addHeader("COOKIE", COOKIE).addParam("type", type); System.out.println(page.addParam("page", "1").request()); Http delete = Http.get("http://profile.zhenai.com/v2/visit/delete.do").addHeader("COOKIE", COOKIE).addParam("type", type); System.out.println(delete.addParam("memberid", "999999999").request()); } @SuppressWarnings("unchecked") private void deleteSee(int type) { Http page = Http.get("http://profile.zhenai.com/v2/visit/ajax.do").addHeader("COOKIE", COOKIE).addParam("type", type); Http delete = Http.get("http://profile.zhenai.com/v2/visit/delete.do").addHeader("COOKIE", COOKIE).addParam("type", type); for (;;) { try { page.addParam("page", 1); Map map = page.request(Map.class); if ((int) map.get("code") == 0) { System.err.println("page fail"); break; } List> list = (List>) map.get("data"); for (Map item : list) { System.out.print(item.get("memberId") + ", "); Thread.sleep(50); Map delRes = delete.addParam("memberid", item.get("memberId")).request(Map.class); if ((int) delRes.get("data") == 0) { System.err.println("del fail"); } } System.out.println(); Thread.sleep(200); } catch (Exception e) {} } } // ================================================================== private void viewSend(int type) { Http page = Http.get("http://profile.zhenai.com/v2/mail/list.do").addHeader("COOKIE", COOKIE).addParam("showType", type); String html = page.addParam("pageNo", 1).request(); Document doc = Jsoup.parse(html, "UTF-8"); Elements mes = doc.select("section[class='mod-msg-item exp-mail-item'] > a[class='new-icon-close deleteMail-js']"); for (Element elem : mes) { System.out.print(elem.attr("memberid") + ", "); } System.out.println(); Http delete = Http.post("http://profile.zhenai.com/v2/mail/deleteMemberNew.do").addHeader("COOKIE", COOKIE); System.out.println(delete.data("memberId=999999999").request()); } private void delSend(int type) { Http page = Http.get("http://profile.zhenai.com/v2/mail/list.do").addHeader("COOKIE", COOKIE).addParam("showType", type); for (;;) { try { String html = page.addParam("pageNo", 1).request(); Document doc = Jsoup.parse(html, "UTF-8"); Elements mes = doc.select("section[class='mod-msg-item exp-mail-item'] > a[class='new-icon-close deleteMail-js']"); boolean found = false; for (Element elem : mes) { found = true; Http delete = Http.post("http://profile.zhenai.com/v2/mail/deleteMemberNew.do").addHeader("COOKIE", COOKIE); String memberid = elem.attr("memberid"); System.out.print(memberid + ", "); delete.data("memberId=" + memberid).request(); Thread.sleep(50); } System.out.println(); if (!found) { break; } Thread.sleep(200); } catch (Exception e) {} } } } ================================================ FILE: src/test/java/test/http/TestOpenApi.java ================================================ package test.http; import java.util.Map; import java.util.Random; import cn.ponfee.commons.util.UuidUtils; import org.junit.Test; import com.google.common.collect.ImmutableMap; import cn.ponfee.commons.http.ContentType; import cn.ponfee.commons.http.Http; import cn.ponfee.commons.json.Jsons; import cn.ponfee.commons.util.Bytes; import cn.ponfee.commons.util.ObjectUtils; @SuppressWarnings("unchecked") public class TestOpenApi { String host = "http://10.202.64.167:8080"; //String host = "http://10.118.58.74:8000"; @Test public void test1() { String username = "rnT2s9YMKGHkannrr-kNFSZYk-XOLBRKIIjnY62szVR0E31j8SQx50cym_KVXT5m1yjzcHUHh-M8g24q-oHnPa-MgaNaKR8IK-TUa9I9S9Vo8Xseg25EjgrvONK2X4ahKbSBHqrJfyIrPmE7rO70XHnhhma5HtLLXVukhY7eVJk"; String passowrd = "m6bZQrh72pkbBXBmvA-OoSSYRDDtht4mnH4c9VZ77dPnE6qbD8mMJPC_1Wn6Dhr28IfkSv2o3bS44zraBVZ0b-wxMRJ9meZ9i7Vvkwab2uL-eJOvYQ25xjHbdEinsdEyb7rMq0u8Flw9_KqmojEPQVJvSQyV_fJFin8Ns6p_li4"; //String username = "d_OWfBL99lSVQC13OmhNA4H5G65brRYIDXatGnNOkIfAnHmZl-qDP8nf-8twOtaDw3ctnH1hO3CiIW85HxTn3M_CmRmYXmjKkLR-Vcrx8RbsoMuJvACliIwbzo5F8xZGHk4-yk-UP9e_NvbJaE1UZBN5jiY5RyHgNvLuy8MvAeQ"; //String passowrd = "LRDCcDwnzMWKQMqoEza2V4aFa5TPmFACcDeJiDfR3BBjiEGGWfCaMgbi_rE3hPM5kWs2iJdPxK-CrLkWozhcLsyB8S0Seg9r0ybJL5VJ0iVRi2dVqN4rUrj7i2x9lSdTFsyrGqCNLz57dK1TuY02QVw8iEgi6m9wwFgGpbnhoME"; Object data = ImmutableMap.of("head", ImmutableMap.of("app_id", "FQ", "trans_id", UuidUtils.uuid22()), "body", ImmutableMap.of("username", username, "password", passowrd)); String resp = Http.post(host+"/open/api/auth") .contentType(ContentType.APPLICATION_JSON, "UTF-8") .data(Jsons.NORMAL.string(data)) .accept(ContentType.APPLICATION_JSON) .request(); System.out.println(resp); } @Test public void test2() { Object data = ImmutableMap.of("head", ImmutableMap.of("app_id", "FQ", "trans_id", UuidUtils.uuid22()), "body", ImmutableMap.of("username", "rnT2s9YMKGHkannrr-kNFSZYk-XOLBRKIIjnY62szVR0E31j8SQx50cym_KVXT5m1yjzcHUHh-M8g24q-oHnPa-MgaNaKR8IK-TUa9I9S9Vo8Xseg25EjgrvONK2X4ahKbSBHqrJfyIrPmE7rO70XHnhhma5HtLLXVukhY7eVJk")); Map resp = Http.post(host+"/open/api/userinfo") .contentType(ContentType.APPLICATION_JSON, "UTF-8") .data(Jsons.NORMAL.string(data)) .accept(ContentType.APPLICATION_JSON) .request(Map.class); System.out.println(resp); } @Test public void test3() { System.out.println(Http.post(host + "/open/api/test?a=1=32=14=12=4=3214=2&abcdef&" + Math.random()).request()); } public static void main(String[] args) { String captcha = "1234"; long number = new Random(captcha.hashCode()).nextLong(); System.out.println(number); System.out.println(captcha.hashCode()); number = Bytes.crc32(Bytes.toBytes(captcha.hashCode())); System.out.println(number); } } ================================================ FILE: src/test/java/test/http/WSClientTester.java ================================================ package test.http; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import javax.xml.namespace.QName; import javax.xml.soap.MessageFactory; import javax.xml.soap.SOAPBody; import javax.xml.soap.SOAPBodyElement; import javax.xml.soap.SOAPElement; import javax.xml.soap.SOAPEnvelope; import javax.xml.soap.SOAPMessage; import javax.xml.ws.Dispatch; import javax.xml.ws.Service; import org.junit.Test; public class WSClientTester { @Test public void testSoap1() throws Exception { String soap = ""; URL url = new URL("http://www.webxml.com.cn/WebServices/WeatherWebService.asmx?wsdl"); URLConnection conn = url.openConnection(); conn.setUseCaches(false); conn.setDoInput(true); conn.setDoOutput(true); conn.setRequestProperty("Content-Length", Integer.toString(soap.length())); conn.setRequestProperty("Content-Type", "text/xml; charset=utf-8"); conn.setRequestProperty("SOAPAction", "http://WebXml.com.cn/getSupportCity"); OutputStream os = conn.getOutputStream(); OutputStreamWriter osw = new OutputStreamWriter(os, "utf-8"); osw.write(soap); osw.flush(); osw.close(); StringBuilder sTotalString = new StringBuilder(); String sCurrentLine = ""; InputStream is = conn.getInputStream(); BufferedReader l_reader = new BufferedReader(new InputStreamReader(is)); while ((sCurrentLine = l_reader.readLine()) != null) { sTotalString.append(sCurrentLine); } System.out.println(sTotalString.toString()); } @Test public void testSoap2() throws Exception { SOAPMessage message = MessageFactory.newInstance().createMessage(); SOAPEnvelope envelope = message.getSOAPPart().getEnvelope(); /*SOAPHeader header = envelope.getHeader(); if (header == null) header = envelope.addHeader(); header.addHeaderElement( new QName("http://www.tyky.com.cn/cMashup/" , "license" , "tns")).setValue("this a license" );*/ SOAPBody body = envelope.getBody(); SOAPBodyElement elem = body.addBodyElement(new QName("http://www.tyky.com.cn/cMashup/", "UpdateCAStatusResult", "tns")); SOAPElement arrayOfKeyValueElem = elem.addChildElement("ArrayOfKeyValueOfstringstring"); SOAPElement keyValueElem1 = arrayOfKeyValueElem.addChildElement("KeyValueOfstringstring"); keyValueElem1.addChildElement("Key").setValue("123456"); keyValueElem1.addChildElement("value").setValue("org"); SOAPElement keyValueElem2 = arrayOfKeyValueElem.addChildElement("KeyValueOfstringstring"); keyValueElem2.addChildElement("Key").setValue("0001"); keyValueElem2.addChildElement("value").setValue("user"); URL url = new URL("http://112.95.149.106:8088/SystemPadServices.svc?wsdl"); QName qName = new QName("http://tempuri.org/", "SystemPadService"); Service service = Service.create(url, qName); Dispatch dispatch = service.createDispatch(new QName("http://www.tyky.com.cn/cMashup/", "SystemPadServicePort"), SOAPMessage.class, Service.Mode.MESSAGE); SOAPMessage msg = dispatch.invoke(message); System.out.println(msg.getSOAPBody().getElementsByTagName("addResult").item(0).getTextContent()); } @Test public void testPost() throws Exception { StringBuilder sTotalString = new StringBuilder(); URL urlTemp = new URL("http://112.95.149.106:8088/SystemPadServices.svc/UpdateCAStatus/Platform"); HttpURLConnection connection = (HttpURLConnection) urlTemp.openConnection(); connection.setDoOutput(true); connection.setDoInput(true); connection.setRequestProperty("Content-type", "application/json"); connection.setRequestMethod("POST"); OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream(), "UTF-8"); out.write("{\"caUser\":[{\"Key\":\"123123\",\"Value\":\"org\"}]}"); out.flush(); out.close(); String sCurrentLine; InputStream l_urlStream = connection.getInputStream();// 请求 BufferedReader l_reader = new BufferedReader(new InputStreamReader(l_urlStream)); while ((sCurrentLine = l_reader.readLine()) != null) { sTotalString.append(sCurrentLine); } System.out.println(sTotalString.toString()); } /*@Test public void testCXFDynamic() { org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory clientFactory = org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory.newInstance(); String url = "http://www.webxml.com.cn/webservices/qqOnlineWebService.asmx?wsdl"; // http://www.fjyxd.com:17001/DefDispatcher/dispatche?wsdl org.apache.cxf.endpoint.Client clientTemp = clientFactory.createClient(url); try { // 查询QQ在线状态:Y在线;N离线;E号码错误;A商业用户验证失败;V免费用户超过数量; Object[] objects = clientTemp.invoke("qqCheckOnline", "8698053"); System.out.println(com.alibaba.fastjson.JSON.toJSONString(objects)); } catch (Exception e) { e.printStackTrace(); } }*/ /*String endpoint = "http://10.202.16.116:8080/cos_webservice/services/CreateComplainService"; String request = "{\"carryId\":\"070034424401\",\"title\":\"\",\"complainLevel\":\"15\",\"urgency\":\"11\",\"complainChannel\":\"60014708\",\"complainDetailChannel\":\"\",\"complainSource\":\"30\",\"linkman\":\"张先生\",\"linkmanPhone\":\"15623850085\",\"receiveSendChoose\":\"19057\",\"contactLink\":\"101000000\",\"focus\":\"101030000\",\"customerFeedback\":\"101030100\",\"content\":\"此单客户来电反馈,商品其在4月3日上午9点订购的,5号19点多才发出来已经超过48小时了,其现在要求退款,还请将此单商品拦截不要派送,点部作废即可,谢谢SSM姜亮\",\"internalComplainSource\":\"CCS5-SYSTEM\",\"dealWithAreaCode\":\"755Y\",\"problemLink\":\"\",\"monthAccount\":\"\"}"; @Test public void test1() throws Exception { net.bingosoft.complain.CreateComplainServicePortType Service = new net.bingosoft.complain.CreateComplainServicePortTypeProxy(endpoint); String resp = Service.newComplainFromCommon(request); System.out.println(resp); } @Test public void test2() throws Exception { org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory dcf = org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory.newInstance(); org.apache.cxf.endpoint.Client client = dcf.createClient(endpoint + "?wsdl"); Object[] objects = client.invoke("newComplainFromCommon", request); System.out.println(com.alibaba.fastjson.JSON.toJSONString(objects)); }*/ } ================================================ FILE: src/test/java/test/http/jdk/HTTPServerSample.java ================================================ package test.http.jdk; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; import java.time.LocalDateTime; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; /** * like as NanoHTTPD */ @SuppressWarnings("restriction") public class HTTPServerSample { private static final int BACK_LOG = 10; // 允许最大连接数 public static void main(String[] args) { try { HttpServer server = HttpServer.create(new InetSocketAddress(8888), BACK_LOG); server.createContext("/HTTPServerSample", new MyHandler()); // 用MyHandler类处理请求 server.setExecutor(null); // creates a default executor server.start(); // http://localhost:8888/HTTPServerSample } catch (IOException e) { e.printStackTrace(); } } } @SuppressWarnings("restriction") class MyHandler implements HttpHandler { public void handle(HttpExchange exchange) throws IOException { InputStream input = exchange.getRequestBody(); String response = "

    " + LocalDateTime.now() + "

    "; exchange.sendResponseHeaders(200, response.length()); OutputStream output = exchange.getResponseBody(); output.write(response.getBytes()); output.close(); input.close(); } } ================================================ FILE: src/test/java/test/http/jdk/WSProvider.java ================================================ package test.http.jdk; import javax.jws.Oneway; import javax.jws.WebMethod; import javax.jws.WebParam; import javax.jws.WebResult; import javax.jws.WebService; import cn.ponfee.commons.ws.JAXWS; @WebService(targetNamespace = "http://jdk6.webservice/demo", serviceName = "HelloService") public class WSProvider { @WebResult(name = "Greetings") //自定义该方法返回值在WSDL中相关的描述 @WebMethod public String sayHi(@WebParam(name = "MyName") String name) { return "Hi," + name; //@WebParam是自定义参数name在WSDL中相关的描述 } @Oneway //表明该服务方法是单向的,既没有返回值,也不应该声明检查异常 @WebMethod(action = "printSystemTime", operationName = "printSystemTime") //自定义该方法在WSDL中相关的描述 public void printTime() { System.out.println(System.currentTimeMillis()); } private static class WSPublisher implements Runnable { public void run() { //发布WSProvider到http://localhost:8889/demo/WSProvider这个地址,之前必须调用wsgen命令 //生成服务类WSProvider的支持类,命令如下: //wsgen -cp . test.http.jdk.WSProvider JAXWS.publish("http://localhost:8889/demo/WSProvider", new WSProvider()); // 访问 http://localhost:8889/demo/WSProvider?wsdl } } public static void main(String[] args) { Thread wsPublisher = new Thread(new WSPublisher()); wsPublisher.start(); } } ================================================ FILE: src/test/java/test/http/ssl/HttpsCert.java ================================================ package test.http.ssl; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.security.KeyStore; import java.security.MessageDigest; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLException; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; /** * 从网站获取java所需的证书,调用时传入域名。 */ public class HttpsCert { public static void main(String[] args) throws Exception { args = new String[] { "www.baidu.com:443", "changeit" }; String host; int port; char[] passphrase; if ((args.length == 1) || (args.length == 2)) { String[] c = args[0].split(":"); host = c[0]; port = (c.length == 1) ? 443 : Integer.parseInt(c[1]); String p = (args.length == 1) ? "changeit" : args[1]; passphrase = p.toCharArray(); } else { System.out.println("Usage: java InstallCert [:port] [passphrase]"); return; } File file = new File("jssecacerts"); if (file.isFile() == false) { char SEP = File.separatorChar; File dir = new File(System.getProperty("java.home") + SEP + "lib" + SEP + "security"); file = new File(dir, "jssecacerts"); if (file.isFile() == false) { file = new File(dir, "cacerts"); } } System.out.println("Loading KeyStore " + file + "..."); InputStream in = new FileInputStream(file); KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); ks.load(in, passphrase); in.close(); SSLContext context = SSLContext.getInstance("TLS"); TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(ks); X509TrustManager defaultTrustManager = (X509TrustManager) tmf.getTrustManagers()[0]; SavingTrustManager tm = new SavingTrustManager(defaultTrustManager); context.init(null, new TrustManager[] { tm }, null); SSLSocketFactory factory = context.getSocketFactory(); System.out.println("Opening connection to " + host + ":" + port + "..."); SSLSocket socket = (SSLSocket) factory.createSocket(host, port); socket.setSoTimeout(10000); try { System.out.println("Starting SSL handshake..."); socket.startHandshake(); socket.close(); System.out.println(); System.out.println("No errors, certificate is already trusted"); } catch (SSLException e) { System.out.println(); e.printStackTrace(System.out); } X509Certificate[] chain = tm.chain; if (chain == null) { System.out.println("Could not obtain server certificate chain"); return; } BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); System.out.println(); System.out.println("Server sent " + chain.length + " certificate(s):"); System.out.println(); MessageDigest sha1 = MessageDigest.getInstance("SHA1"); MessageDigest md5 = MessageDigest.getInstance("MD5"); for (int i = 0; i < chain.length; i++) { X509Certificate cert = chain[i]; System.out.println(" " + (i + 1) + " Subject " + cert.getSubjectDN()); System.out.println(" Issuer " + cert.getIssuerDN()); sha1.update(cert.getEncoded()); System.out.println(" sha1 " + toHexString(sha1.digest())); md5.update(cert.getEncoded()); System.out.println(" md5 " + toHexString(md5.digest())); System.out.println(); } System.out.println("Enter certificate to add to trusted keystore or 'q' to quit: [1]"); String line = reader.readLine().trim(); int k; try { k = (line.length() == 0) ? 0 : Integer.parseInt(line) - 1; } catch (NumberFormatException e) { System.out.println("KeyStore not changed"); return; } X509Certificate cert = chain[k]; String alias = host + "-" + (k + 1); ks.setCertificateEntry(alias, cert); OutputStream out = new FileOutputStream("jssecacerts"); ks.store(out, passphrase); out.close(); System.out.println(); System.out.println(cert); System.out.println(); System.out.println("Added certificate to keystore 'jssecacerts' using alias '" + alias + "'"); } private static final char[] HEXDIGITS = "0123456789abcdef".toCharArray(); private static String toHexString(byte[] bytes) { StringBuilder sb = new StringBuilder(bytes.length * 3); for (int b : bytes) { b &= 0xff; sb.append(HEXDIGITS[b >> 4]); sb.append(HEXDIGITS[b & 15]); sb.append(' '); } return sb.toString(); } private static class SavingTrustManager implements X509TrustManager { private final X509TrustManager tm; private X509Certificate[] chain; SavingTrustManager(X509TrustManager tm) { this.tm = tm; } public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { throw new UnsupportedOperationException(); } public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { this.chain = chain; tm.checkServerTrusted(chain, authType); } } /* * 调用https的webservice接口,如果不注册证书的话就会报错。下面是注册证书的步骤: 设置证书 */ static { javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(new javax.net.ssl.HostnameVerifier() { public boolean verify(String hostname, javax.net.ssl.SSLSession sslSession) { //域名或ip地址 if (hostname.equals("www.baidu.com")) { return true; } return false; } }); //第二个参数为证书的路径 System.setProperty("javax.net.ssl.trustStore", ".\\jssecacerts"); System.setProperty("javax.net.ssl.trustStorePassword", "changeit"); } } ================================================ FILE: src/test/java/test/http/ssl/HttpsClient.java ================================================ //package test.http.ssl; // //import java.io.BufferedReader; //import java.io.File; //import java.io.FileInputStream; //import java.io.InputStream; //import java.io.InputStreamReader; //import java.security.KeyStore; // //import org.apache.http.HttpEntity; //import org.apache.http.HttpResponse; //import org.apache.http.client.HttpClient; //import org.apache.http.client.methods.HttpGet; //import org.apache.http.conn.scheme.Scheme; //import org.apache.http.conn.ssl.SSLSocketFactory; //import org.apache.http.impl.client.DefaultHttpClient; //import org.apache.http.util.EntityUtils; // //public class HttpsClient { // // private static final String KEY_STORE_TYPE_JKS = "jks"; // private static final String KEY_STORE_TYPE_P12 = "PKCS12"; // private static final String SCHEME_HTTPS = "https"; // private static final int HTTPS_PORT = 8443; // private static final String HTTPS_URL = "https://127.0.0.1:8443/"; // private static final String KEY_STORE_CLIENT_PATH = "D:/ssl/client.p12"; // private static final String KEY_STORE_TRUST_PATH = "D:/ssl/client.truststore"; // private static final String KEY_STORE_PASSWORD = "123456"; // private static final String KEY_STORE_TRUST_PASSWORD = "123456"; // // public static void main(String[] args) throws Exception { // ssl(); // } // // private static void ssl() throws Exception { // HttpClient httpClient = new DefaultHttpClient(); // try { // // 加载客户端密钥库 // KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE_P12); // InputStream ksIn = new FileInputStream(KEY_STORE_CLIENT_PATH); // keyStore.load(ksIn, KEY_STORE_PASSWORD.toCharArray()); // ksIn.close(); // // // 加载客户端信任库 // KeyStore trustStore = KeyStore.getInstance(KEY_STORE_TYPE_JKS); // InputStream tsIn = new FileInputStream(new File(KEY_STORE_TRUST_PATH)); // trustStore.load(tsIn, KEY_STORE_TRUST_PASSWORD.toCharArray()); // tsIn.close(); // // // 初始化socketFactory // SSLSocketFactory socketFactory = new SSLSocketFactory(keyStore, KEY_STORE_PASSWORD, trustStore); // Scheme sch = new Scheme(SCHEME_HTTPS, HTTPS_PORT, socketFactory); // httpClient.getConnectionManager().getSchemeRegistry().register(sch); // HttpGet httpget = new HttpGet(HTTPS_URL); // System.out.println("executing request" + httpget.getRequestLine()); // HttpResponse response = httpClient.execute(httpget); // HttpEntity entity = response.getEntity(); // System.out.println("----------------------------------------"); // System.out.println(response.getStatusLine()); // if (entity != null) { // System.out.println("Response content length: " // + entity.getContentLength()); // BufferedReader bufferedReader = new BufferedReader( // new InputStreamReader(entity.getContent())); // String text; // while ((text = bufferedReader.readLine()) != null) { // System.out.println(text); // } // bufferedReader.close(); // } // EntityUtils.consume(entity); // } catch (Exception e) { // e.printStackTrace(); // } finally { // httpClient.getConnectionManager().shutdown(); // } // } // //} ================================================ FILE: src/test/java/test/http/ssl/SSLClient.java ================================================ package test.http.ssl; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; import java.security.KeyStore; import javax.net.SocketFactory; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; public class SSLClient { private static String CLIENT_KEY_STORE = SSLClient.class.getClassLoader().getResource("").getPath()+"/META-INF/client_ks"; private static String CLIENT_KEY_STORE_PASSWORD = "123456"; public static void main(String[] args) throws Exception { // 打印网络通信信息 System.setProperty("javax.net.debug", "ssl,handshake"); // 设置客户端对服务端的信任库 System.setProperty("javax.net.ssl.trustStore", CLIENT_KEY_STORE); SSLClient client = new SSLClient(); // Socket s = client.clientWithoutCert(); // 单向 Socket s = client.clientWithCert(); // 双向 PrintWriter writer = new PrintWriter(s.getOutputStream()); BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream())); writer.println("你 好!"); writer.flush(); System.out.println(reader.readLine()); s.close(); } // 单向认证 private Socket clientWithoutCert() throws Exception { SocketFactory sf = SSLSocketFactory.getDefault(); Socket s = sf.createSocket("localhost", 8000); return s; } // 双向认证 private Socket clientWithCert() throws Exception { // 加载客户端密钥库 KeyStore ks = KeyStore.getInstance("jceks"); ks.load(new FileInputStream(CLIENT_KEY_STORE), null); KeyManagerFactory kf = KeyManagerFactory.getInstance("SunX509"); kf.init(ks, CLIENT_KEY_STORE_PASSWORD.toCharArray()); SSLContext context = SSLContext.getInstance("TLS"); context.init(kf.getKeyManagers(), null, null); SocketFactory factory = context.getSocketFactory(); Socket s = factory.createSocket("localhost", 8000); return s; } } ================================================ FILE: src/test/java/test/http/ssl/SSLServer.java ================================================ package test.http.ssl; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.Socket; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import javax.net.ServerSocketFactory; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLServerSocket; public class SSLServer extends Thread { private static final String SERVER_KEY_STORE = SSLServer.class.getClassLoader().getResource("").getPath()+"/META-INF/server_ks"; private static final String PWD = "123456"; private Socket socket; public SSLServer(Socket socket) { this.socket = socket; } @Override public void run() { BufferedReader reader = null; PrintWriter writer = null; try { reader = new BufferedReader(new InputStreamReader(this.socket.getInputStream())); writer = new PrintWriter(new OutputStreamWriter(this.socket.getOutputStream())); String receptStr = reader.readLine(); writer.write(receptStr); writer.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException, UnrecoverableKeyException, KeyManagementException { // 设置服务端信任库 System.setProperty("javax.net.ssl.trustStore", SERVER_KEY_STORE); // 加载服务端密钥库 KeyStore keyStore = KeyStore.getInstance("jceks"); keyStore.load(new FileInputStream(SERVER_KEY_STORE), null); // 初始化密钥 KeyManagerFactory factory = KeyManagerFactory.getInstance("SunX509"); factory.init(keyStore, PWD.toCharArray()); // 初始化上下文 SSLContext context = SSLContext.getInstance("TLS"); context.init(factory.getKeyManagers(), null, null); // 创建服务端通信接口 ServerSocketFactory serverSocketFactory = context.getServerSocketFactory(); SSLServerSocket sslServerSocket = (SSLServerSocket)serverSocketFactory.createServerSocket(8000); //sslServerSocket.setNeedClientAuth(false); // 单向 sslServerSocket.setNeedClientAuth(true); // 双向 while (true) { new SSLServer(sslServerSocket.accept()).start(); } } } ================================================ FILE: src/test/java/test/jce/Argon2Test.java ================================================ package test.jce; import java.io.IOException; import org.junit.Ignore; import org.junit.Test; import com.google.common.base.Stopwatch; import de.mkammerer.argon2.Argon2; import de.mkammerer.argon2.Argon2Factory; import de.mkammerer.argon2.Argon2Helper; public class Argon2Test { @Test public void test1() throws IOException { // Create instance Argon2 argon2 = Argon2Factory.create(); // Read password from user char[] password = "passwd".toCharArray(); try { // Hash password String hash = argon2.hash(8, 65536, 1, password); System.out.println(hash); // Verify password if (argon2.verify(hash, password)) { // Hash matches password } else { // Hash doesn't match password } } finally { // Wipe confidential data argon2.wipeArray(password); } } @Test @Ignore public void test2() throws IOException { Argon2 argon2 = Argon2Factory.create(); // 1000 = The hash call must take at most 1000 ms // 65536 = Memory cost // 1 = parallelism int iterations = Argon2Helper.findIterations(argon2, 1000, 65536, 1); System.out.println("Optimal number of iterations: " + iterations); } @Test @Ignore public void test3() throws IOException { Stopwatch stopwatch = Stopwatch.createStarted(); for (int i = 0; i < 10; i++) { Argon2Factory.create().hash(8, 65536, 1, "findIterations".toCharArray()); } System.out.println(stopwatch.stop().toString()); } } ================================================ FILE: src/test/java/test/jce/CryptoProviderTest.java ================================================ package test.jce; import java.util.Map; import cn.ponfee.commons.jce.CryptoProvider; import cn.ponfee.commons.jce.ECParameters; import cn.ponfee.commons.jce.Providers; import cn.ponfee.commons.jce.sm.SM2; import cn.ponfee.commons.jce.symmetric.Algorithm; import cn.ponfee.commons.jce.symmetric.Mode; import cn.ponfee.commons.jce.symmetric.Padding; import cn.ponfee.commons.jce.symmetric.SymmetricCryptorBuilder; import cn.ponfee.commons.util.MavenProjects; public class CryptoProviderTest { public static void main(String[] args) { System.out.println("\n============================RSA crypt=========================="); CryptoProvider rsa = CryptoProvider.rsaPrivateKeyProvider("MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEA9pU2mWa+yJwXF1VQb3WL5uk06Rc2jARYPlcV0JK0x4fMXboR9rpMlpJ9cr4B1wbJdBEa8H+kSgbJROFKsmkhFQIDAQABAkAcGiNP1krV+BwVl66EFWRtW5ShH/kiefhImoos7BtYReN5WZyYyxFCAf2yjMJigq2GFm8qdkQK+c+E7Q3lY6zdAiEA/wVfy+wGQcFh3gdFKhaQ12fBYMCtywxZ3Edss0EmxBMCIQD3h4vfENmbIMH+PX5dAPbRfrBFcx77/MxFORMESN0bNwIgL5kJMD51TICTi6U/u4NKtWmgJjbQOT2s5/hMyYg3fBECIEqRc+qUKenYuXg80Dd2VeSQlMunPZtN8b+czQTKaomLAiEA02qUv/p1dT/jc2BDtp9bl8jDiWFg5FNFcH6bBDlwgts="); String str = MavenProjects.getMainJavaFileAsString(CryptoProvider.class); String data = rsa.encrypt(str); System.out.println("加密后:" + data); System.out.println("解密后:" + rsa.decrypt(data)); System.out.println("\n============================RSA sign=========================="); String signed = rsa.sign(str); System.out.println("签名:"+signed); System.out.println("验签:"+rsa.verify(str, signed)); System.out.println("\n============================AES crypt=========================="); CryptoProvider aes = CryptoProvider.symmetricKeyProvider(SymmetricCryptorBuilder.newBuilder(Algorithm.AES, "z]_5Fi!X$ed4OY8j".getBytes(), Providers.BC) .mode(Mode.CBC).parameter("SVE sm2KeyMap = SM2.generateKeyPair(ecParameter); CryptoProvider sm2 = CryptoProvider.sm2PrivateKeyProvider(ecParameter, SM2.getPublicKey(sm2KeyMap), SM2.getPrivateKey(sm2KeyMap)); data = sm2.encrypt(str); System.out.println("加密后:" + data); System.out.println("解密后:" + sm2.decrypt(data)); System.out.println("\n============================SM2 sign=========================="); signed = sm2.sign(str); System.out.println("签名:"+signed); System.out.println("验签:"+sm2.verify(str, signed)); } } ================================================ FILE: src/test/java/test/jce/DigestTest.java ================================================ package test.jce; import static cn.ponfee.commons.util.SecureRandoms.nextBytes; import java.security.Provider; import org.apache.commons.codec.binary.Hex; import cn.ponfee.commons.jce.DigestAlgorithms; import cn.ponfee.commons.jce.HmacAlgorithms; import cn.ponfee.commons.jce.Providers; import cn.ponfee.commons.jce.digest.DigestUtils; import cn.ponfee.commons.jce.digest.HmacUtils; import cn.ponfee.commons.jce.sm.SM3Digest; import cn.ponfee.commons.jce.sm.SM4; import cn.ponfee.commons.jce.symmetric.Algorithm; import cn.ponfee.commons.jce.symmetric.Mode; import cn.ponfee.commons.jce.symmetric.Padding; import cn.ponfee.commons.jce.symmetric.SymmetricCryptor; import cn.ponfee.commons.jce.symmetric.SymmetricCryptorBuilder; import cn.ponfee.commons.util.SecureRandoms; public class DigestTest { public static void main(String[] args) { byte[] data = SecureRandoms.nextBytes(1204); byte[] key = SecureRandoms.nextBytes(1204); //System.out.println(Hex.encodeHexString(DigestUtils.digest(DigestAlgorithms.SHAKE128, Providers.BC, data))); //System.out.println(Hex.encodeHexString(HmacUtils.crypt(key, data, HmacAlgorithms.HmacSHAKE256))); System.out.println(Hex.encodeHexString(DigestUtils.digest(DigestAlgorithms.SM3, Providers.BC, data))); System.out.println(Hex.encodeHexString(SM3Digest.getInstance().doFinal(data))); //System.out.println(Hex.encodeHexString(HmacUtils.crypt(key, data, HmacAlgorithms.HmacSM3))); System.out.println(Hex.encodeHexString(DigestUtils.digest(DigestAlgorithms.SKEIN_512_256, Providers.BC, data))); System.out.println(Hex.encodeHexString(HmacUtils.crypt(key, data, HmacAlgorithms.HmacSKEIN_512_256))); System.out.println(Hex.encodeHexString(DigestUtils.digest(DigestAlgorithms.SHA512, Providers.BC, data))); System.out.println(Hex.encodeHexString(DigestUtils.digest(DigestAlgorithms.SKEIN_512_512, Providers.BC, data))); System.out.println(Hex.encodeHexString(DigestUtils.digest(DigestAlgorithms.SKEIN_1024_512, Providers.BC, data))); System.out.println(Hex.encodeHexString(DigestUtils.digest(DigestAlgorithms.SKEIN_1024_1024, Providers.BC, data))); System.out.println(Hex.encodeHexString(HmacUtils.crypt(key, data, HmacAlgorithms.HmacSKEIN_1024_1024))); System.out.println("========================"); Provider bc = Providers.BC; key = nextBytes(16); byte[] iv = nextBytes(16); SymmetricCryptor coder = null; coder = SymmetricCryptorBuilder.newBuilder(Algorithm.SM4, key, bc).mode(Mode.CBC) .padding(Padding.X9_23Padding).parameter(iv).build(); data = "1234".getBytes(); byte[] encrypted = coder.encrypt(data); System.out.println(new String(coder.decrypt(encrypted))); System.out.println(new String(SM4.decrypt(true, key, iv, encrypted))); } } ================================================ FILE: src/test/java/test/jce/Paillier.java ================================================ package test.jce; /** * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU 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 General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ import java.math.*; import java.util.*; /** * Paillier Cryptosystem
    *
    * References:
    * [1] Pascal Paillier, * "Public-Key Cryptosystems Based on Composite Degree Residuosity Classes," * EUROCRYPT'99. URL: * http: * //www.gemplus.com/smart/rd/publications/pdf/Pai99pai.pdf
    * * [2] Paillier cryptosystem from Wikipedia. URL: * http://en. * wikipedia.org/wiki/Paillier_cryptosystem * * @author Kun Liu (kunliu1@cs.umbc.edu) * @version 1.0 */ public class Paillier { /** * p and q are two large primes. lambda = lcm(p-1, q-1) = * (p-1)*(q-1)/gcd(p-1, q-1). */ private BigInteger p, q, lambda; /** * n = p*q, where p and q are two large primes. */ public BigInteger n; /** * nsquare = n*n */ public BigInteger nsquare; /** * a random integer in Z*_{n^2} where gcd (L(g^lambda mod n^2), n) = 1. */ private BigInteger g; /** * number of bits of modulus */ private int bitLength; /** * Constructs an instance of the Paillier cryptosystem. * * @param bitLengthVal * number of bits of modulus * @param certainty * The probability that the new BigInteger represents a prime * number will exceed (1 - 2^(-certainty)). The execution time of * this constructor is proportional to the value of this * parameter. */ public Paillier(int bitLengthVal, int certainty) { KeyGeneration(bitLengthVal, certainty); } /** * Constructs an instance of the Paillier cryptosystem with 512 bits of * modulus and at least 1-2^(-64) certainty of primes generation. */ public Paillier() { KeyGeneration(512, 64); } /** * Sets up the public key and private key. * * @param bitLengthVal * number of bits of modulus. * @param certainty * The probability that the new BigInteger represents a prime * number will exceed (1 - 2^(-certainty)). The execution time of * this constructor is proportional to the value of this * parameter. */ public void KeyGeneration(int bitLengthVal, int certainty) { bitLength = bitLengthVal; /* * Constructs two randomly generated positive BigIntegers that are * probably prime, with the specified bitLength and certainty. */ p = new BigInteger(bitLength / 2, certainty, new Random()); q = new BigInteger(bitLength / 2, certainty, new Random()); n = p.multiply(q); nsquare = n.multiply(n); g = new BigInteger("2"); lambda = p.subtract(BigInteger.ONE).multiply(q.subtract(BigInteger.ONE)).divide(p.subtract(BigInteger.ONE).gcd(q.subtract(BigInteger.ONE))); /* check whether g is good. */ if (g.modPow(lambda, nsquare).subtract(BigInteger.ONE).divide(n).gcd(n).intValue() != 1) { System.out.println("g is not good. Choose g again."); System.exit(1); } } /** * Encrypts plaintext m. ciphertext c = g^m * r^n mod n^2. This function * explicitly requires random input r to help with encryption. * * @param m * plaintext as a BigInteger * @param r * random plaintext to help with encryption * @return ciphertext as a BigInteger */ public BigInteger Encryption(BigInteger m, BigInteger r) { return g.modPow(m, nsquare).multiply(r.modPow(n, nsquare)).mod(nsquare); } /** * Encrypts plaintext m. ciphertext c = g^m * r^n mod n^2. This function * automatically generates random input r (to help with encryption). * * @param m * plaintext as a BigInteger * @return ciphertext as a BigInteger */ public BigInteger Encryption(BigInteger m) { BigInteger r = new BigInteger(bitLength, new Random()); return g.modPow(m, nsquare).multiply(r.modPow(n, nsquare)).mod(nsquare); } /** * Decrypts ciphertext c. plaintext m = L(c^lambda mod n^2) * u mod n, where * u = (L(g^lambda mod n^2))^(-1) mod n. * * @param c * ciphertext as a BigInteger * @return plaintext as a BigInteger */ public BigInteger Decryption(BigInteger c) { BigInteger u = g.modPow(lambda, nsquare).subtract(BigInteger.ONE).divide(n).modInverse(n); return c.modPow(lambda, nsquare).subtract(BigInteger.ONE).divide(n).multiply(u).mod(n); } /** * sum of (cipher) em1 and em2 * * @param em1 * @param em2 * @return */ public BigInteger cipher_add(BigInteger em1, BigInteger em2) { return em1.multiply(em2).mod(nsquare); } /** * main function * * @param str * intput string */ public static void main(String[] str) { /* instantiating an object of Paillier cryptosystem */ Paillier paillier = new Paillier(); /* instantiating two plaintext msgs */ BigInteger m1 = new BigInteger("20"); BigInteger m2 = new BigInteger("60"); /* encryption */ BigInteger em1 = paillier.Encryption(m1); BigInteger em2 = paillier.Encryption(m2); /* printout encrypted text */ System.out.println(em1); System.out.println(em2); /* printout decrypted text */ System.out.println(paillier.Decryption(em1).toString()); System.out.println(paillier.Decryption(em2).toString()); /* * test homomorphic properties -> D(E(m1)*E(m2) mod n^2) = (m1 + m2) mod * n */ // m1+m2,求明文数值的和 BigInteger sum_m1m2 = m1.add(m2).mod(paillier.n); System.out.println("original sum: " + sum_m1m2.toString()); // em1+em2,求密文数值的乘 BigInteger product_em1em2 = em1.multiply(em2).mod(paillier.nsquare); System.out.println("encrypted sum: " + product_em1em2.toString()); System.out.println("decrypted sum: " + paillier.Decryption(product_em1em2).toString()); /* test homomorphic properties -> D(E(m1)^m2 mod n^2) = (m1*m2) mod n */ // m1*m2,求明文数值的乘 BigInteger prod_m1m2 = m1.multiply(m2).mod(paillier.n); System.out.println("original product: " + prod_m1m2.toString()); // em1的m2次方,再mod paillier.nsquare BigInteger expo_em1m2 = em1.modPow(m2, paillier.nsquare); System.out.println("encrypted product: " + expo_em1m2.toString()); System.out.println("decrypted product: " + paillier.Decryption(expo_em1m2).toString()); //sum test System.out.println("--------------------------------"); Paillier p = new Paillier(); BigInteger t1 = new BigInteger("21"); System.out.println(t1.toString()); BigInteger t2 = new BigInteger("50"); System.out.println(t2.toString()); BigInteger t3 = new BigInteger("50"); System.out.println(t3.toString()); BigInteger et1 = p.Encryption(t1); System.out.println(et1.toString()); BigInteger et2 = p.Encryption(t2); System.out.println(et2.toString()); BigInteger et3 = p.Encryption(t3); System.out.println(et3.toString()); BigInteger sum = new BigInteger("1"); sum = p.cipher_add(sum, et1); sum = p.cipher_add(sum, et2); sum = p.cipher_add(sum, et3); System.out.println("sum: " + sum.toString()); System.out.println("decrypted sum: " + p.Decryption(sum).toString()); System.out.println("--------------------------------"); } } ================================================ FILE: src/test/java/test/jce/cert/CryptoMessageSyntaxTester.java ================================================ package test.jce.cert; import java.io.FileInputStream; import java.security.PrivateKey; import java.security.cert.X509Certificate; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.junit.Test; import com.google.common.io.Files; import cn.ponfee.commons.http.Http; import cn.ponfee.commons.jce.pkcs.CryptoMessageSyntax; import cn.ponfee.commons.jce.pkcs.PKCS7Signature; import cn.ponfee.commons.jce.security.KeyStoreResolver; import cn.ponfee.commons.jce.security.KeyStoreResolver.KeyStoreType; import cn.ponfee.commons.util.MavenProjects; public class CryptoMessageSyntaxTester { public @Test void testEnvelop() throws Exception { KeyStoreResolver resolver = new KeyStoreResolver(KeyStoreType.PKCS12, new FileInputStream("d:/test/subject.pfx"), "123456"); X509Certificate cert = resolver.getX509CertChain()[0]; PrivateKey privateKey = resolver.getPrivateKey("123456"); //byte[] data = Streams.file2bytes(MavenProjects.getTestJavaFile(CMSTester.class)); byte[] data = Http.get("http://www.baidu.com").download(); long start = System.currentTimeMillis(); //System.out.println(Bytes.hexDump(data)); System.out.println("origin len------------" + data.length); System.out.println("==============================================="); //byte[] enveloped = CryptoMessageSyntax.envelop(data, cert, new ASN1ObjectIdentifier("1.2.840.113549.3.7")); byte[] enveloped = CryptoMessageSyntax.envelop(data, cert, new ASN1ObjectIdentifier("2.16.840.1.101.3.4.1.2")); System.out.println("cost" + (System.currentTimeMillis() - start)); start = System.currentTimeMillis(); //System.out.println(Bytes.hexDump(enveloped)); System.out.println("enveloped len------------" + enveloped.length); System.out.println("==============================================="); byte[] unveloped = CryptoMessageSyntax.unenvelop(enveloped, cert, privateKey); System.out.println(new String(unveloped)); // 用PKCS7Envelope解会报错 //unveloped = PKCS7Envelope.unenvelop(enveloped, cert, privateKey); //System.out.println(new String(unveloped)); //System.out.println("==============================================="); } public @Test void testCMSSign() throws Exception { KeyStoreResolver resolver = new KeyStoreResolver(KeyStoreType.PKCS12, new FileInputStream("d:/test/subject.pfx"), "123456"); X509Certificate[] certChain = resolver.getX509CertChain(); PrivateKey privateKey = resolver.getPrivateKey("123456"); byte[] data = Files.toByteArray(MavenProjects.getTestJavaFile(CryptoMessageSyntaxTester.class)); System.out.println("origin len------------" + data.length); byte[] signed = CryptoMessageSyntax.sign(data, privateKey, certChain); System.out.println("signed len------------" + signed.length); CryptoMessageSyntax.verify(signed); PKCS7Signature.verify(signed); // PKCS7验证CMS签名 } public @Test void testPKCS7Sign() throws Exception { KeyStoreResolver resolver = new KeyStoreResolver(KeyStoreType.PKCS12, new FileInputStream("d:/test/cas_test.pfx"), "1234"); X509Certificate[] certChain = resolver.getX509CertChain(); PrivateKey privateKey = resolver.getPrivateKey("1234"); byte[] data = Files.toByteArray(MavenProjects.getTestJavaFile(CryptoMessageSyntaxTester.class)); System.out.println("origin len------------" + data.length); byte[] signed = PKCS7Signature.sign(privateKey, certChain[0], data, true); System.out.println("signed len------------" + signed.length); PKCS7Signature.verify(signed); CryptoMessageSyntax.verify(signed); // CMS验证PKCS7签名 } } ================================================ FILE: src/test/java/test/jce/cert/KeyStoreResolverTester.java ================================================ package test.jce.cert; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.util.Base64; import java.util.Date; import org.apache.commons.codec.binary.Hex; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.ArrayUtils; import org.junit.Test; import cn.ponfee.commons.jce.RSASignAlgorithms; import cn.ponfee.commons.jce.cert.CertSignedVerifier; import cn.ponfee.commons.jce.cert.X509CertGenerator; import cn.ponfee.commons.jce.cert.X509CertInfo; import cn.ponfee.commons.jce.cert.X509CertUtils; import cn.ponfee.commons.jce.pkcs.PKCS1Signature; import cn.ponfee.commons.jce.security.KeyStoreResolver; import cn.ponfee.commons.jce.security.KeyStoreResolver.KeyStoreType; import cn.ponfee.commons.jce.security.RSACryptor; import cn.ponfee.commons.jce.security.RSACryptor.RSAKeyPair; import cn.ponfee.commons.jce.security.RSAPrivateKeys; import cn.ponfee.commons.jce.security.RSAPublicKeys; import cn.ponfee.commons.resource.ResourceLoaderFacade; import cn.ponfee.commons.util.Bytes; import cn.ponfee.commons.date.Dates; public class KeyStoreResolverTester { public @Test void testLoad() { KeyStoreResolver resolver = new KeyStoreResolver(KeyStoreType.PKCS12, ResourceLoaderFacade.getResource("cas_test.pfx").getStream(), "1234"); String alias = resolver.listAlias().get(0); test0((RSAPrivateKey)resolver.getPrivateKey(alias, "1234"), (RSAPublicKey)resolver.getCertificate(alias).getPublicKey()); String pem = X509CertUtils.exportToPem(resolver.getX509CertChain()[0]); resolver = new KeyStoreResolver(KeyStoreType.JKS); resolver.setCertificateEntry("pem", X509CertUtils.loadPemCert(pem)); System.out.println(resolver.getKeyStore()); } public @Test void testCreateCert() throws Exception { Date before = Dates.toDate("2017-03-01 00:00:00"), after = Dates.toDate("2027-08-01 00:00:00"); RSAKeyPair p1 = RSACryptor.generateKeyPair(2048), p2 = RSACryptor.generateKeyPair(2048); RSASignAlgorithms alg = RSASignAlgorithms.SHA256withRSA; String caPwd = "1234", subjectPwd = "123456"; String _issuer = "CN=ca,OU=hackwp,O=wp,L=BJ,S=BJ,C=CN"; String _subject = "CN=subject,OU=hackwp,O=wp,L=BJ,S=BJ,C=CN"; // -------------------------------------------------- X509Certificate ccert = X509CertGenerator.createRootCert(null, _issuer, alg, p1.getPrivateKey(), p1.getPublicKey(), before, after); KeyStoreResolver ca = new KeyStoreResolver(KeyStoreType.PKCS12); ca.setKeyEntry(X509CertUtils.getCertInfo(ccert, X509CertInfo.SUBJECT_CN), p1.getPrivateKey(), caPwd, new X509Certificate[] { ccert }); test0((RSAPrivateKey)ca.getPrivateKey("1234"), (RSAPublicKey)ca.getCertificate().getPublicKey()); System.out.println("\n\n------------------------------------------------------------\n\n"); // -------------------------------------------------- X509Certificate scert = X509CertGenerator.createSubjectCert(ccert, p1.getPrivateKey(), null, _subject, alg, p2.getPrivateKey(), p2.getPublicKey(), before, after); KeyStoreResolver subject = new KeyStoreResolver(KeyStoreType.PKCS12); String scn = X509CertUtils.getCertInfo(scert, X509CertInfo.SUBJECT_CN); subject.setKeyEntry(scn, p2.getPrivateKey(), subjectPwd, new X509Certificate[] { scert, ccert }); test0((RSAPrivateKey)subject.getPrivateKey(subjectPwd), (RSAPublicKey)subject.getCertificate().getPublicKey()); // -------------------------------------------------- ca.export(new FileOutputStream("d:/test/ca.pfx"), caPwd); subject.export(new FileOutputStream("d:/test/subject.pfx"), subjectPwd); } public @Test void testVerify() throws Exception { X509Certificate root = new KeyStoreResolver(KeyStoreType.PKCS12, new FileInputStream("d:/test/ca.pfx"), "1234").getX509CertChain()[0]; X509Certificate[] subjectChain = new KeyStoreResolver(KeyStoreType.PKCS12, new FileInputStream("d:/test/subject.pfx"), "123456").getX509CertChain(); // 方法一:获取证书的签名内容 System.out.println(PKCS1Signature.verify(root.getTBSCertificate(), root.getSignature(), root)); System.out.println(PKCS1Signature.verify(subjectChain[0].getTBSCertificate(), subjectChain[0].getSignature(), subjectChain[1])); // 方法二:通过证书接口验证cert.verify(publicKey); CertSignedVerifier.verifyIssuingSign(root, root); CertSignedVerifier.verifyIssuingSign(subjectChain[0], subjectChain[1]); } public @Test void test1() throws Exception { Date before = Dates.toDate("2017-03-01 00:00:00"), after = Dates.toDate("2027-08-01 00:00:00"); RSAKeyPair p1 = RSACryptor.generateKeyPair(2048), p2 = RSACryptor.generateKeyPair(2048); RSASignAlgorithms alg = RSASignAlgorithms.SHA256withRSA; String caPwd = "1234", subjectPwd = "123456"; String _issuer = "CN=ca,OU=hackwp,O=wp,L=BJ,S=BJ,C=CN"; String _subject = "CN=subject,OU=hackwp,O=wp,L=BJ,S=BJ,C=CN"; // -------------------------------------------------- X509Certificate ccert = X509CertGenerator.createRootCert(null, _issuer, alg, p1.getPrivateKey(), p1.getPublicKey(), before, after); KeyStoreResolver ca = new KeyStoreResolver(KeyStoreType.PKCS12); byte[] pkcs8Key = Base64.getDecoder().decode(RSAPrivateKeys.toEncryptedPkcs8(p1.getPrivateKey(), caPwd)); ca.setKeyEntry(X509CertUtils.getCertInfo(ccert, X509CertInfo.SUBJECT_CN), pkcs8Key, new X509Certificate[] { ccert }); //ca.setKeyEntry(X509CertUtils.getCertInfo(ccert, X509CertInfo.SUBJECT_CN), p1.getPrivateKey(), caPwd, new X509Certificate[] { ccert }); test0((RSAPrivateKey) ca.getPrivateKey("1234"), (RSAPublicKey) ca.getCertificate().getPublicKey()); System.out.println("\n\n------------------------------------------------------------\n\n"); // -------------------------------------------------- X509Certificate scert = X509CertGenerator.createSubjectCert(ccert, p1.getPrivateKey(), null, _subject, alg, p2.getPrivateKey(), p2.getPublicKey(), before, after); KeyStoreResolver subject = new KeyStoreResolver(KeyStoreType.PKCS12); String scn = X509CertUtils.getCertInfo(scert, X509CertInfo.SUBJECT_CN); pkcs8Key = Base64.getDecoder().decode(RSAPrivateKeys.toEncryptedPkcs8(p2.getPrivateKey(), subjectPwd)); subject.setKeyEntry(scn, pkcs8Key, new X509Certificate[] { scert, ccert }); //subject.setKeyEntry(scn, p2.getPrivateKey(), subjectPwd, new X509Certificate[] { scert, ccert }); test0((RSAPrivateKey) subject.getPrivateKey(subjectPwd), (RSAPublicKey) subject.getCertificate().getPublicKey()); // -------------------------------------------------- ca.export(new FileOutputStream("d:/test/ca.pfx"), caPwd); subject.export(new FileOutputStream("d:/test/subject.pfx"), subjectPwd); } public @Test void test2() throws Exception { RSAKeyPair p1 = RSACryptor.generateKeyPair(2048); String caPwd = "1234"; System.out.println(RSAPrivateKeys.fromEncryptedPkcs8(RSAPrivateKeys.toEncryptedPkcs8(p1.getPrivateKey(), caPwd), caPwd)); System.out.println(RSAPrivateKeys.fromEncryptedPkcs8Pem(RSAPrivateKeys.toEncryptedPkcs8Pem(p1.getPrivateKey(), caPwd), caPwd)); System.out.println(RSAPrivateKeys.fromPkcs1(RSAPrivateKeys.toPkcs1(p1.getPrivateKey()))); System.out.println(RSAPrivateKeys.fromPkcs8(RSAPrivateKeys.toPkcs8(p1.getPrivateKey()))); System.out.println(RSAPublicKeys.fromPkcs1(RSAPublicKeys.toPkcs1(p1.getPublicKey()))); System.out.println(RSAPublicKeys.fromPkcs8(RSAPublicKeys.toPkcs8(p1.getPublicKey()))); System.out.println(RSAPublicKeys.fromPkcs8Pem(RSAPublicKeys.toPkcs8Pem(p1.getPublicKey()))); } // ----------------------------------------------------------------------------------- private static void test0(RSAPrivateKey privateKey, RSAPublicKey publicKey) { try { System.out.println("=============================加密测试=============================="); //byte[] data = "加解密测试".getBytes(); byte[] data = IOUtils.toByteArray(ResourceLoaderFacade.getResource("2.png").getStream()); System.out.println("加密前:"); System.out.println(Bytes.dumpHex(ArrayUtils.subarray(data, 0, 100))); byte[] encodedData = RSACryptor.encrypt(data, publicKey); System.out.println("加密后:"); System.out.println(Bytes.dumpHex(ArrayUtils.subarray(encodedData, 0, 100))); System.out.println("解密后:"); System.out.println(Bytes.dumpHex(ArrayUtils.subarray(RSACryptor.decrypt(encodedData, privateKey), 0, 100))); System.out.println("\n\n===========================签名测试========================="); data = Base64.getDecoder().decode(""); byte[] signed = RSACryptor.signSha1(data, privateKey); String hex = Hex.encodeHexString(signed); System.out.println("签名结果:" + hex.length() + " --> " + hex); System.out.println("验签结果:" + RSACryptor.verifySha1(data, publicKey, signed)); } catch (IOException e) { e.printStackTrace(); } } } ================================================ FILE: src/test/java/test/jce/cert/SM2CertTest.java ================================================ package test.jce.cert; import java.security.KeyPair; import java.security.KeyPairGenerator; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder; import org.junit.Test; public class SM2CertTest { public @Test void test() { try { genCSR("CN=subject,OU=hackwp,O=wp,L=BJ,S=BJ,C=CN", "RSA1024", "BC"); } catch (Exception e) { e.printStackTrace(); } } public static String genCSR(String subject, String alg, String provider) throws Exception { String signalg = ""; int alglength = 0; String keyAlg = ""; if (alg.toUpperCase().equals("RSA1024")) { signalg = "SHA1WithRSA"; alglength = 1024; keyAlg = "RSA"; } else if (alg.toUpperCase().equals("RSA2048")) { signalg = "SHA1WithRSA"; alglength = 2048; keyAlg = "RSA"; } else if (alg.toUpperCase().equals("SM2")) { signalg = "SM3withSM2"; alglength = 256; keyAlg = "SM2"; } KeyPairGenerator keyGen = KeyPairGenerator.getInstance(keyAlg); keyGen.initialize(alglength); KeyPair kp = keyGen.generateKeyPair(); PKCS10CertificationRequestBuilder builder = new PKCS10CertificationRequestBuilder(new X500Name(subject), SubjectPublicKeyInfo.getInstance(kp.getPublic().getEncoded())); JcaContentSignerBuilder jcaContentSignerBuilder = new JcaContentSignerBuilder(signalg); jcaContentSignerBuilder.setProvider(provider); ContentSigner contentSigner = jcaContentSignerBuilder.build(kp.getPrivate()); builder.build(contentSigner); return builder.toString(); } } ================================================ FILE: src/test/java/test/jce/cert/TestPem.java ================================================ package test.jce.cert; import java.io.IOException; import java.io.StringReader; import java.security.Key; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Security; import java.util.Base64; import javax.crypto.Cipher; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.junit.Test; import cn.ponfee.commons.jce.Providers; public class TestPem { public @Test void testPEMKeyPair() throws IOException { Security.addProvider(Providers.BC); String privateKeyString = "-----BEGIN RSA PRIVATE KEY-----\n" + "MIICXQIBAAKBgQDKQtJAyCu5FHwDncK2LB/J5ClJhulGggyc7vwtji6TJHtSJfgD\n" + "4TLpHRIHh/cHqf3brhpQtYB9yjKlwogji/OzedY2mdTdSOP8O6suJYu3QENN2xG/\n" + "HvT8UiYK3feVLbJtukhJm7eSuwfMDsjHh4AK7g11fVs6EmY+foh3mjoKLQIDAQAB\n" + "AoGAR8N/wDaFtOx8t/fAv0xWlxaaQ5lXqYm5GfF9jlhVVCXsj5AjOJUtsCJ9ZCis\n" + "0I5TIR/b/Gj5xyf34nJsRViBxbnf6XdLGyXmzsNxWZoWbM70JaqU3iQKm605/EnD\n" + "vPgrI0AMfc/h6Kog0zLrKWKkna+wE5839yMmm7WPqgvxSc0CQQDoud5e3yZu/1e+\n" + "7piFZZl6StAecl+k10Wq5kzJeVQRffDB3JCca65H/W1EZIzEh76pUNr7SYAIIcbK\n" + "jzOdbj1vAkEA3n0AudM3mBzklLEUSHs1ZSqFkUMNP9MNIikwkZ/9Z2AlhW5gnwiv\n" + "dgeXonTqlTFux4e7uyKZoJpJcKAgmMicIwJBAIMl206TalE6y/Po+UKTUr470rSV\n" + "t5hpR/Va+wK+wMVqt3ZIGaZMeFZRVnYoQ7us06EO05iwftoWTrRvpqKdMTkCQBkE\n" + "QzWhy0l+TjFt69Luj6Vtb5FS0cWQbJSfvwdQzwR1qiJjs9eN+XSzC9jHfq0B3uvu\n" + "lixHirClSIayapfjTrMCQQCM8d97py4u9hCdCpsHBDt54dXkHsDA2abNzaPri/YA\n" + "pNFZGrfXKVGSLFOfsuf7Wj+yL7ew6ZVKOMYdJ+zb9Wwv\n" + "-----END RSA PRIVATE KEY-----"; // 128 PEMParser privatePemParser = new PEMParser(new StringReader(privateKeyString)); Object privateObject = privatePemParser.readObject(); System.out.println("PEMParser.readObject(): " + privateObject.getClass()); if (privateObject instanceof PEMKeyPair) { PEMKeyPair pemKeyPair = (PEMKeyPair) privateObject; System.out.println("private: " + pemKeyPair.getPrivateKeyInfo()); System.out.println("public: " + pemKeyPair.getPublicKeyInfo()); JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(Providers.BC); PublicKey publicKey = converter.getPublicKey(pemKeyPair.getPublicKeyInfo()); PrivateKey privateKey = converter.getPrivateKey(pemKeyPair.getPrivateKeyInfo()); String message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."; System.out.println("\npublicKey key encrypt."); byte[] encripted = encrypt(publicKey, message.getBytes()); System.out.println("encripted: " + Base64.getEncoder().encodeToString(encripted)); byte[] decrypted = decrypt(privateKey, encripted); System.out.println("decrypted: " + new String(decrypted)); System.out.println("\nprivate key encrypt."); encripted = encrypt(privateKey, message.getBytes()); System.out.println("encripted: " + Base64.getEncoder().encodeToString(encripted)); decrypted = decrypt(publicKey, encripted); System.out.println("decrypted: " + new String(decrypted)); } privatePemParser.close(); } public @Test void testSubjectPublicKeyInfo() throws IOException { String publicKeyString = "-----BEGIN PUBLIC KEY-----\n" + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDKQtJAyCu5FHwDncK2LB/J5ClJ\n" + "hulGggyc7vwtji6TJHtSJfgD4TLpHRIHh/cHqf3brhpQtYB9yjKlwogji/OzedY2\n" + "mdTdSOP8O6suJYu3QENN2xG/HvT8UiYK3feVLbJtukhJm7eSuwfMDsjHh4AK7g11\n" + "fVs6EmY+foh3mjoKLQIDAQAB\n" + "-----END PUBLIC KEY-----"; PEMParser publicPemParser = new PEMParser(new StringReader(publicKeyString)); Object publicObject = publicPemParser.readObject(); System.out.println("PEMParser.readObject(): " + publicObject.getClass()); if (publicObject instanceof SubjectPublicKeyInfo) { SubjectPublicKeyInfo publicSubjectPublicKeyInfo = (SubjectPublicKeyInfo) publicObject; System.out.println("public: " + publicSubjectPublicKeyInfo); } publicPemParser.close(); } private static byte[] encrypt(Key pubkey, byte[] data) { try { Cipher rsa; rsa = Cipher.getInstance("RSA", "BC"); rsa.init(Cipher.ENCRYPT_MODE, pubkey); return rsa.doFinal(data); } catch (Exception e) { e.printStackTrace(); return null; } } private static byte[] decrypt(Key decryptionKey, byte[] encrypted) { try { Cipher rsa; rsa = Cipher.getInstance("RSA", "BC"); rsa.init(Cipher.DECRYPT_MODE, decryptionKey); return rsa.doFinal(encrypted); } catch (Exception e) { e.printStackTrace(); return null; } } } ================================================ FILE: src/test/java/test/jce/cert/X500NameTest.java ================================================ package test.jce.cert; import java.io.IOException; import java.security.cert.X509Certificate; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.jce.PrincipalUtil; import org.bouncycastle.jce.X509Principal; import cn.ponfee.commons.jce.cert.X509CertUtils; public class X500NameTest { public static void main(String[] args) throws Exception { X509Certificate cert = X509CertUtils.loadX509Cert(Thread.currentThread().getContextClassLoader().getResourceAsStream("sm2-1.cer")); System.out.println(new X509Principal(cert.getIssuerX500Principal().getEncoded()).getName()); System.out.println(cert.getIssuerDN().getName()); System.out.println(cert.getSubjectDN().getName()); //System.out.println(X500Name.getInstance(PrincipalUtil.getIssuerX509Principal(cert).getName())); System.out.println(new sun.security.x509.X500Name(cert.getIssuerX500Principal().getEncoded()).getName()); //new X500Name(rDNs) //System.out.println(new X500NameBuilder(),.(cert.getIssuerX500Principal().getEncoded()).getName()); } } ================================================ FILE: src/test/java/test/jce/cert/X509CertUtilsTester.java ================================================ package test.jce.cert; import java.io.IOException; import java.security.cert.X509Certificate; import org.apache.commons.lang3.StringUtils; import cn.ponfee.commons.jce.cert.X509CertInfo; import cn.ponfee.commons.jce.cert.X509CertUtils; import cn.ponfee.commons.resource.ResourceLoaderFacade; public class X509CertUtilsTester { public static void main(String[] args) throws IOException { //X509Certificate cert = X509CertUtils.loadX509Cert(ResourceLoaderFacade.getResource("cacert.pem").getStream()); X509Certificate cert = X509CertUtils.loadX509Cert(ResourceLoaderFacade.getResource("sm2-1.cer").getStream()); System.out.println(cert); for (X509CertInfo c : X509CertInfo.values()) System.out.println(StringUtils.rightPad(c.name(), 15) + X509CertUtils.getCertInfo(cert, c)); } } ================================================ FILE: src/test/java/test/jce/crypto/EncryptTester.java ================================================ package test.jce.crypto; import static cn.ponfee.commons.util.SecureRandoms.nextBytes; import java.nio.charset.StandardCharsets; import java.security.Provider; import java.util.Base64; import cn.ponfee.commons.jce.Providers; import cn.ponfee.commons.jce.symmetric.Algorithm; import cn.ponfee.commons.jce.symmetric.Mode; import cn.ponfee.commons.jce.symmetric.Padding; import cn.ponfee.commons.jce.symmetric.SymmetricCryptor; import static cn.ponfee.commons.jce.symmetric.SymmetricCryptorBuilder.newBuilder; public class EncryptTester { public static void main(String[] args) { Provider bc = Providers.BC; test(newBuilder(Algorithm.DESede).build()); test(newBuilder(Algorithm.RC2, nextBytes(5)).build()); test(newBuilder(Algorithm.RC2, nextBytes(16), bc).mode(Mode.ECB).padding(Padding.NoPadding).build()); test(newBuilder(Algorithm.AES, nextBytes(16), bc).padding(Padding.ISO10126_Padding).mode(Mode.ECB).build()); test(newBuilder(Algorithm.AES, nextBytes(16)).build()); test(newBuilder(Algorithm.AES, nextBytes(16), bc).mode(Mode.ECB).padding(Padding.PKCS5Padding).build()); test(newBuilder(Algorithm.AES, nextBytes(16), bc).mode(Mode.OFB).padding(Padding.NoPadding).parameter(nextBytes(16)).build()); test(newBuilder(Algorithm.AES, nextBytes(32), bc).mode(Mode.CBC).padding(Padding.PKCS7Padding).parameter(nextBytes(16)).build()); test(newBuilder(Algorithm.DES, nextBytes(8), bc).mode(Mode.CBC).padding(Padding.NoPadding).parameter(nextBytes(8)).build()); test(newBuilder(Algorithm.DES, nextBytes(8), bc).build()); test(newBuilder(Algorithm.DES, nextBytes(8), bc).mode(Mode.CBC).padding(Padding.PKCS5Padding).parameter(nextBytes(8)).build()); test(newBuilder(Algorithm.DESede, nextBytes(16), bc).build()); test(newBuilder(Algorithm.SM4, nextBytes(16), bc).mode(Mode.CBC).padding(Padding.X9_23Padding).parameter(nextBytes(16)).build()); test(newBuilder(Algorithm.DESede, nextBytes(16), bc).mode(Mode.ECB).padding(Padding.PKCS5Padding).build()); test(newBuilder(Algorithm.DESede, nextBytes(16), bc).mode(Mode.CBC).padding(Padding.PKCS5Padding).parameter(nextBytes(8)).build()); test(newBuilder(Algorithm.SEED, 16, bc).mode(Mode.CBC).padding(Padding.PKCS5Padding).parameter(nextBytes(16)).build()); test(newBuilder(Algorithm.GOST, 32, bc).mode(Mode.CBC).padding(Padding.PKCS5Padding).parameter(nextBytes(8)).build()); test(newBuilder(Algorithm.GOST, 32, bc).mode(Mode.CBC).padding(Padding.PKCS7Padding).parameter(nextBytes(8)).build()); test(newBuilder(Algorithm.GOST, 32, bc).mode(Mode.CBC).padding(Padding.ISO10126_Padding).parameter(nextBytes(8)).build()); test(newBuilder(Algorithm.GOST, 32, bc).mode(Mode.CBC).padding(Padding.ISO10126_2Padding).parameter(nextBytes(8)).build()); test(newBuilder(Algorithm.GOST, 32, bc).mode(Mode.CBC).padding(Padding.ISO7816_4Padding).parameter(nextBytes(8)).build()); test(newBuilder(Algorithm.GOST, 32, bc).mode(Mode.CBC).padding(Padding.X9_23Padding).parameter(nextBytes(8)).build()); test(newBuilder(Algorithm.GOST, 32, bc).mode(Mode.CBC).padding(Padding.TBCPadding).parameter(nextBytes(8)).build()); test(newBuilder(Algorithm.GOST, 32, bc).mode(Mode.CBC).padding(Padding.CS3Padding).parameter(nextBytes(8)).build()); test(newBuilder(Algorithm.SM4, 16, bc).mode(Mode.CBC).padding(Padding.ISO10126_Padding).parameter(nextBytes(16)).build()); test(newBuilder(Algorithm.SM4, 16, bc).mode(Mode.CBC).padding(Padding.ISO10126_2Padding).parameter(nextBytes(16)).build()); //test(newBuilder(Algorithm.GOST, 32, bc).mode(Mode.CBC).padding(Padding.CS1Padding).parameter(nextBytes(8)).build()); // Padding CS1Padding unknown. //test(newBuilder(Algorithm.GOST, 32, bc).mode(Mode.CBC).padding(Padding.CS2Padding).parameter(nextBytes(8)).build()); // Padding CS2Padding unknown. } public static void test(SymmetricCryptor cryptor) { byte[] encrypted = cryptor.encrypt("12345678".getBytes()); // 加密 byte[] origin = cryptor.decrypt(encrypted); // 解密 System.out.println(new String(origin)); } } ================================================ FILE: src/test/java/test/jce/crypto/RSACryptoTester.java ================================================ package test.jce.crypto; import static cn.ponfee.commons.jce.security.RSACryptor.generateKeyPair; import java.io.IOException; import java.math.BigInteger; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.util.Base64; import java.util.Random; import org.apache.commons.codec.binary.Hex; import org.apache.commons.lang3.ArrayUtils; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import com.google.common.io.Files; import cn.ponfee.commons.jce.Providers; import cn.ponfee.commons.jce.digest.DigestUtils; import cn.ponfee.commons.jce.security.RSACryptor; import cn.ponfee.commons.jce.security.RSACryptor.RSAKeyPair; import cn.ponfee.commons.jce.security.RSAPrivateKeys; import cn.ponfee.commons.jce.security.RSAPublicKeys; import cn.ponfee.commons.util.Bytes; import cn.ponfee.commons.util.MavenProjects; public class RSACryptoTester { @BeforeClass public static void beforeClass() { Providers.set(Providers.BC); } @Before public void before() { Providers.set(Providers.BC); } @Test public void test1() throws Exception { RSAKeyPair keyPair = generateKeyPair(4096); // 签名解密---- byte [] bytes = "123456".getBytes(); byte[] signed = RSACryptor.signSha1(bytes, keyPair.getPrivateKey()); byte[] decrypted = RSACryptor.decrypt(signed, keyPair.getPublicKey()); System.out.println(Hex.encodeHexString(DigestUtils.sha1(bytes))); // 7c4a8d09ca3762af61e59520943dc26494f8941b System.out.println(Hex.encodeHexString(decrypted)); // 3021300906052b0e03021a050004147c4a8d09ca3762af61e59520943dc26494f8941b // ------------- System.out.println(keyPair.toPkcs8PrivateKey()); System.out.println(keyPair.toPkcs8PublicKey()); test(keyPair.getPrivateKey(), RSAPrivateKeys.extractPublicKey(keyPair.getPrivateKey())); test(RSAPrivateKeys.fromPkcs1Pem(RSAPrivateKeys.toPkcs1Pem(RSAPrivateKeys.fromPkcs1(keyPair.toPkcs1PrivateKey()))), RSAPublicKeys.fromPkcs8Pem(RSAPublicKeys.toPkcs8Pem(RSAPublicKeys.fromPkcs1(keyPair.toPkcs1PublicKey())))); test(RSAPrivateKeys.fromPkcs1(RSAPrivateKeys.toPkcs1(keyPair.getPrivateKey())), RSAPublicKeys.fromPkcs1(keyPair.toPkcs1PublicKey())); test(RSAPrivateKeys.fromPkcs8(keyPair.toPkcs8PrivateKey()), RSAPublicKeys.fromPkcs8(keyPair.toPkcs8PublicKey())); System.out.println(RSAPrivateKeys.fromEncryptedPkcs8Pem(RSAPrivateKeys.toEncryptedPkcs8Pem(keyPair.getPrivateKey(),"123"), "123")); System.out.println(RSAPrivateKeys.toPkcs1(keyPair.getPrivateKey())); System.out.println(RSAPrivateKeys.toPkcs8(keyPair.getPrivateKey())); System.out.println(RSAPrivateKeys.toPkcs1Pem(keyPair.getPrivateKey())); System.out.println(RSAPrivateKeys.toEncryptedPkcs8Pem(keyPair.getPrivateKey(), "1234")); } @Test public void test2() throws Exception { String privateKeyStr = "MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAocbCrurZGbC5GArEHKlAfDSZi7gFBnd4yxOt0rwTqKBFzGyhtQLu5PRKjEiOXVa95aeIIBJ6OhC2f8FjqFUpawIDAQABAkAPejKaBYHrwUqUEEOe8lpnB6lBAsQIUFnQI/vXU4MV+MhIzW0BLVZCiarIQqUXeOhThVWXKFt8GxCykrrUsQ6BAiEA4vMVxEHBovz1di3aozzFvSMdsjTcYRRo82hS5Ru2/OECIQC2fAPoXixVTVY7bNMeuxCP4954ZkXp7fEPDINCjcQDywIgcc8XLkkPcs3Jxk7uYofaXaPbg39wuJpEmzPIxi3k0OECIGubmdpOnin3HuCP/bbjbJLNNoUdGiEmFL5hDI4UdwAdAiEAtcAwbm08bKN7pwwvyqaCBC//VnEWaq39DCzxr+Z2EIk="; String publicKeyStr = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKHGwq7q2RmwuRgKxBypQHw0mYu4BQZ3eMsTrdK8E6igRcxsobUC7uT0SoxIjl1WveWniCASejoQtn/BY6hVKWsCAwEAAQ=="; RSAPrivateKey privateKey = RSAPrivateKeys.fromPkcs8(privateKeyStr); System.out.println(RSAPublicKeys.toPkcs8(RSAPrivateKeys.extractPublicKey(privateKey))); // publicKeyStr test(privateKey, RSAPrivateKeys.extractPublicKey(privateKey)); RSAPublicKey publicKey = RSAPublicKeys.fromPkcs8(publicKeyStr); test(privateKey, publicKey); test(RSAPublicKeys.inverse(publicKey), RSAPrivateKeys.inverse(privateKey)); } @Test public void test3() throws Exception { String privateKeyStr = "MIICXQIBAAKBgQDlOJu6TyygqxfWT7eLtGDwajtNFOb9I5XRb6khyfD1Yt3YiCgQWMNW649887VGJiGr/L5i2osbl8C9+WJTeucF+S76xFxdU6jE0NQ+Z+zEdhUTooNRaY5nZiu5PgDB0ED/ZKBUSLKL7eibMxZtMlUDHjm4gwQco1KRMDSmXSMkDwIDAQABAoGAfY9LpnuWK5Bs50UVep5c93SJdUi82u7yMx4iHFMc/Z2hfenfYEzu+57fI4fvxTQ//5DbzRR/XKb8ulNv6+CHyPF31xk7YOBfkGI8qjLoq06V+FyBfDSwL8KbLyeHm7KUZnLNQbk8yGLzB3iYKkRHlmUanQGaNMIJziWOkN+N9dECQQD0ONYRNZeuM8zd8XJTSdcIX4a3gy3GGCJxOzv16XHxD03GW6UNLmfPwenKu+cdrQeaqEixrCejXdAFz/7+BSMpAkEA8EaSOeP5Xr3ZrbiKzi6TGMwHMvC7HdJxaBJbVRfApFrE0/mPwmP5rN7QwjrMY+0+AbXcm8mRQyQ1+IGEembsdwJBAN6az8Rv7QnD/YBvi52POIlRSSIMV7SwWvSK4WSMnGb1ZBbhgdg57DXaspcwHsFV7hByQ5BvMtIduHcT14ECfcECQATeaTgjFnqE/lQ22Rk0eGaYO80cc643BXVGafNfd9fcvwBMnk0iGX0XRsOozVt5AzilpsLBYuApa66NcVHJpCECQQDTjI2AQhFc1yRnCU/YgDnSpJVm1nASoRUnU8Jfm3Ozuku7JUXcVpt08DFSceCEX9unCuMcT72rAQlLpdZir876"; String publicKeyStr = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDlOJu6TyygqxfWT7eLtGDwajtNFOb9I5XRb6khyfD1Yt3YiCgQWMNW649887VGJiGr/L5i2osbl8C9+WJTeucF+S76xFxdU6jE0NQ+Z+zEdhUTooNRaY5nZiu5PgDB0ED/ZKBUSLKL7eibMxZtMlUDHjm4gwQco1KRMDSmXSMkDwIDAQAB"; RSAPrivateKey privateKey = RSAPrivateKeys.fromPkcs1(privateKeyStr); System.out.println(RSAPublicKeys.toPkcs8(RSAPrivateKeys.extractPublicKey(privateKey))); // publicKeyStr test(privateKey, RSAPrivateKeys.extractPublicKey(privateKey)); RSAPublicKey publicKey = RSAPublicKeys.fromPkcs8(publicKeyStr); test(privateKey, publicKey); test(RSAPublicKeys.inverse(publicKey), RSAPrivateKeys.inverse(privateKey)); } @Test public void test4() throws Exception { String privateKeyStr = "MIICXAIBAAKBgQDBZ86MIZ2ytsFX9jML+nhYTIC2LdlWzXrN9HV9Ba4yK812S1pgeQpgmt0lFkd378eqb4qb2cC7Z+XT7IOEaSJTp9fP+aKjFG/rKEKG4YPvRD0IKTfm6yDEd9A4bf8a1RxO+5wip9KAGCFdNScwT6DlpDH7gmrzHFWOUPpTsPDNPQIDAQABAoGBAIzz7bl9KmQ8Ay7rNIrPUXPw1YFwasxzVsPRHOsv/6N6/vPuuQBEVsbPNsq3sQB9FURmpFsvWOJ8Nyi7X6JZyPRv9Dal0FuzcLMMU0NLSoW7nAJmzjiU5abS3v5Bj3TfTlAGD7QcXRCM4s5wS18Zm9JPl+vFJkK9Tj1gSoqMhuQVAkEA7g45nL5UgpGny0Ua8xV/PCHq6e6q7VVYFPcWetp8ugJw91ZBJuXzmgp0V+FrmkMuGF2Kx0cauoMMiTqKEosU5wJBAM/791qF07auuXdbxEz9a7ofS3n49sTbMuInsiLWB6m5aRtWb7Wawj2oTvfLOEmIYaMcj1GqFlq7PIYC/rOppDsCQHcQ4Fn4jHZd+cnef5MznlbqM//bYtygAhVCXJkH7LhwfiYHm0CkZQoXzoch9VrL3SNMrhvsAX9mCoAcqnCJ5eMCQCz886RBDmqVoMiQsQV2S7cWzdy0Xax3Paptq7qdUUsFMBcZu1AtCZcMsQgojSRau8PsiZPAltVJau4R98YlC8ECQCMdLS8/lZaOASPocm/fvIno4//NoXrsOi7Wph5vt9OQhQEoYVhdCsk6/28kEy51xAywZ2SjD3IiI/Ygt1uUn8E="; String publicKeyStr = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBZ86MIZ2ytsFX9jML+nhYTIC2LdlWzXrN9HV9Ba4yK812S1pgeQpgmt0lFkd378eqb4qb2cC7Z+XT7IOEaSJTp9fP+aKjFG/rKEKG4YPvRD0IKTfm6yDEd9A4bf8a1RxO+5wip9KAGCFdNScwT6DlpDH7gmrzHFWOUPpTsPDNPQIDAQAB"; RSAPrivateKey privateKey = RSAPrivateKeys.fromPkcs1(privateKeyStr); System.out.println(RSAPublicKeys.toPkcs8(RSAPrivateKeys.extractPublicKey(privateKey))); // publicKeyStr test(privateKey, RSAPrivateKeys.extractPublicKey(privateKey)); RSAPublicKey publicKey = RSAPublicKeys.fromPkcs8(publicKeyStr); test(privateKey, publicKey); test(RSAPublicKeys.inverse(publicKey), RSAPrivateKeys.inverse(privateKey)); } @Test public void test5() throws Exception { String privateKeyStr = "MIICXQIBAAKBgQDlOJu6TyygqxfWT7eLtGDwajtNFOb9I5XRb6khyfD1Yt3YiCgQWMNW649887VGJiGr/L5i2osbl8C9+WJTeucF+S76xFxdU6jE0NQ+Z+zEdhUTooNRaY5nZiu5PgDB0ED/ZKBUSLKL7eibMxZtMlUDHjm4gwQco1KRMDSmXSMkDwIDAQABAoGAfY9LpnuWK5Bs50UVep5c93SJdUi82u7yMx4iHFMc/Z2hfenfYEzu+57fI4fvxTQ//5DbzRR/XKb8ulNv6+CHyPF31xk7YOBfkGI8qjLoq06V+FyBfDSwL8KbLyeHm7KUZnLNQbk8yGLzB3iYKkRHlmUanQGaNMIJziWOkN+N9dECQQD0ONYRNZeuM8zd8XJTSdcIX4a3gy3GGCJxOzv16XHxD03GW6UNLmfPwenKu+cdrQeaqEixrCejXdAFz/7+BSMpAkEA8EaSOeP5Xr3ZrbiKzi6TGMwHMvC7HdJxaBJbVRfApFrE0/mPwmP5rN7QwjrMY+0+AbXcm8mRQyQ1+IGEembsdwJBAN6az8Rv7QnD/YBvi52POIlRSSIMV7SwWvSK4WSMnGb1ZBbhgdg57DXaspcwHsFV7hByQ5BvMtIduHcT14ECfcECQATeaTgjFnqE/lQ22Rk0eGaYO80cc643BXVGafNfd9fcvwBMnk0iGX0XRsOozVt5AzilpsLBYuApa66NcVHJpCECQQDTjI2AQhFc1yRnCU/YgDnSpJVm1nASoRUnU8Jfm3Ozuku7JUXcVpt08DFSceCEX9unCuMcT72rAQlLpdZir876"; String publicKeyStr = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDlOJu6TyygqxfWT7eLtGDwajtNFOb9I5XRb6khyfD1Yt3YiCgQWMNW649887VGJiGr/L5i2osbl8C9+WJTeucF+S76xFxdU6jE0NQ+Z+zEdhUTooNRaY5nZiu5PgDB0ED/ZKBUSLKL7eibMxZtMlUDHjm4gwQco1KRMDSmXSMkDwIDAQAB"; RSAPrivateKey privateKey = RSAPrivateKeys.fromPkcs1(privateKeyStr); System.out.println(RSAPrivateKeys.toPkcs1(privateKey)); System.out.println(RSAPrivateKeys.toPkcs8(privateKey)); System.out.println(RSAPublicKeys.toPkcs8(RSAPrivateKeys.extractPublicKey(privateKey))); System.out.println(RSAPublicKeys.toPkcs8(RSAPublicKeys.fromPkcs8(publicKeyStr))); System.out.println(RSAPublicKeys.toPkcs1(RSAPrivateKeys.extractPublicKey(privateKey))); System.out.println(RSAPublicKeys.toPkcs1(RSAPublicKeys.fromPkcs8(publicKeyStr))); test(privateKey, RSAPublicKeys.fromPkcs1(RSAPublicKeys.toPkcs1(RSAPublicKeys.fromPkcs8(publicKeyStr)))); } @Test public void test6() throws Exception { String privateKeyStr = "MIIEpgIBAAKCAQEAudN1YfE4lOSBoRTZC4ErZ8H2IedOjnSKOk9T1MhmefBVkVwi4emT+walqsowtLw6pHP1whoOBbkqZcQjhz5EgDiMJQ8fmeFYjV5eb+5jZVYefdUKYw1kPqt1aOLysnLpLZRaIQfR+6yHJRbBRWEKWvXWkJ1/3nZulYs4UHpJlPc/1pFyppOsYji/xZj74bCbQkMtasngcuemXphU5zxB/ll2P/jR9x3zxjM8/6/nrF3c4dSFDzMzh8qTAwumT5L3G6tQTErZ4bKsDL/AU1wBUx6Y49iPNxnf8IWG+k7TBK9rIgPIQ/rQUKVJ+3NnoEEWQyPvSwS2yFPnqoDq9qijgwIDAQABAoIBAQC3qCqndkU5wu3rSjOJj0xa6/RbZcTaPowvPR/ZeYbulX28gJdpN/Wtb9BkkBi7SB2dU45dHGsndO5WThffHseNAlZgeiX9bB6c+dvUPIO4L/lK3De71gxxc/xCgarke3XCOpEpfBUo7EdVfLvf2hzl8XryyvcJ43tACazKvVHkCxir/tRkHECEt8ssOibvG2GeWpP3pNhAWcfH7ienVnYBerzLCn7ojtkQM11Z2CkoAMT3D+OcfHewkHhhkYYDJMgEsK6awiZhHHr8kgabzYOhO7CKlSURFOS4WmWQQjiBd6lc4m4IC4l5db4FHd1fZ2Kf/kGD+AKsb7pW/f+azvCBAoGBAPDB6l0hQxIB3ZxCV7z608huSGs/oMUGrwTlrYMnupQTeAqBEDPpO80IEhc1LzQy84fN70Md5JfhqUlINvbclr4bs9qSCIPIl/Cv0FSX5zjswO7xW17dV7K2QxVCturqHA08732EW7WHRda7EHVO0KM5V3XlPwE/1/aKNxljZP+RAoGBAMWXPU1xRCCDtFHFSPrSbYXwyIXtJz32GhYvhdjTIiNxrvrweT+9XBUCJ7vlQwx64MWTziJCYHjJDVlHMa1kLTwbxb4OrYTVUZLvOwIifCLpvmzyKooLNcx8TGmMj7LX8FSAgFZHjznhKz/8iJAXBVlh2mFj5pkLAxWqnk8hKI/TAoGBALwF1XBx/51ak6XrMfZGtYr8hdYsVPRKafkbHk0lg9MM+VzKusqvxaI0QVyajojnmcVfkRILkHEFLV4r5bEZSSijHez+y2OQDwlLZRoLn+qXC34QRFlr54eMTAuYlJ4Vw16bTjXqXm0Afgxa/1l9+fbfW2yZYoEpSRIjkzBirYfhAoGBAJIuCr9Rbap0ZaIdR5mwtjBia6eRRPf1K2WAcRBxWw9H2sFxyPIcAJTWTFkZCtqfyczCRb1YyBB0BbkoD5uMwl522Xt7VmowezIuZMR2iMo3jZcCLfCEzJ9k0g9AW0tfsECD9O5f8JlMeXfUN6AKN/3hg/OLOh29ZOHRoV8/U8fbAoGBAKLvvaAlcSaB57GC3BWc+ckQjCVItM+sunPePY4WSCytT6zjyB6EQBQzfimjY7O4xktDQYb9b9m0WNKAHTLGf+0Otk2iMRhL8dKLICodwoHNR+4izQTzcuHpouMNndRLXTqrMiBIIO0k0LtvQzDffPi1AYw/PNwruvZRmaFsOLWu"; String publicKeyStr = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAudN1YfE4lOSBoRTZC4ErZ8H2IedOjnSKOk9T1MhmefBVkVwi4emT+walqsowtLw6pHP1whoOBbkqZcQjhz5EgDiMJQ8fmeFYjV5eb+5jZVYefdUKYw1kPqt1aOLysnLpLZRaIQfR+6yHJRbBRWEKWvXWkJ1/3nZulYs4UHpJlPc/1pFyppOsYji/xZj74bCbQkMtasngcuemXphU5zxB/ll2P/jR9x3zxjM8/6/nrF3c4dSFDzMzh8qTAwumT5L3G6tQTErZ4bKsDL/AU1wBUx6Y49iPNxnf8IWG+k7TBK9rIgPIQ/rQUKVJ+3NnoEEWQyPvSwS2yFPnqoDq9qijgwIDAQAB"; RSAPrivateKey privateKey = RSAPrivateKeys.fromPkcs1(privateKeyStr); System.out.println(RSAPublicKeys.toPkcs8(RSAPrivateKeys.extractPublicKey(privateKey))); // publicKeyStr test(privateKey, RSAPrivateKeys.extractPublicKey(privateKey)); RSAPublicKey publicKey = RSAPublicKeys.fromPkcs8(publicKeyStr); test(privateKey, publicKey); test(RSAPublicKeys.inverse(publicKey), RSAPrivateKeys.inverse(privateKey)); } @Test public void test7() { String privateKeyStr = "MIIEpgIBAAKCAQEAudN1YfE4lOSBoRTZC4ErZ8H2IedOjnSKOk9T1MhmefBVkVwi4emT+walqsowtLw6pHP1whoOBbkqZcQjhz5EgDiMJQ8fmeFYjV5eb+5jZVYefdUKYw1kPqt1aOLysnLpLZRaIQfR+6yHJRbBRWEKWvXWkJ1/3nZulYs4UHpJlPc/1pFyppOsYji/xZj74bCbQkMtasngcuemXphU5zxB/ll2P/jR9x3zxjM8/6/nrF3c4dSFDzMzh8qTAwumT5L3G6tQTErZ4bKsDL/AU1wBUx6Y49iPNxnf8IWG+k7TBK9rIgPIQ/rQUKVJ+3NnoEEWQyPvSwS2yFPnqoDq9qijgwIDAQABAoIBAQC3qCqndkU5wu3rSjOJj0xa6/RbZcTaPowvPR/ZeYbulX28gJdpN/Wtb9BkkBi7SB2dU45dHGsndO5WThffHseNAlZgeiX9bB6c+dvUPIO4L/lK3De71gxxc/xCgarke3XCOpEpfBUo7EdVfLvf2hzl8XryyvcJ43tACazKvVHkCxir/tRkHECEt8ssOibvG2GeWpP3pNhAWcfH7ienVnYBerzLCn7ojtkQM11Z2CkoAMT3D+OcfHewkHhhkYYDJMgEsK6awiZhHHr8kgabzYOhO7CKlSURFOS4WmWQQjiBd6lc4m4IC4l5db4FHd1fZ2Kf/kGD+AKsb7pW/f+azvCBAoGBAPDB6l0hQxIB3ZxCV7z608huSGs/oMUGrwTlrYMnupQTeAqBEDPpO80IEhc1LzQy84fN70Md5JfhqUlINvbclr4bs9qSCIPIl/Cv0FSX5zjswO7xW17dV7K2QxVCturqHA08732EW7WHRda7EHVO0KM5V3XlPwE/1/aKNxljZP+RAoGBAMWXPU1xRCCDtFHFSPrSbYXwyIXtJz32GhYvhdjTIiNxrvrweT+9XBUCJ7vlQwx64MWTziJCYHjJDVlHMa1kLTwbxb4OrYTVUZLvOwIifCLpvmzyKooLNcx8TGmMj7LX8FSAgFZHjznhKz/8iJAXBVlh2mFj5pkLAxWqnk8hKI/TAoGBALwF1XBx/51ak6XrMfZGtYr8hdYsVPRKafkbHk0lg9MM+VzKusqvxaI0QVyajojnmcVfkRILkHEFLV4r5bEZSSijHez+y2OQDwlLZRoLn+qXC34QRFlr54eMTAuYlJ4Vw16bTjXqXm0Afgxa/1l9+fbfW2yZYoEpSRIjkzBirYfhAoGBAJIuCr9Rbap0ZaIdR5mwtjBia6eRRPf1K2WAcRBxWw9H2sFxyPIcAJTWTFkZCtqfyczCRb1YyBB0BbkoD5uMwl522Xt7VmowezIuZMR2iMo3jZcCLfCEzJ9k0g9AW0tfsECD9O5f8JlMeXfUN6AKN/3hg/OLOh29ZOHRoV8/U8fbAoGBAKLvvaAlcSaB57GC3BWc+ckQjCVItM+sunPePY4WSCytT6zjyB6EQBQzfimjY7O4xktDQYb9b9m0WNKAHTLGf+0Otk2iMRhL8dKLICodwoHNR+4izQTzcuHpouMNndRLXTqrMiBIIO0k0LtvQzDffPi1AYw/PNwruvZRmaFsOLWu"; String publicKeyStr = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAudN1YfE4lOSBoRTZC4ErZ8H2IedOjnSKOk9T1MhmefBVkVwi4emT+walqsowtLw6pHP1whoOBbkqZcQjhz5EgDiMJQ8fmeFYjV5eb+5jZVYefdUKYw1kPqt1aOLysnLpLZRaIQfR+6yHJRbBRWEKWvXWkJ1/3nZulYs4UHpJlPc/1pFyppOsYji/xZj74bCbQkMtasngcuemXphU5zxB/ll2P/jR9x3zxjM8/6/nrF3c4dSFDzMzh8qTAwumT5L3G6tQTErZ4bKsDL/AU1wBUx6Y49iPNxnf8IWG+k7TBK9rIgPIQ/rQUKVJ+3NnoEEWQyPvSwS2yFPnqoDq9qijgwIDAQAB"; RSAPrivateKey privateKey = RSAPrivateKeys.fromPkcs1(privateKeyStr); RSAPublicKey publicKey = RSAPublicKeys.fromPkcs8(publicKeyStr); byte[] data = "1234".getBytes(); String sha1 = DigestUtils.sha1Hex(data); byte[] signature = RSACryptor.signSha1(data, privateKey); byte[] array = RSACryptor.decrypt(signature, publicKey); System.out.println(sha1); System.out.println(Hex.encodeHexString(array)); } @Test // RSA乘法同态加密 public void test8() { RSAKeyPair kp = RSACryptor.generateKeyPair(1024); RSAPublicKey publicKey = kp.getPublicKey(); RSAPrivateKey privateKey = kp.getPrivateKey(); BigInteger n = privateKey.getModulus(); BigInteger d = privateKey.getPrivateExponent(); BigInteger e = publicKey.getPublicExponent(); Random ran = new Random(); long num1 = ran.nextInt(65537), num2 = ran.nextInt(65537), num3 = ran.nextInt(65537), product = num1*num2*num3; BigInteger r1 = new BigInteger(num1 + "").modPow(e, n); BigInteger r2 = new BigInteger(num2 + "").modPow(e, n); BigInteger r3 = new BigInteger(num3 + "").modPow(e, n); Assert.assertEquals(product, r1.multiply(r2).multiply(r3).modPow(d, n).longValue()); // num1*num2*num3 System.out.println(product); } private static void test(RSAPrivateKey privateKey, RSAPublicKey publicKey) throws IOException { byte[] data = Files.toByteArray(MavenProjects.getTestJavaFile(RSACryptoTester.class)); System.out.println("=============================加密测试=============================="); long i = System.currentTimeMillis(); System.out.println("原文:"); System.out.println(Bytes.dumpHex(ArrayUtils.subarray(data, 0, 100))); byte[] encodedData = RSACryptor.encrypt(data, publicKey); System.out.println("密文:"); System.out.println(Bytes.dumpHex(ArrayUtils.subarray(encodedData, 0, 100))); System.out.println("解密:"); System.out.println(Bytes.dumpHex(ArrayUtils.subarray(RSACryptor.decrypt(encodedData, privateKey), 0, 100))); System.out.println("=============================加密测试=============================="); i = System.currentTimeMillis(); System.out.println("原文:"); System.out.println(Bytes.dumpHex(ArrayUtils.subarray(data, 0, 100))); encodedData = RSACryptor.encrypt(data, privateKey); System.out.println("密文:"); System.out.println(Bytes.dumpHex(ArrayUtils.subarray(encodedData, 0, 100))); System.out.println("解密:"); System.out.println(Bytes.dumpHex(ArrayUtils.subarray(RSACryptor.decrypt(encodedData, publicKey), 0, 100))); System.out.println("===========================签名测试========================="); data = Base64.getDecoder().decode(""); byte[] signed = RSACryptor.signSha1(data, privateKey); System.out.println("签名数据:len->" + signed.length + " , b64->" + Base64.getEncoder().encodeToString(signed)); System.out.println("验签结果:" + RSACryptor.verifySha1(data, publicKey, signed)); System.out.println("cost time: " + (System.currentTimeMillis() - i)); } } ================================================ FILE: src/test/java/test/jce/demo/CertService.java ================================================ //package test.jce.demo; // //import java.util.Map; //import java.util.Date; //import java.util.Vector; //import java.util.HashMap; //import java.util.Hashtable; //import java.math.BigInteger; //import java.security.KeyPair; //import java.security.Security; //import java.security.PublicKey; //import java.security.Signature; //import java.io.FileOutputStream; //import java.security.PrivateKey; //import org.bouncycastle.asn1.DERSet; //import java.security.KeyPairGenerator; //import org.bouncycastle.asn1.DERUTCTime; //import org.bouncycastle.asn1.DERInteger; //import org.bouncycastle.asn1.DERSequence; //import org.bouncycastle.asn1.DERBitString; //import org.bouncycastle.asn1.x500.X500Name; //import org.bouncycastle.asn1.x509.KeyUsage; //import org.bouncycastle.asn1.x509.Attribute; //import org.bouncycastle.asn1.DERTaggedObject; //import org.bouncycastle.util.encoders.Base64; //import org.bouncycastle.asn1.x509.GeneralName; //import org.bouncycastle.asn1.x509.GeneralNames; //import org.bouncycastle.asn1.x509.CRLDistPoint; //import org.bouncycastle.asn1.x509.KeyPurposeId; //import org.bouncycastle.asn1.DERPrintableString; //import org.bouncycastle.asn1.DERGeneralizedTime; //import org.bouncycastle.asn1.x509.GeneralSubtree; //import org.bouncycastle.asn1.x509.PolicyMappings; //import org.bouncycastle.asn1.DERObjectIdentifier; //import org.bouncycastle.asn1.ASN1EncodableVector; //import org.bouncycastle.asn1.x509.X509Extensions; //import org.bouncycastle.asn1.x509.NameConstraints; //import org.bouncycastle.asn1.x509.ExtendedKeyUsage; //import org.bouncycastle.asn1.x509.BasicConstraints; //import org.bouncycastle.asn1.x509.AccessDescription; //import org.bouncycastle.asn1.x509.PolicyInformation; //import org.bouncycastle.asn1.x509.DistributionPoint; //import org.bouncycastle.asn1.x509.AlgorithmIdentifier; //import org.bouncycastle.asn1.x509.SubjectKeyIdentifier; //import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; //import org.bouncycastle.asn1.x509.DistributionPointName; //import org.bouncycastle.asn1.x509.PrivateKeyUsagePeriod; //import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier; //import org.bouncycastle.asn1.x509.TBSCertificateStructure; //import org.bouncycastle.asn1.x509.X509ExtensionsGenerator; //import org.bouncycastle.jce.provider.BouncyCastleProvider; //import org.bouncycastle.asn1.x509.X509CertificateStructure; //import org.bouncycastle.jce.provider.X509CertificateObject; //import org.bouncycastle.asn1.x509.V3TBSCertificateGenerator; //import org.bouncycastle.asn1.x509.SubjectDirectoryAttributes; // //public class CertService { // // public static void main(String[] agrs) throws Exception { // // Security.addProvider(new BouncyCastleProvider()); // 加载BC Provider // // int certValidity = 1; // 证书有效期 // // X500Name subject = new X500Name("CN=root,O=O,OU=OU"); //证书主题 // // X500Name issuer = new X500Name("CN=root,O=O,OU=OU"); //证书颁发者 // // KeyPair kp = genKeyPair("RSA"); // 产生RSA算法密钥对 // // PrivateKey priKey = kp.getPrivate(); // PublicKey pubKey = kp.getPublic(); // // // 签发根证书 // TBSCertificateStructure tbsCert = createTbsCert(certValidity, issuer, subject, pubKey, pubKey); // // // 签名算法 // AlgorithmIdentifier alg = tbsCert.getSignature(); // // // 对证书主题进行签名 // byte[] signData = sign(pubKey.getAlgorithm(), tbsCert.getEncoded(), priKey); // // // 构建证书 // ASN1EncodableVector asn1Vector = new ASN1EncodableVector(); // asn1Vector.add(tbsCert.getDERObject()); // asn1Vector.add(alg.getDERObject()); // asn1Vector.add(new DERBitString(signData)); // X509CertificateObject cert = new X509CertificateObject(new X509CertificateStructure(new DERSequence(asn1Vector))); // // // 打印证书的base64编码 // System.out.println("certBuf:" // + new String(Base64.encode(cert.getEncoded()))); // cert.verify(cert.getPublicKey()); // 验证签名,无异常验签通过 // // FileOutputStream fos = new FileOutputStream("cert.cer"); // fos.write(cert.getEncoded()); // fos.flush(); // fos.close(); // } // // // 创建证书 // public static TBSCertificateStructure createTbsCert(int certValidity, // X500Name IssuerName, X500Name subjectName, PublicKey certPubKey, // PublicKey issuerKey) throws Exception { // // 证书有效期 // Date notBefore = new Date(); // long validity = certValidity * 1000 * 60 * 60 * 24; // Date notAfter = new Date(notBefore.getTime() + validity); // // // 证书公钥信息 // SubjectPublicKeyInfo pubInfo = SubjectPublicKeyInfo.getInstance(certPubKey.getEncoded()); // // // 组装证书主体 // V3TBSCertificateGenerator genCert = new V3TBSCertificateGenerator(); // genCert.setStartDate(new DERUTCTime(notBefore)); // genCert.setSubject(new X500Name("CN=root,O=O,OU=OU")); // genCert.setIssuer(new X500Name("CN=root,O=O,OU=OU")); // genCert.setEndDate(new DERUTCTime(notAfter)); // genCert.setSerialNumber(new DERInteger((int) System.currentTimeMillis())); // genCert.setSubjectPublicKeyInfo(pubInfo); // genCert.setSignature(new AlgorithmIdentifier("1.2.840.113549.1.1.5")); // SHA1withRSA // X509Extensions exts = genCertExtensions(issuerKey, certPubKey); // 创建证书扩展项 // genCert.setExtensions(exts); // // return genCert.generateTBSCertificate(); // } // // @SuppressWarnings("deprecation") // public static X509Extensions genCertExtensions(PublicKey issuerKey, // PublicKey subjectKey) throws Exception { // X509ExtensionsGenerator extGen = new X509ExtensionsGenerator(); // // /** // * 基本用途限制 // * // * BasicConstraints := SEQUENCE { cA BOOLEAN DEFAULT FALSE, 是否是CA证书 // * pathLenConstraint INTEGER (0..MAX) OPTIONAL 证书链长度约束 } // */ // BasicConstraints basicConstraints = new BasicConstraints(false, 0); // extGen.addExtension(X509Extensions.BasicConstraints, true, basicConstraints); // // /** // * 密钥用法 The KeyUsage object. // * // * id-ce-keyUsage OBJECT IDENTIFIER ::= { id-ce 15 } // * // * KeyUsage ::= BIT STRING { // * digitalSignature (0), 数据验签:除了签发证书/签发CRL之外的各种数字签名操作, // * 数据完整性、身份鉴别、数据源鉴别。检查算法可以做签名 digitalSignature位被断 // * 言,当主题公开密钥用一数字的签名算法来支持安全 服务而非抗抵赖性(位1)、签名 // * 证书(位5)或者签名撤销信息(位6) 的时候。数字的签名算法常常为实体和数据起源 // * (做)完整性验证。 // * // * nonRepudiation (1), 不可抵赖性:证书对应的私钥,用于生成非否认的证据,证书 // * 用于验证非否认证据。 // * // * keyEncipherment (2), 密钥加密:用于加密传输其他的密钥。检查可以加密密钥 // * keyEncipherment位被断言,当主题公开密钥被用于密钥传输的时候。例如,当一 // * RSA密钥用于密钥管理时候,那么这位将被断言。 // * // * dataEncipherment (3), 数据加密:用于直接加解密应用数据, // * 通常都是公钥->对称密钥->应用数据。一般很少用这种方式的应用,因为:在密钥长度 // * 安全的情况下,公钥密钥计算都是慢于对称密钥计算。检查可以加密数据 当主题公开密 // * 钥用于(除了密码学的密钥)将用户数据加密使用的时候,dataEncipherment位被断言。 // * keyAgreement (4), 密钥协商:在通信方之间协商对称密钥,例如:TLS、 // * Diffie-Hellman的密钥协商。不同于keyEncipherment. KeyEncipherment是直接对 // * Session Key进行加密 KeyAgreement是协商,别公钥加密的数据并不是直接作为密钥, // * 而是经过了一个多次步骤的过程,再导出Session Key。 // * // * keyCertSign (5), 签发证书:用于签发CA证书。 keyAgreement位被断言,当主题 // * 公开密钥为用于密钥协议的时候。例如,当一Diffie Hellman密钥是要为密钥管理被使 // * 用的时候,那么这位将被断言。 // * // * cRLSign (6), 签发crl:签发CRL,CA或者CRL Issuer // * // * encipherOnly (7), 证书公钥在密钥协商过程中,仅仅进行加密计算,配合 // * KeyAgreement用法才有意义 // * // * decipherOnly (8) }证书公钥在密钥协商过程中,仅仅进行解密计算,配合 // * KeyAgreement用法才有意义 // * } // * // */ // // int usage = KeyUsage.digitalSignature; // usage += KeyUsage.nonRepudiation; // usage += KeyUsage.keyEncipherment; // usage += KeyUsage.dataEncipherment; // usage += KeyUsage.keyAgreement; // usage += KeyUsage.keyCertSign; // usage += KeyUsage.cRLSign; // usage += KeyUsage.encipherOnly; // usage += KeyUsage.decipherOnly; // // KeyUsage keyUsage = new KeyUsage(usage); // // extGen.addExtension(X509Extensions.KeyUsage, true, keyUsage); // // /** // * 增强型密钥用法 The extendedKeyUsage object. // * // *
    //         *      extendedKeyUsage ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId
    //         * 
    // */ // ASN1EncodableVector asn1ExtKeyUsage = new ASN1EncodableVector(); // asn1ExtKeyUsage.add(KeyPurposeId.anyExtendedKeyUsage); // 任何用途 // asn1ExtKeyUsage.add(KeyPurposeId.id_kp_serverAuth); // SSL的服务器认证 // asn1ExtKeyUsage.add(KeyPurposeId.id_kp_clientAuth); // SSL的客户端认证 // asn1ExtKeyUsage.add(KeyPurposeId.id_kp_codeSigning); // 代码签名 // asn1ExtKeyUsage.add(KeyPurposeId.id_kp_emailProtection); // 电子邮件的加解密、签名等 // asn1ExtKeyUsage.add(KeyPurposeId.id_kp_ipsecEndSystem); // // asn1ExtKeyUsage.add(KeyPurposeId.id_kp_ipsecTunnel); // // asn1ExtKeyUsage.add(KeyPurposeId.id_kp_ipsecUser); // // asn1ExtKeyUsage.add(KeyPurposeId.id_kp_timeStamping); // 时间戳 认证 // asn1ExtKeyUsage.add(KeyPurposeId.id_kp_OCSPSigning); // ocsp证书认证 // /* // * asn1ExtKeyUsage.add(KeyPurposeId.id_kp_dvcs); // * asn1ExtKeyUsage.add(KeyPurposeId.id_kp_sbgpCertAAServerAuth); // * asn1ExtKeyUsage.add(KeyPurposeId.id_kp_scvp_responder); // * asn1ExtKeyUsage.add(KeyPurposeId.id_kp_eapOverPPP); // * asn1ExtKeyUsage.add(KeyPurposeId.id_kp_eapOverLAN); // * asn1ExtKeyUsage.add(KeyPurposeId.id_kp_scvpServer); // * asn1ExtKeyUsage.add(KeyPurposeId.id_kp_scvpClient); // * asn1ExtKeyUsage.add(KeyPurposeId.id_kp_ipsecIKE); // * asn1ExtKeyUsage.add(KeyPurposeId.id_kp_capwapAC); // * asn1ExtKeyUsage.add(KeyPurposeId.id_kp_capwapWTP); // */ // asn1ExtKeyUsage.add(KeyPurposeId.id_kp_smartcardlogon); // // ExtendedKeyUsage extendedKeyUsage = new ExtendedKeyUsage(new DERSequence(asn1ExtKeyUsage)); // // extGen.addExtension(X509Extensions.ExtendedKeyUsage, true, extendedKeyUsage); // // /** // * 证书撤销:已证书扩展的形式,给出了“检查本证书所需要的CRL文件,到上面地方获取” CRL // * DP中的信息是有多个DisributionPoint组成:每个DisributionPoint都存放CRL, // * CA可以在多个地方存放CRL // * // * crl Produce an object suitable for an ASN1OutputStream. // * // *
    //         * CRLDistPoint ::= SEQUENCE SIZE {1..MAX} OF DistributionPoint
    //         * 
    // * // * -- CRL distribution points extension OID and syntax // * // * id-ce-cRLDistributionPoints OBJECT IDENTIFIER ::= {id-ce 31} // * // * CRLDistributionPoints ::= SEQUENCE SIZE (1..MAX) OF DistributionPoint // * // * DistributionPoint ::= SEQUENCE { distributionPoint [0] // * DistributionPointName OPTIONAL, reasons [1] ReasonFlags OPTIONAL, // * cRLIssuer [2] GeneralNames OPTIONAL } // * // * DistributionPointName ::= CHOICE { fullName [0] GeneralNames, // * nameRelativeToCRLIssuer [1] RelativeDistinguishedName } // */ // Map map = new HashMap(); // // map.put(GeneralName.otherName, "cn=otherName"); // map.put(GeneralName.rfc822Name, "cn=rfc822Name"); // map.put(GeneralName.dNSName, "192.168.30.241"); // // map.put(GeneralName.x400Address, "cn=x400Address"); // map.put(GeneralName.directoryName, "cn=root"); // // map.put(GeneralName.ediPartyName, "cn=ediPartyName"); // map.put(GeneralName.uniformResourceIdentifier, "http://certService/crl"); // map.put(GeneralName.iPAddress, "192.168.30.24"); // map.put(GeneralName.registeredID, "1.1.0.2.1"); // // DistributionPoint[] dps = new DistributionPoint[map.size()]; // int i = 0; // for (int key : map.keySet()) { // GeneralName gn = null; // if (key == GeneralName.otherName || key == GeneralName.ediPartyName) { // continue; // } else if (key == GeneralName.x400Address) { // continue; // } else { // gn = new GeneralName(key, map.get(key)); // } // // GeneralNames gns = new GeneralNames(gn); // DistributionPointName dpn = new DistributionPointName(gns); // DistributionPoint dp = new DistributionPoint(dpn, null, null); // dps[i] = dp; // i++; // } // // CRLDistPoint crlDistPoint = new CRLDistPoint(dps); // // extGen.addExtension(X509Extensions.CRLDistributionPoints, true, crlDistPoint); // // /** // * 增量crl Produce an object suitable for an ASN1OutputStream. // * // *
    //         * CRLDistPoint ::= SEQUENCE SIZE {1..MAX} OF DistributionPoint
    //         * 
    // */ // // extGen.addExtension(X509Extensions.FreshestCRL, false, crlDistPoint); // /** // * 主题备用名称 // */ // GeneralName subjectAlternativeName = new GeneralName(GeneralName.directoryName, map.get(GeneralName.directoryName)); // extGen.addExtension(X509Extensions.SubjectAlternativeName, false, new DERSequence(subjectAlternativeName)); // // /** // * 颁发者备用名称 : 放置签发者的各种不同的命名,map 是各种名称形式 // */ // GeneralName issuerAlternativeName = new GeneralName(GeneralName.directoryName, map.get(GeneralName.directoryName)); // extGen.addExtension(X509Extensions.IssuerAlternativeName, false, new DERSequence(issuerAlternativeName)); // // /** // * 密钥周期 : 对应私钥的使用期限 // * // *
    //         *    PrivateKeyUsagePeriod ::= SEQUENCE {
    //         *      notBefore       [0]     GeneralizedTime OPTIONAL,
    //         *      notAfter        [1]     GeneralizedTime OPTIONAL }
    //         * 
    // */ // Date notAfter = new Date(); // Date notBefter = new Date(); // notBefter.setYear(notAfter.getYear() + 10); // DERGeneralizedTime notAfterKey = new DERGeneralizedTime(notAfter); // DERGeneralizedTime notBefterKey = new DERGeneralizedTime(notBefter); // // DERTaggedObject dtoNotBefterKey = new DERTaggedObject(false, 0, notBefterKey); // DERTaggedObject dtoNotAfterKey = new DERTaggedObject(false, 1, notAfterKey); // // ASN1EncodableVector aevPriKeyUsagePeriod = new ASN1EncodableVector(); // aevPriKeyUsagePeriod.add(dtoNotBefterKey); // aevPriKeyUsagePeriod.add(dtoNotAfterKey); // PrivateKeyUsagePeriod pkup = PrivateKeyUsagePeriod.getInstance(new DERSequence(aevPriKeyUsagePeriod)); // extGen.addExtension(X509Extensions.PrivateKeyUsagePeriod, false, pkup); // // /** // * 策略限制 PolicyConstraints ::= SEQUENCE { requireExplicitPolicy [0] // * SkipCerts OPTIONAL, inhibitPolicyMapping [1] SkipCerts OPTIONAL } // */ // // int requireExplicitPolicy = 10; // 表明额外的证书的数量 // int inhibitPolicyMapping = 10; // 应用程序支持数量 // ASN1EncodableVector pcVector = new ASN1EncodableVector(); // pcVector.add(new DERTaggedObject(false, 0, new DERInteger(requireExplicitPolicy))); // pcVector.add(new DERTaggedObject(false, 1, new DERInteger(inhibitPolicyMapping))); // // extGen.addExtension(X509Extensions.PolicyConstraints, false, new DERSequence(pcVector)); // // /** // * 禁止任何策略: // * 扩展项的值是整数N,N表示:在证书路径中,本证书之下的N个证书可带有Any-Policy的证书 // * (N+1之下的证书就不能有Any-policy) // * // * id-ce-inhibitAnyPolicy OBJECT IDENTIFIER ::= { id-ce 54 } // * // * InhibitAnyPolicy ::= SkipCerts // * // * SkipCerts ::= INTEGER (0..MAX) // */ // int inhibitAnyPolicy = 10; // extGen.addExtension(X509Extensions.InhibitAnyPolicy, false, new DERInteger(inhibitAnyPolicy)); // // /** // * 策略映射 扩展项仅仅存在于交叉证书中,说明了不同CA域之间的CP等级的相互映射关系 // * // * PolicyMappings ::= SEQUENCE SIZE (1..MAX) OF SEQUENCE { // * issuerDomainPolicy CertPolicyId, subjectDomainPolicy CertPolicyId } // */ // Hashtable policyHashMap = new Hashtable(); // policyHashMap.put("1.1.1.2.3.1", "1.1.1.2.3.4"); // policyHashMap.put("1.1.1.2.3.2", "1.1.1.2.3.5"); // // PolicyMappings pms = new PolicyMappings(policyHashMap); // // extGen.addExtension(X509Extensions.PolicyMappings, false, pms); // // /** // * 使用者密钥标示符 SubjectKeyIdentifier ::= KeyIdentifier // */ // extGen.addExtension(X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifier(SubjectPublicKeyInfo.getInstance(subjectKey.getEncoded()))); // /** // * 颁发者密钥标示符 IssuerKeyIdentifier ::= KeyIdentifier // */ // // extGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifier(SubjectPublicKeyInfo.getInstance(subjectKey.getEncoded()))); // /** // * 主题目录属性 原则上可以加入与Subject有关信息 因为使用了Atrribute Type的OID的、然后说明相对应的值 // */ // // http://asn1.elibel.tm.fr/cgi-bin/oid/display?oid=1.3.6.1.5.5.7.9&action=display // // PKIX personal data gender // String genderOidStr = "1.3.6.1.5.5.7.9.4"; // // // PKIX personal data dateOfBirth // String dateOfBirthOidStr = "1.3.6.1.5.5.7.9.1"; // // // 2.5.4.20 - id-at-telephoneNumber // // http://www.alvestrand.no/objectid/2.5.4.html // String streetAddressOidStr = "2.5.4.9"; // // String telephoneNumberOidStr = "2.5.4.20"; // // http://oid.elibel.tm.fr/0.9.2342.19200300.100.1.41 // String mobileTelephoneNumberOidStr = "0.9.2342.19200300.100.1.41"; // // Vector attributes = new Vector(); // // Attribute genderAttribute = new Attribute(new DERObjectIdentifier(genderOidStr), new DERSet(new DERPrintableString("汉族".getBytes("UTF-8")))); // Attribute dateOfBirthAttribute = // new Attribute(new DERObjectIdentifier(genderOidStr), new DERSet(new DERPrintableString("1992-02-20".getBytes("UTF-8")))); // Attribute streetAddressAttribute = // new Attribute(new DERObjectIdentifier(genderOidStr), new DERSet(new DERPrintableString("北京市大王庄胡同13号".getBytes("UTF-8")))); // Attribute telephoneNumberAttribute = // new Attribute(new DERObjectIdentifier(genderOidStr), new DERSet(new DERPrintableString("010-82961368".getBytes("UTF-8")))); // Attribute mobileTelephoneNumberAttribute = // new Attribute(new DERObjectIdentifier(genderOidStr), new DERSet(new DERPrintableString("13843838438".getBytes("UTF-8")))); // // attributes.add(genderAttribute); // attributes.add(dateOfBirthAttribute); // attributes.add(streetAddressAttribute); // attributes.add(telephoneNumberAttribute); // attributes.add(mobileTelephoneNumberAttribute); // // // 构建主题目录属性 // SubjectDirectoryAttributes sda = new SubjectDirectoryAttributes(attributes); // // extGen.addExtension(X509Extensions.SubjectDirectoryAttributes, false, sda); // // /** // * 名称限制 : 只在ca中出现,并不是出现在用户证书中,名称限制同时对Subject和SubjeectAltertiveName // * 起作用。可以对多种命名进行限制如Email、DNS、X509 DN 等 // * 如果发现用户证书中的命名(Subject和SubjeectAltertiveName)与CA证书中的Name // * Constraints违背,就直接认为该证书无效。必须满足Name Constraints 中的多个不同类型命名的限制 // * NameConstraints ::= SEQUENCE { permittedSubtrees [0] GeneralSubtrees // * OPTIONAL, excludedSubtrees [1] GeneralSubtrees OPTIONAL } // * GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree // */ // Vector permitted = new Vector(); // 允许名称列表 // Vector excluded = new Vector(); // 限制名称列表 // // // 添加允许名称 // GeneralName permitteedNcGn = new GeneralName(GeneralName.directoryName, map.get(GeneralName.directoryName)); // GeneralSubtree permittedGsNcGn = new GeneralSubtree(permitteedNcGn, BigInteger.ONE, BigInteger.TEN); // permitted.add(permittedGsNcGn); // // 添加限制名称 // GeneralName excludedNcGn = new GeneralName(GeneralName.directoryName, map.get(GeneralName.directoryName)); // GeneralSubtree excludedGsNcGn = new GeneralSubtree(excludedNcGn, BigInteger.ONE, BigInteger.TEN); // excluded.add(excludedGsNcGn); // // NameConstraints nc = new NameConstraints(permitted, excluded); // // extGen.addExtension(X509Extensions.NameConstraints, false, nc); // // /** // * 机构信息访问 id-pe-authorityInfoAccess OBJECT IDENTIFIER ::= { id-pe 1 } // * AuthorityInfoAccessSyntax ::= SEQUENCE SIZE (1..MAX) OF // * AccessDescription AccessDescription ::= SEQUENCE { accessMethod // * OBJECT IDENTIFIER, accessLocation GeneralName } id-ad OBJECT // * IDENTIFIER ::= { id-pkix 48 } id-ad-caIssuers OBJECT IDENTIFIER ::= { // * id-ad 2 } id-ad-ocsp OBJECT IDENTIFIER ::= { id-ad 1 } // */ // ASN1EncodableVector authorityInnfoAccess = new ASN1EncodableVector(); // // DERObjectIdentifier id_ad_caIssuers = AccessDescription.id_ad_caIssuers; // DERObjectIdentifier id_ad_ocsp = AccessDescription.id_ad_ocsp; // DERObjectIdentifier id_ad_caRepository = new DERObjectIdentifier("1.3.6.1.5.5.7.48.5"); // // AccessDescription caIssuers = // new AccessDescription(id_ad_caIssuers, new GeneralName(GeneralName.uniformResourceIdentifier, "http://certService/caIssuers")); // AccessDescription ocsp = new AccessDescription(id_ad_caIssuers, new GeneralName(GeneralName.uniformResourceIdentifier, "http://certService/ocsp")); // AccessDescription caRepository = // new AccessDescription(id_ad_caIssuers, new GeneralName(GeneralName.uniformResourceIdentifier, "http://certService/caRepository")); // // authorityInnfoAccess.add(caIssuers); // authorityInnfoAccess.add(ocsp); // authorityInnfoAccess.add(caRepository); // // extGen.addExtension(X509Extensions.AuthorityInfoAccess, false, new DERSequence(authorityInnfoAccess)); // /** // * // */ // // /** // * 证书策略 // * // *
    //         * 
    //         * certificatePolicies ::= SEQUENCE SIZE (1..MAX) OF PolicyInformation
    //         * 
    //         * PolicyInformation ::= SEQUENCE {
    //         *   policyIdentifier   CertPolicyId,
    //         *   policyQualifiers   SEQUENCE SIZE (1..MAX) OF
    //         *                           PolicyQualifierInfo OPTIONAL }
    //         * 
    //         * CertPolicyId ::= OBJECT IDENTIFIER
    //         * 
    //         * PolicyQualifierInfo ::= SEQUENCE {
    //         *   policyQualifierId  PolicyQualifierId,
    //         *   qualifier          ANY DEFINED BY policyQualifierId }
    //         * 
    //         * PolicyQualifierId ::=
    //         *   OBJECT IDENTIFIER (id-qt-cps | id-qt-unotice)
    //         * 
    // * // * @deprecated use an ASN1Sequence of PolicyInformation // */ // PolicyInformation policyInfo1 = new PolicyInformation(new DERObjectIdentifier("1.1.1.2.3.1")); // PolicyInformation policyInfo2 = new PolicyInformation(new DERObjectIdentifier("1.1.1.2.3.2")); // PolicyInformation policyInfo3 = new PolicyInformation(new DERObjectIdentifier("1.1.1.2.3.3")); // // ASN1EncodableVector certificatePolicies = new ASN1EncodableVector(); // certificatePolicies.add(policyInfo1); // certificatePolicies.add(policyInfo2); // certificatePolicies.add(policyInfo3); // // extGen.addExtension(X509Extensions.CertificatePolicies, true, new DERSequence(certificatePolicies)); // // return extGen.generate(); // } // // /** // * 产生密钥对 // * @param alg 签名算法 // * @return 密钥对 // * @throws Exception // */ // public static KeyPair genKeyPair(String alg) throws Exception { // KeyPairGenerator genKeyPair = KeyPairGenerator.getInstance(alg); // genKeyPair.initialize(2048); // return genKeyPair.genKeyPair(); // } // // /** // * 签名 // * @param alg 签名算法 // * @param planText 签名原文 // * @param priKey 签名私钥 // * @return 签名信息 byte[] // * @throws Exception // */ // public static byte[] sign(String alg, byte[] planText, PrivateKey priKey) // throws Exception { // if (alg == null) return null; // if ("RSA".equals(alg)) alg = "SHA1withRSA"; // else throw new Exception("不支持的算法。"); // // Signature sign = Signature.getInstance(alg); // sign.initSign(priKey); // sign.update(planText); // return sign.sign(); // } // //} ================================================ FILE: src/test/java/test/jce/demo/CreateCert.java ================================================ package test.jce.demo; //package test.jce.cert; // //import java.io.ByteArrayInputStream; //import java.io.IOException; //import java.io.InputStream; //import java.math.BigInteger; //import java.security.PrivateKey; //import java.security.PublicKey; //import java.security.cert.Certificate; //import java.security.cert.CertificateFactory; //import java.security.cert.Extension; //import java.security.cert.X509Certificate; //import java.util.Date; //import java.util.List; // //import javax.security.cert.CertificateException; // //import org.bouncycastle.asn1.ASN1Encodable; //import org.bouncycastle.asn1.ASN1ObjectIdentifier; //import org.bouncycastle.asn1.x500.X500Name; //import org.bouncycastle.cert.X509CertificateHolder; //import org.bouncycastle.cert.X509v3CertificateBuilder; //import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; //import org.bouncycastle.operator.ContentSigner; //import org.bouncycastle.operator.OperatorCreationException; //import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; // //public class CreateCert { // // public static Certificate generateV3(String issuer, String subject, // BigInteger serial, Date notBefore, Date notAfter, // PublicKey publicKey, PrivateKey privKey, List extensions) // throws OperatorCreationException, CertificateException, IOException { // // X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(new X500Name(issuer), serial, notBefore, notAfter, new X500Name(subject), publicKey); // ContentSigner sigGen = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(privKey); // //privKey:使用自己的私钥进行签名,CA证书 // if (extensions != null) { // for (Extension ext : extensions) { // builder.addExtension(new ASN1ObjectIdentifier(ext.getId()), ext.isCritical(), ASN1Encodable.fromByteArray(ext.getValue())); // } // } // X509CertificateHolder holder = builder.build(sigGen); // CertificateFactory cf = CertificateFactory.getInstance("X.509"); // InputStream is1 = new ByteArrayInputStream(holder.toASN1Structure().getEncoded()); // X509Certificate theCert = (X509Certificate) cf.generateCertificate(is1); // is1.close(); // return theCert; // } //} ================================================ FILE: src/test/java/test/jce/demo/GenX509Cert.java ================================================ package test.jce.demo; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.SecureRandom; import java.security.Signature; import java.security.SignatureException; import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Vector; import sun.misc.BASE64Encoder; import sun.security.tools.keytool.CertAndKeyGen; import sun.security.util.ObjectIdentifier; import sun.security.x509.AlgorithmId; import sun.security.x509.CertificateAlgorithmId; import sun.security.x509.CertificateExtensions; import sun.security.x509.CertificateSerialNumber; import sun.security.x509.CertificateValidity; import sun.security.x509.CertificateVersion; import sun.security.x509.CertificateX509Key; import sun.security.x509.ExtendedKeyUsageExtension; import sun.security.x509.Extension; import sun.security.x509.KeyIdentifier; import sun.security.x509.KeyUsageExtension; import sun.security.x509.SubjectKeyIdentifierExtension; import sun.security.x509.X500Name; import sun.security.x509.X509CertImpl; import sun.security.x509.X509CertInfo; /** * 首先生成CA的根证书,然后有CA的根证书签署生成ScriptX的证书 * * @author Ponfee * */ public class GenX509Cert { public static final Map HASH_SIGN_ALG = new HashMap() { private static final long serialVersionUID = 8252202658190109593L; { put("1.2.840.113549.1.1.5", "SHA-1"); put("1.2.840.113549.1.1.11", "SHA-256"); put("1.2.840.113549.1.1.12", "SHA-384"); put("1.2.840.113549.1.1.13", "SHA-512"); put("1.2.840.113549.1.1.4", "MD5"); } }; /** * 获取证书HASH算法 * @param oid * @return */ private static AlgorithmId getHashAlg(String oid) { try { return AlgorithmId.get(HASH_SIGN_ALG.get(oid)); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } private String rootCertPath; private String selfCertPath; private String rootCertName; private String selfCertName; /** 提供强加密随机数生成器 (RNG)* */ private SecureRandom sr; public GenX509Cert(String rootCertPath,String rootCertName,String selfCertPath,String selfCertName) throws NoSuchAlgorithmException, NoSuchProviderException { // 返回实现指定随机数生成器 (RNG) 算法的 SecureRandom 对象。 sr = SecureRandom.getInstance("SHA1PRNG", "SUN"); this.rootCertName = rootCertName; this.rootCertPath = rootCertPath; this.selfCertName = selfCertName; this.selfCertPath = selfCertPath; } public void createCert(X509Certificate certificate,PrivateKey rootPrivKey,KeyPair kp) throws CertificateException, IOException, InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, SignatureException{ byte certbytes[] = certificate.getEncoded(); X509CertImpl x509certimpl = new X509CertImpl(certbytes); X509CertInfo x509certinfo = (X509CertInfo)x509certimpl.get("x509.info"); x509certinfo.set("key", new CertificateX509Key(kp.getPublic())); CertificateExtensions certificateextensions = new CertificateExtensions(); certificateextensions.set("SubjectKeyIdentifier", new SubjectKeyIdentifierExtension((new KeyIdentifier(kp.getPublic())).getIdentifier())); x509certinfo.set("extensions", certificateextensions); //设置issuer域 X500Name issuer = new X500Name("CN="+rootCertName+",OU=hackwp,O=wp,L=BJ,S=BJ,C=CN"); x509certinfo.set("issuer.dname",issuer); X500Name subject = new X500Name("CN="+selfCertName+", OU=wps, O=wps, L=BJ, ST=BJ, C=CN"); x509certinfo.set("subject.dname",subject); Signature signature = Signature.getInstance("SHA1WithRSA"); signature.initSign(kp.getPrivate()); //X500Signer signer=new X500Signer(signature,issuer); //AlgorithmId algorithmid = signer.getAlgorithmId(); AlgorithmId algorithmid = getHashAlg(certificate.getSigAlgOID()); x509certinfo.set("algorithmID", new CertificateAlgorithmId(algorithmid)); Date bdate = new Date(); Date edate = new Date(); //天 小时 分 秒 毫秒 edate.setTime(bdate.getTime() + 3650 * 24L * 60L * 60L * 1000L); //validity为有效时间长度 单位为秒 CertificateValidity certificatevalidity = new CertificateValidity(bdate, edate); x509certinfo.set("validity", certificatevalidity); //设置有效期域(包含开始时间和到期时间)域名等同与x509certinfo.VALIDITY x509certinfo.set("serialNumber", new CertificateSerialNumber((int)(new Date().getTime() / 1000L))); //设置序列号域 CertificateVersion cv = new CertificateVersion(CertificateVersion.V3); x509certinfo.set(X509CertInfo.VERSION,cv); //设置版本号 只有v1 ,v2,v3这几个合法值 /** *以上是证书的基本信息 如果要添加用户扩展信息 则比较麻烦 首先要确定version必须是v3否则不行 然后按照以下步骤 **/ ObjectIdentifier oid = new ObjectIdentifier(new int[]{2,5,29,15}); //生成扩展域的id 是个int数组 第1位最大2 第2位最大39 最多可以几位不明.... String userData = "Digital Signature, Non-Repudiation, Key Encipherment, Data Encipherment (f0)"; byte l = (byte)userData.length();//数据总长17位 byte f = 0x04; byte[] bs = new byte[userData.length()+2]; bs[0] = f; bs[1] = l; for(int i=2;i vkeyOid=new Vector(); vkeyOid.add(ekeyOid); ExtendedKeyUsageExtension exKeyUsage=new ExtendedKeyUsageExtension(vkeyOid); CertificateExtensions exts = new CertificateExtensions(); exts.set("keyUsage",keyUsage); exts.set("extendedKeyUsage",exKeyUsage); //如果有多个extension则都放入CertificateExtensions 类中, x509certinfo.set(X509CertInfo.EXTENSIONS,exts); //设置extensions域 X509CertImpl x509certimpl1 = new X509CertImpl(x509certinfo); x509certimpl1.sign(rootPrivKey, "SHA1WithRSA"); //使用另一个证书的私钥来签名此证书 这里使用 md5散列 用rsa来加密 BASE64Encoder base64 = new BASE64Encoder(); FileOutputStream fos = new FileOutputStream(new File(selfCertPath+selfCertName+".crt")); base64.encodeBuffer(x509certimpl1.getEncoded(), fos); try { Certificate[] certChain={x509certimpl1}; savePfx("scriptx",kp.getPrivate(),"123456", certChain,selfCertPath+selfCertName+".pfx"); FileInputStream in = new FileInputStream(selfCertPath+selfCertName+".pfx"); KeyStore inputKeyStore = KeyStore.getInstance("pkcs12"); inputKeyStore.load(in, "123456".toCharArray()); Certificate cert=inputKeyStore.getCertificate("scriptx"); System.out.print(cert.getPublicKey()); PrivateKey privk=(PrivateKey)inputKeyStore.getKey("scriptx", "123456".toCharArray()); FileOutputStream privKfos = new FileOutputStream(new File(selfCertPath+selfCertName+".pvk")); privKfos.write(privk.getEncoded()); System.out.print(privk); //base64.encode(key.getEncoded(), privKfos); in.close(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } //生成文件 //x509certimpl1.verify(certificate.getPublicKey(),null); } /** * 保存此根证书信息KeyStore Personal Information Exchange * * @param alias * @param privKey * @param pwd * @param certChain * @param filepath * @throws Exception */ public void savePfx(String alias, PrivateKey privKey, String pwd, Certificate[] certChain, String filepath) throws Exception { // 此类表示密钥和证书的存储设施。 // 返回指定类型的 keystore 对象。此方法从首选 Provider 开始遍历已注册安全提供者列表。返回一个封装 KeyStoreSpi // 实现的新 KeyStore 对象,该实现取自第一个支持指定类型的 Provider。 KeyStore outputKeyStore = KeyStore.getInstance("pkcs12"); System.out.println("KeyStore类型:" + outputKeyStore.getType()); // 从给定输入流中加载此 KeyStore。可以给定一个密码来解锁 keystore(例如,驻留在硬件标记设备上的 keystore)或检验 // keystore 数据的完整性。如果没有指定用于完整性检验的密码,则不会执行完整性检验。如果要创建空 // keystore,或者不能从流中初始化 keystore,则传递 null 作为 stream 的参数。注意,如果此 keystore // 已经被加载,那么它将被重新初始化,并再次从给定输入流中加载。 outputKeyStore.load(null, pwd.toCharArray()); // 将给定密钥(已经被保护)分配给给定别名。如果受保护密钥的类型为 // java.security.PrivateKey,则它必须附带证明相应公钥的证书链。如果底层 keystore 实现的类型为 // jks,则必须根据 PKCS #8 标准中的定义将 key 编码为 // EncryptedPrivateKeyInfo。如果给定别名已经存在,则与别名关联的 keystore // 信息将被给定密钥(还可能包括证书链)重写。 outputKeyStore .setKeyEntry(alias, privKey, pwd.toCharArray(), certChain); FileOutputStream out = new FileOutputStream(filepath); // 将此 keystore 存储到给定输出流,并用给定密码保护其完整性。 outputKeyStore.store(out, pwd.toCharArray()); out.close(); } public void saveJks(String alias, PrivateKey privKey, String pwd, Certificate[] certChain, String filepath) throws Exception { KeyStore outputKeyStore = KeyStore.getInstance("jks"); System.out.println(outputKeyStore.getType()); outputKeyStore.load(null, pwd.toCharArray()); outputKeyStore .setKeyEntry(alias, privKey, pwd.toCharArray(), certChain); FileOutputStream out = new FileOutputStream(filepath); outputKeyStore.store(out, pwd.toCharArray()); out.close(); } /** * 颁布根证书,自己作为CA */ public void createRootCA() throws Exception { // 参数分别为公钥算法、签名算法 providername(因为不知道确切的 只好使用null 既使用默认的provider) // Generate a pair of keys, and provide access to them. CertAndKeyGen cak = new CertAndKeyGen("RSA", "SHA1WithRSA", null); // Sets the source of random numbers used when generating keys. cak.setRandom(sr); // Generates a random public/private key pair, with a given key size. cak.generate(1024); // Constructs a name from a conventionally formatted string, such as // "CN=Dave, OU=JavaSoft, O=Sun Microsystems, C=US". (RFC 1779 or RFC // 2253 style) X500Name subject = new X500Name( "CN="+rootCertName+",OU=hackwp,O=wp,L=BJ,S=BJ,C=CN"); // Returns a self-signed X.509v3 certificate for the public key. The // certificate is immediately valid. No extensions. // Such certificates normally are used to identify a "Certificate // Authority" (CA). Accordingly, they will not always be accepted by // other parties. However, such certificates are also useful when you // are bootstrapping your security infrastructure, or deploying system // prototypes.自签名的根证书 X509Certificate certificate = cak.getSelfCertificate(subject, new Date(), 3650 * 24L * 60L * 60L); X509Certificate[] certs = { certificate }; try { savePfx(rootCertName, cak.getPrivateKey(), "123456", certs, rootCertPath+rootCertName+".pfx"); } catch (Exception e) { e.printStackTrace(); } // 后一个long型参数代表从现在开始的有效期 单位为秒(如果不想从现在开始算 可以在后面改这个域) BASE64Encoder base64 = new BASE64Encoder(); FileOutputStream fos = new FileOutputStream(new File(rootCertPath+rootCertName+".crt")); // fos.write(certificate.getEncoded()); // 生成(保存)cert文件 base64加密 当然也可以不加密 base64.encodeBuffer(certificate.getEncoded(), fos); fos.close(); } public void signCert() throws NoSuchAlgorithmException, CertificateException, IOException, UnrecoverableKeyException, InvalidKeyException, NoSuchProviderException, SignatureException { try { KeyStore ks = KeyStore.getInstance("pkcs12"); FileInputStream ksfis = new FileInputStream(rootCertPath+rootCertName+".pfx"); char[] storePwd = "123456".toCharArray(); char[] keyPwd = "123456".toCharArray(); // 从给定输入流中加载此 KeyStore。 ks.load(ksfis, storePwd); ksfis.close(); // 返回与给定别名关联的密钥(私钥),并用给定密码来恢复它。必须已经通过调用 setKeyEntry,或者以 // PrivateKeyEntry // 或 SecretKeyEntry 为参数的 setEntry 关联密钥与别名。 PrivateKey privK = (PrivateKey) ks.getKey(rootCertName, keyPwd); // 返回与给定别名关联的证书。如果给定的别名标识通过调用 setCertificateEntry 创建的条目,或者通过调用以 // TrustedCertificateEntry 为参数的 setEntry // 创建的条目,则返回包含在该条目中的可信证书。如果给定的别名标识通过调用 setKeyEntry 创建的条目,或者通过调用以 // PrivateKeyEntry 为参数的 setEntry 创建的条目,则返回该条目中证书链的第一个元素。 X509Certificate certificate = (X509Certificate) ks .getCertificate(rootCertName); createCert(certificate, privK, genKey()); } catch (KeyStoreException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public KeyPair genKey() throws NoSuchAlgorithmException { KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); kpg.initialize(1024, sr); KeyPair kp = kpg.generateKeyPair(); return kp; } //加上标示否则ca无法被CertificateFactory读取 public void addDeco(String caPath){ File file = new File(caPath); StringBuilder builder = new StringBuilder(); builder.append("-----BEGIN CERTIFICATE-----"+"\n"); try { BufferedReader reader = new BufferedReader(new FileReader(caPath)); String line = null; while((line = reader.readLine()) != null){ builder.append(line+"\n"); } builder.append("-----END CERTIFICATE-----"); BufferedWriter writer = new BufferedWriter(new FileWriter(caPath)); writer.write(builder.toString()); reader.close(); writer.close(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static void main(String[] args) { try { GenX509Cert gcert = new GenX509Cert("D://cert//","Server","D://cert//","Bob"); gcert.createRootCA(); gcert.signCert(); gcert.addDeco("D://cert//Bob.crt"); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } ================================================ FILE: src/test/java/test/jce/ecc0/CryptoInputStream.java ================================================ package test.jce.ecc0; import java.io.DataInputStream; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import cn.ponfee.commons.jce.implementation.Cryptor; import cn.ponfee.commons.jce.implementation.Key; public class CryptoInputStream extends InputStream { private DataInputStream in; private Cryptor cs; private Key key; private byte[] buffer; private int top; private int blocksize; public CryptoInputStream(InputStream in, Cryptor cs, Key key) { this.in = new DataInputStream(in); this.cs = cs; this.key = key; buffer = new byte[0]; } public @Override int read() throws IOException { if (top == buffer.length) { try { blocksize = in.readInt(); } catch (EOFException e) { return -1; } byte[] cipher = new byte[blocksize]; in.read(cipher); buffer = cs.decrypt(cipher, key); top = 0; } top++; return buffer[top - 1]; } public @Override void close() throws IOException { in.close(); } } ================================================ FILE: src/test/java/test/jce/ecc0/CryptoOutputStream.java ================================================ package test.jce.ecc0; import java.io.DataOutputStream; import java.io.IOException; import java.io.OutputStream; import cn.ponfee.commons.jce.implementation.Cryptor; import cn.ponfee.commons.jce.implementation.Key; public class CryptoOutputStream extends OutputStream { private DataOutputStream out; private Cryptor cs; private Key key; private byte[] buffer; private int top; public CryptoOutputStream(OutputStream out, Cryptor cs, Key key) { this.out = new DataOutputStream(out); this.cs = cs; this.key = key; buffer = new byte[64]; } private void writeOut() throws IOException { if (top == 0) return; byte[] cipher = cs.encrypt(buffer, top, key); out.writeInt(cipher.length); out.write(cipher); top = 0; } public @Override void write(int b) throws IOException { buffer[top] = (byte) b; top++; if (top == buffer.length) writeOut(); } public @Override void flush() throws IOException { writeOut(); out.flush(); } public @Override void close() throws IOException { out.close(); } } ================================================ FILE: src/test/java/test/jce/ecc0/ECCryptor.java.bak ================================================ package cn.ponfee.commons.jce.security; import java.math.BigInteger; import java.security.interfaces.ECKey; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.security.spec.ECFieldF2m; import java.security.spec.ECParameterSpec; import java.security.spec.ECPoint; import java.security.spec.EllipticCurve; import java.util.Map; import javax.crypto.Cipher; import javax.crypto.NullCipher; import com.google.common.collect.ImmutableMap; import sun.security.ec.ECPrivateKeyImpl; import sun.security.ec.ECPublicKeyImpl; /** * 基于椭圆曲线算法的非对称加解密(未实现) * @author Ponfee */ @SuppressWarnings("restriction") public abstract class ECCryptor { public static final String ALGORITHM = "EC"; private static final String PUBLIC_KEY = "ECPublicKey"; private static final String PRIVATE_KEY = "ECPrivateKey"; /** * 初始化密钥 * @return * @throws Exception */ public static Map initKey() throws Exception { BigInteger x1 = new BigInteger("2fe13c0537bbc11acaa07d793de4e6d5e5c94eee8", 16); BigInteger x2 = new BigInteger("289070fb05d38ff58321f2e800536d538ccdaa3d9", 16); ECPoint g = new ECPoint(x1, x2); // the order of generator BigInteger n = new BigInteger("5846006549323611672814741753598448348329118574063", 10); // the cofactor int h = 2; int m = 163; int[] ks = { 7, 6, 3 }; ECFieldF2m ecField = new ECFieldF2m(m, ks); // y^2+xy=x^3+x^2+1 BigInteger a = new BigInteger("1", 2); BigInteger b = new BigInteger("1", 2); EllipticCurve ellipticCurve = new EllipticCurve(ecField, a, b); ECParameterSpec ecParameterSpec = new ECParameterSpec(ellipticCurve, g, n, h); BigInteger s = new BigInteger("1234006549323611672814741753598448348329118574063", 10); return ImmutableMap.of(PUBLIC_KEY, new ECPublicKeyImpl(g, ecParameterSpec), // 66 byte PRIVATE_KEY, new ECPrivateKeyImpl(s, ecParameterSpec) // 52 byte ); } public static byte[] boByteArray(ECPrivateKey priKey) throws Exception { return priKey.getEncoded(); } public static byte[] boByteArray(ECPublicKey pubKey) throws Exception { return pubKey.getEncoded(); } /** * 解密
    * 用私钥解密 * * @param data * @param key * @return * @throws Exception */ public static byte[] decrypt(byte[] data, ECPrivateKey priKey) throws Exception { // 对数据解密 // TODO Chipher不支持EC算法 未能实现 Cipher cipher = new NullCipher(); // Cipher.getInstance(ALGORITHM, keyFactory.getProvider()); cipher.init(Cipher.DECRYPT_MODE, priKey, priKey.getParams()); return cipher.doFinal(data); } /** * 加密
    * 用公钥加密 * * @param data * @param privateKey * @return * @throws Exception */ public static byte[] encrypt(byte[] data, ECPublicKey pubKey) throws Exception { // 对数据加密 // TODO Chipher不支持EC算法 未能实现 Cipher cipher = new NullCipher(); // Cipher.getInstance(ALGORITHM, keyFactory.getProvider()); cipher.init(Cipher.ENCRYPT_MODE, pubKey, pubKey.getParams()); return cipher.doFinal(data); } /** * 取得私钥 * * @param keyMap * @return * @throws Exception */ public static ECPrivateKey getPrivateKey(Map keyMap) throws Exception { return (ECPrivateKey) keyMap.get(PRIVATE_KEY); } /** * 取得公钥 * @param keyMap * @return * @throws Exception */ public static ECPublicKey getPublicKey(Map keyMap) throws Exception { return (ECPublicKey) keyMap.get(PUBLIC_KEY); } public static void main(String[] args) throws Exception { String inputStr = "abc"; byte[] data = inputStr.getBytes(); Map keyMap = ECCryptor.initKey(); byte[] encodedData = ECCryptor.encrypt(data, ECCryptor.getPublicKey(keyMap)); byte[] decodedData = ECCryptor.decrypt(encodedData, ECCryptor.getPrivateKey(keyMap)); System.out.println(new String(data)); System.out.println(new String(encodedData)); System.out.println(new String(decodedData)); } } ================================================ FILE: src/test/java/test/jce/ecc0/ECCryptorTest.java ================================================ package test.jce.ecc0; import java.util.Arrays; import org.junit.Test; import cn.ponfee.commons.jce.ECParameters; import cn.ponfee.commons.jce.implementation.Cryptor; import cn.ponfee.commons.jce.implementation.Key; import cn.ponfee.commons.jce.implementation.NoopCryptor; import cn.ponfee.commons.jce.implementation.ecc.ECCryptor; import cn.ponfee.commons.jce.implementation.ecc.EllipticCurve; import cn.ponfee.commons.util.IdcardResolver; import cn.ponfee.commons.util.MavenProjects; public class ECCryptorTest { private static byte[] origin = MavenProjects.getMainJavaFileAsBytes(IdcardResolver.class); @Test public void testECCryptor() { Cryptor cs = new ECCryptor(new EllipticCurve(ECParameters.secp112r1)); Key dk = cs.generateKey(); Key ek = dk.getPublic(); System.out.println(dk + "\n" + ek); byte[] encrypted = cs.encrypt(origin, ek); byte[] decrypted = cs.decrypt(encrypted, dk); if (!Arrays.equals(origin, decrypted)) { System.err.println("FAIL!"); System.out.println("=====ECCryptor Decrypted text is: \n" + new String(decrypted)); } else { System.out.println("=====ECCryptor Decrypted text is: \n" + new String(decrypted)); } } @Test public void testNullCryptor() { Cryptor cs = NoopCryptor.SINGLETON; Key dk = cs.generateKey(); Key ek = dk.getPublic(); byte[] encrypted = cs.encrypt(origin, ek); byte[] decrypted = cs.decrypt(encrypted, dk); if (!Arrays.equals(origin, decrypted)) { System.err.println("FAIL!"); } else { System.out.println("\n\n=====NullCryptor Decrypted text is: \n" + new String(decrypted)); } } } ================================================ FILE: src/test/java/test/jce/ecc0/EllipticCurveTest.java ================================================ package test.jce.ecc0; import java.math.BigInteger; import cn.ponfee.commons.jce.ECParameters; import cn.ponfee.commons.jce.implementation.ecc.ECPoint; import cn.ponfee.commons.jce.implementation.ecc.EllipticCurve; public class EllipticCurveTest { public static void main(String[] args) { BigInteger a = new BigInteger("1"); BigInteger b = new BigInteger("6"); BigInteger mod = new BigInteger("11"); EllipticCurve e = new EllipticCurve(a, b, mod); System.out.println("EllipticCurve: " + e + " created succesfully!"); ECPoint p1 = new ECPoint(e, new BigInteger("2"), new BigInteger("7")); ECPoint p2 = new ECPoint(e, new BigInteger("7"), new BigInteger("2")); ECPoint p3 = new ECPoint(e, new BigInteger("2"), new BigInteger("-7")); System.out.println(p1 + " + " + p2 + " = " + p1.add(p2)); System.out.println(p1 + " + " + p1 + " = " + p1.add(p1)); System.out.println(p1 + " + " + p3 + " = " + p1.add(p3)); System.out.println(p1 + " * " + mod + " = " + p1.multiply(mod)); System.out.println(p1 + " + " + e.getZero() + " = " + p1.add(e.getZero())); System.out.println(e.getZero() + " + " + e.getZero() + " = " + e.getZero().add(e.getZero())); System.out.println("\nTesting secp256r1==============="); e = new EllipticCurve(ECParameters.secp256r1); System.out.println("New curve: " + e + " OK!"); System.out.println("Generator: " + e.getBasePointG()); System.out.println("N: " + e.getN()); System.out.println("\nTesting decompression of compression..."); p1 = new ECPoint(e.getBasePointG().compress(), e); if (e.getBasePointG().getX().compareTo(p1.getX()) == 0) { System.out.println("x values agree..."); } else { System.out.println("x values disagree..."); System.out.println("x-before:"); System.out.println(e.getBasePointG().getY()); System.out.println("y-after:"); System.out.println(p1.getY()); } if (e.getBasePointG().getY().compareTo(p1.getY()) == 0) { System.out.println("y values agree..."); } else { System.out.println("y values disagree..."); System.out.println("y-before:"); System.out.println(e.getBasePointG().getY()); System.out.println("y-after:"); System.out.println(p1.getY()); } } } ================================================ FILE: src/test/java/test/jce/ecc0/Login.java ================================================ package test.jce.ecc0; import java.awt.Font; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPasswordField; import javax.swing.JTextField; public class Login extends JFrame implements ActionListener { private static final long serialVersionUID = -8266964331082261609L; JTextField untf; JPasswordField pwpf; JLabel unl, pwl; JButton Next, Close; public Login() { try { setLayout(null); unl = new JLabel("User Name : "); untf = new JTextField(15); untf.setFont(new Font("Times New Roman", Font.BOLD, 12)); addc(unl, 50, 40, 100, 25); addc(untf, 120, 40, 150, 25); pwl = new JLabel("PassWord : "); pwpf = new JPasswordField(15); pwpf.setEchoChar('*'); pwpf.setFont(new Font("Times New Roman", Font.BOLD, 12)); addc(pwl, 50, 80, 100, 25); addc(pwpf, 120, 80, 150, 25); Next = new JButton("Next"); Next.setMnemonic('N'); Next.addActionListener(this); addc(Next, 90, 140, 70, 20); Close = new JButton("Close"); Close.setMnemonic('C'); Close.addActionListener(this); addc(Close, 180, 140, 70, 20); setTitle("Login Screen"); setVisible(true); setSize(300, 220); setResizable(false); setDefaultCloseOperation(EXIT_ON_CLOSE); } catch (Exception e) { System.out.println("Exception in Constructor:" + e); System.exit(0); } } public void addc(JComponent c, int x, int y, int w, int h) { c.setBounds(x, y, w, h); add(c); } public void actionPerformed(ActionEvent ae) { JButton b = (JButton) ae.getSource(); if (b == Close) { System.exit(0); } if (b != Next) { return; } new Screen(); dispose(); } } ================================================ FILE: src/test/java/test/jce/ecc0/Main.java ================================================ package test.jce.ecc0; public class Main { public static void main(String[] args) { new Login(); // new View(600,600, new RSACryptor()); } } ================================================ FILE: src/test/java/test/jce/ecc0/Screen.java ================================================ package test.jce.ecc0; import java.awt.FileDialog; import java.awt.Font; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; import cn.ponfee.commons.jce.ECParameters; import cn.ponfee.commons.jce.implementation.ecc.ECCryptor; import cn.ponfee.commons.jce.implementation.ecc.EllipticCurve; public class Screen extends JFrame implements ActionListener { private static final long serialVersionUID = 1L; JTextArea ta; JLabel path, title; JTextField pathtf; FileDialog fd; JButton Load, Close, Next, fdl; JPanel p1, p2; String s, file, p; File targetFile; String filePath; public Screen() { try { setLayout(null); path = new JLabel("Path :"); title = new JLabel("Choose a text file for encryption"); pathtf = new JTextField(15); pathtf.setFont(new Font("TimesNewRoman", Font.BOLD, 12)); pathtf.setEditable(false); fdl = new JButton("Select File"); fdl.setMnemonic('S'); fdl.addActionListener(this); Load = new JButton("Load"); Load.setMnemonic('L'); Load.addActionListener(this); addc(path, 50, 40, 180, 25); addc(pathtf, 120, 40, 120, 25); addc(fdl, 300, 40, 100, 25); addc(Load, 50, 280, 90, 25); Next = new JButton("Next"); Next.setMnemonic('N'); Next.addActionListener(this); Close = new JButton("Close"); Close.setMnemonic('C'); Close.addActionListener(this); ta = new JTextArea(); ta.setFont(new Font("Times New Roman", Font.BOLD, 12)); ta.setEditable(false); ta.setText("<>"); addc(new JScrollPane(ta, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER), 50, 100, 350, 150); ta.setLineWrap(true); addc(Close, 190, 280, 90, 25); addc(Next, 300, 280, 90, 25); setTitle("Pick a text file for encrypting using ECC"); setVisible(true); setSize(450, 400); setResizable(false); setDefaultCloseOperation(EXIT_ON_CLOSE); } catch (Exception e) { System.out.println("Exception Here" + e); System.exit(0); } } public void addc(JComponent c, int x, int y, int w, int h) { c.setBounds(x, y, w, h); add(c); } public void actionPerformed(ActionEvent a) { JButton b = (JButton) a.getSource(); if (b == Close) System.exit(0); if (b == Next) { s = ta.getText(); try { EllipticCurve ec = new EllipticCurve(ECParameters.secp112r1); View v = new View(600, 600, new ECCryptor(ec));//ch); v.filePath = this.filePath; } catch (Exception e) { System.out.println("EC Exception"); } } if (b == fdl) { //JFileChooser x=new JFileChooser(); File f = new File("."); String currdir = new String("hi"); try { currdir = f.getCanonicalPath(); } catch (Exception e) { System.out.println("\n Error getting the current dir"); } JFileChooser chooser = new JFileChooser(currdir); int returnVal = chooser.showOpenDialog(this); if (returnVal == JFileChooser.APPROVE_OPTION) { targetFile = chooser.getSelectedFile(); filePath = targetFile.getPath(); pathtf.setText(filePath); System.out.println("\nPath is " + filePath); } else System.out.println("Error in opening File"); } if (b == Load) { try { ta.setText(""); FileInputStream fin = new FileInputStream(filePath); ByteArrayOutputStream bout = new ByteArrayOutputStream(); while (fin.available() > 0) { bout.write(fin.read()); } fin.close(); s = bout.toString(); ta.insert(s, 0); ta.setFont(new Font("TimesNewRoman", Font.BOLD, 12)); } catch (Exception e) { System.out.println("Error at File loading :" + e); } } } } ================================================ FILE: src/test/java/test/jce/ecc0/View.java ================================================ package test.jce.ecc0; import java.awt.BorderLayout; import java.awt.Container; import java.awt.FlowLayout; import java.awt.Font; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import javax.swing.JButton; import javax.swing.JEditorPane; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import cn.ponfee.commons.jce.implementation.Cryptor; import cn.ponfee.commons.jce.implementation.Key; public class View extends JFrame implements ActionListener { private static final long serialVersionUID = 1L; public final JButton ENCRYPT = new JButton("Encrypt"); public final JButton DECRYPT = new JButton("Decrypt"); public final JButton LOADKEY = new JButton("Load key"); public final JButton SAVEKEY = new JButton("Display key"); public final JButton QUIT = new JButton("Quit"); public final String TITLE = "JECC"; public String filePath; private JScrollPane scroll_pane; private JLabel infoLabel = new JLabel("Welcome to JECC"); private JLabel statusLabel = new JLabel(); private JEditorPane pane; private Cryptor cs; private File targetFile; private Key pk; private Key sk; public View(int width, int height, Cryptor cs) { try { System.out.print("Loading " + TITLE + "..."); Container cp = getContentPane(); cp.setLayout(new BorderLayout()); pane = new JEditorPane(); pane.setEditable(false); pane.setFont(new Font("TimesNewRoman", Font.BOLD, 12)); String text = "Welcome to the encryption phase. Here the text from the file displayed in the last " + "step will be encrypted, using the standard key for ECC( " + cs + " ). You can view " + "the secret and public keys used by clicking the Display Key button, or load a set of" + " keys from a file."; pane.setText(text); scroll_pane = new JScrollPane(pane); cp.add(scroll_pane, BorderLayout.CENTER); JPanel top = new JPanel(new FlowLayout()); top.add(ENCRYPT); ENCRYPT.addActionListener(this); top.add(DECRYPT); DECRYPT.addActionListener(this); // top.add(GENKEY); GENKEY.addActionListener(this); top.add(LOADKEY); LOADKEY.addActionListener(this); top.add(SAVEKEY); SAVEKEY.addActionListener(this); top.add(infoLabel); top.add(QUIT); QUIT.addActionListener(this); cp.add(top, BorderLayout.NORTH); JPanel bottom = new JPanel(new FlowLayout()); bottom.add(statusLabel); cp.add(bottom, BorderLayout.SOUTH); addWindowListener(new ExitController()); setTitle(TITLE); setSize(width, height); setVisible(true); this.cs = cs; if (cs != null) { setInfo("Using: " + this.cs); sk = cs.generateKey(); pk = sk.getPublic(); } else setInfo("No CS loaded"); setStatus("Ready"); System.out.println("[OK]"); System.out.println("Using: " + cs); System.out.println("Ready"); } catch (Exception e) { pane.setText("\n\tError\n\tE GUI-error in " + TITLE); } } public void setStatus(String s) { statusLabel.setText(s); statusLabel.repaint(); try { Thread.sleep(10); } catch (InterruptedException e) { } } public void setInfo(String s) { infoLabel.setText(s); infoLabel.repaint(); try { Thread.sleep(10); } catch (InterruptedException e) { } } public void actionPerformed(ActionEvent e) { if (e.getSource() == QUIT) { System.out.println("Exiting " + TITLE); System.exit(0); } else if (e.getSource() == ENCRYPT) { encrypt(); this.setVisible(false); try { Thread.sleep(250); } catch (InterruptedException E) { } ENCRYPT.setVisible(false); LOADKEY.setVisible(false); SAVEKEY.setVisible(false); this.repaint(); this.setVisible(true); } else if (e.getSource() == DECRYPT) { this.setVisible(false); decrypt(); try { Thread.sleep(250); } catch (InterruptedException E) { } this.setVisible(true); } else if (e.getSource() == LOADKEY) { loadKey(); } else if (e.getSource() == SAVEKEY) { saveKey(); } } public File openFile() { JFileChooser chooser = new JFileChooser(); int returnVal = chooser.showOpenDialog(this); if (returnVal == JFileChooser.APPROVE_OPTION) { targetFile = chooser.getSelectedFile(); filePath = targetFile.getPath(); System.out.println("\nPath is " + filePath); return targetFile; } else return null; } public File saveFile() { JFileChooser chooser = new JFileChooser(); int returnVal = chooser.showSaveDialog(this); if (returnVal == JFileChooser.APPROVE_OPTION) { targetFile = chooser.getSelectedFile(); filePath = targetFile.getPath(); return targetFile; } else return null; } private void encrypt() { File f = new File(filePath); if (!f.exists()) { new JOptionPane("No file selected for encryption!"); return; } try { InputStream in = new FileInputStream(f); OutputStream out = new CryptoOutputStream(new FileOutputStream(new File(f.getParent(), f.getName() + ".enc")), cs, pk); ByteArrayOutputStream bout = new ByteArrayOutputStream(); String s; setStatus("Encrypting: " + f.getName() + " -> " + "encrypted.txt ..."); System.out.print("Encrypting: " + f.getName() + " -> " + "encrypted.txt ..."); //For storing encrypted text in encrypted.txt file OutputStream eout = new FileOutputStream(new File(f.getParent(), "encrypted.txt")); int read; while ((read = in.read()) != -1) { bout.write(read); out.write(read); } out.flush(); in.close(); out.close(); setStatus("done"); InputStream enc = new FileInputStream(new File(f.getParent(), f.getName() + ".enc")); ByteArrayOutputStream encout = new ByteArrayOutputStream(); while ((read = enc.read()) != -1) { encout.write(read); } String encrypted = encout.toString(); String plain = bout.toString(); String printed = ""; printed = encrypted; eout.write(printed.getBytes()); eout.flush(); eout.close(); s = "Plain Text: " + plain + "\n" + "Encrypted Text:\n " + printed; pane.setFont(new Font("TimesNewRoman", Font.BOLD, 12)); pane.setText(s); enc.close(); System.out.println("OK"); } catch (IOException e) { e.printStackTrace(); System.out.println("Error in encryption!"); } } private void decrypt() { filePath = filePath + ".enc"; File f = new File(filePath); if (!f.exists()) { new JOptionPane("No file selected for decryption!"); return; } try { InputStream in = new CryptoInputStream(new FileInputStream(f), cs, sk); OutputStream out = new FileOutputStream(new File(f.getParent(), f.getName().substring(0, f.getName().length() - 4) + ".dec")); System.out.print("Decrypting: " + f.getName() + " -> " + f.getName().substring(0, f.getName().length() - 4) + ".dec ..."); setStatus("Decrypting: " + f.getName() + " -> " + f.getName().substring(0, f.getName().length() - 4) + ".dec ..."); ByteArrayOutputStream bout = new ByteArrayOutputStream(); String s; int read; while ((read = in.read()) != -1) { out.write(read); bout.write(read); } s = "Decrypted Text: " + bout.toString(); pane.setFont(new Font("TimesNewRoman", Font.BOLD, 12)); pane.setText(s); out.flush(); in.close(); out.close(); System.out.println("OK"); setStatus("done"); } catch (Exception e) { System.out.println("Error in decryption!"); } } private void loadKey() { File f = openFile(); if (f == null) { new JOptionPane("No file selected for key"); return; } try { sk = readKey(f); System.out.println("sk is:" + sk); pk = sk.getPublic(); } catch (Exception e) { System.out.println("Error loading key!"); e.printStackTrace(); } } private void saveKey() { String keydisp = " " + sk + "\n" + " " + pk; pane.setFont(new Font("TimesNewRoman", Font.BOLD, 12)); pane.setText(keydisp); } /** Writes the Key instance to a File f*/ public void writeKey(File f, Key k) { try { FileOutputStream out = new FileOutputStream(f); k.writeKey(out); out.flush(); out.close(); } catch (IOException e) { System.out.println("Error writing key!\n"); e.printStackTrace(System.out); System.exit(0); } } public Key readKey(File f) { try { Key k = cs.generateKey(); FileInputStream in = new FileInputStream(f); k = k.readKey(in); in.close(); return k; } catch (IOException e) { System.out.println("Error reading key file!\n" + e); return null; } } public class ExitController extends WindowAdapter { public void windowClosing(WindowEvent e) { System.out.println("Exiting " + TITLE); System.exit(0); } } } ================================================ FILE: src/test/java/test/jce/ecc2/BaseConvert.java ================================================ package test.jce.ecc2; import static javax.xml.bind.DatatypeConverter.parseBase64Binary; import static javax.xml.bind.DatatypeConverter.parseHexBinary; import static javax.xml.bind.DatatypeConverter.printBase64Binary; import static javax.xml.bind.DatatypeConverter.printHexBinary; /** * Utilities for converting various strings into byte arrays they encode for given bases */ public class BaseConvert { /** * Convert a string to a byte array it encodes * * @param string A string representing an array of bytes * @param radix The base the string is encoded with * @return The byte array the string represents * @throws UnsupportedBaseException Thrown if an unsupported base is handed as an argument */ public static byte[] baseEncodedStringToByteArray( String string, int radix) throws UnsupportedBaseException { switch (radix) { case 16: return parseHexBinary(string); case 64: return parseBase64Binary(string); default: throw new UnsupportedBaseException("Unknown base given when trying to parse an encoded string to a byte array"); } } /** * Convert a byte array into a encoded string * * @param bytes The bytes to encode as a string * @param radix The base of the encoding * @return An encoded string * @throws UnsupportedBaseException Thrown if an unsupported base is handed as an argument */ public static String byteArrayToBaseEncodedString( byte[] bytes, int radix) throws UnsupportedBaseException { switch (radix) { case 16: return printHexBinary(bytes).toLowerCase(); case 64: return printBase64Binary(bytes); default: throw new UnsupportedBaseException("Unknown base given when trying to write an string encoding a byte array"); } } } ================================================ FILE: src/test/java/test/jce/ecc2/CurveParameters.java ================================================ package test.jce.ecc2; import org.bouncycastle.asn1.sec.SECNamedCurves; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.crypto.params.ECDomainParameters; public class CurveParameters { /** * Creates an ECDomainParameters from a named curve. * @param curveName The name of the curve to use */ public static ECDomainParameters getCurveParametersByName(String curveName) { X9ECParameters x9ECParameters = SECNamedCurves.getByName(curveName); return new ECDomainParameters( x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN(), x9ECParameters.getH()); } public static final ECDomainParameters secp256k1 = getCurveParametersByName("secp256k1"); public static final ECDomainParameters secp256r1 = getCurveParametersByName("secp256r1"); } ================================================ FILE: src/test/java/test/jce/ecc2/PrivateKey.java ================================================ package test.jce.ecc2; import static javax.xml.bind.DatatypeConverter.printHexBinary; import static test.jce.ecc2.BaseConvert.byteArrayToBaseEncodedString; import static test.jce.ecc2.Utils.*; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.math.BigInteger; import java.security.SecureRandom; import java.util.Arrays; import org.bouncycastle.asn1.ASN1Integer; import org.bouncycastle.asn1.DERSequenceGenerator; import org.bouncycastle.crypto.digests.GeneralDigest; import org.bouncycastle.crypto.digests.SHA256Digest; import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.crypto.signers.HMacDSAKCalculator; import org.bouncycastle.math.ec.ECPoint; final public class PrivateKey { private final BigInteger d; private final ECDomainParameters curveParameters; private final PublicKey publicKey; /** * Construct a PrivateKey * * @param curveParameters The parameters of the elliptic curve to be used * @param d The coefficient to be used as a private key * @throws SecurityException Thrown if the specified private key coefficient is not a valid value permitted by the elliptic curve specified by curveParameters */ public PrivateKey(ECDomainParameters curveParameters, BigInteger d) throws SecurityException { if (d.compareTo(BigInteger.ZERO) != 1) throw new SecurityException("Private key must be positive."); if (curveParameters.getN().compareTo(d) != 1) throw new SecurityException("Private key cannot be larger than the curve modulus."); this.d = d; this.curveParameters = curveParameters; this.publicKey = new PublicKey(curveParameters, curveParameters.getG().multiply(d).normalize()); } /** * Parse a PrivateKey from a byte array * * @param curveParameters The parameters of the elliptic curve to be used * @param bytes The byte array to be parsed * @return The private key corresponding to the specified bytes */ public static PrivateKey fromByteArray(ECDomainParameters curveParameters, byte[] bytes) { return new PrivateKey(curveParameters, new BigInteger(1, bytes)); } /** * Parse a PrivateKey from an encoded string * * @param curveParameters The parameters of the elliptic curve to be used * @param string The encoded string, which is to be parsed into bytes specified by the radix * @param radix The radix of the encoded string * @return The private key corresponding to the specified bytes * @throws UnsupportedBaseException Thrown if the specified radix is not a supported base for encoding strings */ public static PrivateKey fromString( ECDomainParameters curveParameters, String string, int radix) throws UnsupportedBaseException { if (radix == 10) return new PrivateKey(curveParameters, new BigInteger(string, 10)); return fromByteArray(curveParameters, BaseConvert.baseEncodedStringToByteArray(string, radix)); } /** * Convert the coefficient of the private key into a byte array * * @return A byte array representing the coefficient of the private key */ public byte[] toByteArray() { final byte[] data = d.toByteArray(); return Arrays.copyOfRange(data, countLeadingZeroBytes(data), data.length); } /** * Convert the coefficient of the private key into a string encoding a byte array using the specified radix * * @param radix The radix to use for encoding the coefficient's representative byte array as a string * @return A string encoding a byte array representing the coefficient of the private key * @throws UnsupportedBaseException Thrown if the specified radix is not a supported base for encoding strings */ public String toString(int radix) throws UnsupportedBaseException { if (radix == 10) return d.toString(10); return byteArrayToBaseEncodedString(toByteArray(), radix); } /** * Convert the coefficient of the private key into a hexadecimal string * * @return A hexadecimal string encoding a byte array representing the coefficient of the private key */ @Override public String toString() { return printHexBinary(toByteArray()).toLowerCase(); } /** * Get the public key for this private key * * @return The public key that corresponds to this private key */ public PublicKey getPublicKey() { return publicKey; } /** * Create a new RFC 6979 deterministic nonce generator for deterministic ECDSA signatures * * @param hash The hash of value to be signed * @param hashDigest A hashing function to use in signing * @return A cryptographic deterministic PRNG where the first returned value is compliant with RFC 6979 * @throws SecurityException Thrown if a fresh hash digest cannot be generated from the one hash handed as an argument */ private HMacDSAKCalculator deterministicKGenerator( byte[] hash, GeneralDigest hashDigest) throws SecurityException { final GeneralDigest freshHashDigest = freshDigestFromDigest(hashDigest); final HMacDSAKCalculator generator = new HMacDSAKCalculator(freshHashDigest); generator.init(curveParameters.getN(), d, hash); return generator; } /** * Compute a recovery byte for a compressed ECDSA signature given R and S parameters * * @param kp The elliptic curve point computed as part of the ECDSA algorithm that must be recovered * @param r The R value of the ECDSA signature * @param s The S value of the ECDSA signature * @return The recovery byte, following the convention in BitCoin */ private byte computeRecoveryByte(ECPoint kp, BigInteger r, BigInteger s, boolean canonical) { final BigInteger n = curveParameters.getN(); final boolean bigR = r.compareTo(n) >= 0; final boolean bigS = canonical && s.add(s).compareTo(n) >= 0; final boolean yOdd = kp.getYCoord().toBigInteger().testBit(0); return (byte) (0x1B + ((bigS != yOdd) ? 1 : 0) + (bigR ? 2 : 0)); } private static SecureRandom rng = new SecureRandom(); private static class TimeStampAndNonce { final long timeStamp; final long nonce; private TimeStampAndNonce() { this.timeStamp = System.currentTimeMillis(); this.nonce = rng.nextLong(); } } /** * A configuration that is read when constructing ECDSA signatures */ public static class SignatureConfig { final boolean recover; final TimeStampAndNonce timeStampAndNonce; final boolean canonical; final GeneralDigest rfc6979Digest; final GeneralDigest messageDigest; public SignatureConfig( boolean recover, boolean timeStampAndNonce, boolean canonical, GeneralDigest rfc6979Digest, GeneralDigest messageDigest) throws IllegalArgumentException { if (timeStampAndNonce && !recover) { throw new IllegalArgumentException( "Cannot configure signatures to include a timestamp and nonce without a recovery byte"); } this.recover = recover; this.canonical = canonical; this.timeStampAndNonce = timeStampAndNonce ? new TimeStampAndNonce() : null; this.rfc6979Digest = rfc6979Digest; this.messageDigest = messageDigest; } } /** * A builder for configurations for ECDSA signatures */ public static class SignatureConfigBuilder { private boolean recover = true; private boolean timeStampAndNonce = true; private boolean canonical = true; private GeneralDigest rfc6979Digest = new SHA256Digest(); private GeneralDigest messageDigest = new SHA256Digest(); public SignatureConfigBuilder() { } public SignatureConfigBuilder setRecover(boolean recover) { this.recover = recover; if (!recover) this.timeStampAndNonce = false; return this; } public SignatureConfigBuilder setTimeStampAndNonce(boolean timeStampAndNonce) { if (timeStampAndNonce && !recover) throw new IllegalArgumentException( "Cannot configure signatures to include a timestamp and nonce without a recovery byte"); this.timeStampAndNonce = timeStampAndNonce; return this; } public SignatureConfigBuilder setCanonical(boolean canonical) { this.canonical = canonical; return this; } public SignatureConfigBuilder setRfc6979Digest(GeneralDigest rfc6979Digest) { this.rfc6979Digest = rfc6979Digest; return this; } public SignatureConfigBuilder setMessageDigest(GeneralDigest messageDigest) { this.messageDigest = messageDigest; return this; } public SignatureConfig build() { return new SignatureConfig(recover, timeStampAndNonce, canonical, rfc6979Digest, messageDigest); } } /** * A default configuration for ECDSA signatures */ public static SignatureConfig getDefaultSignatureConfig() { return new SignatureConfigBuilder().build(); } private byte[] signHash(byte[] hash, SignatureConfig config) throws SecurityException { if ((hash.length << 3) > curveParameters.getN().bitLength()) throw new SecurityException("Hash must not have more bytes than the curve specifies"); final HMacDSAKCalculator rng = deterministicKGenerator(hash, config.rfc6979Digest); final BigInteger z = new BigInteger(1, hash); while (true) { final BigInteger k = rng.nextK(); final BigInteger n = curveParameters.getN(); final ECPoint kp = curveParameters.getG().multiply(k).normalize(); final BigInteger r = kp.getXCoord().toBigInteger().mod(n); if (r.equals(BigInteger.ZERO)) continue; final BigInteger s_ = k.modInverse(n).multiply(r.multiply(d).add(z)).mod(n); if (s_.equals(BigInteger.ZERO)) continue; final BigInteger s = config.canonical && (s_.add(s_).compareTo(n) >= 0) ? n.subtract(s_) : s_; ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { final DERSequenceGenerator derSequenceGenerator = new DERSequenceGenerator(bos); derSequenceGenerator.addObject(new ASN1Integer(r)); derSequenceGenerator.addObject(new ASN1Integer(s)); if (config.recover || config.timeStampAndNonce != null) derSequenceGenerator.addObject(new ASN1Integer(computeRecoveryByte(kp, r, s_, config.canonical))); if (config.timeStampAndNonce != null) { derSequenceGenerator.addObject(new ASN1Integer(config.timeStampAndNonce.timeStamp)); derSequenceGenerator.addObject(new ASN1Integer(config.timeStampAndNonce.nonce)); } derSequenceGenerator.close(); return bos.toByteArray(); } catch (IOException e) { throw new SecurityException(e); } } } public byte[] sign(byte[] data, SignatureConfig config) throws SecurityException { GeneralDigest hashDigest = freshDigestFromDigest(config.messageDigest); final byte[] hash = new byte[hashDigest.getDigestSize()]; if (config.timeStampAndNonce != null) { final byte[] timeStampBytes = longToBytes(config.timeStampAndNonce.timeStamp); hashDigest.update(timeStampBytes, 0, timeStampBytes.length); final byte[] nonceBytes = longToBytes(config.timeStampAndNonce.nonce); hashDigest.update(nonceBytes, 0, nonceBytes.length); } hashDigest.update(data, 0, data.length); hashDigest.doFinal(hash, 0); return signHash(hash, config); } public byte[] sign(byte[] data) throws SecurityException { return sign(data, getDefaultSignatureConfig()); } public byte[] signUTF8String(String string, SignatureConfig config) throws SecurityException { return sign(stringToUTF8Bytes(string), config); } public byte[] signUTF8String(String string) throws SecurityException { return signUTF8String(string, getDefaultSignatureConfig()); } public byte[] diffieHelmanSharedSecret(PublicKey publicKey) throws SecurityException { if (!(curveParameters.getCurve().equals(publicKey.curveParameters.getCurve()) && curveParameters.getG().equals(publicKey.curveParameters.getG()) && curveParameters.getN().equals(publicKey.curveParameters.getN()) && curveParameters.getH().equals(publicKey.curveParameters.getH()))) throw new SecurityException("Public key does not have the same curve parameters as private key"); return publicKey.point.multiply(this.d).getEncoded(true); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final PrivateKey privateKey = (PrivateKey) o; return d.equals(privateKey.d) && curveParameters.getCurve().equals(privateKey.curveParameters.getCurve()) && curveParameters.getG().equals(privateKey.curveParameters.getG()) && curveParameters.getN().equals(privateKey.curveParameters.getN()) && curveParameters.getH().equals(privateKey.curveParameters.getH()); } @Override public int hashCode() { int code = d.hashCode(); code = 31 * code + curveParameters.getCurve().hashCode(); code = 37 * code + curveParameters.getG().hashCode(); code = 41 * code + curveParameters.getN().hashCode(); code = 43 * code + curveParameters.getH().hashCode(); return code; } } ================================================ FILE: src/test/java/test/jce/ecc2/PrivateKeyTest.java ================================================ package test.jce.ecc2; import static test.jce.ecc2.BaseConvert.baseEncodedStringToByteArray; import static test.jce.ecc2.BaseConvert.byteArrayToBaseEncodedString; import static test.jce.ecc2.CurveParameters.secp256k1; import static test.jce.ecc2.CurveParameters.secp256r1; import static test.jce.ecc2.PrivateKey.getDefaultSignatureConfig; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.math.BigInteger; import org.bouncycastle.crypto.digests.SHA256Digest; import org.junit.Assert; import org.junit.Test; public class PrivateKeyTest { @Test public void testFromAndToString() throws Exception { Assert.assertEquals( "c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c", PrivateKey.fromString( secp256k1, "c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c", 16).toString(16)); Assert.assertEquals(PrivateKey.fromString( secp256k1, "c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c", 16).toString(), "c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c"); Assert.assertEquals(PrivateKey.fromString( secp256k1, "01", 16).toString(), "01"); Assert.assertEquals(PrivateKey.fromString( secp256k1, "01", 10).toString(10), "1"); } @Test(expected = SecurityException.class) public void testFromStringEmptyStringThrows() throws Exception { PrivateKey.fromString(secp256k1, "", 16); } @Test(expected = SecurityException.class) public void testFromStringZeroStringThrows() throws Exception { PrivateKey.fromString(secp256k1, "00", 16); } @Test(expected = SecurityException.class) public void testFromStringVeryBigInputStringThrows() throws Exception { PrivateKey.fromString(secp256k1, "c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08cc6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c", 16); } @Test(expected = UnsupportedBaseException.class) public void testFromStringUnsupportedBase() throws Exception { PrivateKey.fromString(secp256k1, "01", 1234); } @Test public void testGetPublicKey() throws Exception { Assert.assertEquals( PrivateKey.fromString( secp256k1, "c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c", 16).getPublicKey().toString(16), "0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1" ); Assert.assertEquals( PrivateKey.fromString( secp256k1, "c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c", 16).getPublicKey(), PublicKey.fromString(secp256k1, "0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1", 16)); Assert.assertEquals( PrivateKey.fromString( CurveParameters.getCurveParametersByName("secp256k1"), "c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c", 16).getPublicKey(), PublicKey.fromString(secp256k1, "0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1", 16)); } @Test(expected = IllegalArgumentException.class) public void testSetTimeStampAndNonceSignatureConfigBuilderSadPath() { new PrivateKey.SignatureConfigBuilder().setRecover(false).setTimeStampAndNonce(true); } @Test(expected = IllegalArgumentException.class) public void testSetTimeStampAndNonceSignatureConfigSadPath() { //noinspection ConstantConditions new PrivateKey.SignatureConfig(false, true, true, new SHA256Digest(), new SHA256Digest()); } private static PrivateKey examplePrivateKey; static { try { examplePrivateKey = PrivateKey.fromString(secp256k1, "AgC/Dji4Yyn4TqkJcuDd3ltenDh", 64); } catch (UnsupportedBaseException ignored) { } } @Test(expected = UnsupportedBaseException.class) public void testToStringUnsupportedBaseException() throws Exception { String out = examplePrivateKey.toString(12345); throw new RuntimeException(out); } @Test public void testEquality() throws Exception { Assert.assertEquals( PrivateKey.fromString( secp256k1, "c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c", 16), PrivateKey.fromString( secp256k1, "c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c", 16)); Assert.assertEquals( PrivateKey.fromString( secp256k1, "c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c", 16), PrivateKey.fromString( secp256k1, "000000c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c", 16)); Assert.assertEquals( PrivateKey.fromString( secp256k1, "c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c", 16), PrivateKey.fromString( CurveParameters.getCurveParametersByName("secp256k1"), "c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c", 16)); } @Test public void testDeterministicSignatureNoRecover() throws Exception { PrivateKey privateKey = PrivateKey.fromString( secp256k1, "22c49372a7506d162e6551fca36eb59235a9252c7f55610b8d0859d8752235a9", 16); String input = "コトドリ属(コトドリぞく、学名 Menura)はコトドリ上科コトドリ科 Menuridae に属する鳥の属の一つ。コトドリ科は単型である。"; byte[] signatureBytes = privateKey.signUTF8String(input, new PrivateKey.SignatureConfigBuilder() .setRecover(false) .build()); Assert.assertTrue(privateKey.getPublicKey().verifySignedUTF8String(input, signatureBytes)); String signature = byteArrayToBaseEncodedString(signatureBytes, 16); Assert.assertEquals("3045022100a28224c02e60f4e0a345cfc1043de9be408301393eec9225ab849d6bed8b794302205d09d76f6ae27094c005883d41e7059bb14afb0d9b61f9c051dea384b5048834", signature); } @Test public void testDeterministicSignatureWithRecover() throws Exception { PrivateKey privateKey = PrivateKey.fromString( secp256k1, "22c49372a7506d162e6551fca36eb59235a9252c7f55610b8d0859d8752235a9", 16); String input = "コトドリ属(コトドリぞく、学名 Menura)はコトドリ上科コトドリ科 Menuridae に属する鳥の属の一つ。コトドリ科は単型である。"; byte[] signatureBytes = privateKey.signUTF8String(input, new PrivateKey.SignatureConfigBuilder() .setRecover(true) .setTimeStampAndNonce(false) .build()); Assert.assertTrue(privateKey.getPublicKey().verifySignedUTF8String(input, signatureBytes)); String signature = byteArrayToBaseEncodedString(signatureBytes, 16); Assert.assertEquals("3048022100a28224c02e60f4e0a345cfc1043de9be408301393eec9225ab849d6bed8b794302205d09d76f6ae27094c005883d41e7059bb14afb0d9b61f9c051dea384b504883402011b", signature); } @Test public void testDeterministicSignatureWithRecoverTestCanonical() throws Exception { PrivateKey privateKey = PrivateKey.fromString( secp256k1, "22c49372a7506d162e6551fca36eb59235a9252c7f55610b8d0859d8752235a9", 16); String input = "コトドリ属(コトドリぞく、学名 Menura)はコトドリ上科コトドリ科 Menuridae に属する鳥の属の一つ。コトドリ科は単型である。XXX"; byte[] canonicalSignatureBytes = privateKey.signUTF8String(input, new PrivateKey.SignatureConfigBuilder() .setRecover(true) .setTimeStampAndNonce(false) .setCanonical(true) .build()); Assert.assertTrue(privateKey.getPublicKey().verifySignedUTF8String(input, canonicalSignatureBytes)); String canonicalSignature = byteArrayToBaseEncodedString(canonicalSignatureBytes, 16); byte[] nonCanonicalSignatureBytes = privateKey.signUTF8String(input, new PrivateKey.SignatureConfigBuilder() .setRecover(true) .setTimeStampAndNonce(false) .setCanonical(false) .build()); Assert.assertTrue(privateKey.getPublicKey().verifySignedUTF8String(input, nonCanonicalSignatureBytes)); String nonCanonicalSignature = byteArrayToBaseEncodedString(nonCanonicalSignatureBytes, 16); Assert.assertFalse(canonicalSignature.equals(nonCanonicalSignature)); } @Test public void testDeterministicSignatureWithRecoverByteArray() throws Exception { PrivateKey privateKey = PrivateKey.fromByteArray( secp256k1, BaseConvert.baseEncodedStringToByteArray("22c49372a7506d162e6551fca36eb59235a9252c7f55610b8d0859d8752235a9", 16)); String input = "コトドリ属(コトドリぞく、学名 Menura)はコトドリ上科コトドリ科 Menuridae に属する鳥の属の一つ。コトドリ科は単型である。"; byte[] signatureBytes = privateKey.signUTF8String(input, new PrivateKey.SignatureConfigBuilder() .setRecover(true) .setTimeStampAndNonce(false) .build()); Assert.assertTrue(privateKey.getPublicKey().verifySignedUTF8String(input, signatureBytes)); String signature = byteArrayToBaseEncodedString(signatureBytes, 16); Assert.assertEquals("3048022100a28224c02e60f4e0a345cfc1043de9be408301393eec9225ab849d6bed8b794302205d09d76f6ae27094c005883d41e7059bb14afb0d9b61f9c051dea384b504883402011b", signature); } @Test public void testDeterministicSignatureWithRecoverByteArrayBackAndForth() throws Exception { PrivateKey privateKey = PrivateKey.fromByteArray( secp256k1, PrivateKey.fromByteArray( secp256k1, BaseConvert.baseEncodedStringToByteArray("22c49372a7506d162e6551fca36eb59235a9252c7f55610b8d0859d8752235a9", 16)) .toByteArray()); String input = "コトドリ属(コトドリぞく、学名 Menura)はコトドリ上科コトドリ科 Menuridae に属する鳥の属の一つ。コトドリ科は単型である。"; byte[] signatureBytes = privateKey.signUTF8String(input, new PrivateKey.SignatureConfigBuilder() .setRecover(true) .setTimeStampAndNonce(false) .build()); Assert.assertTrue(privateKey.getPublicKey().verifySignedUTF8String(input, signatureBytes)); String signature = byteArrayToBaseEncodedString(signatureBytes, 16); Assert.assertEquals("3048022100a28224c02e60f4e0a345cfc1043de9be408301393eec9225ab849d6bed8b794302205d09d76f6ae27094c005883d41e7059bb14afb0d9b61f9c051dea384b504883402011b", signature); } @Test public void testDeterministicSignatureWithRecoverConstructor() throws Exception { PrivateKey privateKey = new PrivateKey( secp256k1, new BigInteger(1, BaseConvert.baseEncodedStringToByteArray("22c49372a7506d162e6551fca36eb59235a9252c7f55610b8d0859d8752235a9", 16))); String input = "コトドリ属(コトドリぞく、学名 Menura)はコトドリ上科コトドリ科 Menuridae に属する鳥の属の一つ。コトドリ科は単型である。"; byte[] signatureBytes = privateKey.signUTF8String(input, new PrivateKey.SignatureConfigBuilder() .setRecover(true) .setTimeStampAndNonce(false) .build()); Assert.assertTrue(privateKey.getPublicKey().verifySignedUTF8String(input, signatureBytes)); String signature = byteArrayToBaseEncodedString(signatureBytes, 16); Assert.assertEquals("3048022100a28224c02e60f4e0a345cfc1043de9be408301393eec9225ab849d6bed8b794302205d09d76f6ae27094c005883d41e7059bb14afb0d9b61f9c051dea384b504883402011b", signature); } @Test public void testDeterministicSignatureWithRecoverBigIntegerONE() throws Exception { PrivateKey privateKey = new PrivateKey(secp256k1, BigInteger.ONE); String input = "コトドリ属(コトドリぞく、学名 Menura)はコトドリ上科コトドリ科 Menuridae に属する鳥の属の一つ。コトドリ科は単型である。"; byte[] signatureBytes = privateKey.signUTF8String(input, new PrivateKey.SignatureConfigBuilder() .setRecover(true) .setTimeStampAndNonce(false) .build()); Assert.assertTrue(privateKey.getPublicKey().verifySignedUTF8String(input, signatureBytes)); String signature = byteArrayToBaseEncodedString(signatureBytes, 16); Assert.assertEquals("3048022100972b6487837a509cc781ad73fa07c92bbbb65fb8aa35de97a341a0dcdb5244ba022031d27cbe8d4998806862d4df7a75b5b4a102db13aea1b51a080d13f45fda57a402011c", signature); } @Test public void testSignHash() throws Exception { PrivateKey privateKey = PrivateKey.fromString( secp256k1, "22c49372a7506d162e6551fca36eb59235a9252c7f55610b8d0859d8752235a9", 16); Method signHash = PrivateKey.class.getDeclaredMethod("signHash", byte[].class, PrivateKey.SignatureConfig.class); signHash.setAccessible(true); signHash.invoke(privateKey, new byte[secp256k1.getN().bitLength() / 8], getDefaultSignatureConfig()); } @Test(expected = InvocationTargetException.class) public void testSignHashBigHashSadPath() throws Exception { PrivateKey privateKey = PrivateKey.fromString( secp256k1, "22c49372a7506d162e6551fca36eb59235a9252c7f55610b8d0859d8752235a9", 16); Method signHash = PrivateKey.class.getDeclaredMethod("signHash", byte[].class, PrivateKey.SignatureConfig.class); signHash.setAccessible(true); signHash.invoke(privateKey, new byte[512], getDefaultSignatureConfig()); } @Test public void testHashCode() throws Exception { Assert.assertEquals( PrivateKey.fromString( secp256k1, "c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c", 16).hashCode(), PrivateKey.fromString( secp256k1, "c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c", 16).hashCode()); Assert.assertEquals( PrivateKey.fromString( secp256k1, "c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c", 16).hashCode(), PrivateKey.fromString( secp256k1, "000000c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c", 16).hashCode()); Assert.assertEquals( PrivateKey.fromString( secp256k1, "c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c", 16).hashCode(), PrivateKey.fromString( CurveParameters.getCurveParametersByName("secp256k1"), "c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c", 16).hashCode()); } @Test public void testDiffieHelman() throws Exception { PrivateKey privateKey1 = PrivateKey.fromString( secp256k1, "22c49372a7506d162e6551fca36eb59235a9252c7f55610b8d0859d8752235a9", 16); PrivateKey privateKey2 = PrivateKey.fromString( secp256k1, "0ffffffffffffffffffffffffffffffffff9252c7f55610b8d0859d8752235a9", 16); Assert.assertArrayEquals( privateKey1.diffieHelmanSharedSecret(privateKey2.getPublicKey()), privateKey2.diffieHelmanSharedSecret(privateKey1.getPublicKey())); } @Test(expected = SecurityException.class) public void testDiffieHelmanSadPath() throws Exception { PrivateKey privateKey1 = PrivateKey.fromString( secp256k1, "22c49372a7506d162e6551fca36eb59235a9252c7f55610b8d0859d8752235a9", 16); PrivateKey privateKey2 = PrivateKey.fromString( secp256r1, "0ffffffffffffffffffffffffffffffffff9252c7f55610b8d0859d8752235a9", 16); privateKey1.diffieHelmanSharedSecret(privateKey2.getPublicKey()); } @Test public void testSignatureSadPath() throws Exception { PrivateKey privateKey1 = PrivateKey.fromString( secp256k1, "22c49372a7506d162e6551fca36eb59235a9252c7f55610b8d0859d8752235a9", 16); PrivateKey privateKey2 = PrivateKey.fromString( secp256k1, "0ffffffffffffffffffffffffffffffffff9252c7f55610b8d0859d8752235a9", 16); String message = "Moloch!"; String data = byteArrayToBaseEncodedString(privateKey2.signUTF8String(message), 16); Assert.assertFalse( privateKey1.getPublicKey().verifySignedUTF8String( message, baseEncodedStringToByteArray(data, 16))); } @Test public void testTimeStampChronologicalOrder() throws Exception { PrivateKey privateKey1 = PrivateKey.fromString( secp256k1, "22c49372a7506d162e6551fca36eb59235a9252c7f55610b8d0859d8752235a9", 16); PrivateKey privateKey2 = PrivateKey.fromString( secp256r1, "22c49372a7506d162e6551fca36eb59235a9252c7f55610b8d0859d8752235a9", 16); String message = "Moloch!"; Long timeStamp1 = PublicKey.getTimeStampFromSignature(privateKey1.signUTF8String(message)); Long timeStamp2 = PublicKey.getTimeStampFromSignature(privateKey2.signUTF8String(message)); Long now = System.currentTimeMillis(); Assert.assertTrue(timeStamp1 <= timeStamp2); Assert.assertTrue(timeStamp2 <= now); } } ================================================ FILE: src/test/java/test/jce/ecc2/PublicKey.java ================================================ package test.jce.ecc2; import static javax.xml.bind.DatatypeConverter.printHexBinary; import static test.jce.ecc2.BaseConvert.baseEncodedStringToByteArray; import static test.jce.ecc2.BaseConvert.byteArrayToBaseEncodedString; import static test.jce.ecc2.Utils.*; import java.math.BigInteger; import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1Integer; import org.bouncycastle.asn1.DLSequence; import org.bouncycastle.crypto.digests.GeneralDigest; import org.bouncycastle.crypto.digests.SHA256Digest; import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.crypto.params.ECPublicKeyParameters; import org.bouncycastle.crypto.signers.ECDSASigner; import org.bouncycastle.math.ec.ECAlgorithms; import org.bouncycastle.math.ec.ECCurve; import org.bouncycastle.math.ec.ECPoint; final public class PublicKey { final ECPoint point; final ECDomainParameters curveParameters; private final ECDSASigner verifier = new ECDSASigner(); /** * Verify if a point is valid for this curve. * * @param curveParameters The parameters of the elliptic curve to be used * @param point An elliptic curve point * @return Whether this point was valid or not. */ private static boolean isValidPoint(ECDomainParameters curveParameters, ECPoint point) { final BigInteger x = point.getXCoord().toBigInteger(); final BigInteger y = point.getYCoord().toBigInteger(); final ECCurve ec = curveParameters.getCurve(); final BigInteger a = ec.getA().toBigInteger(); final BigInteger b = ec.getB().toBigInteger(); final BigInteger p = ec.getField().getCharacteristic(); return x.multiply(x).multiply(x).add(a.multiply(x)).add(b).mod(p) .equals(y.multiply(y).mod(p)); } /** * Construct a PublicKey given specified curve parameters and an elliptic curve point. * * @param curveParameters The parameters of the elliptic curve to use * @param point The point to use as a public key on the elliptic curve * @throws SecurityException A security exception is thrown in the event that point is not valid */ public PublicKey(ECDomainParameters curveParameters, ECPoint point) throws SecurityException { if (point.getCurve() != curveParameters.getCurve()) throw new SecurityException("Point is not on curve specified by curve parameters"); if (!isValidPoint(curveParameters, point)) throw new SecurityException("Cannot initialize an invalid elliptic curve point"); this.point = point; this.curveParameters = curveParameters; verifier.init(false, new ECPublicKeyParameters(this.point, curveParameters)); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PublicKey publicKey = (PublicKey) o; return point.equals(publicKey.point) && curveParameters.getCurve().equals(publicKey.curveParameters.getCurve()) && curveParameters.getG().equals(publicKey.curveParameters.getG()) && curveParameters.getN().equals(publicKey.curveParameters.getN()) && curveParameters.getH().equals(publicKey.curveParameters.getH()); } @Override public int hashCode() { int code = point.hashCode(); code = 31 * code + curveParameters.getCurve().hashCode(); code = 37 * code + curveParameters.getG().hashCode(); code = 41 * code + curveParameters.getN().hashCode(); code = 43 * code + curveParameters.getH().hashCode(); return code; } /** * Recover an X9.62 encoded public key from an array of bytes * * @param curveParameters The parameters of the elliptic curve to be used * @param bytes The X9.62 encoded public key * @return The public key corresponding to the input bytes * @throws SecurityException Throws a SecurityException if the point given is invalid (ie, not on the specified curve) */ public static PublicKey fromByteArray( ECDomainParameters curveParameters, byte[] bytes) throws SecurityException { return new PublicKey(curveParameters, curveParameters.getCurve().decodePoint(bytes)); } /** * Recover an uncompressed X9.62 encoded public key from an array of bytes * * @param curveParameters The parameters of the elliptic curve to be used * @param bytes The X9.62 encoded public key * @return The public key corresponding to the input bytes * @throws SecurityException Throws a SecurityException if the point given is invalid (ie, not on the specified curve or compressed) */ public static PublicKey fromUncompressedByteArray( ECDomainParameters curveParameters, byte[] bytes) throws SecurityException { if (bytes[0] != 0x04) throw new SecurityException(String.format("Expected first bytes of array to be 0x04: %s", printHexBinary(bytes))); return new PublicKey(curveParameters, curveParameters.getCurve().decodePoint(bytes)); } /** * Recover an X9.62 encoded public key from a string encoding an array of bytes * * @param curveParameters The parameters of the elliptic curve to be used * @param string The X9.62 encoded public key as a string encoding a byte array * @param base The base of encoded string * @return The public key corresponding to the input string * @throws SecurityException In the event of an invalid key * @throws UnsupportedBaseException If the user is trying to convert from an unsupported base */ public static PublicKey fromString( ECDomainParameters curveParameters, String string, int base) throws SecurityException, UnsupportedBaseException { return fromByteArray(curveParameters, baseEncodedStringToByteArray(string, base)); } /** * Convert the elliptic curve point to a X9.62 compressed byte array encoding * * @return The compressed X9.62 byte array encoding of the curve point associated with the public key */ public byte[] toByteArray() { return toByteArray(true); } /** * Convert the elliptic curve point to a X9.62 encoded byte array, with compression used as specified * * @param compressed Whether the resulting curve point is to be compressed when converted to a byte array * @return A X9.62 encoded byte array representing the curve point associated with the public key */ public byte[] toByteArray(boolean compressed) { return point.getEncoded(compressed); } /** * Convert the elliptic curve point to a string representing a X9.62 compressed byte array encoding with the given base * * @param radix Base to use when encoding * @return A string representing the compressed X9.62 byte array encoding * @throws UnsupportedBaseException Thrown when the specified base is not supported */ public String toString(int radix) throws UnsupportedBaseException { return byteArrayToBaseEncodedString(toByteArray(), radix); } /** * Convert the elliptic curve point to a string representing a X9.62 byte array encoding with the given base * * @param base Base to use when encoding * @param compressed Whether to use compression or not when constructing the byte array * @return A string representing the X9.62 byte array encoding * @throws UnsupportedBaseException Thrown when the specified base is not supported */ public String toString(int base, boolean compressed) throws UnsupportedBaseException { return byteArrayToBaseEncodedString(toByteArray(compressed), base); } /** * Convert the elliptic curve point to a hexadecimal string encoding a X9.62 compressed byte array * * @return A hexadecimal string encoding a X9.62 compressed byte array */ @Override public String toString() { return printHexBinary(toByteArray()).toLowerCase(); } private static class TimeStampAndNonce { final byte[] timeStampBytes; final byte[] nonceBytes; TimeStampAndNonce(long timeStamp, long nonce) { this.timeStampBytes = longToBytes(timeStamp); this.nonceBytes = longToBytes(nonce); } } private static class DeserializedSignature { final BigInteger r; final BigInteger s; final byte recover; final TimeStampAndNonce timeStampAndNonce; DeserializedSignature(byte[] signature) { try (ASN1InputStream decoder = new ASN1InputStream(signature)) { final DLSequence sequence = (DLSequence) decoder.readObject(); final int length = sequence.toArray().length; this.r = ((ASN1Integer) sequence.getObjectAt(0)).getValue(); this.s = ((ASN1Integer) sequence.getObjectAt(1)).getValue(); this.recover = (length >= 3) ? (byte) ((ASN1Integer) sequence.getObjectAt(2)).getValue().intValue() : 0; this.timeStampAndNonce = (length >= 5) ? new TimeStampAndNonce( ((ASN1Integer) sequence.getObjectAt(3)).getValue().longValue(), ((ASN1Integer) sequence.getObjectAt(4)).getValue().longValue()) : null; if (length == 4) throw new SecurityException("Signature cannot specify a time stamp without a nonce"); if (length > 5) throw new SecurityException("Signature cannot have more than 5 entries"); } catch (Throwable e) { throw new SecurityException(e); } } } /** * Extracts the timestamp from an ASN.1 signature * * @param signature The signature bytes to extract the timestamp from * @return The timestamp in the signature, represented as milliseconds since epoch */ public static Long getTimeStampFromSignature(byte[] signature) { final DeserializedSignature deserializedSignature = new DeserializedSignature(signature); if (deserializedSignature.timeStampAndNonce == null) throw new SecurityException("Signature did not contain a timestamp"); return bytesToLong(deserializedSignature.timeStampAndNonce.timeStampBytes); } /** * Compute an elliptic curve point from a specified X coordinate given the parity of the Y coordinate * * @param curveParameters The parameters of the curve to use when specifying the X coordinate * @param yEven Whether the Y coordinate is even or not * @param xCoordinate The X coordinate of the curve point to be computed * @return The elliptic curve point given the specified curve parameters that has the appropriate X coordinate and Y coordinate * @throws SecurityException Thrown when the specified point is invalid (ie, not on the given curve) */ private static ECPoint computePoint(ECDomainParameters curveParameters, boolean yEven, BigInteger xCoordinate) throws SecurityException { final int bitCount = curveParameters.getN().bitLength(); if ((bitCount & 0x07) != 0) throw new SecurityException(String.format("Curve does not have an even number of bytes (number of bits is: %d)", bitCount)); final int curveLength = bitCount / 8; final byte[] raw = xCoordinate.toByteArray(); final byte[] input = new byte[curveLength + 1]; if (raw.length > curveLength) { final int zeros = countLeadingZeroBytes(raw); if (raw.length - zeros > curveLength) throw new SecurityException("X Coordinate has more bytes than curve length"); System.arraycopy(raw, zeros, input, curveLength - (raw.length - zeros) + 1, raw.length - zeros); } else System.arraycopy(raw, 0, input, curveLength - raw.length + 1, raw.length); input[0] = (byte) (yEven ? 0x02 : 0x03); try { return curveParameters.getCurve().decodePoint(input); } catch (IllegalArgumentException e) { throw new SecurityException(e); } } /** * Recovers a {@code PublicKey}, given a hash, from an extended ECDSA signature that includes a recovery byte * * @param curveParameters The parameters of the curve to use when recovering a {@code PublicKey} * @param hash The hash of the data that has been signed * @param signature An extended ECDSA signature including a recovery byte * @return The public key recovered from the signature * @throws SecurityException Thrown when there is an invalid recovery byte */ public static PublicKey recoverPublicKeyWithHash( ECDomainParameters curveParameters, byte[] hash, byte[] signature) throws SecurityException { DeserializedSignature sig = new DeserializedSignature(signature); if (!(0x1B <= sig.recover && sig.recover <= 0x1E)) throw new SecurityException("Invalid recovery byte"); final boolean yEven = ((sig.recover - 0x1B) & 1) == 0; final boolean isSecondKey = (((sig.recover - 0x1B) >> 1) & 1) == 1; final BigInteger n = curveParameters.getN(); final ECPoint kp = computePoint(curveParameters, yEven, isSecondKey ? sig.r.add(n) : sig.r); final BigInteger eInverse = n.subtract(new BigInteger(1, hash)); final BigInteger rInverse = sig.r.modInverse(n); final ECPoint recoveredPointCandidate = ECAlgorithms .sumOfTwoMultiplies(curveParameters.getG(), eInverse, kp, sig.s) .multiply(rInverse) .normalize(); return new PublicKey(curveParameters, recoveredPointCandidate); } /** * Recovers a {@code PublicKey}, given a byte array of data, from an extended ECDSA signature of a hash of that data that includes a recovery byte * * @param curveParameters The parameters of the curve * @param data The data that is to be hashed and signed * @param signature An extended ECDSA signature including a recovery byte * @param hashDigest The hashing digest to use to generate the hash that was signed * @return A recovered public key * @throws SecurityException Thrown when the recovered elliptic curve point is not on the specified curve */ public static PublicKey recoverPublicKey( ECDomainParameters curveParameters, byte[] data, byte[] signature, GeneralDigest hashDigest) throws SecurityException { final GeneralDigest freshHashDigest = freshDigestFromDigest(hashDigest); final byte[] hash = new byte[freshHashDigest.getDigestSize()]; final DeserializedSignature sig = new DeserializedSignature(signature); if (sig.timeStampAndNonce != null) { freshHashDigest.update(sig.timeStampAndNonce.timeStampBytes, 0, sig.timeStampAndNonce.timeStampBytes.length); freshHashDigest.update(sig.timeStampAndNonce.nonceBytes, 0, sig.timeStampAndNonce.nonceBytes.length); } freshHashDigest.update(data, 0, data.length); freshHashDigest.doFinal(hash, 0); return recoverPublicKeyWithHash(curveParameters, hash, signature); } /** * Recovers a {@code PublicKey}, given a byte array of data, from an extended ECDSA signature of a SHA-256 hash of that data that includes a recovery byte * * @param curveParameters The parameters of the curve * @param data The data that is to be SHA-256 hashed and signed * @param signature An extended ECDSA signature including a recovery byte * @return A recovered public key * @throws SecurityException Thrown when the recovered elliptic curve point is not on the specified curve or the signature is otherwise incorrectly formatted */ public static PublicKey recoverPublicKey( ECDomainParameters curveParameters, byte[] data, byte[] signature) throws SecurityException { return recoverPublicKey(curveParameters, data, signature, new SHA256Digest()); } public static PublicKey recoverPublicKeyFromSignedUTF8String( ECDomainParameters curveParameters, String string, byte[] signature, GeneralDigest hashDigest) throws SecurityException { return recoverPublicKey(curveParameters, stringToUTF8Bytes(string), signature, hashDigest); } public static PublicKey recoverPublicKeyFromSignedUTF8String( ECDomainParameters curveParameters, String string, byte[] signature) throws SecurityException { return recoverPublicKeyFromSignedUTF8String(curveParameters, string, signature, new SHA256Digest()); } /** * Verify an ECDSA signature of a hash * * @param hash A hashed value that has been signed * @param signature An ECDSA signature * @return Whether the signature is valid * @throws SecurityException If there is an invalid recovery byte */ private boolean verifySignatureFromHash(byte[] hash, byte[] signature) throws SecurityException { DeserializedSignature sig = new DeserializedSignature(signature); if (sig.recover != 0) return this.equals(recoverPublicKeyWithHash(this.curveParameters, hash, signature)); return verifier.verifySignature(hash, sig.r, sig.s); } /** * Verify an ECDSA Signature of a hash of specified input * * @param data Data to be hashed * @param signature Signature of the hashed data * @param hashDigest The hashing digest to use to generate the hash to check against the signature * @return Whether the signature is valid * @throws SecurityException If the signature cannot be safely deserialized or there is an invalid recovery byte */ public boolean verifySignature(byte[] data, byte[] signature, GeneralDigest hashDigest) throws SecurityException { final GeneralDigest freshHashDigest = freshDigestFromDigest(hashDigest); final byte[] hash = new byte[hashDigest.getDigestSize()]; final DeserializedSignature sig = new DeserializedSignature(signature); if (sig.timeStampAndNonce != null) { freshHashDigest.update(sig.timeStampAndNonce.timeStampBytes, 0, sig.timeStampAndNonce.timeStampBytes.length); freshHashDigest.update(sig.timeStampAndNonce.nonceBytes, 0, sig.timeStampAndNonce.nonceBytes.length); } freshHashDigest.update(data, 0, data.length); freshHashDigest.doFinal(hash, 0); return verifySignatureFromHash(hash, signature); } /** * Verify an ECDSA Signature of a SHA-256 hash of specified input * * @param data Data to be hashed * @param signature Signature of the hashed data * @return Whether the signature is valid * @throws SecurityException When a signature cannot be properly deserialized or contains an invalid recovery byte */ public boolean verifySignature(byte[] data, byte[] signature) throws SecurityException { return verifySignature(data, signature, new SHA256Digest()); } /** * Verify an ECDSA Signature of a hash of a UTF-8 encoded string * * @param string The string to be hashed * @param signature Signature of the hashed string * @param hashDigest The hashing digest to use to generate the hash to check against the signature * @return Whether the signature is valid * @throws SecurityException If the string is not a properly formatted ASN.1 signature */ public boolean verifySignedUTF8String(String string, byte[] signature, GeneralDigest hashDigest) throws SecurityException { return verifySignature(stringToUTF8Bytes(string), signature, hashDigest); } /** * Verify an ECDSA Signature of a SHA-256 hash of a UTF-8 encoded string * * @param string The string to be hashed * @param signature Signature of the hashed string * @return Whether the signature is valid * @throws SecurityException If the string is not a properly formatted ASN.1 signature */ public boolean verifySignedUTF8String(String string, byte[] signature) throws SecurityException { return verifySignedUTF8String(string, signature, new SHA256Digest()); } } ================================================ FILE: src/test/java/test/jce/ecc2/PublicKeyTest.java ================================================ package test.jce.ecc2; import org.bouncycastle.crypto.digests.GeneralDigest; import org.bouncycastle.crypto.digests.SHA224Digest; import org.bouncycastle.crypto.digests.SHA256Digest; import org.junit.Assert; import org.junit.Test; import static test.jce.ecc2.BaseConvert.baseEncodedStringToByteArray; import static test.jce.ecc2.BaseConvert.byteArrayToBaseEncodedString; import static test.jce.ecc2.CurveParameters.secp256k1; import static test.jce.ecc2.CurveParameters.secp256r1; import static test.jce.ecc2.PrivateKey.getDefaultSignatureConfig; import java.math.BigInteger; import java.security.MessageDigest; public class PublicKeyTest { @Test public void testToString() throws Exception { Assert.assertEquals(PublicKey.fromString(secp256k1, "0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1", 16).toString(), "0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1"); Assert.assertEquals(PublicKey.fromString(secp256k1, "0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1", 16).toString(16), "0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1"); Assert.assertEquals(PublicKey.fromString(secp256k1, "0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1", 16).toString(16, true), "0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1"); Assert.assertEquals(PublicKey.fromString(secp256k1, "0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1", 16).toString(16, false), "0400bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1be9e001b7ece071fb3986b5e96699fe28dbdeec8956682da78a5f6a115b9f14c"); Assert.assertEquals(PublicKey.fromString(secp256k1, "0400bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1be9e001b7ece071fb3986b5e96699fe28dbdeec8956682da78a5f6a115b9f14c", 16).toString(16, false), "0400bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1be9e001b7ece071fb3986b5e96699fe28dbdeec8956682da78a5f6a115b9f14c"); Assert.assertEquals(PublicKey.fromString(secp256k1, "0400bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1be9e001b7ece071fb3986b5e96699fe28dbdeec8956682da78a5f6a115b9f14c", 16).toString(16, true), "0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1"); Assert.assertEquals( "AgC/Dji4Yyn4TqkJcuD5AdXqAUXx66yMUP3td3ltenDh", PublicKey.fromString(secp256k1, "0400bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1be9e001b7ece071fb3986b5e96699fe28dbdeec8956682da78a5f6a115b9f14c", 16).toString(64, true)); Assert.assertEquals( "0400bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1be9e001b7ece071fb3986b5e96699fe28dbdeec8956682da78a5f6a115b9f14c", PublicKey.fromString(secp256k1, "AgC/Dji4Yyn4TqkJcuD5AdXqAUXx66yMUP3td3ltenDh", 64).toString(16, false)); } @Test public void testToByteArray() throws Exception { Assert.assertEquals("0400bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1be9e001b7ece071fb3986b5e96699fe28dbdeec8956682da78a5f6a115b9f14c", PublicKey.fromByteArray(secp256k1, PublicKey.fromString(secp256k1, "0400bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1be9e001b7ece071fb3986b5e96699fe28dbdeec8956682da78a5f6a115b9f14c", 16).toByteArray()).toString(16, false)); Assert.assertEquals("0400bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1be9e001b7ece071fb3986b5e96699fe28dbdeec8956682da78a5f6a115b9f14c", PublicKey.fromUncompressedByteArray(secp256k1, PublicKey.fromString(secp256k1, "0400bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1be9e001b7ece071fb3986b5e96699fe28dbdeec8956682da78a5f6a115b9f14c", 16).toByteArray(false)).toString(16, false)); Assert.assertEquals( "0400bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1be9e001b7ece071fb3986b5e96699fe28dbdeec8956682da78a5f6a115b9f14c", PublicKey.fromByteArray(secp256k1, PublicKey.fromString(secp256k1, "0400bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1be9e001b7ece071fb3986b5e96699fe28dbdeec8956682da78a5f6a115b9f14c", 16).toByteArray(true)).toString(16, false)); Assert.assertEquals( "0400bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1be9e001b7ece071fb3986b5e96699fe28dbdeec8956682da78a5f6a115b9f14c", PublicKey.fromByteArray(secp256k1, PublicKey.fromString(secp256k1, "0400bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1be9e001b7ece071fb3986b5e96699fe28dbdeec8956682da78a5f6a115b9f14c", 16).toByteArray(false)).toString(16, false)); Assert.assertEquals( "0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1", PublicKey.fromByteArray(secp256k1, PublicKey.fromString(secp256k1, "0400bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1be9e001b7ece071fb3986b5e96699fe28dbdeec8956682da78a5f6a115b9f14c", 16).toByteArray(false)).toString(16, true)); } private static PublicKey examplePublicKey; static { try { examplePublicKey = PublicKey.fromString(secp256k1, "AgC/Dji4Yyn4TqkJcuD5AdXqAUXx66yMUP3td3ltenDh", 64); } catch (UnsupportedBaseException ignored) { } } @Test(expected = UnsupportedBaseException.class) public void testToStringUnsupportedBaseException() throws Exception { String out = examplePublicKey.toString(12345); throw new RuntimeException(out); } @Test(expected = UnsupportedBaseException.class) public void testFromStringSadPathUnsupportedBaseException() throws Exception { PublicKey.fromString(secp256k1, "AgC/Dji4Yyn4TqkJcuD5AdXqAUXx66yMUP3td3ltenDh", 12345); } @Test public void testVerifySignedUTF8String() throws Exception { PrivateKey privateKey = PrivateKey.fromString( secp256k1, "c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c", 16); PublicKey publicKey = privateKey.getPublicKey(); PrivateKey.SignatureConfig noRecoverNotCanonicalConfig = new PrivateKey.SignatureConfigBuilder() .setRecover(false) .setCanonical(false) .build(); PrivateKey.SignatureConfig recoverButNoTimeStampsAndNonce = new PrivateKey.SignatureConfigBuilder() .setRecover(false) .setTimeStampAndNonce(false) .build(); Assert.assertTrue(publicKey.verifySignedUTF8String("foo", privateKey.signUTF8String("foo"))); Assert.assertTrue(publicKey.verifySignedUTF8String("foo", privateKey.sign("foo".getBytes("UTF-8")))); Assert.assertTrue(publicKey.verifySignedUTF8String("foo", privateKey.signUTF8String("foo", noRecoverNotCanonicalConfig))); Assert.assertTrue(publicKey.verifySignedUTF8String("foo", privateKey.sign("foo".getBytes("UTF-8"), noRecoverNotCanonicalConfig))); Assert.assertTrue(publicKey.verifySignedUTF8String("foo", privateKey.sign("foo".getBytes("UTF-8"), recoverButNoTimeStampsAndNonce))); Assert.assertArrayEquals( privateKey.sign("foo".getBytes("UTF-8"), noRecoverNotCanonicalConfig), privateKey.signUTF8String("foo", noRecoverNotCanonicalConfig)); PrivateKey.SignatureConfig defaultConfig = getDefaultSignatureConfig(); Assert.assertArrayEquals( privateKey.sign("foo".getBytes("UTF-8"), defaultConfig), privateKey.signUTF8String("foo", defaultConfig)); Assert.assertTrue(publicKey.verifySignature("foo".getBytes("UTF-8"), privateKey.sign("foo".getBytes("UTF-8"), noRecoverNotCanonicalConfig))); Assert.assertTrue(publicKey.verifySignature("foo".getBytes("UTF-8"), privateKey.sign("foo".getBytes("UTF-8"), noRecoverNotCanonicalConfig), new SHA256Digest())); } @Test public void testVerifySignedUTF8StringWithSecp256r1() throws Exception { PrivateKey privateKey = PrivateKey.fromString( secp256r1, "c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c", 16); PublicKey publicKey = privateKey.getPublicKey(); PrivateKey.SignatureConfig noRecoverButCanonicalConfig = new PrivateKey.SignatureConfigBuilder() .setRecover(false) .setTimeStampAndNonce(false) .build(); PrivateKey.SignatureConfig noRecoverNotCanonicalConfig = new PrivateKey.SignatureConfigBuilder() .setRecover(false) .setCanonical(false) .setTimeStampAndNonce(false) .build(); PrivateKey.SignatureConfig recoverNotCanonicalConfig = new PrivateKey.SignatureConfigBuilder() .setRecover(true) .setCanonical(false) .setTimeStampAndNonce(false) .build(); GeneralDigest sha256digest = new SHA256Digest(); PrivateKey.SignatureConfig recoverNotCanonicalExplicitMessageDigestConfig = new PrivateKey.SignatureConfigBuilder() .setRecover(true) .setCanonical(false) .setTimeStampAndNonce(false) .setMessageDigest(sha256digest) .build(); PrivateKey.SignatureConfig recoverNotCanonicalExplicitRFC6979DigestConfig = new PrivateKey.SignatureConfigBuilder() .setRecover(true) .setCanonical(false) .setTimeStampAndNonce(false) .setRfc6979Digest(sha256digest) .build(); Assert.assertTrue(publicKey.verifySignedUTF8String("foo", privateKey.signUTF8String("foo"))); Assert.assertTrue(publicKey.verifySignedUTF8String("foo", privateKey.sign("foo".getBytes("UTF-8")))); Assert.assertTrue(publicKey.verifySignedUTF8String("bar", privateKey.signUTF8String("bar", noRecoverButCanonicalConfig))); Assert.assertTrue(publicKey.verifySignedUTF8String("bar", privateKey.signUTF8String("bar", noRecoverButCanonicalConfig), new SHA256Digest())); Assert.assertTrue(publicKey.verifySignedUTF8String("baz", privateKey.sign("baz".getBytes("UTF-8"), noRecoverButCanonicalConfig))); Assert.assertTrue(publicKey.verifySignature("قفقاز".getBytes("UTF-8"), privateKey.sign("قفقاز".getBytes("UTF-8"), noRecoverNotCanonicalConfig))); Assert.assertTrue(publicKey.verifySignedUTF8String("कॉकेशस", privateKey.signUTF8String("कॉकेशस", recoverNotCanonicalConfig))); Assert.assertTrue(publicKey.verifySignedUTF8String("კავკაცია", privateKey.sign("კავკაცია".getBytes("UTF-8"), recoverNotCanonicalConfig))); Assert.assertTrue(publicKey.verifySignature("കൊക്കേഷ്യ".getBytes("UTF-8"), privateKey.sign("കൊക്കേഷ്യ".getBytes("UTF-8"), noRecoverNotCanonicalConfig))); Assert.assertTrue(publicKey.verifySignature("കൊക്കേഷ്യ".getBytes("UTF-8"), privateKey.sign("കൊക്കേഷ്യ".getBytes("UTF-8"), recoverNotCanonicalExplicitMessageDigestConfig))); Assert.assertTrue(publicKey.verifySignature("കൊക്കേഷ്യ".getBytes("UTF-8"), privateKey.sign("കൊക്കേഷ്യ".getBytes("UTF-8"), recoverNotCanonicalExplicitRFC6979DigestConfig))); } @Test public void testRecoverPublicKey() throws Exception { final PrivateKey privateKey = PrivateKey.fromString( secp256k1, "c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c", 16); final PublicKey publicKey = privateKey.getPublicKey(); Assert.assertEquals( "0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1", publicKey.toString(16)); final String message = "foo"; final byte[] bareSignature = privateKey.signUTF8String(message, new PrivateKey.SignatureConfigBuilder() .setRecover(false) .build()); Assert.assertEquals( "304402203dece00b786bb9d49ce00b87323e98afdd3c7ff67f45f56502dc281e98fae20102206efbfc836f990775edc60f50e8a74f913968288e30ae94703813b09db3f4f3dd", byteArrayToBaseEncodedString(bareSignature, 16)); final byte[] signature = privateKey.signUTF8String(message, new PrivateKey.SignatureConfigBuilder() .setRecover(true) .setTimeStampAndNonce(false) .build()); final PublicKey recoveredPublicKey = PublicKey.recoverPublicKey( secp256k1, message.getBytes("UTF-8"), signature); Assert.assertEquals(publicKey.toString(16), recoveredPublicKey.toString(16)); final byte[] hash = MessageDigest.getInstance("SHA-256").digest(message.getBytes("UTF-8")); final PublicKey recoveredPublicKeyFromHash = PublicKey.recoverPublicKeyWithHash( secp256k1, hash, signature); Assert.assertEquals(publicKey.toString(16), recoveredPublicKeyFromHash.toString(16)); final byte[] signatureWithTimeStampAndNonce = privateKey.signUTF8String(message, new PrivateKey.SignatureConfigBuilder() .setRecover(true) .setTimeStampAndNonce(true) .build()); Assert.assertEquals(publicKey.toString(16), PublicKey.recoverPublicKey( secp256k1, message.getBytes("UTF-8"), signatureWithTimeStampAndNonce).toString(16)); Assert.assertEquals(publicKey.toString(16), PublicKey.recoverPublicKey( secp256k1, message.getBytes("UTF-8"), signatureWithTimeStampAndNonce, new SHA256Digest()).toString(16)); } @Test public void testRecoverPublicKeySHA224() throws Exception { final PrivateKey privateKey = PrivateKey.fromString( secp256k1, "c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c", 16); final PublicKey publicKey = privateKey.getPublicKey(); Assert.assertEquals( "0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1", publicKey.toString(16)); final String message = "foo"; final byte[] bareSignature = privateKey.signUTF8String(message, new PrivateKey.SignatureConfigBuilder() .setRecover(false) .setMessageDigest(new SHA224Digest()) .build()); Assert.assertEquals( "3044022060d071aa96053205693a3c78ee4e1dd62676c4f73bc70f98fc44792d7b6a6c9902206d1b2532cee810d7f264d2cb77341b0e1197dc37a84858cdcdcbe82173ffdd38", byteArrayToBaseEncodedString(bareSignature, 16)); final byte[] signature = privateKey.signUTF8String(message, new PrivateKey.SignatureConfigBuilder() .setRecover(true) .setMessageDigest(new SHA224Digest()) .setTimeStampAndNonce(false) .build()); final PublicKey recoveredPublicKey = PublicKey.recoverPublicKey( secp256k1, message.getBytes("UTF-8"), signature, new SHA224Digest()); Assert.assertEquals(publicKey.toString(16), recoveredPublicKey.toString(16)); final byte[] hash = MessageDigest.getInstance("SHA-224").digest(message.getBytes("UTF-8")); final PublicKey recoveredPublicKeyFromHash = PublicKey.recoverPublicKeyWithHash( secp256k1, hash, signature); Assert.assertEquals(publicKey.toString(16), recoveredPublicKeyFromHash.toString(16)); final byte[] signatureWithTimeStampAndNonce = privateKey.signUTF8String(message, new PrivateKey.SignatureConfigBuilder() .setRecover(true) .setMessageDigest(new SHA224Digest()) .setTimeStampAndNonce(true) .build()); Assert.assertEquals(publicKey.toString(16), PublicKey.recoverPublicKey( secp256k1, message.getBytes("UTF-8"), signatureWithTimeStampAndNonce, new SHA224Digest()).toString(16)); } @Test public void testHashCodeTest() throws Exception { Assert.assertEquals( PrivateKey.fromString( secp256k1, "c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c", 16).getPublicKey().hashCode(), PublicKey.fromString(secp256k1, "0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1", 16).hashCode()); Assert.assertEquals( PrivateKey.fromString( CurveParameters.getCurveParametersByName("secp256k1"), "c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c", 16).getPublicKey().hashCode(), PublicKey.fromString(secp256k1, "0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1", 16).hashCode()); } @Test(expected = SecurityException.class) public void testPublicKeyConstructorPointOnWrongCurveSadPath() { BigInteger d = new BigInteger("c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c", 16); new PublicKey(secp256k1, secp256r1.getG().multiply(d)); } @Test(expected = SecurityException.class) public void testPublicKeyConstructorInvalidPointSadPath() { BigInteger d = new BigInteger("c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c", 16); // FYI, you need to normalize points before trying to make them PublicKeys! new PublicKey(secp256k1, secp256k1.getG().multiply(d)); } @Test public void testRecoverPublicKeyFromSignedUTF8String() throws Exception { byte[] signature = baseEncodedStringToByteArray("305a022100d55f884dd948b035a78c088af461f65dac80b35940891e914bbd9ba185778771022067eef94e9ae8217b24927a4d4016e9f6d2ac27e077ad11d02f2496d83ecdc9b502011c020601573f8f27af02086662fe5c7db4b76b", 16); PrivateKey privateKey2 = PrivateKey.fromString( secp256r1, "0ffffffffffffffffffffffffffffffffff9252c7f55610b8d0859d8752235a9", 16); String message = "Moloch!"; Assert.assertEquals(privateKey2.getPublicKey(), PublicKey.recoverPublicKeyFromSignedUTF8String(secp256r1, message, signature)); Assert.assertEquals(privateKey2.getPublicKey(), PublicKey.recoverPublicKeyFromSignedUTF8String(secp256r1, message, signature, new SHA256Digest())); } @Test(expected = SecurityException.class) public void testRecoverPublicKeyFromSignedUTF8StringSadPath() throws Exception { byte[] signature = baseEncodedStringToByteArray("305a022100d55f884dd948b035a78c088af461f65dac80b35940891e914bbd9ba185778771022067eef94e9ae8217b24927a4d4016e9f6d2ac27e077ad11d02f2496d83ecdc9b502011c020601573f8f27af02086662fe5c7db4b76b", 16); String message = "Moloch!"; PublicKey.recoverPublicKeyFromSignedUTF8String(secp256k1, message, signature); } @Test(expected = SecurityException.class) public void testRecoverPublicKeyFromSignedUTF8StringSadPathBadDER() throws Exception { byte[] signature = baseEncodedStringToByteArray("305c022100d55f884dd948b035a78c088af461f65dac80b35940891e914bbd9ba185778771022067eef94e9ae8217b24927a4d4016e9f6d2ac27e077ad11d02f2496d83ecdc9b502011c020601573f8f27af02086662fe5c7db4b76b", 16); String message = "Moloch!"; PublicKey.recoverPublicKeyFromSignedUTF8String(secp256k1, message, signature); } } ================================================ FILE: src/test/java/test/jce/ecc2/UnsupportedBaseException.java ================================================ package test.jce.ecc2; /** * An exception for reporting unsupported bases encountered when * marshalling/unmarshalling byte arrays from encoded strings. */ public class UnsupportedBaseException extends Exception { private static final long serialVersionUID = -8963563995880472365L; public UnsupportedBaseException(String message) { super(message); } } ================================================ FILE: src/test/java/test/jce/ecc2/Utils.java ================================================ package test.jce.ecc2; import org.bouncycastle.crypto.digests.GeneralDigest; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; public class Utils { /** * Construct a fresh GeneralDigest from an existing one * * @param hashDigest A general hash digest to make a new instance of * @return A new GeneralDigest with the same class as the input * @throws SecurityException Thrown if a fresh hash digest cannot be constructed from the digest handed as an argument */ public static GeneralDigest freshDigestFromDigest(GeneralDigest hashDigest) throws SecurityException { GeneralDigest freshHashDigest; try { freshHashDigest = hashDigest.getClass().getConstructor().newInstance(); } catch (Throwable e) { throw new SecurityException(e); } return freshHashDigest; } /** * Convert a UTF8 encoded string as bytes * * @param string The byte to convert * @return An array of bytes that correspond to the UTF8 encoded string * @throws SecurityException Thrown if the string cannot be validly converted to a UTF-8 string */ public static byte[] stringToUTF8Bytes(String string) throws SecurityException { try { return string.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { throw new SecurityException(e); } } /** * Convert a long to an array of bytes * * @param x Long to convert * @return An array of bytes corresponding to the converted long */ public static byte[] longToBytes(long x) { ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); buffer.putLong(x); return buffer.array(); } /** * Convert an array of bytes to a long * * @param bytes Bytes of a long * @return The long represented by the bytes */ public static long bytesToLong(byte[] bytes) { ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); buffer.put(bytes); buffer.flip(); return buffer.getLong(); } public static int countLeadingZeroBytes(byte[] bytes) { int i = -1; //noinspection StatementWithEmptyBody while (++i < bytes.length && bytes[i] == 0) { } return i; } } ================================================ FILE: src/test/java/test/jce/rsa/RSAKeyTest.java ================================================ package test.jce.rsa; import cn.ponfee.commons.jce.security.RSACryptor; import cn.ponfee.commons.jce.security.RSACryptor.RSAKeyPair; import cn.ponfee.commons.jce.security.RSAPrivateKeys; import cn.ponfee.commons.jce.security.RSAPublicKeys; public class RSAKeyTest { public static void main(String[] args) { RSAKeyPair keyPair = RSACryptor.generateKeyPair(2048); System.out.println("pkcs1 pub: " + keyPair.toPkcs1PublicKey()); System.out.println("pkcs8 pub: " + keyPair.toPkcs8PublicKey()); System.out.println("\n"); System.out.println("pkcs1 pri: " + keyPair.toPkcs1PrivateKey()); System.out.println("pkcs8 pri: " + keyPair.toPkcs8PrivateKey()); System.out.println("\n"); System.out.println("pkcs1 pub: " + RSAPublicKeys.fromPkcs1(keyPair.toPkcs1PublicKey())); System.out.println("pkcs8 pub: " + RSAPublicKeys.fromPkcs8(keyPair.toPkcs8PublicKey())); System.out.println("\n"); System.out.println("pkcs1 pri: " + RSAPrivateKeys.fromPkcs1(keyPair.toPkcs1PrivateKey())); System.out.println("pkcs8 pri: " + RSAPrivateKeys.fromPkcs8(keyPair.toPkcs8PrivateKey())); } } ================================================ FILE: src/test/java/test/jce/rsa/RSASignerTest.java ================================================ package test.jce.rsa; import static org.junit.Assert.assertTrue; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import org.junit.Test; import com.google.common.base.Stopwatch; import cn.ponfee.commons.jce.implementation.rsa.RSAKey; import cn.ponfee.commons.jce.implementation.rsa.RSASigner; import cn.ponfee.commons.jce.security.RSACryptor; import cn.ponfee.commons.jce.security.RSAPrivateKeys; import cn.ponfee.commons.jce.security.RSAPublicKeys; import cn.ponfee.commons.util.IdcardResolver; import cn.ponfee.commons.util.MavenProjects; public class RSASignerTest { private static byte[] origin = MavenProjects.getMainJavaFileAsBytes(IdcardResolver.class); @Test public void testRSASign() { RSAKey dk = new RSAKey(1024); RSAKey ek = dk.getPublic(); RSAPublicKey pub = RSAPublicKeys.toRSAPublicKey(dk.n, dk.e); RSAPrivateKey pri = RSAPrivateKeys.toRSAPrivateKey(dk.n, dk.d); // ========================验证签名 Stopwatch watch = Stopwatch.createStarted(); byte[] signature = new RSASigner(dk).signSha1(origin); // 签名 assertTrue(new RSASigner(ek).verifySha1(origin, signature)); // 验证 assertTrue(RSACryptor.verifySha1(origin, pub, signature)); // 验证 // ========================验证签名 signature = RSACryptor.signSha1(origin, pri); // 签名 assertTrue(RSACryptor.verifySha1(origin, pub, signature)); // 验证 assertTrue(new RSASigner(ek).verifySha1(origin, signature)); // 验证 System.out.println(watch.stop()); } } ================================================ FILE: src/test/java/test/jce/rsa/RSAryptorTest.java ================================================ package test.jce.rsa; import java.io.ByteArrayInputStream; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.util.Arrays; import java.util.concurrent.ThreadLocalRandom; import org.apache.commons.codec.binary.Hex; import org.apache.commons.io.output.ByteArrayOutputStream; import org.apache.commons.lang3.StringUtils; import org.junit.Test; import com.google.common.base.Stopwatch; import cn.ponfee.commons.jce.implementation.Key; import cn.ponfee.commons.jce.implementation.rsa.AbstractRSACryptor; import cn.ponfee.commons.jce.implementation.rsa.RSAHashCryptor; import cn.ponfee.commons.jce.implementation.rsa.RSAKey; import cn.ponfee.commons.jce.implementation.rsa.RSANoPaddingCryptor; import cn.ponfee.commons.jce.implementation.rsa.RSAPKCS1PaddingCryptor; import cn.ponfee.commons.jce.security.RSACryptor; import cn.ponfee.commons.jce.security.RSAPrivateKeys; import cn.ponfee.commons.jce.security.RSAPublicKeys; import cn.ponfee.commons.util.IdcardResolver; import cn.ponfee.commons.util.MavenProjects; import cn.ponfee.commons.util.SecureRandoms; public class RSAryptorTest { private static byte[] origin = MavenProjects.getMainJavaFileAsBytes(IdcardResolver.class); private static boolean isPrint = false; private static void print(String s) { if (isPrint) { System.out.println(s); } } @Test public void testRSANoPadding() { System.out.println("\n\ntestRSANoPadding======================================"); RSAKey dk = new RSAKey(1024); Key ek = dk.getPublic(); RSANoPaddingCryptor cs = new RSANoPaddingCryptor(); Stopwatch watch = Stopwatch.createStarted(); byte[] encrypted = cs.encrypt(origin, ek); byte[] decrypted = cs.decrypt(encrypted, dk); if (!Arrays.equals(origin, decrypted)) { System.err.println("FAIL!"); } else { print("\n\n=====RSA1 Decrypted text is: \n" + new String(decrypted)); } System.out.println(watch.stop()); watch.reset().start(); /*RSAPublicKey pub = RSAPublicKeys.toRSAPublicKey(dk.n, dk.e); encrypted = RSACryptor.encrypt(origin, pub);*/ RSAPrivateKey pri = RSAPrivateKeys.toRSAPrivateKey(dk.n, dk.d); decrypted = RSACryptor.decryptNoPadding(encrypted, pri); if (!Arrays.equals(origin, decrypted)) { System.err.println("FAIL!"); } else { print("\n\n=====RSA2 Decrypted text is: \n" + new String(decrypted)); } System.out.println(watch.stop()); } @Test public void testRSAPKCS1Padding() { System.out.println("\n\ntestRSAPKCS1Padding======================================"); RSAKey dk = new RSAKey(1024); Key ek = dk.getPublic(); AbstractRSACryptor cs = new RSAPKCS1PaddingCryptor(); RSAPublicKey pub = RSAPublicKeys.toRSAPublicKey(dk.n, dk.e); RSAPrivateKey pri = RSAPrivateKeys.toRSAPrivateKey(dk.n, dk.d); // ========================公钥加密,私钥解密 Stopwatch watch = Stopwatch.createStarted(); byte[] encrypted = cs.encrypt(origin, ek); byte[] decrypted = cs.decrypt(encrypted, dk); if (!Arrays.equals(origin, decrypted)) { System.err.println("FAIL!"); } else { print("\n\n=====RSA1 Decrypted text is: \n" + new String(decrypted)); } System.out.println(watch.stop()); watch.reset().start(); decrypted = RSACryptor.decrypt(encrypted, pri); if (!Arrays.equals(origin, decrypted)) { System.err.println("FAIL!"); } else { print("\n\n=====RSA2 Decrypted text is: \n" + new String(decrypted)); } System.out.println(watch.stop()); // =========================私钥加密,公钥解密 watch = Stopwatch.createStarted(); encrypted = cs.encrypt(origin, dk); decrypted = cs.decrypt(encrypted, ek); if (!Arrays.equals(origin, decrypted)) { System.err.println("FAIL!"); } else { print("\n\n=====RSA1 Decrypted text is: \n" + new String(decrypted)); } System.out.println(watch.stop()); watch.reset().start(); decrypted = RSACryptor.decrypt(encrypted, pub); if (!Arrays.equals(origin, decrypted)) { System.err.println("FAIL!"); } else { print("\n\n=====RSA2 Decrypted text is: \n" + new String(decrypted)); } System.out.println(watch.stop()); // =======================================加密-解密 encrypted = RSACryptor.encrypt(origin, pub); decrypted = cs.decrypt(encrypted, dk); if (!Arrays.equals(origin, decrypted)) { System.err.println("FAIL!"); } else { print("\n\n=====RSA1 Decrypted text is: \n" + new String(decrypted)); } // =======================================加密-解密 encrypted = RSACryptor.encrypt(origin, pri); decrypted = cs.decrypt(encrypted, ek); if (!Arrays.equals(origin, decrypted)) { System.err.println("FAIL!"); } else { print("\n\n=====RSA1 Decrypted text is: \n" + new String(decrypted)); } } @Test public void testRSAHash() { System.out.println("\n\ntestRSAHash======================================"); for (int i = 0; i < 10; i++) { byte[] data = SecureRandoms.nextBytes(ThreadLocalRandom.current().nextInt(99999) + 1); RSAKey dk = new RSAKey(1024); Key ek = dk.getPublic(); RSAHashCryptor cs = new RSAHashCryptor(); byte[] encrypted = cs.encrypt(data, ek); byte[] decrypted = cs.decrypt(encrypted, dk); if (!Arrays.equals(data, decrypted)) { System.err.println("FAIL!"); } else { print("\n\n=====RSAHashCryptor Decrypted text is: \n" + new String(decrypted)); } } } @Test public void testRSARandom() { System.out.println("\n\ntestRSARandom======================================"); RSAKey dk = new RSAKey(1024); Key ek = dk.getPublic(); RSANoPaddingCryptor cs = new RSANoPaddingCryptor(); RSAPKCS1PaddingCryptor cs2 = new RSAPKCS1PaddingCryptor(); RSAPublicKey pub = RSAPublicKeys.toRSAPublicKey(dk.n, dk.e); RSAPrivateKey pri = RSAPrivateKeys.toRSAPrivateKey(dk.n, dk.d); for (int i = 0; i < 1000; i++) { /*int length = ThreadLocalRandom.current().nextInt(65537) + 1; int offset = ThreadLocalRandom.current().nextInt(origin.length - length); System.out.println(length + " -> " + offset); byte[] data = Arrays.copyOfRange(origin, offset, offset + length);*/ //byte[] data = new byte[] { 0, 1, 1, 0, 0 }; // occur wrong byte[] data = SecureRandoms.nextBytes(ThreadLocalRandom.current().nextInt(255) + 1); // 1 byte[] encrypted1 = cs.encrypt(data, ek); byte[] decrypted1 = cs.decrypt(encrypted1, dk); // XXX DATA!=decrypted1 1% // 2 byte[] encrypted2 = encrypted1; byte[] decrypted2 = RSACryptor.decryptNoPadding(encrypted2, pri); // XXX DATA!=decrypted1 1% // 3 byte[] encrypted3 = RSACryptor.encryptNoPadding(data, pub); byte[] decrypted3 = RSACryptor.decryptNoPadding(encrypted3, pri); // XXX DATA!=decrypted1 5% // 4 byte[] encrypted4 = RSACryptor.encrypt(data, pub); byte[] decrypted4 = RSACryptor.decrypt(encrypted4, pri); // 5 byte[] encrypted5 = cs2.encrypt(data, ek); byte[] decrypted5 = cs2.decrypt(encrypted5, dk); // ------------------------------------------------------- if (!Arrays.equals(data, decrypted1)) { System.err.println("[" + StringUtils.leftPad(i + "", 4, "0") + "]decrypt1 FAIL!: " + Hex.encodeHexString(data) + " -> " + Hex.encodeHexString(decrypted1)); } if (!Arrays.equals(data, decrypted2)) { System.err.println("[" + StringUtils.leftPad(i + "", 4, "0") + "]decrypt2 FAIL!: " + Hex.encodeHexString(data) + " -> " + Hex.encodeHexString(decrypted2)); } if (!Arrays.equals(data, decrypted3)) { System.err.println("[" + StringUtils.leftPad(i + "", 4, "0") + "]decrypt3 FAIL!: " + Hex.encodeHexString(data) + " -> " + Hex.encodeHexString(decrypted3)); } if (!Arrays.equals(data, decrypted4)) { System.err.println("[" + StringUtils.leftPad(i + "", 4, "0") + "]decrypt4 FAIL!: " + Hex.encodeHexString(data) + " -> " + Hex.encodeHexString(decrypted4)); } if (!Arrays.equals(data, decrypted5)) { System.err.println("[" + StringUtils.leftPad(i + "", 4, "0") + "]decrypt5 FAIL!: " + Hex.encodeHexString(data) + " -> " + Hex.encodeHexString(decrypted5)); } } } @Test public void testRSAHashInverseKey() { System.out.println("\n\ntestRSAHashInverseKey======================================"); RSAKey dk = new RSAKey(1024); Key ek = dk.getPublic(); RSAHashCryptor cs = new RSAHashCryptor(); for (int i = 0; i < 100; i++) { byte[] data = SecureRandoms.nextBytes(ThreadLocalRandom.current().nextInt(255) + 1); byte[] encrypted1 = cs.encrypt(data, dk); // 私钥加密 byte[] decrypted1 = cs.decrypt(encrypted1, ek); // 公钥解密 if (!Arrays.equals(data, decrypted1)) { System.err.println("[" + StringUtils.leftPad(i + "", 4, "0") + "]decrypt1 FAIL!: " + Hex.encodeHexString(data) + " -> " + Hex.encodeHexString(decrypted1)); } } } @Test public void testRSANoPaddingInverseKey() { System.out.println("\n\ntestRSANoPaddingInverseKey======================================"); RSAKey dk = new RSAKey(1024); Key ek = dk.getPublic(); RSANoPaddingCryptor cs = new RSANoPaddingCryptor(); for (int i = 0; i < 1000; i++) { byte[] data = SecureRandoms.nextBytes(ThreadLocalRandom.current().nextInt(255) + 1); byte[] encrypted1 = cs.encrypt(data, dk); // 私钥加密 byte[] decrypted1 = cs.decrypt(encrypted1, ek); // 公钥解密 if (!Arrays.equals(data, decrypted1)) { System.err.println("[" + StringUtils.leftPad(i + "", 4, "0") + "]decrypt1 FAIL!: " + Hex.encodeHexString(data) + " -> " + Hex.encodeHexString(decrypted1)); } } } @Test public void testRSAPKCS1InverseKey() { System.out.println("\n\ntestRSAPKCS1InverseKey======================================"); RSAKey dk = new RSAKey(1024); Key ek = dk.getPublic(); RSAPKCS1PaddingCryptor cs = new RSAPKCS1PaddingCryptor(); for (int i = 0; i < 100; i++) { byte[] data = SecureRandoms.nextBytes(ThreadLocalRandom.current().nextInt(255) + 1); byte[] encrypted1 = cs.encrypt(data, dk); // 私钥加密 byte[] decrypted1 = cs.decrypt(encrypted1, ek); // 公钥解密 if (!Arrays.equals(data, decrypted1)) { System.err.println("[" + StringUtils.leftPad(i + "", 4, "0") + "]decrypt1 FAIL!: " + Hex.encodeHexString(data) + " -> " + Hex.encodeHexString(decrypted1)); } } } @Test public void testRSANoPaddingStream() { System.out.println("\n\ntestRSANoPaddingStream======================================"); RSAKey dk = new RSAKey(2048); Key ek = dk.getPublic(); RSANoPaddingCryptor cs = new RSANoPaddingCryptor(); ByteArrayInputStream input = new ByteArrayInputStream(origin); ByteArrayOutputStream output = new ByteArrayOutputStream(); Stopwatch watch = Stopwatch.createStarted(); cs.encrypt(input, ek, output); byte[] encrypted = output.toByteArray(); input = new ByteArrayInputStream(encrypted); output = new ByteArrayOutputStream(); cs.decrypt(input, dk, output); byte[] decrypted = output.toByteArray(); if (!Arrays.equals(origin, decrypted)) { System.err.println("FAIL!"); } else { print("\n\n=====RSAStream Decrypted text is: \n" + new String(decrypted)); } System.out.println(watch.stop()); watch.reset().start(); input = new ByteArrayInputStream(encrypted); output = new ByteArrayOutputStream(); RSAPrivateKey pri = RSAPrivateKeys.toRSAPrivateKey(dk.n, dk.d); RSACryptor.decryptNoPadding(input, pri, output); decrypted = output.toByteArray(); if (!Arrays.equals(origin, decrypted)) { System.err.println("FAIL!"); } else { print("\n\n=====RSAStream Decrypted text is: \n" + new String(decrypted)); } System.out.println(watch.stop()); } @Test public void testRSAPKCS1PaddingStream() { System.out.println("\n\ntestRSAPKCS1PaddingStream======================================"); RSAKey dk = new RSAKey(1024); Key ek = dk.getPublic(); AbstractRSACryptor cs = new RSAPKCS1PaddingCryptor(); ByteArrayInputStream input = new ByteArrayInputStream(origin); ByteArrayOutputStream output = new ByteArrayOutputStream(); Stopwatch watch = Stopwatch.createStarted(); cs.encrypt(input, ek, output); byte[] encrypted = output.toByteArray(); input = new ByteArrayInputStream(encrypted); output = new ByteArrayOutputStream(); cs.decrypt(input, dk, output); byte[] decrypted = output.toByteArray(); if (!Arrays.equals(origin, decrypted)) { System.err.println("FAIL!"); } else { print("\n\n=====RSAStream Decrypted text is: \n" + new String(decrypted)); } System.out.println(watch.stop()); watch.reset().start(); input = new ByteArrayInputStream(encrypted); output = new ByteArrayOutputStream(); RSAPrivateKey pri = RSAPrivateKeys.toRSAPrivateKey(dk.n, dk.d); RSACryptor.decrypt(input, pri, output); decrypted = output.toByteArray(); if (!Arrays.equals(origin, decrypted)) { System.err.println("FAIL!"); } else { print("\n\n=====RSAStream Decrypted text is: \n" + new String(decrypted)); } System.out.println(watch.stop()); } @Test public void testRSAHashStream() { System.out.println("\n\ntestRSAHashStream======================================"); RSAKey dk = new RSAKey(2048); Key ek = dk.getPublic(); RSAHashCryptor cs = new RSAHashCryptor(); ByteArrayInputStream input = new ByteArrayInputStream(origin); ByteArrayOutputStream output = new ByteArrayOutputStream(); Stopwatch watch = Stopwatch.createStarted(); cs.encrypt(input, ek, output); byte[] encrypted = output.toByteArray(); print("encrypted len: " + encrypted.length + ", origin len: " + origin.length); input = new ByteArrayInputStream(encrypted); output = new ByteArrayOutputStream(); cs.decrypt(input, dk, output); byte[] decrypted = output.toByteArray(); if (!Arrays.equals(origin, decrypted)) { System.err.println("FAIL!"); } else { print("\n\n=====RSAStream Decrypted text is: \n" + new String(decrypted)); } System.out.println(watch.stop()); } } ================================================ FILE: src/test/java/test/jce/sha1/SHA1BrokenTest.java ================================================ package test.jce.sha1; import java.io.File; import org.apache.commons.codec.binary.Hex; import org.junit.Test; import cn.ponfee.commons.io.Files; import cn.ponfee.commons.jce.DigestAlgorithms; import cn.ponfee.commons.jce.HmacAlgorithms; import cn.ponfee.commons.jce.Providers; import cn.ponfee.commons.jce.digest.DigestUtils; import cn.ponfee.commons.jce.digest.HmacUtils; import cn.ponfee.commons.jce.implementation.digest.SHA1Digest; import cn.ponfee.commons.jce.sm.SM3Digest; import cn.ponfee.commons.util.MavenProjects; public class SHA1BrokenTest { public @Test void test1() { byte[] pdf1 = Files.toByteArray(new File(MavenProjects.getTestJavaPath("test.jce.sha1", "shattered-1.pdf"))); byte[] pdf2 = Files.toByteArray(new File(MavenProjects.getTestJavaPath("test.jce.sha1", "shattered-2.pdf"))); System.out.println(Hex.encodeHexString(SHA1Digest.getInstance().doFinal(pdf1))); System.out.println(Hex.encodeHexString(SHA1Digest.getInstance().doFinal(pdf2))); System.out.println(Hex.encodeHexString(DigestUtils.sha1(pdf1))); System.out.println(Hex.encodeHexString(DigestUtils.sha1(pdf2))); System.out.println(Hex.encodeHexString(DigestUtils.sha256(pdf1))); System.out.println(Hex.encodeHexString(DigestUtils.sha256(pdf2))); } public @Test void test2() { byte[] pdf2 = Files.toByteArray(new File(MavenProjects.getTestJavaPath("test.jce.sha1", "shattered-2.pdf"))); System.out.println(Hex.encodeHexString(DigestUtils.digest(DigestAlgorithms.SHA3_256, Providers.BC, pdf2))); System.out.println(Hex.encodeHexString(DigestUtils.digest(DigestAlgorithms.KECCAK256, Providers.BC, pdf2))); System.out.println(); System.out.println(Hex.encodeHexString(DigestUtils.digest(DigestAlgorithms.SHA3_512, Providers.BC, pdf2))); System.out.println(Hex.encodeHexString(DigestUtils.digest(DigestAlgorithms.KECCAK512, Providers.BC, pdf2))); System.out.println(Hex.encodeHexString(DigestUtils.digest(DigestAlgorithms.SM3, Providers.BC, pdf2))); System.out.println(Hex.encodeHexString(SM3Digest.getInstance().doFinal(pdf2))); System.out.println(); System.out.println(Hex.encodeHexString(HmacUtils.crypt("1234".getBytes(), pdf2, HmacAlgorithms.HmacSHA3_256))); System.out.println(Hex.encodeHexString(HmacUtils.crypt("1234".getBytes(), pdf2, HmacAlgorithms.HmacKECCAK256))); System.out.println(); System.out.println(Hex.encodeHexString(HmacUtils.crypt("1234".getBytes(), pdf2, HmacAlgorithms.HmacSHA3_512))); System.out.println(Hex.encodeHexString(HmacUtils.crypt("1234".getBytes(), pdf2, HmacAlgorithms.HmacKECCAK512))); System.out.println(); } } ================================================ FILE: src/test/java/test/jce/sm/SM2KeyExchangeTest.java ================================================ package test.jce.sm; import java.util.Arrays; import java.util.Map; import org.apache.commons.codec.binary.Hex; import cn.ponfee.commons.jce.ECParameters; import cn.ponfee.commons.jce.sm.SM2; import cn.ponfee.commons.jce.sm.SM2KeyExchanger; import cn.ponfee.commons.jce.sm.SM2KeyExchanger.TransportEntity; public class SM2KeyExchangeTest { public static void main(String[] args) { ECParameters ecParameter = ECParameters.SM2_BEST; System.out.println("=============================密钥协商============================"); Map keyMap = SM2.generateKeyPair(ecParameter); byte[] id1 = "AAAAAAAAAAAAA".getBytes(); SM2KeyExchanger aKeyExchange = new SM2KeyExchanger(id1, SM2.getPublicKey(ecParameter, SM2.getPublicKey(keyMap)), SM2.getPrivateKey(SM2.getPrivateKey(keyMap))); Map keyMap2 = SM2.generateKeyPair(ecParameter); byte[] id2 = "BBBBBBBBBBBBB".getBytes(); SM2KeyExchanger bKeyExchange = new SM2KeyExchanger(id2, SM2.getPublicKey(ecParameter, SM2.getPublicKey(keyMap2)), SM2.getPrivateKey(SM2.getPrivateKey(keyMap2))); TransportEntity entity1 = aKeyExchange.step1PartA(); TransportEntity entity2 = bKeyExchange.step2PartB(entity1); TransportEntity entity3 = aKeyExchange.step3PartA(entity2); System.out.println(Hex.encodeHexString(bKeyExchange.getKey())); if (!bKeyExchange.step4PartB(entity3) || !Arrays.equals(aKeyExchange.getKey(), bKeyExchange.getKey())) { System.err.println("FAIL!"); } System.out.println(Hex.encodeHexString(aKeyExchange.getKey())); // 16 byte } } ================================================ FILE: src/test/java/test/jce/sm/SM2Test.java ================================================ package test.jce.sm; import java.util.Base64; import java.util.Map; import org.apache.commons.codec.binary.Hex; import org.junit.Test; import cn.ponfee.commons.jce.ECParameters; import cn.ponfee.commons.jce.sm.SM2; import cn.ponfee.commons.util.MavenProjects; public class SM2Test { static ECParameters ecParameter = ECParameters.SM2_BEST; @Test public void test1() { byte[] data = MavenProjects.getMainJavaFileAsString(SM2.class).substring(0, 100).getBytes(); Map keyMap = SM2.generateKeyPair(ecParameter); System.out.println(Base64.getUrlEncoder().encodeToString(SM2.getPublicKey(keyMap))); System.out.println(Base64.getUrlEncoder().encodeToString(SM2.getPrivateKey(keyMap))); byte[] encrypted = SM2.encrypt(ecParameter, SM2.getPublicKey(keyMap), data); System.out.println(Base64.getUrlEncoder().encodeToString(encrypted)); byte[] decrypted = SM2.decrypt(ecParameter, SM2.getPrivateKey(keyMap), encrypted); System.out.println(new String(decrypted)); } @Test public void test2() throws Exception { byte[] privateKey = Hex.decodeHex("19F4279987CACA6780592C2B330E932B7C6C27919ED56D63785E398A7C3B568F"); byte[] encrypted = Hex.decodeHex("04AD22DF19CC60E231B74E4F510537C9D71EB57D3734F7EC44188E5655374BC742469A3BF8D247769AF712A06A4D2EAC24470F9851AB6A2F106600E3B5D8DADD2DCD81D6C781A26C7DCBACD9F947A461FF555537A75E9A19B92EE6E447373C9B776CF0236BCA769A7515795A32AF00F94E9B5EBCDE9B8774F19129815AF04C7D03122982448AFB586F8895C2A695FFC034B1B77E350E925E0BC04A683759C763AC396FC3EFDD9EF4A09E53EFFCCA80C29B42B11AB82A7CEACBBF2E748E028035E4D4E71693"); byte[] decrypted = SM2.decrypt(ecParameter, privateKey, encrypted); System.out.println(new String(decrypted)); } @Test public void test3() { //ECParameters ecParameter = ECParameters.secp256r1; //ECParameters ecParameter = ECParameters.EC_PARAMETERS.get("secp256r1"); //ECParameters ecParameter = ECParameters.EC_PARAMETERS.get("secp256k1"); ECParameters ecParameter = ECParameters.SM2_BEST; //byte[] publicKey1 = Base64.getDecoder().decode("BDky9CNygRTBGh7eGdzdbxP5eGRozk4wQfFmREncwEKnYHhNy1OoBvh0wY/RMH/3ikfYejClHxlI1T+jRa0m2wU="); byte[] privateKey1 = Base64.getDecoder().decode("AJaz6VNm1Wl9ba1YCEkYi36+m+8DDL4LDuLM172tg5Ao"); byte[] encrypted1 = Base64.getDecoder().decode("BDrcVKWOJ2J/x0YRmX99ksvwZJ5DgbgxPpwDNf3u94FH65JHuxG6U6Xp7ST7G7dsTlg6QFiSIANKyb42DvmpLpby1CU5kw0q/C4t1eH08VTAtpZrg2M6+Qz4pdOpp0iGpUp1w5ympuezCxgsEUfhDYihG5MjerLz+8Ss8qTRz3/ZpNvRJaCDk9k="); System.out.println(new String(SM2.decrypt(privateKey1, encrypted1))); for (int i = 0; i < 5; i++) { byte[] data = MavenProjects.getMainJavaFileAsString(SM2.class).getBytes(); Map keyMap = SM2.generateKeyPair(ecParameter); System.out.println("\n=============================加密/解密============================"); byte[] encrypted = SM2.encrypt(ecParameter, keyMap.get(SM2.PUBLIC_KEY), data); System.out.println(Base64.getEncoder().encodeToString(SM2.getPublicKey(keyMap))); System.out.println(Base64.getEncoder().encodeToString(SM2.getPrivateKey(keyMap))); System.out.println(Base64.getEncoder().encodeToString(encrypted)); byte[] decrypted = SM2.decrypt(ecParameter, keyMap.get(SM2.PRIVATE_KEY), encrypted); System.out.println(new String(decrypted)); System.out.println("\n=============================签名/验签============================"); byte[] signed = SM2.sign(ecParameter, data, "IDA".getBytes(), SM2.getPublicKey(keyMap), SM2.getPrivateKey(keyMap)); System.out.println(Base64.getUrlEncoder().withoutPadding().encodeToString(signed)); System.out.println(SM2.verify(ecParameter, data, "IDA".getBytes(), signed, SM2.getPublicKey(keyMap))); } byte[] data = MavenProjects.getMainJavaFileAsString(SM2.class).substring(0, 100).getBytes(); Map keyMap = SM2.generateKeyPair(ecParameter); System.out.println("\ncheckPublicKey: "+SM2.checkPublicKey(ecParameter, SM2.getPublicKey(keyMap))); for (int i = 0; i < 5; i++) { System.out.println("\n=============================加密/解密============================"); byte[] encrypted = SM2.encrypt(ecParameter, keyMap.get(SM2.PUBLIC_KEY), data); byte[] decrypted = SM2.decrypt(ecParameter, keyMap.get(SM2.PRIVATE_KEY), encrypted); System.out.println(new String(decrypted)); System.out.println("\n=============================签名/验签============================"); byte[] signed = SM2.sign(ecParameter, data, "IDA".getBytes(), SM2.getPublicKey(keyMap), SM2.getPrivateKey(keyMap)); System.out.println(Base64.getUrlEncoder().withoutPadding().encodeToString(signed)); System.out.println(SM2.verify(ecParameter, data, "IDA".getBytes(), signed, SM2.getPublicKey(keyMap))); } } @Test public void test4() { byte[] publicKey1 = Base64.getDecoder().decode("BDky9CNygRTBGh7eGdzdbxP5eGRozk4wQfFmREncwEKnYHhNy1OoBvh0wY/RMH/3ikfYejClHxlI1T+jRa0m2wU="); byte[] privateKey1 = Base64.getDecoder().decode("AJaz6VNm1Wl9ba1YCEkYi36+m+8DDL4LDuLM172tg5Ao"); byte[] encrypted1 = Base64.getDecoder().decode("BBdOqZ+7WMaYzyyEZui0b5tdfJqquXH0pMxyoSv04BzYCqKIaeXPcc/vIek8tcK4kCkp022OCZ2TW2bLgVgGvFxeMOzGUgliTQP521HmqbQaawS4FFHxGa1vy+lk9UrkOIaTiEqjIRFSB2SW4cOi3u1mwvqK8EuaYFb143K539HIuqu84Bo3RD2zIpssO+yZzduG4KDcn4iqOJ+NJ1RB5wlI1xEyyiKit6c0mVBfSnXbUkuAyml50dM="); System.out.println(new String(SM2.decrypt(privateKey1, encrypted1))); byte[] encrypted = SM2.encrypt(publicKey1, MavenProjects.getTestJavaFileAsBytes(SM2Test.class)); System.out.println(Base64.getEncoder().encodeToString(encrypted)); } } ================================================ FILE: src/test/java/test/jce/sm/SM3DigestTest.java ================================================ package test.jce.sm; import java.util.concurrent.ThreadLocalRandom; import org.apache.commons.codec.binary.Hex; import org.junit.Assert; import cn.ponfee.commons.jce.sm.SM3Digest; import cn.ponfee.commons.util.SecureRandoms; public class SM3DigestTest { public static void main(String[] args) { String actual = Hex.encodeHexString(SM3Digest.getInstance().doFinal("0123456789".getBytes())); if (!"09093b72553f5d9d622d6c62f5ffd916ee959679b1bd4d169c3e12aa8328e743".equals(actual)) { System.err.println("sm3 digest error!"); } else { System.out.println("SUCCESS!"); } byte[] data = "0123456789".getBytes(); byte[] hash = SM3Digest.getInstance().doFinal(data); System.out.println(Hex.encodeHexString(hash)); SM3Digest sm3 = SM3Digest.getInstance(); hash = sm3.doFinal(data); System.out.println(Hex.encodeHexString(hash)); hash = sm3.doFinal(data); System.out.println(Hex.encodeHexString(hash)); hash = sm3.doFinal(data); System.out.println(Hex.encodeHexString(hash)); SM3Digest sm3_1 = SM3Digest.getInstance(); org.bouncycastle.crypto.digests.SM3Digest sm3_2 = new org.bouncycastle.crypto.digests.SM3Digest(); for (int i = 0; i < 100; i++) { byte[] data1 = SecureRandoms.nextBytes(ThreadLocalRandom.current().nextInt(65537) + 1); byte[] data2 = SecureRandoms.nextBytes(ThreadLocalRandom.current().nextInt(65537) + 1); sm3_1.update(data1); sm3_2.update(data1, 0, data1.length); sm3_2.update(data2, 0, data2.length); byte[] dig = new byte[sm3_2.getDigestSize()]; sm3_2.doFinal(dig, 0); Assert.assertArrayEquals(sm3_1.doFinal(data2), dig); } } } ================================================ FILE: src/test/java/test/jce/sm/SM4Test.java ================================================ package test.jce.sm; import java.util.Base64; import cn.ponfee.commons.jce.sm.SM4; import cn.ponfee.commons.util.MavenProjects; public class SM4Test { public static void main(String[] args) { //byte[] data = Files.toString(MavenProjects.getMainJavaFile(SM4.class)).replaceAll("\r|\n", "").getBytes(); byte[] data = MavenProjects.getMainJavaFileAsString(SM4.class).substring(0, 997).getBytes(); byte[] key = "1234567785465466".getBytes(); byte[] iv = "1a345677b546d4de".getBytes(); System.out.println(new String(SM4.decrypt( key, iv, Base64.getDecoder().decode("+31e6VuKcGDl4qG5rxfiYy35LFwmbS4VY4AF/t7lmeu2wjEUneKEVWTEPBnaSo3+lRKsqfBVp4khbD830Qiy8R66AdHm1/ato7OzepfCxAs=")))+"|"); System.out.println(new String(SM4.decrypt( key, iv, Base64.getDecoder().decode("A5/GbdIvh2v44y8izoqzhu6ne96tkt/xI0ZBkFaPyX+rD6G/+MuyARV4lawjH/Cy6N+vVRYTwb6T5l4dMTJ7mEN1X0XiYxlrbX3PDGd96OM=")))+"|"); byte[] encrypted = SM4.encrypt(key, data); System.out.println(data.length + "-->" + encrypted.length + "\t|" + new String(SM4.decrypt(key, encrypted)) + "|"); encrypted = SM4.encrypt(key, iv, data); System.out.println(Base64.getEncoder().encodeToString(encrypted)); System.out.println(data.length + "-->" + encrypted.length + "\t|" + new String(SM4.decrypt(key, iv, encrypted)) + "|"); encrypted = SM4.encrypt(false, key, data); System.out.println(data.length + "-->" + encrypted.length + "\t|" + new String(SM4.decrypt(false, key, encrypted)) + "|"); encrypted = SM4.encrypt(false, key, iv, data); System.out.println(data.length + "-->" + encrypted.length + "\t|" + new String(SM4.decrypt(false, key, iv, encrypted)) + "|"); } } ================================================ FILE: src/test/java/test/log4j/TestLog4j.java ================================================ package test.log4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import cn.ponfee.commons.util.MavenProjects; public class TestLog4j { private static Logger logger = LoggerFactory.getLogger(TestLog4j.class); public static void main(String[] args) { logger.trace("abcd"); logger.info("abcd"); logger.info(MavenProjects.getTestJavaFileAsString(TestLog4j.class)); logger.info("abcd"); logger.info("abcd"); logger.warn("abcd"); logger.error("abcd"); } } ================================================ FILE: src/test/java/test/model/PageInfo.java ================================================ package test.model; import java.io.Serializable; public class PageInfo implements Serializable { private static final long serialVersionUID = 587754556498974978L; // pagesize ,每一页显示多少 private int showCount = 3; // 总页数 private int totalPage; // 总记录数 private int totalResult; // 当前页数 private int currentPage; // 当前显示到的ID, 在mysql limit 中就是第一个参数. private int currentResult; private String sortField; private String order; public int getShowCount() { return showCount; } public void setShowCount(int showCount) { this.showCount = showCount; } public int getTotalPage() { return totalPage; } public void setTotalPage(int totalPage) { this.totalPage = totalPage; } public int getTotalResult() { return totalResult; } public void setTotalResult(int totalResult) { this.totalResult = totalResult; } public int getCurrentPage() { return currentPage; } public void setCurrentPage(int currentPage) { this.currentPage = currentPage; } public int getCurrentResult() { return currentResult; } public void setCurrentResult(int currentResult) { this.currentResult = currentResult; } public String getSortField() { return sortField; } public void setSortField(String sortField) { this.sortField = sortField; } public String getOrder() { return order; } public void setOrder(String order) { this.order = order; } } ================================================ FILE: src/test/java/test/model/PagePlugin.java ================================================ package test.model; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; import java.util.Map; import java.util.Properties; import javax.xml.bind.PropertyException; import org.apache.ibatis.scripting.xmltags.ForEachSqlNode; import org.apache.ibatis.executor.ErrorContext; import org.apache.ibatis.executor.ExecutorException; import org.apache.ibatis.executor.statement.BaseStatementHandler; import org.apache.ibatis.executor.statement.RoutingStatementHandler; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.ParameterMapping; import org.apache.ibatis.mapping.ParameterMode; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.plugin.Intercepts; import org.apache.ibatis.plugin.Invocation; import org.apache.ibatis.plugin.Plugin; import org.apache.ibatis.plugin.Signature; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.property.PropertyTokenizer; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.type.TypeHandler; import org.apache.ibatis.type.TypeHandlerRegistry; import cn.ponfee.commons.reflect.Fields; /** * * * * * * * * * * * * * * @author Ponfee */ @Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class }) }) public class PagePlugin implements Interceptor { private static String dialect = ""; private static String pageSqlId = ""; @SuppressWarnings("unchecked") public Object intercept(Invocation ivk) throws Throwable { if (ivk.getTarget() instanceof RoutingStatementHandler) { RoutingStatementHandler statementHandler = (RoutingStatementHandler) ivk.getTarget(); BaseStatementHandler delegate = (BaseStatementHandler) Fields.get(statementHandler, "delegate"); MappedStatement mappedStatement = (MappedStatement) Fields.get(delegate, "mappedStatement"); if (mappedStatement.getId().matches(pageSqlId)) { BoundSql boundSql = delegate.getBoundSql(); Object parameterObject = boundSql.getParameterObject(); if (parameterObject == null) { throw new NullPointerException("parameterObject error"); } else { Connection connection = (Connection) ivk.getArgs()[0]; String sql = boundSql.getSql(); String countSql = "select count(0) from (" + sql + ") myCount"; PreparedStatement countStmt = connection.prepareStatement(countSql); BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql, boundSql.getParameterMappings(), parameterObject); setParameters(countStmt, mappedStatement, countBS, parameterObject); ResultSet rs = countStmt.executeQuery(); int count = 0; if (rs.next()) { count = rs.getInt(1); } rs.close(); countStmt.close(); PageInfo page = null; if (parameterObject instanceof PageInfo) { page = (PageInfo) parameterObject; page.setTotalResult(count); } else if (parameterObject instanceof Map) { Map map = (Map) parameterObject; page = (PageInfo) map.get("page"); if (page == null) page = new PageInfo(); page.setTotalResult(count); } else { page = (PageInfo) Fields.get(parameterObject, "page"); if (page == null) page = new PageInfo(); page.setTotalResult(count); Fields.put(parameterObject, "page", page); } String pageSql = generatePageSql(sql, page); Fields.put(boundSql, "sql", pageSql); } } } return ivk.proceed(); } @SuppressWarnings({ "rawtypes", "unchecked" }) private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql, Object parameterObject) throws SQLException { ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); List parameterMappings = boundSql.getParameterMappings(); if (parameterMappings != null) { Configuration configuration = mappedStatement.getConfiguration(); TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject); for (int i = 0; i < parameterMappings.size(); i++) { ParameterMapping parameterMapping = parameterMappings.get(i); if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; String propertyName = parameterMapping.getProperty(); PropertyTokenizer prop = new PropertyTokenizer(propertyName); if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else if (boundSql.hasAdditionalParameter(propertyName)) { value = boundSql.getAdditionalParameter(propertyName); } else if (propertyName.startsWith(ForEachSqlNode.ITEM_PREFIX) && boundSql.hasAdditionalParameter(prop.getName())) { value = boundSql.getAdditionalParameter(prop.getName()); if (value != null) { value = configuration.newMetaObject(value).getValue(propertyName.substring(prop.getName().length())); } } else { value = metaObject == null ? null : metaObject.getValue(propertyName); } TypeHandler typeHandler = parameterMapping.getTypeHandler(); if (typeHandler == null) { throw new ExecutorException("There was no TypeHandler found for parameter " + propertyName + " of statement " + mappedStatement.getId()); } typeHandler.setParameter(ps, i + 1, value, parameterMapping.getJdbcType()); } } } } private String generatePageSql(String sql, PageInfo page) { if (page != null && (dialect != null || !dialect.equals(""))) { StringBuffer pageSql = new StringBuffer(); if ("mysql".equals(dialect)) { pageSql.append(sql); pageSql.append(" limit " + page.getCurrentResult() + "," + page.getShowCount()); } else if ("oracle".equals(dialect)) { pageSql.append("select * from (select tmp_tb.*,ROWNUM row_id from ("); pageSql.append(sql); pageSql.append(") tmp_tb where ROWNUM<="); pageSql.append(page.getCurrentResult() + page.getShowCount()); pageSql.append(") where row_id>"); pageSql.append(page.getCurrentResult()); } return pageSql.toString(); } else { return sql; } } public Object plugin(Object arg0) { // TODO Auto-generated method stub return Plugin.wrap(arg0, this); } public void setProperties(Properties p) { dialect = p.getProperty("dialect"); if (dialect == null || dialect.equals("")) { try { throw new PropertyException("dialect property is not found!"); } catch (PropertyException e) { // TODO Auto-generated catch block e.printStackTrace(); } } pageSqlId = p.getProperty("pageSqlId"); if (dialect == null || dialect.equals("")) { try { throw new PropertyException("pageSqlId property is not found!"); } catch (PropertyException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } ================================================ FILE: src/test/java/test/pdf/ItextUtil.java ================================================ package test.pdf; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.security.GeneralSecurityException; import java.security.PrivateKey; import java.security.cert.Certificate; import com.itextpdf.text.DocumentException; import com.itextpdf.text.Image; import com.itextpdf.text.Rectangle; import com.itextpdf.text.pdf.PdfReader; import com.itextpdf.text.pdf.PdfSignatureAppearance; import com.itextpdf.text.pdf.PdfStamper; import com.itextpdf.text.pdf.security.BouncyCastleDigest; import com.itextpdf.text.pdf.security.DigestAlgorithms; import com.itextpdf.text.pdf.security.ExternalDigest; import com.itextpdf.text.pdf.security.ExternalSignature; import com.itextpdf.text.pdf.security.MakeSignature; import com.itextpdf.text.pdf.security.PrivateKeySignature; import cn.ponfee.commons.jce.security.KeyStoreResolver; import cn.ponfee.commons.jce.security.KeyStoreResolver.KeyStoreType; import cn.ponfee.commons.resource.ResourceLoaderFacade; public class ItextUtil { /** * 单多次签章通用 * @param src * @param target * @param signatureInfos * @throws GeneralSecurityException * @throws IOException * @throws DocumentException */ public void sign(String src, String target, SignatureInfo... signatureInfos){ InputStream inputStream = null; FileOutputStream outputStream = null; ByteArrayOutputStream result = new ByteArrayOutputStream(); try { inputStream = new FileInputStream(src); for (SignatureInfo signatureInfo : signatureInfos) { ByteArrayOutputStream tempArrayOutputStream = new ByteArrayOutputStream(); PdfReader reader = new PdfReader(inputStream); //创建签章工具PdfStamper ,最后一个boolean参数是否允许被追加签名 PdfStamper stamper = PdfStamper.createSignature(reader, tempArrayOutputStream, '\0', null, true); // 获取数字签章属性对象 PdfSignatureAppearance appearance = stamper.getSignatureAppearance(); appearance.setReason(signatureInfo.getReason()); appearance.setLocation(signatureInfo.getLocation()); //设置签名的签名域名称,多次追加签名的时候,签名域名称不能一样,图片大小受表单域大小影响(过小导致压缩) //appearance.setVisibleSignature(signatureInfo.getFieldName()); appearance.setVisibleSignature(new Rectangle(200, 200, 400, 400), 1, "pdf seal[" + System.nanoTime() + "]"); //读取图章图片 Image image = Image.getInstance(signatureInfo.getImagePath()); appearance.setSignatureGraphic(image); appearance.setCertificationLevel(signatureInfo.getCertificationLevel()); //设置图章的显示方式,如下选择的是只显示图章(还有其他的模式,可以图章和签名描述一同显示) appearance.setRenderingMode(signatureInfo.getRenderingMode()); // 摘要算法 ExternalDigest digest = new BouncyCastleDigest(); // 签名算法 ExternalSignature signature = new PrivateKeySignature(signatureInfo.getPk(), signatureInfo.getDigestAlgorithm(), null); // 调用itext签名方法完成pdf签章 MakeSignature.signDetached(appearance, digest, signature, signatureInfo.getChain(), null, null, null, 0, signatureInfo.getSubfilter()); //定义输入流为生成的输出流内容,以完成多次签章的过程 inputStream = new ByteArrayInputStream(tempArrayOutputStream.toByteArray()); result = tempArrayOutputStream; } outputStream = new FileOutputStream(new File(target)); outputStream.write(result.toByteArray()); outputStream.flush(); } catch (Exception e) { e.printStackTrace(); } finally { try { if(null!=outputStream){ outputStream.close(); } if(null!=inputStream){ inputStream.close(); } if(null!=result){ result.close(); } } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) { try { ItextUtil app = new ItextUtil(); //将证书文件放入指定路径,并读取keystore ,获得私钥和证书链 KeyStoreResolver resolver = new KeyStoreResolver(KeyStoreType.PKCS12, ResourceLoaderFacade.getResource("cas_test.pfx").getStream(), "1234"); PrivateKey pk = resolver.getPrivateKey("1234"); Certificate[] chain = resolver.getX509CertChain(); String src = ResourceLoaderFacade.getResource("ElasticSearch.pdf").getFilePath(); //封装签章信息 SignatureInfo info = new SignatureInfo(); info.setReason("理由"); info.setLocation("位置"); info.setPk(pk); info.setChain(chain); info.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED); info.setDigestAlgorithm(DigestAlgorithms.SHA1); info.setFieldName("sig1"); info.setImagePath(ResourceLoaderFacade.getResource("2.png").getFilePath()); info.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC); SignatureInfo info1 = new SignatureInfo(); info1.setReason("理由1"); info1.setLocation("位置1"); info1.setPk(pk); info1.setChain(chain); info1.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED); info1.setDigestAlgorithm(DigestAlgorithms.SHA1); info1.setFieldName("sig2"); info1.setImagePath(ResourceLoaderFacade.getResource("2.png").getFilePath()); info1.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC); app.sign(src, "D://sign.pdf", info,info1); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static class SignatureInfo { private String reason; //理由 private String location;//位置 private String digestAlgorithm;//摘要类型 private String imagePath;//图章路径 private String fieldName;//表单域名称 private Certificate[] chain;//证书链 private PrivateKey pk;//私钥 private int certificationLevel = 0; //批准签章 private PdfSignatureAppearance.RenderingMode renderingMode;//表现形式:仅描述,仅图片,图片和描述,签章者和描述 private MakeSignature.CryptoStandard subfilter;//支持标准,CMS,CADES public String getReason() { return reason; } public void setReason(String reason) { this.reason = reason; } public String getLocation() { return location; } public void setLocation(String location) { this.location = location; } public String getDigestAlgorithm() { return digestAlgorithm; } public void setDigestAlgorithm(String digestAlgorithm) { this.digestAlgorithm = digestAlgorithm; } public String getImagePath() { return imagePath; } public void setImagePath(String imagePath) { this.imagePath = imagePath; } public String getFieldName() { return fieldName; } public void setFieldName(String fieldName) { this.fieldName = fieldName; } public Certificate[] getChain() { return chain; } public void setChain(Certificate[] chain) { this.chain = chain; } public PrivateKey getPk() { return pk; } public void setPk(PrivateKey pk) { this.pk = pk; } public int getCertificationLevel() { return certificationLevel; } public void setCertificationLevel(int certificationLevel) { this.certificationLevel = certificationLevel; } public PdfSignatureAppearance.RenderingMode getRenderingMode() { return renderingMode; } public void setRenderingMode(PdfSignatureAppearance.RenderingMode renderingMode) { this.renderingMode = renderingMode; } public MakeSignature.CryptoStandard getSubfilter() { return subfilter; } public void setSubfilter(MakeSignature.CryptoStandard subfilter) { this.subfilter = subfilter; } } } ================================================ FILE: src/test/java/test/pdf/PdfP7Sign.java ================================================ package test.pdf; import java.io.FileOutputStream; import java.util.Base64; import java.util.Calendar; import java.util.HashMap; import org.apache.commons.io.IOUtils; import com.itextpdf.text.Image; import com.itextpdf.text.Rectangle; import com.itextpdf.text.pdf.PdfDate; import com.itextpdf.text.pdf.PdfDictionary; import com.itextpdf.text.pdf.PdfName; import com.itextpdf.text.pdf.PdfReader; import com.itextpdf.text.pdf.PdfSignature; import com.itextpdf.text.pdf.PdfSignatureAppearance; import com.itextpdf.text.pdf.PdfSignatureAppearance.RenderingMode; import com.itextpdf.text.pdf.PdfStamper; import com.itextpdf.text.pdf.PdfString; import com.itextpdf.text.pdf.security.MakeSignature.CryptoStandard; import com.itextpdf.text.pdf.security.PdfPKCS7; import com.itextpdf.text.pdf.security.TSAClient; import cn.ponfee.commons.jce.digest.DigestUtils; import cn.ponfee.commons.jce.pkcs.PKCS1Signature; import cn.ponfee.commons.jce.security.KeyStoreResolver; import cn.ponfee.commons.jce.security.KeyStoreResolver.KeyStoreType; import cn.ponfee.commons.resource.ResourceLoaderFacade; public class PdfP7Sign { private static void sign(String src, String dest) throws Exception { byte[] img = IOUtils.toByteArray(ResourceLoaderFacade.getResource("2.png").getStream()); // ------------------------------------------------------------------// // 1、用户上传自己的证书到服务器,从服务器上拿取待签名文件的hash数据 KeyStoreResolver resolver = new KeyStoreResolver(KeyStoreType.PKCS12, ResourceLoaderFacade.getResource("subject.pfx").getStream(), "123456"); PdfReader reader = new PdfReader(src); FileOutputStream fout = new FileOutputStream(dest); PdfStamper stp = PdfStamper.createSignature(reader, fout, '\0', null, true); PdfSignatureAppearance appearance = stp.getSignatureAppearance(); appearance.setVisibleSignature(new Rectangle(100, 250, 288, 426), 1, "Signature"); appearance.setSignDate(Calendar.getInstance()); // 设置签名时间为当前日期 appearance.setSignatureGraphic(Image.getInstance(img)); appearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED); appearance.setRenderingMode(RenderingMode.GRAPHIC); PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, new PdfName("adbe.pkcs7.detached")); dic.setReason(appearance.getReason()); dic.setLocation(appearance.getLocation()); dic.setContact(appearance.getContact()); dic.setDate(new PdfDate(appearance.getSignDate())); appearance.setCryptoDictionary(dic); int estimatedSize = 8192; HashMap exc = new HashMap(); exc.put(PdfName.CONTENTS, new Integer((estimatedSize << 1) + 2)); appearance.preClose(exc); byte[] bytes1 = IOUtils.toByteArray(appearance.getRangeStream()); byte[] hash = DigestUtils.sha1(bytes1); // ------------------------------------------------------------------// // ------------------------------------------------------------------// //2、用户本地签名此HASH数据 TSAClient tsc = null; /*boolean withTS = false; if (withTS) { String tsa_url = properties.getProperty("TSA"); String tsa_login = properties.getProperty("TSA_LOGIN"); String tsa_passw = properties.getProperty("TSA_PASSWORD"); tsc = new TSAClientBouncyCastle(tsa_url, tsa_login, tsa_passw); }*/ byte[] ocsp = null; /*boolean withOCSP = false; if (withOCSP) { String url = PdfPKCS7.getOCSPURL((X509Certificate)chain[0]); CertificateFactory cf = CertificateFactory.getInstance("X509"); FileInputStream is = new FileInputStream(properties.getProperty("ROOTCERT")); X509Certificate root = (X509Certificate)cf.generateCertificate(is); ocsp = new OcspClientBouncyCastle((X509Certificate)chain[0], root, url).getEncoded(); }*/ PdfPKCS7 pkcs7 = new PdfPKCS7(resolver.getPrivateKey("123456"), resolver.getX509CertChain(), "SHA1", null, null, false); byte[] bytes = pkcs7.getAuthenticatedAttributeBytes(hash, ocsp, null, CryptoStandard.CMS); byte[] signed = PKCS1Signature.sign(bytes, resolver.getPrivateKey("123456"), resolver.getX509CertChain()[0]); System.out.println("signed:" + Base64.getEncoder().encodeToString(signed)); pkcs7.setExternalDigest(signed, null, "RSA"); //sgn.update(sh, 0, sh.length); byte[] encodedSig = pkcs7.getEncodedPKCS7(hash, tsc, ocsp, null, CryptoStandard.CMS); byte[] paddedSig = new byte[encodedSig.length]; System.arraycopy(encodedSig, 0, paddedSig, 0, encodedSig.length); PdfDictionary dic2 = new PdfDictionary(); dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true)); appearance.close(dic2); } public static void main(String[] args) throws Exception { String src = "D:\\test\\123.pdf"; String dest = "D:\\test\\result.pdf"; sign(src, dest); } } ================================================ FILE: src/test/java/test/pdf/TestPdfSign.java ================================================ package test.pdf; import java.io.File; import java.io.IOException; import org.apache.commons.io.IOUtils; import com.google.common.io.Files; import cn.ponfee.commons.jce.security.KeyStoreResolver; import cn.ponfee.commons.jce.security.KeyStoreResolver.KeyStoreType; import cn.ponfee.commons.pdf.sign.PdfSignature; import cn.ponfee.commons.pdf.sign.Signer; import cn.ponfee.commons.pdf.sign.Stamp; import cn.ponfee.commons.resource.ResourceLoaderFacade; public class TestPdfSign { public static void main(String[] args) throws IOException { String sign = "subject.pfx"; String pwd = "123456"; KeyStoreResolver r = new KeyStoreResolver(KeyStoreType.PKCS12, ResourceLoaderFacade.getResource(sign).getStream(), pwd); byte[] img = IOUtils.toByteArray(ResourceLoaderFacade.getResource("2.png").getStream()); Signer signer = new Signer(r.getPrivateKey(pwd), r.getX509CertChain(), img, true); Stamp stamp1 = new Stamp(1, 50, 250); Stamp stamp2 = new Stamp(2, 150, 250); Stamp stamp3 = new Stamp(3, 300, 250); byte[] pdf = IOUtils.toByteArray(ResourceLoaderFacade.getResource("SM3密码杂凑算法.pdf").getStream()); byte[] result = PdfSignature.sign(pdf, new Stamp[] { stamp1, stamp2, stamp3 }, signer); Files.write(result, new File("d:/test/123.pdf")); } } ================================================ FILE: src/test/java/test/qrcode/Qrcode.java ================================================ package test.qrcode; import java.io.*; /** * * QRcode class library 0.50beta10
    * (c)2003-2005 Y.Swetake
    * This version supports QRcode model2 version 1-40.
    * Some functions are not supported.
    *
    * @author Y.Swetake * @version 0.50beta10 * * */ public class Qrcode { static final String QRCODE_DATA_PATH = "qrcode_data"; char qrcodeErrorCorrect; char qrcodeEncodeMode; int qrcodeVersion; int qrcodeStructureappendN; int qrcodeStructureappendM; int qrcodeStructureappendParity; String qrcodeStructureappendOriginaldata; public Qrcode() { qrcodeErrorCorrect = 'M'; qrcodeEncodeMode = 'B'; qrcodeVersion = 0; qrcodeStructureappendN = 0; qrcodeStructureappendM = 0; qrcodeStructureappendParity = 0; qrcodeStructureappendOriginaldata = ""; } /** *エラ〖柠赖レベルを肋年します。 *@param ecc-エラ〖柠赖レベル('L','M','Q','H') * */ public void setQrcodeErrorCorrect(char ecc) { qrcodeErrorCorrect = ecc; } /** *附哼肋年されているエラ〖柠赖レベルを艰评します。 *@return エラ〖柠赖レベル('L','M','Q','H') * */ public char getQrcodeErrorCorrect() { return qrcodeErrorCorrect; } /** *附哼肋年されているバ〖ジョンを艰评します。 *@return バ〖ジョン 0から40の腊眶。0の眷圭は极瓢肋年。 * */ public int getQrcodeVersion() { return qrcodeVersion; } /** *バ〖ジョンを肋年します。 *0を肋年すると极瓢肋年になります。 *@param version 0から40の腊眶 * */ public void setQrcodeVersion(int ver) { if (ver >= 0 && ver <= 40) { qrcodeVersion = ver; } } /** *エンコ〖ドモ〖ドを肋年します。 *'N':眶机モ〖ド 'A':毖眶机モ〖ド その戮:8bit byteモ〖ド *@param encMode エンコ〖ドモ〖ド('N','A' or other) * */ public void setQrcodeEncodeMode(char encMode) { qrcodeEncodeMode = encMode; } /** *附哼肋年されているエンコ〖ドモ〖ドを艰评します。 *@return エンコ〖ドモ〖ド ('N','A' or other) * */ public char getQrcodeEncodeMode() { return qrcodeEncodeMode; } /** *息冯簇息のメソッドです。 *(活赋瞥掐です。) * */ public void setStructureappend(int m, int n, int p) { if (n > 1 && n <= 16 && m > 0 && m <= 16 && p >= 0 && p <= 255) { qrcodeStructureappendM = m; qrcodeStructureappendN = n; qrcodeStructureappendParity = p; } } /** *息冯に脱いるパリティを换叫します。 *(活赋瞥掐です。) */ public int calStructureappendParity(byte[] originaldata) { int originaldataLength; int i = 0; int structureappendParity = 0; originaldataLength = originaldata.length; if (originaldataLength > 1) { structureappendParity = 0; while (i < originaldataLength) { structureappendParity = (structureappendParity ^ (originaldata[i] & 0xFF)); i++; } } else { structureappendParity = -1; } return structureappendParity; } /** *涂えられたデ〖タ误からQRコ〖ドエンコ〖ドデ〖タを *boolean企肌傅芹误で手します。 *@param data エンコ〖ドするデ〖タ *@return QRコ〖ドエンコ〖ドデ〖タ */ public boolean[][] calQrcode(byte[] qrcodeData) { int dataLength; int dataCounter = 0; dataLength = qrcodeData.length; int[] dataValue = new int[dataLength + 32]; byte[] dataBits = new byte[dataLength + 32]; if (dataLength <= 0) { boolean ret[][] = { { false } }; return ret; } if (qrcodeStructureappendN > 1) { dataValue[0] = 3; dataBits[0] = 4; dataValue[1] = qrcodeStructureappendM - 1; dataBits[1] = 4; dataValue[2] = qrcodeStructureappendN - 1; dataBits[2] = 4; dataValue[3] = qrcodeStructureappendParity; dataBits[3] = 8; dataCounter = 4; } dataBits[dataCounter] = 4; /* --- determine encode mode --- */ int[] codewordNumPlus; int codewordNumCounterValue; switch (qrcodeEncodeMode) { /* ---- alphanumeric mode --- */ case 'A': codewordNumPlus = new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 }; dataValue[dataCounter] = 2; dataCounter++; dataValue[dataCounter] = dataLength; dataBits[dataCounter] = 9; codewordNumCounterValue = dataCounter; dataCounter++; for (int i = 0; i < dataLength; i++) { char chr = (char) qrcodeData[i]; byte chrValue = 0; if (chr >= 48 && chr < 58) { chrValue = (byte) (chr - 48); } else { if (chr >= 65 && chr < 91) { chrValue = (byte) (chr - 55); } else { if (chr == 32) { chrValue = 36; } if (chr == 36) { chrValue = 37; } if (chr == 37) { chrValue = 38; } if (chr == 42) { chrValue = 39; } if (chr == 43) { chrValue = 40; } if (chr == 45) { chrValue = 41; } if (chr == 46) { chrValue = 42; } if (chr == 47) { chrValue = 43; } if (chr == 58) { chrValue = 44; } } } if ((i % 2) == 0) { dataValue[dataCounter] = chrValue; dataBits[dataCounter] = 6; } else { dataValue[dataCounter] = dataValue[dataCounter] * 45 + chrValue; dataBits[dataCounter] = 11; if (i < dataLength - 1) { dataCounter++; } } } dataCounter++; break; /* ---- numeric mode ---- */ case 'N': codewordNumPlus = new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 }; dataValue[dataCounter] = 1; dataCounter++; dataValue[dataCounter] = dataLength; dataBits[dataCounter] = 10; /* #version 1-9*/ codewordNumCounterValue = dataCounter; dataCounter++; for (int i = 0; i < dataLength; i++) { if ((i % 3) == 0) { dataValue[dataCounter] = (int) (qrcodeData[i] - 0x30); dataBits[dataCounter] = 4; } else { dataValue[dataCounter] = dataValue[dataCounter] * 10 + (int) (qrcodeData[i] - 0x30); if ((i % 3) == 1) { dataBits[dataCounter] = 7; } else { dataBits[dataCounter] = 10; if (i < dataLength - 1) { dataCounter++; } } } } dataCounter++; break; /* ---- 8bit byte ---- */ default: codewordNumPlus = new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 }; dataValue[dataCounter] = 4; dataCounter++; dataValue[dataCounter] = dataLength; dataBits[dataCounter] = 8; /* #version 1-9 */ codewordNumCounterValue = dataCounter; dataCounter++; for (int i = 0; i < dataLength; i++) { dataValue[i + dataCounter] = (qrcodeData[i] & 0xFF); dataBits[i + dataCounter] = 8; } dataCounter += dataLength; break; } int totalDataBits = 0; for (int i = 0; i < dataCounter; i++) { totalDataBits += dataBits[i]; } int ec; switch (qrcodeErrorCorrect) { case 'L': ec = 1; break; case 'Q': ec = 3; break; case 'H': ec = 2; break; default: ec = 0; } int[][] maxDataBitsArray = { { 0, 128, 224, 352, 512, 688, 864, 992, 1232, 1456, 1728, 2032, 2320, 2672, 2920, 3320, 3624, 4056, 4504, 5016, 5352, 5712, 6256, 6880, 7312, 8000, 8496, 9024, 9544, 10136, 10984, 11640, 12328, 13048, 13800, 14496, 15312, 15936, 16816, 17728, 18672 }, { 0, 152, 272, 440, 640, 864, 1088, 1248, 1552, 1856, 2192, 2592, 2960, 3424, 3688, 4184, 4712, 5176, 5768, 6360, 6888, 7456, 8048, 8752, 9392, 10208, 10960, 11744, 12248, 13048, 13880, 14744, 15640, 16568, 17528, 18448, 19472, 20528, 21616, 22496, 23648 }, { 0, 72, 128, 208, 288, 368, 480, 528, 688, 800, 976, 1120, 1264, 1440, 1576, 1784, 2024, 2264, 2504, 2728, 3080, 3248, 3536, 3712, 4112, 4304, 4768, 5024, 5288, 5608, 5960, 6344, 6760, 7208, 7688, 7888, 8432, 8768, 9136, 9776, 10208 }, { 0, 104, 176, 272, 384, 496, 608, 704, 880, 1056, 1232, 1440, 1648, 1952, 2088, 2360, 2600, 2936, 3176, 3560, 3880, 4096, 4544, 4912, 5312, 5744, 6032, 6464, 6968, 7288, 7880, 8264, 8920, 9368, 9848, 10288, 10832, 11408, 12016, 12656, 13328 } }; int maxDataBits = 0; if (qrcodeVersion == 0) { /* auto version select */ qrcodeVersion = 1; for (int i = 1; i <= 40; i++) { if ((maxDataBitsArray[ec][i]) >= totalDataBits + codewordNumPlus[qrcodeVersion]) { maxDataBits = maxDataBitsArray[ec][i]; break; } qrcodeVersion++; } } else { maxDataBits = maxDataBitsArray[ec][qrcodeVersion]; } totalDataBits += codewordNumPlus[qrcodeVersion]; dataBits[codewordNumCounterValue] += codewordNumPlus[qrcodeVersion]; int[] maxCodewordsArray = { 0, 26, 44, 70, 100, 134, 172, 196, 242, 292, 346, 404, 466, 532, 581, 655, 733, 815, 901, 991, 1085, 1156, 1258, 1364, 1474, 1588, 1706, 1828, 1921, 2051, 2185, 2323, 2465, 2611, 2761, 2876, 3034, 3196, 3362, 3532, 3706 }; int maxCodewords = maxCodewordsArray[qrcodeVersion]; int maxModules1side = 17 + (qrcodeVersion << 2); int[] matrixRemainBit = { 0, 0, 7, 7, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0 }; /* ---- read version ECC data file */ int byte_num = matrixRemainBit[qrcodeVersion] + (maxCodewords << 3); byte[] matrixX = new byte[byte_num]; byte[] matrixY = new byte[byte_num]; byte[] maskArray = new byte[byte_num]; byte[] formatInformationX2 = new byte[15]; byte[] formatInformationY2 = new byte[15]; byte[] rsEccCodewords = new byte[1]; byte[] rsBlockOrderTemp = new byte[128]; try { String filename = QRCODE_DATA_PATH + "/qrv" + Integer.toString(qrcodeVersion) + "_" + Integer.toString(ec) + ".dat"; InputStream fis = Qrcode.class.getResourceAsStream(filename); BufferedInputStream bis = new BufferedInputStream(fis); bis.read(matrixX); bis.read(matrixY); bis.read(maskArray); bis.read(formatInformationX2); bis.read(formatInformationY2); bis.read(rsEccCodewords); bis.read(rsBlockOrderTemp); bis.close(); fis.close(); } catch (Exception e) { e.printStackTrace(); } byte rsBlockOrderLength = 1; for (byte i = 1; i < 128; i++) { if (rsBlockOrderTemp[i] == 0) { rsBlockOrderLength = i; break; } } byte[] rsBlockOrder = new byte[rsBlockOrderLength]; System.arraycopy(rsBlockOrderTemp, 0, rsBlockOrder, 0, rsBlockOrderLength); byte[] formatInformationX1 = { 0, 1, 2, 3, 4, 5, 7, 8, 8, 8, 8, 8, 8, 8, 8 }; byte[] formatInformationY1 = { 8, 8, 8, 8, 8, 8, 8, 8, 7, 5, 4, 3, 2, 1, 0 }; int maxDataCodewords = maxDataBits >> 3; /* -- read frame data -- */ int modules1Side = 4 * qrcodeVersion + 17; int matrixTotalBits = modules1Side * modules1Side; byte[] frameData = new byte[matrixTotalBits + modules1Side]; try { String filename = QRCODE_DATA_PATH + "/qrvfr" + Integer.toString(qrcodeVersion) + ".dat"; InputStream fis = Qrcode.class.getResourceAsStream(filename); BufferedInputStream bis = new BufferedInputStream(fis); bis.read(frameData); bis.close(); fis.close(); } catch (Exception e) { e.printStackTrace(); } /* --- set terminator */ if (totalDataBits <= maxDataBits - 4) { dataValue[dataCounter] = 0; dataBits[dataCounter] = 4; } else { if (totalDataBits < maxDataBits) { dataValue[dataCounter] = 0; dataBits[dataCounter] = (byte) (maxDataBits - totalDataBits); } else { if (totalDataBits > maxDataBits) { System.out.println("overflow"); } } } byte[] dataCodewords = divideDataBy8Bits(dataValue, dataBits, maxDataCodewords); byte[] codewords = calculateRSECC(dataCodewords, rsEccCodewords[0], rsBlockOrder, maxDataCodewords, maxCodewords); /* ---- flash matrix */ byte[][] matrixContent = new byte[modules1Side][modules1Side]; for (int i = 0; i < modules1Side; i++) { for (int j = 0; j < modules1Side; j++) { matrixContent[j][i] = 0; } } /* --- attach data */ for (int i = 0; i < maxCodewords; i++) { byte codeword_i = codewords[i]; for (int j = 7; j >= 0; j--) { int codewordBitsNumber = (i * 8) + j; matrixContent[matrixX[codewordBitsNumber] & 0xFF][matrixY[codewordBitsNumber] & 0xFF] = (byte) ((255 * (codeword_i & 1)) ^ maskArray[codewordBitsNumber]); codeword_i = (byte) ((codeword_i & 0xFF) >>> 1); } } for (int matrixRemain = matrixRemainBit[qrcodeVersion]; matrixRemain > 0; matrixRemain--) { int remainBitTemp = matrixRemain + (maxCodewords * 8) - 1; matrixContent[matrixX[remainBitTemp] & 0xFF][matrixY[remainBitTemp] & 0xFF] = (byte) (255 ^ maskArray[remainBitTemp]); } /* --- mask select --- */ byte maskNumber = selectMask(matrixContent, matrixRemainBit[qrcodeVersion] + maxCodewords * 8); byte maskContent = (byte) (1 << maskNumber); /* --- format information --- */ byte formatInformationValue = (byte) (ec << 3 | maskNumber); String[] formatInformationArray = { "101010000010010", "101000100100101", "101111001111100", "101101101001011", "100010111111001", "100000011001110", "100111110010111", "100101010100000", "111011111000100", "111001011110011", "111110110101010", "111100010011101", "110011000101111", "110001100011000", "110110001000001", "110100101110110", "001011010001001", "001001110111110", "001110011100111", "001100111010000", "000011101100010", "000001001010101", "000110100001100", "000100000111011", "011010101011111", "011000001101000", "011111100110001", "011101000000110", "010010010110100", "010000110000011", "010111011011010", "010101111101101" }; for (int i = 0; i < 15; i++) { byte content = Byte.parseByte(formatInformationArray[formatInformationValue].substring(i, i + 1)); matrixContent[formatInformationX1[i] & 0xFF][formatInformationY1[i] & 0xFF] = (byte) (content * 255); matrixContent[formatInformationX2[i] & 0xFF][formatInformationY2[i] & 0xFF] = (byte) (content * 255); } boolean[][] out = new boolean[modules1Side][modules1Side]; int c = 0; for (int i = 0; i < modules1Side; i++) { for (int j = 0; j < modules1Side; j++) { if ((matrixContent[j][i] & maskContent) != 0 || frameData[c] == (char) 49) { out[j][i] = true; } else { out[j][i] = false; } c++; } c++; } return out; } private static byte[] divideDataBy8Bits(int[] data, byte[] bits, int maxDataCodewords) { /* divide Data By 8bit and add padding char */ int l1 = bits.length; int l2; int codewordsCounter = 0; int remainingBits = 8; int max = 0; int buffer; int bufferBits; boolean flag; if (l1 != data.length) { } for (int i = 0; i < l1; i++) { max += bits[i]; } l2 = (max - 1) / 8 + 1; byte[] codewords = new byte[maxDataCodewords]; for (int i = 0; i < l2; i++) { codewords[i] = 0; } for (int i = 0; i < l1; i++) { buffer = data[i]; bufferBits = bits[i]; flag = true; if (bufferBits == 0) { break; } while (flag) { if (remainingBits > bufferBits) { codewords[codewordsCounter] = (byte) ((codewords[codewordsCounter] << bufferBits) | buffer); remainingBits -= bufferBits; flag = false; } else { bufferBits -= remainingBits; codewords[codewordsCounter] = (byte) ((codewords[codewordsCounter] << remainingBits) | (buffer >> bufferBits)); if (bufferBits == 0) { flag = false; } else { buffer = (buffer & ((1 << bufferBits) - 1)); flag = true; } codewordsCounter++; remainingBits = 8; } } } if (remainingBits != 8) { codewords[codewordsCounter] = (byte) (codewords[codewordsCounter] << remainingBits); } else { codewordsCounter--; } if (codewordsCounter < maxDataCodewords - 1) { flag = true; while (codewordsCounter < maxDataCodewords - 1) { codewordsCounter++; if (flag) { codewords[codewordsCounter] = -20; } else { codewords[codewordsCounter] = 17; } flag = !(flag); } } return codewords; } private static byte[] calculateRSECC(byte[] codewords, byte rsEccCodewords, byte[] rsBlockOrder, int maxDataCodewords, int maxCodewords) { byte[][] rsCalTableArray = new byte[256][rsEccCodewords]; try { String filename = QRCODE_DATA_PATH + "/rsc" + Byte.toString(rsEccCodewords) + ".dat"; InputStream fis = Qrcode.class.getResourceAsStream(filename); BufferedInputStream bis = new BufferedInputStream(fis); for (int i = 0; i < 256; i++) { bis.read(rsCalTableArray[i]); } bis.close(); fis.close(); } catch (Exception e) { e.printStackTrace(); } /* ---- RS-ECC prepare */ int i = 0; int j = 0; int rsBlockNumber = 0; byte[][] rsTemp = new byte[rsBlockOrder.length][]; byte res[] = new byte[maxCodewords]; System.arraycopy(codewords, 0, res, 0, codewords.length); i = 0; while (i < rsBlockOrder.length) { rsTemp[i] = new byte[(rsBlockOrder[i] & 0xFF) - rsEccCodewords]; i++; } i = 0; while (i < maxDataCodewords) { rsTemp[rsBlockNumber][j] = codewords[i]; j++; if (j >= (rsBlockOrder[rsBlockNumber] & 0xFF) - rsEccCodewords) { j = 0; rsBlockNumber++; } i++; } /* --- RS-ECC main --- */ rsBlockNumber = 0; while (rsBlockNumber < rsBlockOrder.length) { byte[] rsTempData; rsTempData = (byte[]) rsTemp[rsBlockNumber].clone(); int rsCodewords = (rsBlockOrder[rsBlockNumber] & 0xFF); int rsDataCodewords = rsCodewords - rsEccCodewords; j = rsDataCodewords; while (j > 0) { byte first = rsTempData[0]; if (first != 0) { byte[] leftChr = new byte[rsTempData.length - 1]; System.arraycopy(rsTempData, 1, leftChr, 0, rsTempData.length - 1); byte[] cal = rsCalTableArray[(first & 0xFF)]; rsTempData = calculateByteArrayBits(leftChr, cal, "xor"); } else { if (rsEccCodewords < rsTempData.length) { byte[] rsTempNew = new byte[rsTempData.length - 1]; System.arraycopy(rsTempData, 1, rsTempNew, 0, rsTempData.length - 1); rsTempData = (byte[]) rsTempNew.clone(); } else { byte[] rsTempNew = new byte[rsEccCodewords]; System.arraycopy(rsTempData, 1, rsTempNew, 0, rsTempData.length - 1); rsTempNew[rsEccCodewords - 1] = 0; rsTempData = (byte[]) rsTempNew.clone(); } } j--; } System.arraycopy(rsTempData, 0, res, codewords.length + rsBlockNumber * rsEccCodewords, rsEccCodewords); rsBlockNumber++; } return res; } private static byte[] calculateByteArrayBits(byte[] xa, byte[] xb, String ind) { int ll; int ls; byte[] res; byte[] xl; byte[] xs; if (xa.length > xb.length) { xl = (byte[]) xa.clone(); xs = (byte[]) xb.clone(); } else { xl = (byte[]) xb.clone(); xs = (byte[]) xa.clone(); } ll = xl.length; ls = xs.length; res = new byte[ll]; for (int i = 0; i < ll; i++) { if (i < ls) { if (ind == "xor") { res[i] = (byte) (xl[i] ^ xs[i]); } else { res[i] = (byte) (xl[i] | xs[i]); } } else { res[i] = xl[i]; } } return res; } private static byte selectMask(byte[][] matrixContent, int maxCodewordsBitWithRemain) { int l = matrixContent.length; int[] d1 = { 0, 0, 0, 0, 0, 0, 0, 0 }; int[] d2 = { 0, 0, 0, 0, 0, 0, 0, 0 }; int[] d3 = { 0, 0, 0, 0, 0, 0, 0, 0 }; int[] d4 = { 0, 0, 0, 0, 0, 0, 0, 0 }; int d2And = 0; int d2Or = 0; int[] d4Counter = { 0, 0, 0, 0, 0, 0, 0, 0 }; for (int y = 0; y < l; y++) { int[] xData = { 0, 0, 0, 0, 0, 0, 0, 0 }; int[] yData = { 0, 0, 0, 0, 0, 0, 0, 0 }; boolean[] xD1Flag = { false, false, false, false, false, false, false, false }; boolean[] yD1Flag = { false, false, false, false, false, false, false, false }; for (int x = 0; x < l; x++) { if (x > 0 && y > 0) { d2And = matrixContent[x][y] & matrixContent[x - 1][y] & matrixContent[x][y - 1] & matrixContent[x - 1][y - 1] & 0xFF; d2Or = (matrixContent[x][y] & 0xFF) | (matrixContent[x - 1][y] & 0xFF) | (matrixContent[x][y - 1] & 0xFF) | (matrixContent[x - 1][y - 1] & 0xFF); } for (int maskNumber = 0; maskNumber < 8; maskNumber++) { xData[maskNumber] = ((xData[maskNumber] & 63) << 1) | (((matrixContent[x][y] & 0xFF) >>> maskNumber) & 1); yData[maskNumber] = ((yData[maskNumber] & 63) << 1) | (((matrixContent[y][x] & 0xFF) >>> maskNumber) & 1); if ((matrixContent[x][y] & (1 << maskNumber)) != 0) { d4Counter[maskNumber]++; } if (xData[maskNumber] == 93) { d3[maskNumber] += 40; } if (yData[maskNumber] == 93) { d3[maskNumber] += 40; } if (x > 0 && y > 0) { if (((d2And & 1) != 0) || ((d2Or & 1) == 0)) { d2[maskNumber] += 3; } d2And = d2And >> 1; d2Or = d2Or >> 1; } if (((xData[maskNumber] & 0x1F) == 0) || ((xData[maskNumber] & 0x1F) == 0x1F)) { if (x > 3) { if (xD1Flag[maskNumber]) { d1[maskNumber]++; } else { d1[maskNumber] += 3; xD1Flag[maskNumber] = true; } } } else { xD1Flag[maskNumber] = false; } if (((yData[maskNumber] & 0x1F) == 0) || ((yData[maskNumber] & 0x1F) == 0x1F)) { if (x > 3) { if (yD1Flag[maskNumber]) { d1[maskNumber]++; } else { d1[maskNumber] += 3; yD1Flag[maskNumber] = true; } } } else { yD1Flag[maskNumber] = false; } } } } int minValue = 0; byte res = 0; int[] d4Value = { 90, 80, 70, 60, 50, 40, 30, 20, 10, 0, 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 90 }; for (int maskNumber = 0; maskNumber < 8; maskNumber++) { d4[maskNumber] = d4Value[(int) ((20 * d4Counter[maskNumber]) / maxCodewordsBitWithRemain)]; int demerit = d1[maskNumber] + d2[maskNumber] + d3[maskNumber] + d4[maskNumber]; if (demerit < minValue || maskNumber == 0) { res = (byte) maskNumber; minValue = demerit; } } return res; } /*--- class end ---*/ } ================================================ FILE: src/test/java/test/qrcode/QrcodeTest.java ================================================ package test.qrcode; /** *QRcodeクラスライブラリ脱sample * *妈办苞眶をデ〖タとしたQRcodeを *テキストで叫蜗します */ public class QrcodeTest { public static void main(String[] args) { Qrcode x = new Qrcode(); x.setQrcodeErrorCorrect('M'); //エラ〖柠赖レベルM x.setQrcodeEncodeMode('B'); //8bit byte モ〖ド boolean[][] matrix = x.calQrcode(args[0].getBytes()); for (int i = 0; i < matrix.length; i++) { for (int j = 0; j < matrix.length; j++) { if (matrix[j][i]) { System.out.print("@"); } else { System.out.print(" "); } } System.out.print("\n"); } } } ================================================ FILE: src/test/java/test/reflect/ClassUtilsTest.java ================================================ package test.reflect; import cn.ponfee.commons.base.Predicates; import cn.ponfee.commons.base.PrimitiveTypes; import cn.ponfee.commons.base.tuple.*; import cn.ponfee.commons.cache.Cache; import cn.ponfee.commons.cache.CacheBuilder; import cn.ponfee.commons.collect.ByteArrayWrapper; import cn.ponfee.commons.collect.Collects; import cn.ponfee.commons.concurrent.ThreadPoolExecutors; import cn.ponfee.commons.json.Jsons; import cn.ponfee.commons.model.Result; import cn.ponfee.commons.reflect.ClassUtils; import cn.ponfee.commons.reflect.Fields; import cn.ponfee.commons.reflect.GenericUtils; import cn.ponfee.commons.spring.LocalizedMethodArgumentResolver; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Assert; import org.junit.Test; import org.openjdk.jol.vm.VM; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; /** * @author Ponfee */ public class ClassUtilsTest { static int n = 10000000; @Test public void test0() { for (int i = 0; i < n; i++) { ClassUtils.getConstructor(ByteArrayWrapper.class, byte[].class); } } @Test public void test1() throws Exception { for (int i = 0; i < n; i++) { ByteArrayWrapper.class.getConstructor(byte[].class); } } @Test public void test20() { for (int i = 0; i < n; i++) { ClassUtils.getConstructor(ClassUtilsTest.class); } } @Test public void test21() throws NoSuchMethodException { for (int i = 0; i < n; i++) { ClassUtilsTest.class.getConstructor(); } } @Test public void test3() throws Exception { int n = 1000; Method method = ClassUtils.class.getMethod("newInstance", Class.class, Class[].class, Object[].class); System.out.println(Arrays.toString(ClassUtils.getMethodParamNames(method))); for (int i = 0; i < n; i++) { ClassUtils.getMethodParamNames(method); } } @Test public void test4() throws Exception { int n = 1000000; Cache METHOD_ARGSNAME = CacheBuilder.newBuilder().build(); Method method = ClassUtils.class.getMethod("newInstance", Class.class, Class[].class, Object[].class); for (int i = 0; i < n; i++) { String[] argsName = METHOD_ARGSNAME.get(method); if (argsName == null) { argsName = ClassUtils.getMethodParamNames(method); METHOD_ARGSNAME.put(method, argsName); System.out.println(Arrays.toString(argsName)); } } } @Test public void test5() throws Exception { Assert.assertEquals(ClassUtils.class.getMethod("newInstance", Class.class, Class[].class, Object[].class), ClassUtils.class.getMethod("newInstance", Class.class, Class[].class, Object[].class)); Assert.assertEquals(Result.class.getDeclaredField("code"), Result.class.getDeclaredField("code")); } @Test public void test6() throws Exception { System.out.println(Arrays.toString(ClassUtils.getMethodParamNames(ClassUtils.class.getMethod("newInstance", Class.class, Class[].class, Object[].class)))); System.out.println(ClassUtils.getMethodSignature(ClassUtils.class.getMethod("newInstance", Class.class, Class[].class, Object[].class))); System.out.println(ClassUtils.getClassName(ClassUtils.class)); System.out.println(ClassUtils.getPackagePath(ClassUtils.class)); System.out.println(ClassUtils.getClassFilePath(ClassUtils.class)); System.out.println(ClassUtils.getClassFilePath(org.apache.commons.lang3.StringUtils.class)); System.out.println(ClassUtils.getClasspath(ClassUtils.class)); System.out.println(ClassUtils.getClasspath(org.apache.commons.lang3.StringUtils.class)); System.out.println(ClassUtils.getClasspath()); System.out.println(ClassUtils.newInstance(Tuple3.class, new Object[]{1, 2, 3})); System.out.println(ClassUtils.newInstance(Tuple3.class, new Object[]{null, null, null})); System.out.println(ClassUtils.newInstance(Tuple2.class, new Object[]{new String[]{"a", "b"}, new Integer[]{1, 2}})); System.out.println("Tuple.class.isAssignableFrom(Tuple.class)=" + Tuple.class.isAssignableFrom(Tuple.class)); System.out.println("Tuple.class.isAssignableFrom(Tuple1.class)=" + Tuple.class.isAssignableFrom(Tuple1.class)); System.out.println("Tuple1.class.isAssignableFrom(Tuple.class)=" + Tuple1.class.isAssignableFrom(Tuple.class)); System.out.println(PrimitiveTypes.FLOAT.isCastable(PrimitiveTypes.DOUBLE)); System.out.println(PrimitiveTypes.FLOAT.isCastable(PrimitiveTypes.FLOAT)); System.out.println(PrimitiveTypes.FLOAT.isCastable(PrimitiveTypes.LONG)); System.out.println(ClassUtils.newInstance(Tuple0.class)); System.out.println(ClassUtils.newInstance(Tuple0.class, new Object[]{})); System.out.println(ClassUtils.newInstance(Tuple1.class, new Object[]{null})); System.out.println(ClassUtils.newInstance(A.class, new Object[]{1})); Assert.assertTrue(new Object().getClass() == Object.class); Assert.assertTrue("".getClass() == String.class); Assert.assertEquals(String.class.getSimpleName(), "String"); Assert.assertFalse(String.class.equals(Class.class)); Assert.assertTrue(String.class.getClass() == Class.class); Assert.assertTrue(Object.class.getClass() == Class.class); Assert.assertTrue(Class.class.getClass() == Class.class); Assert.assertTrue(Class.class.getClass().getClass().getClass().getClass() == Class.class); Assert.assertTrue(String.class instanceof Class); Assert.assertTrue(Object.class instanceof Class); Assert.assertTrue(Class.class instanceof Class); Assert.assertTrue(Predicates.Y.equals(true)); Assert.assertFalse(Predicates.Y.equals(false)); Assert.assertTrue(Predicates.N.equals(false)); Assert.assertFalse(Predicates.N.equals(true)); Assert.assertEquals(Predicates.N.code(), 'N'); Assert.assertTrue(Predicates.Y.state()); Assert.assertFalse(Predicates.N.state()); Assert.assertEquals(ClassUtils.invoke(A.class, "max", new Object[]{2, 4}), new Integer(4)); Assert.assertEquals(ClassUtils.invoke(new A(5), "add", new Object[]{2}), new Integer(7)); Assert.assertEquals(Boolean.TYPE, boolean.class); Assert.assertEquals((byte) 0B1_0_0_0_0_0_0_0, -128); Assert.assertEquals(0B1_0_0_0_0_0_0_0, 128); Assert.assertThrows(RuntimeException.class, () -> ClassUtils.invoke(String.class, "getName")); // 普通Class类实例(如String.class):只处理其所表示类的静态方法,如String.valueOf(1)。不支持Class类中的实例方法,如String.class.getName() Assert.assertEquals(ClassUtils.invoke(String.class, "valueOf", new Object[]{1}), "1"); Assert.assertEquals(ClassUtils.invoke(String.class, "join", new Object[]{"|", new String[]{"a", "b"}}), "a|b"); Assert.assertNull(ClassUtils.getMethod(String.class, "getName")); // Class.class对象:只处理Class类中的实例方法,如Class.class.getName()。不支持Class类中的静态方法,如Class.forName("cn.ponfee.commons.base.tuple.Tuple0"); Assert.assertEquals(ClassUtils.invoke(Class.class, "getName"), "java.lang.Class"); Assert.assertNull(ClassUtils.getMethod(Class.class, "forName", String.class)); //double a = 1; //float a = 1; //long a = 1; int a = 1; //short a = 1; //char a = 1; //byte a = 1; //boolean a = true; new A(a); ClassUtils.newInstance(A.class, new Class[]{int.class}, new Object[]{a}); System.out.println(0B1_0_0_0_0_0_0_0); System.out.println(0B1_1_1_1_1_1_1_1); } @Test public void test00() throws Exception { System.out.println(Arrays.toString(A.class.getInterfaces())); System.out.println(Arrays.toString(Tuple.class.getInterfaces())); Assert.assertEquals(31, Fields.get(Tuple.class, "HASH_FACTOR")); Fields.put(Tuple.class, "HASH_FACTOR", 311); // final可以修改 Assert.assertEquals(311, Fields.get(Tuple.class, "HASH_FACTOR")); System.out.println(B.INTF_1); Assert.assertNull(Fields.get(A.class, "static_2")); A.max(1, 2); // new A(1); 要初始化 Assert.assertEquals(10, Fields.get(A.class, "static_2")); Fields.put(A.class, "static_2", 20); Assert.assertEquals(20, Fields.get(A.class, "static_2")); Assert.assertNotNull(null, ClassUtils.getStaticField(A.class, "static_1")); B.static_2 = 123; Assert.assertEquals(123, Fields.get(A.class, "static_2")); Assert.assertEquals(123, Fields.get(B.class, "static_2")); Assert.assertEquals(Fields.get(Tuple1.of("xx"), "a"), "xx"); System.out.println(PrimitiveTypes.allPrimitiveTypes()); System.out.println(GenericUtils.getFieldActualType(A.class, ClassUtils.getField(A.class, "member_1")));// java.util.List System.out.println(GenericUtils.getFieldActualType(A.class, ClassUtils.getField(A.class, "member_2"))); // java.util.List System.out.println(GenericUtils.getFieldActualType(A.class, ClassUtils.getField(A.class, "member_3"))); // java.lang.Object System.out.println(GenericUtils.getFieldActualType(B.class, ClassUtils.getField(B.class, "member_1"))); // java.lang.Object System.out.println(GenericUtils.getFieldActualType(B.class, ClassUtils.getField(B.class, "member_2"))); // java.lang.Object System.out.println(GenericUtils.getFieldActualType(B.class, ClassUtils.getField(B.class, "member_3"))); // java.lang.Integer System.out.println(GenericUtils.getFieldActualType(A.class, ClassUtils.getStaticFieldInClassChain(A.class, "static_1").b)); } @Test public void test8() throws Exception { A a = new B(1); B b = new B(1); Assert.assertEquals("A#func1", a.func1()); Assert.assertEquals("A#func2", a.func2()); Assert.assertEquals("B#func1", b.func1()); Assert.assertEquals("A#func2", b.func2()); Assert.assertEquals("test-static", (A.get()).test()); Fields.put(Double.class, "SIZE", 999); Assert.assertEquals(999, Fields.get(Double.class, "SIZE")); Assert.assertEquals(64, Double.SIZE); Assert.assertEquals(999, Fields.get(Double.class, "SIZE")); } @Test public void test9() { Assert.assertTrue(Collects.toArray(new Object[]{"1", "2"})[0] instanceof String); Assert.assertEquals(String[].class, Collects.toArray(new String[]{"1", "2"}).getClass()); Assert.assertTrue(toArray(new Object[]{"1", "2"})[0] instanceof String); Assert.assertEquals(String[].class, toArray(new String[]{"1", "2"}).getClass()); Assert.assertTrue(get(new Object[]{"1", "2"}, 0) instanceof String); Assert.assertTrue(get(new String[]{"1", "2"}, 0) instanceof String); Assert.assertTrue(get(new Integer[]{1, 2}, 0) instanceof Integer); } @Test public void tes10() { Object obj = new Object(); Assert.assertEquals(obj.hashCode(), System.identityHashCode(obj)); Assert.assertEquals(Fields.addressOf(obj), VM.current().addressOf(obj)); System.out.println("identityHashCode: " + System.identityHashCode(obj)); System.out.println("Fields.addressOf: " + Fields.addressOf(obj)); System.out.println("VM.addressOf: " + VM.current().addressOf(obj)); obj = ClassUtilsTest.class; Assert.assertEquals(obj.hashCode(), System.identityHashCode(obj)); Assert.assertEquals(Fields.addressOf(obj), VM.current().addressOf(obj)); Assert.assertNotEquals("123".hashCode(), System.identityHashCode(new String("123"))); Assert.assertNotEquals(Fields.addressOf("123"), VM.current().addressOf(new String("123"))); Assert.assertTrue(String.class == "123".getClass()); Assert.assertFalse(new Integer(1) == new Integer(1)); Assert.assertTrue(Integer.valueOf(1) == Integer.valueOf(1)); Assert.assertEquals(Fields.addressOf(1), VM.current().addressOf(1)); Assert.assertEquals(Fields.addressOf(1), VM.current().addressOf(1)); Assert.assertEquals(Fields.addressOf(1L), VM.current().addressOf(1L)); Assert.assertEquals(Fields.addressOf((byte) 1), VM.current().addressOf((byte) 1)); Assert.assertEquals(Fields.addressOf(new Object[]{1, 2, 3, obj}, 3), VM.current().addressOf(obj)); Assert.assertEquals(Fields.addressOf(new Object[]{1, 2, obj, 6, new Object(), "abc"}, 2), VM.current().addressOf(obj)); String str = "fdsaf23"; Assert.assertEquals(Fields.addressOf(new String[]{"abc", str, new String(), null, ""}, 1), VM.current().addressOf(str)); Byte a = 1; Assert.assertEquals(Fields.addressOf(new Byte[]{127, a, 21, 4}, 1), VM.current().addressOf(a)); } /*@Test public void test() throws Exception { Method method = ThreadPoolExecutors.class.getMethod("create", int.class, int.class, long.class); LocalizedMethodArgumentResolver resolver = new LocalizedMethodArgumentResolver(new ObjectMapper()); String args = Jsons.toJson(new Object[]{1, 2, 10}); Object[] objects = resolver.parseRequestBody(method, args); Arrays.stream(objects).forEach(e -> System.out.println(e + ":" + e.getClass())); }*/ public static Object[] toArray(Object... args) { return args; } public static Object get(Object[] args, int i) { return args[i]; } public static class A { public List member_1; public List member_2; public T member_3; private int x; public static List static_1; public static Integer static_2 = 10; public A(int x) { this.x = x; } public void a(int i) { } public int add(int i) { return x + i; } public static String func1() { return "A#func1"; } public static String func2() { return "A#func2"; } public static int max(int a, int b) { return a > b ? a : b; } /*public static void a(int i) { }*/ public static String test() { return "test-static"; } public static A get() { return null; } } public static interface X { String INTF_1 = "test1"; } public static class B extends A implements X { public B(int x) { super(x); } public static String func1() { return "B#func1"; } } } ================================================ FILE: src/test/java/test/reflect/ProxyTest.java ================================================ package test.reflect; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import org.apache.commons.io.IOUtils; import cn.ponfee.commons.util.Bytes; public class ProxyTest { @SuppressWarnings("restriction") public static void main(String[] args) throws IOException { Echo target = new MountainEcho(); EchoInvocationHandler handler = new EchoInvocationHandler(target); Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[] { Echo.class }, handler); String proxyRtn = ((Echo) proxy).echo("hello"); System.out.println(proxy.getClass()); // com.sun.proxy.$Proxy0 System.out.println(proxyRtn); // return EchoInvocationHandler.invoke result System.out.println("\n==============================导出代理类"); // 根据类信息和提供的代理类名称,生成字节码 byte[] classBytes = sun.misc.ProxyGenerator.generateProxyClass("ProxyClass", proxy.getClass().getInterfaces()); // 打印字节码 System.out.println(Bytes.dumpHex(classBytes)); // 导出到文件 IOUtils.write(classBytes, new FileOutputStream(new File("d:/ProxyClass.class"))); // TODO 用jd-gui反编译ProxyClass.class文件 } // ----------------------------------------------------------------------------------------------------------------------------- public static interface Echo { String echo(String s); } public static class MountainEcho implements Echo { @Override public String echo(String saying) { return saying + " " + saying + " " + saying + " ..."; } } public static class EchoInvocationHandler implements InvocationHandler { private final Echo target; public EchoInvocationHandler(Echo target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // proxy <= Proxy.newProxyInstance(loader, interfaces, h); System.out.println("shout: " + args[0]); System.out.println("echo : " + method.invoke(target, args) + "."); return "return ((Echo) proxy).echo(\"hello\")"; } } } ================================================ FILE: src/test/java/test/swing/SM2Crypto.java ================================================ package test.swing; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Font; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Base64; import java.util.Map; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.JToolBar; import cn.ponfee.commons.jce.ECParameters; import cn.ponfee.commons.jce.sm.SM2; public class SM2Crypto extends JFrame{ private static final long serialVersionUID = 1L; Map map = SM2.generateKeyPair(ECParameters.SM2_BEST); StringBuffer str=new StringBuffer(); // 显示面板 JTextArea textArea; // 文件URL输入栏 JTextField urlField=new JTextField(); public SM2Crypto() { super("SM2加密"); /*// 新建显示HTML的面板,并设置它不可编辑 textPane = new JEditorPane(); */ //textPane.setEditable(false); // 初始化菜单和工具栏 this.initMenu(); this.initToolbar(); textArea=new JTextArea(); textArea.setTabSize(4); textArea.setFont(new Font("标楷体", Font.BOLD, 16)); textArea.setLineWrap(true);// 激活自动换行功能 textArea.setWrapStyleWord(true);// 激活断行不断字功能 textArea.setBackground(Color.white); // 将HTML显示面板放入主窗口,居中显示 this.add(new JScrollPane(textArea),BorderLayout.CENTER); } private void initToolbar() { // 输入网址的文本框 urlField = new JTextField(); JToolBar toolbar = new JToolBar(); // 地址标签 toolbar.add(new JLabel(" 地址:")); toolbar.add(urlField); // 将工具栏放在主窗口的北部 this.getContentPane().add(toolbar, BorderLayout.NORTH); } private void initMenu() { // 文件菜单,下面有两个菜单项:打开、退出 JMenu fileMenu = new JMenu("文件"); JMenuItem openMenuItem = new JMenuItem("打开"); openMenuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { JFileChooser jfc=new JFileChooser(); jfc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES ); jfc.showDialog(new JLabel(), "选择"); File file=jfc.getSelectedFile(); if(file.isDirectory()){ urlField.setText("文件夹:"+file.getAbsolutePath()); }else if(file.isFile()){ urlField.setText("文件:"+file.getAbsolutePath()); } //建立数据的输入通道 FileInputStream fileInputStream = null; try { fileInputStream = new FileInputStream(file); } catch (FileNotFoundException e2) { e2.printStackTrace(); } //建立缓冲数组配合循环读取文件的数据。 int length = 0; //保存每次读取到的字节个数。 byte[] buf = new byte[1024]; //存储读取到的数据 缓冲数组 的长度一般是1024的倍数,因为与计算机的处理单位。 理论上缓冲数组越大,效率越高 try { while((length = fileInputStream.read(buf))!=-1){ // read方法如果读取到了文件的末尾,那么会返回-1表示。 try { str.append(new String(buf,0,length)); textArea.append(new String(buf,0,length)); } catch (Exception e1) { e1.printStackTrace(); } } } catch (IOException e1) { e1.printStackTrace(); } } }); JMenuItem exitMenuItem = new JMenuItem("退出"); // 当“退出”时退出应用程序 exitMenuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { exit(); } }); fileMenu.add(openMenuItem); fileMenu.add(exitMenuItem); //帮助菜单,就一个菜单项:关于 JMenu helpMenu = new JMenu("帮助"); JMenu doMenu = new JMenu("操作"); JMenuItem pItem = new JMenuItem("加密"); pItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { textArea.setText(Base64.getEncoder().encodeToString(SM2.encrypt(ECParameters.SM2_BEST, map.get(SM2.PUBLIC_KEY), urlField.getText().getBytes()))); } }); JMenuItem cItem = new JMenuItem("解密"); cItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { urlField.setText(new String(SM2.decrypt(ECParameters.SM2_BEST, map.get(SM2.PRIVATE_KEY), Base64.getDecoder().decode(textArea.getText())))); } }); doMenu.add(pItem); doMenu.add(cItem); JMenuItem aboutMenuItem = new JMenuItem("关于"); helpMenu.add(aboutMenuItem); JMenuBar menuBar = new JMenuBar(); menuBar.add(fileMenu); menuBar.add(doMenu); menuBar.add(helpMenu); // 将菜单栏添加到主窗口 this.setJMenuBar(menuBar); } public void exit() { // 弹出对话框,请求确认,如果确认退出,则退出应用程序 if ((JOptionPane.showConfirmDialog(this, "你确定退出SM2加密器?", "退出", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION)){ System.exit(0); } } public static void main(String[] args) { // 设置浏览器,当所有浏览器窗口都被关闭时,退出应用程序 SM2Crypto window = new SM2Crypto(); window.setSize(800, 600); // 显示窗口 window.setVisible(true); } } ================================================ FILE: src/test/java/test/swing/WebBrowser.java ================================================ package test.swing; import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.List; import javax.swing.JButton; import javax.swing.JEditorPane; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JScrollPane; import javax.swing.JTextField; import javax.swing.JToolBar; import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkListener; import javax.swing.filechooser.FileFilter; public class WebBrowser extends JFrame implements HyperlinkListener, PropertyChangeListener { //超文本监听,属性改变监听 private static final long serialVersionUID = -7123717969723532732L; // 显示HTML的面板 JEditorPane textPane; // 最底下的状态栏 JLabel messageLine; // 网址URL输入栏 JTextField urlField; // 文件选择器 JFileChooser fileChooser; // 后退和前进 按钮 JButton backButton; JButton forwardButton; // 保存历史记录的列表 List history = new ArrayList<>(); // 在历史记录列表中位置 int currentHistoryPage = -1; // 历史记录超过MAX_HISTORY时,清除旧的历史 public static final int MAX_HISTORY = 50; // 当前已经打开的浏览器窗口数 static int numBrowserWindows = 0; // 标识当所有浏览器窗口都被关闭时,是否退出应用程序 static boolean exitWhenLastWindowClosed = false; // 默认主页 String home = "http://www.baidu.com"; public WebBrowser() { super("WebBrowser"); // 新建显示HTML的面板,并设置它不可编辑 textPane = new JEditorPane(); textPane.setEditable(false); // 注册事件处理器,用于超连接事件。 textPane.addHyperlinkListener(this); // 注册事件处理器,用于处理属性改变事件。当页面加载结束时,触发该事件 textPane.addPropertyChangeListener(this); // 将HTML显示面板放入主窗口,居中显示 this.getContentPane().add(new JScrollPane(textPane),BorderLayout.CENTER); // 创建状态栏标签,并放在主窗口底部 messageLine = new JLabel(" "); this.getContentPane().add(messageLine, BorderLayout.SOUTH); // 初始化菜单和工具栏 this.initMenu(); this.initToolbar(); // 将当前打开窗口数增加1 WebBrowser.numBrowserWindows++; // 当关闭窗口时,调用close方法处理,采用匿名内部类的方式窗口添加监听 this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { close(); } }); } private void initMenu(){ // 文件菜单,下面有四个菜单项:新建、打开、关闭窗口、退出 JMenu fileMenu = new JMenu("文件"); JMenuItem newMenuItem = new JMenuItem("新建"); newMenuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { newBrowser(); } }); JMenuItem openMenuItem = new JMenuItem("打开"); openMenuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { openLocalPage(); } }); JMenuItem closeMenuItem = new JMenuItem("关闭窗口"); closeMenuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { close(); } }); JMenuItem exitMenuItem = new JMenuItem("退出"); // 当“退出”时退出应用程序 exitMenuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { exit(); } }); fileMenu.add(newMenuItem); fileMenu.add(openMenuItem); fileMenu.add(closeMenuItem); fileMenu.add(exitMenuItem); //帮助菜单,就一个菜单项:关于 JMenu helpMenu = new JMenu("帮助"); JMenuItem aboutMenuItem = new JMenuItem("关于"); helpMenu.add(aboutMenuItem); JMenuBar menuBar = new JMenuBar(); menuBar.add(fileMenu); menuBar.add(helpMenu); // 将菜单栏添加到主窗口 this.setJMenuBar(menuBar); } private void initToolbar(){ // 后退按钮,退到前一页面。初始情况下该按钮不可用 backButton = new JButton("后退"); backButton.setEnabled(false); //刚打开时按钮不可用 backButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { back(); } }); // 前进按钮,进到前一页面。初始情况下该按钮不可用 forwardButton = new JButton("前进"); forwardButton.setEnabled(false); forwardButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { forward(); } }); // 前进按钮,进到前一页面。 JButton refreshButton = new JButton("刷新"); refreshButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { reload(); } }); // 主页按钮,打开主页 JButton homeButton = new JButton("主页"); homeButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { home(); } }); JToolBar toolbar = new JToolBar(); toolbar.add(backButton); toolbar.add(forwardButton); toolbar.add(refreshButton); toolbar.add(homeButton); // 输入网址的文本框 urlField = new JTextField(); // 当用户输入网址、按下回车键时,触发该事件 urlField.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { displayPage(urlField.getText()); } }); // 地址标签 toolbar.add(new JLabel(" 地址:")); toolbar.add(urlField); // 将工具栏放在主窗口的北部 this.getContentPane().add(toolbar, BorderLayout.NORTH); } /** * 设置浏览器是否在所有窗口都关闭时退出 * @param b */ public static void setExitWhenLastWindowClosed(boolean b) { exitWhenLastWindowClosed = b; } public void setHome(String home) { this.home = home; } /** * 获取主页 */ public String getHome() { return home; } /** * FIXME 访问网址URL */ private boolean visit(URL url) { try { String href = url.toString(); // 启动动画,当网页被加载完成时,触发propertyChanged事件,动画停止。 startAnimation("加载 " + href + "..."); // 设置显示HTML面板的page属性为待访问的URL, // 在这个方法里,将打开URL,将URL的流显示在textPane中。 // 当完全打开后,该方法才结束。. /*String vNewReportFileName = "D:\\temp.html"; File f = new File(vNewReportFileName); FileWriter fw = new FileWriter(f, false); System.out.println(href); String html=new Httpcli().visit(href); fw.write(new String(html.getBytes("iso-8859-1"),"gbk")); fw.flush(); fw.close(); textPane.setPage(new URL("file:///"+vNewReportFileName)); */ textPane.setPage(url); // 页面打开后,将浏览器窗口的标题设为URL this.setTitle(href); // 网址输入框的内容也设置为URL urlField.setText(href); return true; } catch (IOException ex) { // 停止动画 stopAnimation(); // 状态栏中显示错误信息 messageLine.setText("不能打开页面:" + ex.getMessage()); return false; } } /** * 浏览器打开URL指定的页面,如果成功,将URL放入历史列表中 */ public void displayPage(URL url) { // 尝试访问页面 if (visit(url)) { // 如果成功,则将URL放入历史列表中。 history.add(url); int numentries = history.size(); if (numentries > MAX_HISTORY+10) { history = history.subList(numentries-MAX_HISTORY, numentries); numentries = MAX_HISTORY; } // 将当前页面下标置为numentries-1 currentHistoryPage = numentries - 1; // 如果当前页面不是第一个页面,则可以后退,允许点击后退按钮。 if (currentHistoryPage > 0){ backButton.setEnabled(true); } } } /** * 浏览器打开字符串指定的页面 * @param href 网址 */ public void displayPage(String href) { try { // 默认为HTTP协议 if (!href.startsWith("http://")){ href = "http://" + href; } displayPage(new URL(href)); } catch (MalformedURLException ex) { messageLine.setText("错误的网址: " + href); } } /** * 打开本地文件 */ public void openLocalPage() { // 使用“懒创建”模式,当需要时,才创建文件选择器。 if (fileChooser == null) { fileChooser = new JFileChooser(); // 使用文件过滤器限制只能够HTML和HTM文件 FileFilter filter = new FileFilter() { public boolean accept(File f) { String fn = f.getName(); return fn.endsWith(".html") || fn.endsWith(".htm"); } public String getDescription() { return "HTML Files"; } }; fileChooser.setFileFilter(filter); // 只允许选择HTML和HTM文件 fileChooser.addChoosableFileFilter(filter); } // 打开文件选择器 int result = fileChooser.showOpenDialog(this); // 如果确定打开文件,则在当前窗口中打开选择的文件 if (result == JFileChooser.APPROVE_OPTION) { File selectedFile = fileChooser.getSelectedFile( ); try { displayPage(selectedFile.toURI().toURL()); } catch (MalformedURLException e) { e.printStackTrace(); } } } /** * 后退,回到前一页 */ public void back() { if (currentHistoryPage > 0){ // 访问前一页 visit(history.get(--currentHistoryPage)); } // 如果当前页面下标大于0,允许后退 backButton.setEnabled((currentHistoryPage > 0)); // 如果当前页面下标不是最后,允许前进 forwardButton.setEnabled((currentHistoryPage < history.size()-1)); } /** * 前进,回到后一页 */ public void forward() { if (currentHistoryPage < history.size( )-1){ visit(history.get(++currentHistoryPage)); } // 如果当前页面下标大于0,允许后退 backButton.setEnabled((currentHistoryPage > 0)); // 如果当前页面下标不是最后,允许前进 forwardButton.setEnabled((currentHistoryPage < history.size()-1)); } public void reload() { if (currentHistoryPage != -1) { // 先显示为空白页 textPane.setDocument(new javax.swing.text.html.HTMLDocument()); // 再访问当前页 visit(history.get(currentHistoryPage)); } } public void home() { displayPage(getHome()); } public void newBrowser() { WebBrowser b = new WebBrowser(); // 新窗口大小和当前窗口一样大 b.setSize(this.getWidth(), this.getHeight()); b.setVisible(true); } /** * 关闭当前窗口,如果所有窗口都关闭,则根据exitWhenLastWindowClosed属性, * 判断是否退出应用程序 */ public void close() { // 先隐藏当前窗口,销毁窗口中的组件。 this.setVisible(false); this.dispose(); // 将当前打开窗口数减1。 // 如果所有窗口都已关闭,而且exitWhenLastWindowClosed为真,则退出 // 这里采用了synchronized关键字,保证线程安全 synchronized(WebBrowser.class) { WebBrowser.numBrowserWindows--; if ((numBrowserWindows==0) && exitWhenLastWindowClosed){ System.exit(0); } } } public void exit() { // 弹出对话框,请求确认,如果确认退出,则退出应用程序 if ((JOptionPane.showConfirmDialog(this, "你确定退出Web浏览器?", "退出", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION)){ System.exit(0); } } /** * 实现HyperlinkListener接口。处理超连接事件 */ public void hyperlinkUpdate(HyperlinkEvent e) { // 获取事件类型 HyperlinkEvent.EventType type = e.getEventType(); // 如果是点击了一个超连接,则显示被点击的连接 if (type == HyperlinkEvent.EventType.ACTIVATED) { displayPage(e.getURL()); } // 如果是鼠标移动到超连接上,则在状态栏中显示超连接的网址 else if (type == HyperlinkEvent.EventType.ENTERED) { messageLine.setText(e.getURL().toString()); } // 如果是鼠标离开了超连接,则状态栏显示为空 else if (type == HyperlinkEvent.EventType.EXITED) { messageLine.setText(" "); } } /** * 实现PropertyChangeListener接口。处理属性改变事件。 * 显示HTML面板textPane的属性改变时,由该方法处理。当textPane调用完setPage方法时,page属性便改变了。 */ public void propertyChange(PropertyChangeEvent e) { if (e.getPropertyName().equals("page")) { // 页面加载完毕时,textPane的page属性发生改变,此时停止动画。 stopAnimation(); } } // 动画消息,显示在最底下状态栏标签上,用于反馈浏览器的状态 String animationMessage; // 动画当前的帧的索引 int animationFrame = 0; // 动画所用到的帧,是一些字符。 String[] animationFrames = new String[] { "-", "\\", "|", "/", "-", "\\", "|", "/", ",", ".", "o", "0", "O", "#", "*", "+" }; //定时器 javax.swing.Timer animator = new javax.swing.Timer(125, new ActionListener() { public void actionPerformed(ActionEvent e) { animate(); } }); private void animate() { String frame = animationFrames[animationFrame++]; messageLine.setText(animationMessage + " " + frame); animationFrame = animationFrame % animationFrames.length; } private void startAnimation(String msg) { animationMessage = msg; animationFrame = 0; // 启动定时器 animator.start(); } private void stopAnimation() { // 停止定时器 animator.stop(); messageLine.setText(" "); } public static void main(String[] args) { // 设置浏览器,当所有浏览器窗口都被关闭时,退出应用程序 WebBrowser.setExitWhenLastWindowClosed(true); WebBrowser browser = new WebBrowser(); browser.setSize(800, 600); // 显示窗口 browser.setVisible(true); // 打开主页 browser.displayPage(browser.getHome()); } } ================================================ FILE: src/test/java/test/tree/NodePathSerialTest.java ================================================ package test.tree; import cn.ponfee.commons.collect.ImmutableArrayList; import cn.ponfee.commons.serial.JdkSerializer; import cn.ponfee.commons.serial.KryoSerializer; import cn.ponfee.commons.tree.NodePath; import com.google.common.collect.ImmutableList; import org.junit.Ignore; import org.junit.Test; /** * @author Ponfee */ public class NodePathSerialTest { /** * add method UnsupportedOperationException */ @Test @Ignore public void test1() { byte[] serialized1 = KryoSerializer.INSTANCE.serialize(new ImmutableArrayList<>(new Integer[]{1, 2, 3, 4})); System.out.println(KryoSerializer.INSTANCE.deserialize(serialized1, ImmutableArrayList.class)); byte[] serialized2 = KryoSerializer.INSTANCE.serialize(new NodePath<>(new Integer[]{1, 2, 3, 4})); System.out.println(KryoSerializer.INSTANCE.deserialize(serialized2, NodePath.class)); } @Test public void test2() { JdkSerializer jdkSerializer = new JdkSerializer(); byte[] serialized1 = jdkSerializer.serialize(new ImmutableArrayList<>(new Integer[]{1, 2, 3, 4})); System.out.println(jdkSerializer.deserialize(serialized1, ImmutableArrayList.class).toArray().getClass()); byte[] serialized2 = jdkSerializer.serialize(new NodePath<>(new Integer[]{1, 2, 3, 4})); System.out.println(jdkSerializer.deserialize(serialized2, NodePath.class).toArray().getClass()); } @Test public void test3() { JdkSerializer jdkSerializer = new JdkSerializer(); byte[] serialized1 = jdkSerializer.serialize(ImmutableList.of(1,2,3)); System.out.println(jdkSerializer.deserialize(serialized1, ImmutableList.class).getClass()); } } ================================================ FILE: src/test/java/test/tree/NodePathTest.java ================================================ package test.tree; import cn.ponfee.commons.base.tuple.Tuple2; import cn.ponfee.commons.collect.ImmutableArrayList; import cn.ponfee.commons.json.Jsons; import cn.ponfee.commons.tree.NodePath; import cn.ponfee.commons.tree.NodePath.FastjsonDeserializer; import cn.ponfee.commons.util.Asserts; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.annotation.JSONField; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.ListUtils; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; /** * @author Ponfee */ public class NodePathTest { @Test public void test0() { System.out.println(ImmutableList.of(new Integer[]{1,2}).get(0).getClass()); System.out.println(ImmutableArrayList.of(new Integer[]{1,2}).get(0).getClass()); for (Object o : Tuple2.of(1, 2)) { System.out.println(o); } System.out.println(); System.out.println(new NodePath<>(1, 2, 3, 4)); System.out.println(new NodePath<>(new Integer[]{1, 2, 3, 4}, 5)); System.out.println(new NodePath<>(Arrays.asList(1, 2, 3))); System.out.println(new NodePath<>(Arrays.asList(1, 2, 3), 4)); NodePath ids = new NodePath<>(1, 2, 3, 4); System.out.println(new NodePath<>(ids)); System.out.println(new NodePath<>(ids, 1)); } @Test public void test1() { System.out.println(CollectionUtils.isEqualCollection(Arrays.asList(1, 2, 3), Arrays.asList(3, 2, 1))); System.out.println(ListUtils.isEqualList(Arrays.asList(1, 2, 3), Arrays.asList(3, 2, 1))); System.out.println(ListUtils.isEqualList(Arrays.asList(1, 2, 3), Arrays.asList(1, 2, 3))); } @Test public void test2() { NodePath p1 = new NodePath<>(1, 2, 3, 4); NodePath p2 = new NodePath<>(1, 2, 3, 4); System.out.println(p1.equals(p2)); System.out.println(p1.compareTo(p2)); System.out.println("\n\n========================="); p1 = new NodePath<>(1, 2, 3, 4); p2 = new NodePath<>(1, 2); System.out.println(p1.equals(p2)); System.out.println(p1.compareTo(p2)); System.out.println("\n\n========================="); p1 = new NodePath<>(1, 2, 3, 4); p2 = new NodePath<>(4, 2); System.out.println(p1.equals(p2)); System.out.println(p1.compareTo(p2)); } @Test public void test3() { Map, Object> map = new HashMap<>(); map.put(new NodePath<>(1, 2, 3, 4), "xx"); Assert.assertNotNull(map.get(new NodePath<>(1, 2, 3, 4))); Assert.assertNull(map.get(new NodePath<>(1, 2, 3))); } @Test public void test4() { System.out.println("\n\n========================test4"); List list = Lists.newArrayList(1, 2, 3, 4); String json = Jsons.toJson(list); System.out.println(json); System.out.println(Jsons.fromJson(json, List.class).get(0).getClass()); System.out.println(JSON.parseArray(json).get(0).getClass()); } @Test public void test5() { System.out.println("\n\n========================test5"); NodePath ids = new NodePath<>(1, 2, 3, 4); String json = Jsons.toJson(ids); System.out.println(json); Asserts.isTrue(json.equals(JSON.toJSONString(ids))); System.out.println(Jsons.fromJson(json, NodePath.class).get(0).getClass()); System.out.println(JSON.parseObject(json, NodePath.class).get(0).getClass()); System.out.println(JSON.parseObject("[\"a\",\"b\"]", NodePath.class).get(0).getClass()); } // -------------------------------------------------------------- private static final String DATA = "{\"id\":123,\"path\":[1,2,3,4]}"; @Test public void test6() { System.out.println(Jsons.fromJson(DATA, NodePathBean1.class).getPath()); System.out.println(JSON.parseObject(DATA, NodePathBean1.class).getPath()); } @Test public void test7() { System.out.println(Jsons.fromJson(DATA, NodePathBean2.class).getPath()); System.out.println(JSON.parseObject(DATA, NodePathBean2.class).getPath()); } @Test public void test8() { System.out.println(Jsons.fromJson(DATA, NodePathBean3.class).getPath()); System.out.println(JSON.parseObject(DATA, NodePathBean3.class).getPath()); } // -----------------------------------------------------------ERROR @Test public void test10() { System.out.println(Jsons.fromJson(DATA, NodePathBean5.class).getPath().getClass()); // class java.util.ArrayList System.out.println(JSON.parseObject(DATA, NodePathBean5.class).getPath().getClass()); // class java.util.ArrayList } @Test public void test11() { System.out.println(Jsons.fromJson(DATA, NodePathBean6.class).getPath()); System.out.println(JSON.parseObject(DATA, NodePathBean6.class).getPath()); } @Test @Ignore public void test12() { System.out.println(Jsons.fromJson(DATA, NodePathBean4.class).getPath()); System.out.println(JSON.parseObject(DATA, NodePathBean4.class).getPath()); // add method UnsupportedOperationException } /** * add method UnsupportedOperationException */ @Test @Ignore public void test13() { System.out.println(Jsons.fromJson(DATA, NodePathBean7.class).getPath()); System.out.println(JSON.parseObject(DATA, NodePathBean7.class).getPath()); } /** * ERROR non-concrete Collection */ @Test @Ignore public void test14() { System.out.println(ImmutableList.of().getClass()); System.out.println(ImmutableList.of(1).getClass()); System.out.println(ImmutableList.of(1,2).getClass()); System.out.println(Jsons.fromJson("[1,2,3,4]", ImmutableList.class)); System.out.println(JSON.parseObject("[1,2,3,4]", ImmutableList.class)); } @SuppressWarnings("rawtypes") public static class NodePathBean1 implements java.io.Serializable { private static final long serialVersionUID = 1L; private int id; private NodePath path; public int getId() { return id; } public void setId(int id) { this.id = id; } public NodePath getPath() { return path; } public void setPath(NodePath path) { this.path = path; } } public static class NodePathBean2> implements java.io.Serializable { private static final long serialVersionUID = 1L; private int id; private NodePath path; // public int getId() { return id; } public void setId(int id) { this.id = id; } public NodePath getPath() { return path; } public void setPath(NodePath path) { this.path = path; } } public static class NodePathBean3 implements java.io.Serializable { private static final long serialVersionUID = 1L; private int id; @JSONField(deserializeUsing = FastjsonDeserializer.class) private NodePath path; public int getId() { return id; } public void setId(int id) { this.id = id; } public NodePath getPath() { return path; } public void setPath(NodePath path) { this.path = path; } } public static class NodePathBean4 implements java.io.Serializable { private static final long serialVersionUID = 1L; private int id; // FIXME ERROR // 当字段有泛型参数时的类型信息type为ParameterizedType,所以必须用JSONField注解, // 否则当成Collection来解析(此字段的类型不是NodePath,而是ParameterizedType) private NodePath path; public int getId() { return id; } public void setId(int id) { this.id = id; } public NodePath getPath() { return path; } public void setPath(NodePath path) { this.path = path; } } public static class NodePathBean5 implements java.io.Serializable { private static final long serialVersionUID = 1L; private int id; private List path; public int getId() { return id; } public void setId(int id) { this.id = id; } public List getPath() { return path; } public void setPath(List path) { this.path = path; } } public static class NodePathBean6 implements java.io.Serializable { private static final long serialVersionUID = 1L; private int id; private ArrayList path; public int getId() { return id; } public void setId(int id) { this.id = id; } public ArrayList getPath() { return path; } public void setPath(ArrayList path) { this.path = path; } } public static class NodePathBean7 implements java.io.Serializable { private static final long serialVersionUID = 1L; private int id; private ImmutableArrayList path; public int getId() { return id; } public void setId(int id) { this.id = id; } public ImmutableArrayList getPath() { return path; } public void setPath(ImmutableArrayList path) { this.path = path; } } } ================================================ FILE: src/test/java/test/tree/NodeTreeTest.java ================================================ package test.tree; import cn.ponfee.commons.json.Jsons; import cn.ponfee.commons.tree.*; import org.junit.Assert; import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.concurrent.ThreadLocalRandom; /** * * @author Ponfee */ public class NodeTreeTest { @Test public void test1() { List> list = new ArrayList<>(); list.add(new PlainNode<>("100000", null, true, "nid100000")); list.add(new PlainNode<>("100010", "100000", true, "nid100010")); list.add(new PlainNode<>("100011", "100010", false, "nid100011")); list.add(new PlainNode<>("100012", "100010", true, "nid100012")); list.add(new PlainNode<>("100020", "100000", false, "nid100020")); list.add(new PlainNode<>("100021", "100020", true, "nid100020")); list.add(new PlainNode<>("100022", "100020", false, "nid100022")); list.add(new PlainNode<>("200000", null, true, "nid200000")); list.add(new PlainNode<>("300000", null, true, "nid300000")); list.add(new PlainNode<>("400000", null, true, "nid400000")); // do mount first TreeNode subtree = TreeNode.builder("400010") .siblingNodesComparator(Comparator.comparing(node -> ThreadLocalRandom.current().nextInt(10))) .pid("400000").enabled(true).build(); // do mount second subtree.mount(Arrays.asList( new PlainNode<>("400011", "400010", true, "nid400011"), new PlainNode<>("400012", "400010", false, "nid400012") )); list.add(subtree); // add a tree node list.add(new PlainNode<>("500000", null, false, "nid500000")); list.add(new PlainNode<>("500010", "500000", true, "nid500010")); list.add(new PlainNode<>("500011", "500010", true, "nid500011")); // do mount third SiblingNodesComparator.comparing(e -> e.getChildren().size(), false, true).thenComparing(TreeNode::getNid, false, true).get(); TreeNode root = TreeNode.builder(TreeNode.DEFAULT_ROOT_ID) .siblingNodesComparator(SiblingNodesComparator.comparing(e -> e.getChildren().size(),false, false).thenComparing(TreeNode::getNid, false, false).get()) .build(); // do mount fourth root.mount(list); // mount System.out.println("sorted: " + Jsons.toJson(root)); System.out.println("dfs: " + Jsons.toJson(root.flatDFS())); System.out.println("cfs: " + Jsons.toJson(root.flatCFS())); System.out.println("convert-true: " + Jsons.toJson(root.convert(this::convert, true))); System.out.println("convert-false: " + Jsons.toJson(root.convert(this::convert, false))); } private MapTreeTrait convert(TreeNode node) { MapTreeTrait map = new MapTreeTrait<>(); map.put("nid", node.getNid()); map.put("pid", node.getPid()); map.put("attach", node.getAttach()); map.put("path", node.getPath()); map.put("enabled", node.isEnabled()); map.put("available", node.isAvailable()); return map; } @Test public void test2() { List> list = new ArrayList<>(); list.add(new PlainNode<>("a", "b", true, "")); // 节点循环依赖 list.add(new PlainNode<>("b", "a", true, "")); TreeNode root = TreeNode. builder(TreeNode.DEFAULT_ROOT_ID).build(); Assert.assertThrows(RuntimeException.class, ()->root.mount(list)) ; System.out.println(Jsons.toJson(root)); } @Test public void test3() { List> list = new ArrayList<>(); list.add(new PlainNode<>("100001", null, true, "nid100010")); Assert.assertThrows(IllegalArgumentException.class, () -> list.add(new PlainNode<>(null, "100001", true, "nid100010")));// 节点编号不能为空 TreeNode root = TreeNode. builder(TreeNode.DEFAULT_ROOT_ID).build(); root.mount(list); System.out.println(Jsons.toJson(root)); } @Test public void test4() { List> list = new ArrayList<>(); list.add(new PlainNode<>("100000", "notfound", true, "nid100000")); // 无效的孤儿节点 list.add(new PlainNode<>("200000", "notfound", true, "nid200000")); // 无效的孤儿节点 Assert.assertThrows(RuntimeException.class, () -> TreeNode.builder(TreeNode.DEFAULT_ROOT_ID).build().mount(list)); } @Test public void test5() { List> list = new ArrayList<>(); list.add(new PlainNode<>("100000", null, true, null)); list.add(new PlainNode<>("100010", "100000", true, null)); list.add(new PlainNode<>("100011", "100010", false, random())); list.add(new PlainNode<>("100012", "100010", true, random())); list.add(new PlainNode<>("100020", "100000", false, random())); list.add(new PlainNode<>("100021", "100020", true, random())); list.add(new PlainNode<>("100022", "100020", false, random())); list.add(new PlainNode<>("200000", null, true, random())); list.add(new PlainNode<>("300000", null, true, random())); list.add(new PlainNode<>("400000", null, true, random())); // do mount first TreeNode subtree = TreeNode. builder("400010").pid("400000").build(); // do mount second subtree.mount(Arrays.asList( new PlainNode<>("400011", "400010", true, "nid400011"), new PlainNode<>("400012", "400010", false, "nid400012") )); list.add(subtree); // add a tree node list.add(new PlainNode<>("500000", null, false, random())); list.add(new PlainNode<>("500010", "500000", true, random())); list.add(new PlainNode<>("500011", "500010", true, random())); // do mount third TreeNode root = TreeNode.builder(TreeNode.DEFAULT_ROOT_ID).build(); System.out.println(Jsons.toJson(root)); // do mount fouth root.mount(list); // mount System.out.println(Jsons.toJson(root)); System.out.println(Jsons.toJson(root.flatDFS())); System.out.println(Jsons.toJson(root.flatCFS())); } private String random() { String[] s = new String[] { "a", null, "b", null, "c", "d", null }; return s[ThreadLocalRandom.current().nextInt(s.length)]; } } ================================================ FILE: src/test/java/test/tree/TreeNodePrinterTest.java ================================================ package test.tree; import cn.ponfee.commons.collect.Collects; import cn.ponfee.commons.export.Thead; import cn.ponfee.commons.io.Files; import cn.ponfee.commons.json.Jsons; import cn.ponfee.commons.tree.*; import cn.ponfee.commons.tree.print.BinaryTreePrinter; import cn.ponfee.commons.tree.print.BinaryTreePrinterBuilder; import cn.ponfee.commons.util.MavenProjects; import org.apache.commons.collections4.CollectionUtils; import org.junit.Test; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Optional; import java.util.concurrent.ThreadLocalRandom; /** * * @author Ponfee */ public class TreeNodePrinterTest { @Test public void test1() throws IOException { List> list = new ArrayList<>(); list.add(new PlainNode<>("100000", null, true, "nid100000")); list.add(new PlainNode<>("100010", "100000", true, "nid100010")); list.add(new PlainNode<>("100011", "100010", false, "nid100011")); list.add(new PlainNode<>("100012", "100010", true, "nid100012")); list.add(new PlainNode<>("100020", "100000", false, "nid100020")); list.add(new PlainNode<>("100021", "100020", true, "nid100020")); list.add(new PlainNode<>("100022", "100020", false, "nid100022")); list.add(new PlainNode<>("200000", null, true, "nid200000")); list.add(new PlainNode<>("300000", null, true, "nid300000")); list.add(new PlainNode<>("400000", null, true, "nid400000")); // do mount first TreeNode subtree = TreeNode.builder("400010") .siblingNodesComparator(Comparator.comparing(node -> ThreadLocalRandom.current().nextInt(10))) .pid("400000") .enabled(true) .build(); // do mount second subtree.mount(Arrays.asList( new PlainNode<>("400011", "400010", true, "nid400011"), new PlainNode<>("400012", "400010", false, "nid400012") )); list.add(subtree); // add a tree node list.add(new PlainNode<>("500000", null, false, "nid500000")); list.add(new PlainNode<>("500010", "500000", true, "nid500010")); list.add(new PlainNode<>("500011", "500010", true, "nid500011")); // do mount third TreeNode root = TreeNode. builder(TreeNode.DEFAULT_ROOT_ID).build(); System.out.println("unmount: "+Jsons.toJson(root)); // do mount fouth root.mount(list); // mount System.out.println("mounted: "+Jsons.toJson(root)); System.out.println("dfs: "+Jsons.toJson(root.flatDFS())); System.out.println("cfs: "+Jsons.toJson(root.flatCFS())); System.out.println("bfs: "+Jsons.toJson(root.flatBFS())); System.out.println("convert-true: " + Jsons.toJson(root.convert(this::convert, true))); System.out.println("convert-false: " + Jsons.toJson(root.convert(this::convert, false))); System.out.println("\n\n-----------------\n\n"); System.out.println(root.print(TreeNode::getNid)); } @Test public void test2() throws IOException { List> list = new ArrayList<>(); list.add(new PlainNode<>(1, 0, new Thead("区域"))); list.add(new PlainNode<>(2, 0, new Thead("分公司"))); list.add(new PlainNode<>(3, 0, new Thead("昨天"))); list.add(new PlainNode<>(4, 3, new Thead("项目数"))); list.add(new PlainNode<>(5, 3, new Thead("项目应收(元)"))); list.add(new PlainNode<>(6, 3, new Thead("成交套数"))); list.add(new PlainNode<>(7, 3, new Thead("套均收入(元)"))); list.add(new PlainNode<>(8, 3, new Thead("团购项目数"))); list.add(new PlainNode<>(9, 3, new Thead("导客项目数"))); list.add(new PlainNode<>(10, 3, new Thead("代收项目数"))); list.add(new PlainNode<>(11, 3, new Thead("线上项目数"))); list.add(new PlainNode<>(12, 0, new Thead("本月"))); list.add(new PlainNode<>(13, 12,new Thead("应收(万)"))); list.add(new PlainNode<>(14, 12,new Thead("实收(万)"))); list.add(new PlainNode<>(15, 12,new Thead("成交套数"))); list.add(new PlainNode<>(16, 12,new Thead("套均收入(元)"))); list.add(new PlainNode<>(17, 12,new Thead("团购项目应收(万)"))); list.add(new PlainNode<>(18, 12,new Thead("团购项目成交套数"))); list.add(new PlainNode<>(19, 12,new Thead("团购项目经服成交套数"))); list.add(new PlainNode<>(20, 12,new Thead("团购项目套均收入(元)"))); list.add(new PlainNode<>(21, 12,new Thead("团购项目经服成交应收(万)"))); list.add(new PlainNode<>(22, 12,new Thead("团购项目中介应付外佣(万)"))); list.add(new PlainNode<>(23, 12,new Thead("团购项目经服成交套数占比"))); list.add(new PlainNode<>(24, 12,new Thead("团购项目中介分佣比例"))); list.add(new PlainNode<>(25, 12,new Thead("导客项目应收(万)"))); list.add(new PlainNode<>(26, 12,new Thead("导客项目成交套数"))); list.add(new PlainNode<>(27, 12,new Thead("导客项目套均收入(元)"))); list.add(new PlainNode<>(28, 12,new Thead("导客项目中介应付外佣(万)"))); list.add(new PlainNode<>(29, 12,new Thead("导客项目中介分佣比例"))); list.add(new PlainNode<>(30, 12,new Thead("代收项目应收(万)"))); list.add(new PlainNode<>(31, 12,new Thead("代收项目成交套数"))); list.add(new PlainNode<>(32, 12,new Thead("线上项目应收(万)"))); list.add(new PlainNode<>(33, 12,new Thead("线上项目成交套数"))); list.add(new PlainNode<>(34, 12,new Thead("月指标(万)"))); list.add(new PlainNode<>(35, 12,new Thead("指标完成率"))); System.out.println(TreeNode.builder(0).build().mount(list).print(e -> Optional.ofNullable(e.getAttach()).map(Thead::getName).orElse(null))); } @Test public void test3() throws IOException { System.out.println("\n\n\n"); System.out.println( Files.listFiles(MavenProjects.getProjectBaseDir()) .print(e -> e.getSiblingOrdinal() + ":" + e.getChildrenCount() + ":" + e.getAttach().getName()) ); } @Test public void test4() throws IOException { System.out.println("\n\n\n"); TreeNode files = Files.listFiles(MavenProjects.getProjectBaseDir()); BinaryTreePrinter printer = new BinaryTreePrinterBuilder>( e -> e.getAttach().getName(), e -> CollectionUtils.isEmpty(e.getChildren()) ? null : Collects.getFirst(e.getChildren()), e -> CollectionUtils.isEmpty(e.getChildren()) ? null :Collects.getLast(e.getChildren()) ) //.branch(BinaryTreePrinter.Branch.TRIANGLE) .directed(false) .build(); printer.print(files.getChildren(), 1000); //printer.print(files.getChildren().get(5)); } @Test public void test5() throws IOException { System.out.println(Files.tree(MavenProjects.getMainJavaPath(""))); } private MapTreeTrait convert(TreeNode node) { MapTreeTrait map = new MapTreeTrait<>(); map.put("nid", node.getNid()); map.put("pid", node.getPid()); map.put("attach", node.getAttach()); map.put("path", node.getPath()); map.put("enabled", node.isEnabled()); map.put("available", node.isAvailable()); return map; } } ================================================ FILE: src/test/java/test/utils/AtomicStampedReferenceTest.java ================================================ package test.utils; import java.util.concurrent.atomic.AtomicStampedReference; // ABA public class AtomicStampedReferenceTest { static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(0, 0); public static void main(String[] args) throws InterruptedException { final int stamp = atomicStampedReference.getStamp(); final Integer reference = atomicStampedReference.getReference(); System.out.println(reference+"============"+stamp); Thread t1 = new Thread(new Runnable() { @Override public void run() { System.out.println(reference + "-" + stamp + "-" + atomicStampedReference.compareAndSet(reference, reference + 10, stamp, stamp + 1)); System.out.println(reference + "-" + stamp + "-" + atomicStampedReference.compareAndSet(reference+10, reference, stamp+1, stamp + 2)); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { Integer reference = atomicStampedReference.getReference(); System.out.println(reference + "-" + stamp + "-" + atomicStampedReference.compareAndSet(reference, reference + 10, stamp, stamp + 1)); } }); t1.start(); t1.join(); t2.start(); t2.join(); System.out.println(atomicStampedReference.getReference()); System.out.println(atomicStampedReference.getStamp()); } } ================================================ FILE: src/test/java/test/utils/Base58Test.java ================================================ package test.utils; import java.math.BigInteger; import java.util.Arrays; import org.junit.Test; import cn.ponfee.commons.util.Base58; import junit.framework.TestCase; /** * https://blog.csdn.net/qq_41185868/article/details/80806532 * * @author Ponfee */ public class Base58Test extends TestCase { @Test public void testEncode() { byte[] testbytes = "Hello World".getBytes(); assertEquals("JxF12TrwUP45BMd", Base58.encode(testbytes)); BigInteger bi = BigInteger.valueOf(3471844090L); assertEquals("16Ho7Hs", Base58.encode(bi.toByteArray())); byte[] zeroBytes1 = new byte[1]; assertEquals("1", Base58.encode(zeroBytes1)); byte[] zeroBytes7 = new byte[7]; assertEquals("1111111", Base58.encode(zeroBytes7)); // test empty encode assertEquals("", Base58.encode(new byte[0])); } @Test public void testDecode() { byte[] testbytes = "Hello World".getBytes(); byte[] actualbytes = Base58.decode("JxF12TrwUP45BMd"); assertTrue(new String(actualbytes), Arrays.equals(testbytes, actualbytes)); assertTrue("1", Arrays.equals(Base58.decode("1"), new byte[1])); assertTrue("1111", Arrays.equals(Base58.decode("1111"), new byte[4])); try { Base58.decode("This isn't valid base58"); fail(); } catch (Exception e) { // expected } Base58.decodeChecked("4stwEBjT6FYyVV"); // Checksum should fail. try { Base58.decodeChecked("4stwEBjT6FYyVW"); fail(); } catch (Exception e) { // expected } // Input is too short. try { Base58.decodeChecked("4s"); fail(); } catch (Exception e) { // expected } // Test decode of empty String. assertEquals(0, Base58.decode("").length); // Now check we can correctly decode the case where the high bit of the first byte is not zero, so BigInteger // sign extends. Fix for a bug that stopped us parsing keys exported using sipas patch. Base58.decodeChecked("93VYUMzRG9DdbRP72uQXjaWibbQwygnvaCu9DumcqDjGybD864T"); Base58.decodeChecked("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"); Base58.decodeChecked("1XPTgDRhN8RFnzniWCddobD9iKZatrvH4"); Base58.decodeChecked("14rE7Jqy4a6P27qWCCsngkUfBxtevZhPHB"); } @Test public void testDecodeToBigInteger() { byte[] input = Base58.decode("129"); assertEquals(new BigInteger(1, input), Base58.decodeToBigInteger("129")); } } ================================================ FILE: src/test/java/test/utils/Base64.java ================================================ package test.utils; import java.io.UnsupportedEncodingException; import java.util.Arrays; /** *

    * Encodes and decodes to and from Base64 notation. *

    *

    * I am placing this code in the Public Domain. Do with it as you will. This * software comes with no guarantees or warranties but with plenty of * well-wishing instead! Please visit http://iharder.net/base64 periodically * to check for updates or to contribute improvements. *

    * * @author Robert Harder * @author rob@iharder.net * @version 2.3.7 */ public final class Base64 { /** The equals sign (=) as a byte. */ private static final byte EQUALS_SIGN = (byte) '='; /** Preferred encoding. */ private static final String PREFERRED_ENCODING = "US-ASCII"; /** The 64 valid Base64 values. */ private static final byte[] STANDARD_ALPHABET = { (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) '+', (byte) '/' }; /** Defeats instantiation. */ private Base64() {} /** *

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

    *

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

    * * @param source * the array to convert * @param srcOffset * the index where conversion begins * @param numSigBytes * the number of significant bytes in your array * @param destination * the array to hold the conversion * @param destOffset * the index where output will be put * @return the destination array */ private static byte[] encode3to4(byte[] source, int srcOffset, int numSigBytes, byte[] destination, int destOffset) { int inBuff = (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0) | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0) | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0); switch (numSigBytes) { case 3: destination[destOffset] = STANDARD_ALPHABET[(inBuff >>> 18)]; destination[destOffset + 1] = STANDARD_ALPHABET[(inBuff >>> 12) & 0x3f]; destination[destOffset + 2] = STANDARD_ALPHABET[(inBuff >>> 6) & 0x3f]; destination[destOffset + 3] = STANDARD_ALPHABET[(inBuff) & 0x3f]; return destination; case 2: destination[destOffset] = STANDARD_ALPHABET[(inBuff >>> 18)]; destination[destOffset + 1] = STANDARD_ALPHABET[(inBuff >>> 12) & 0x3f]; destination[destOffset + 2] = STANDARD_ALPHABET[(inBuff >>> 6) & 0x3f]; destination[destOffset + 3] = EQUALS_SIGN; return destination; case 1: destination[destOffset] = STANDARD_ALPHABET[(inBuff >>> 18)]; destination[destOffset + 1] = STANDARD_ALPHABET[(inBuff >>> 12) & 0x3f]; destination[destOffset + 2] = EQUALS_SIGN; destination[destOffset + 3] = EQUALS_SIGN; return destination; default: return destination; } } /** * Encode string as a byte array in Base64 annotation. * * @param string * @return The Base64-encoded data as a string */ public static String encode(String string) { byte[] bytes; try { bytes = string.getBytes(PREFERRED_ENCODING); } catch (UnsupportedEncodingException e) { bytes = string.getBytes(); } return encodeBytes(bytes); } /** * Encodes a byte array into Base64 notation. * * @param source * The data to convert * @return The Base64-encoded data as a String * @throws NullPointerException * if source array is null * @throws IllegalArgumentException * if source array, offset, or length are invalid * @since 2.0 */ public static String encodeBytes(byte[] source) { return encodeBytes(source, 0, source.length); } /** * Encodes a byte array into Base64 notation. * * @param source * The data to convert * @param off * Offset in array where conversion should begin * @param len * Length of data to convert * @return The Base64-encoded data as a String * @throws NullPointerException * if source array is null * @throws IllegalArgumentException * if source array, offset, or length are invalid * @since 2.0 */ public static String encodeBytes(byte[] source, int off, int len) { byte[] encoded = encodeBytesToBytes(source, off, len); try { return new String(encoded, PREFERRED_ENCODING); } catch (UnsupportedEncodingException uue) { return new String(encoded); } } /** * Similar to {@link #encodeBytes(byte[], int, int)} but returns a byte * array instead of instantiating a String. This is more efficient if you're * working with I/O streams and have large data sets to encode. * * * @param source * The data to convert * @param off * Offset in array where conversion should begin * @param len * Length of data to convert * @return The Base64-encoded data as a String if there is an error * @throws NullPointerException * if source array is null * @throws IllegalArgumentException * if source array, offset, or length are invalid * @since 2.3.1 */ public static byte[] encodeBytesToBytes(byte[] source, int off, int len) { if (source == null) { throw new NullPointerException("Cannot serialize a null array."); } if (off < 0) { throw new IllegalArgumentException("Cannot have negative offset: " + off); } if (len < 0) { throw new IllegalArgumentException("Cannot have length offset: " + len); } if (off + len > source.length) { throw new IllegalArgumentException(String.format("Cannot have offset of %d and length of %d with " + "array of length %d", off, len, source.length)); } // Bytes needed for actual encoding int encLen = ((len / 3) << 2) + (len % 3 > 0 ? 4 : 0); byte[] outBuff = new byte[encLen]; int d = 0; int e = 0; int len2 = len - 2; for (; d < len2; d += 3, e += 4) { encode3to4(source, d + off, 3, outBuff, e); } if (d < len) { encode3to4(source, d + off, len - d, outBuff, e); e += 4; } if (e <= outBuff.length - 1) { return Arrays.copyOf(outBuff, e); } else { return outBuff; } } } ================================================ FILE: src/test/java/test/utils/BytesTest.java ================================================ package test.utils; import cn.ponfee.commons.util.Bytes; import cn.ponfee.commons.util.ObjectUtils; import cn.ponfee.commons.util.SecureRandoms; import cn.ponfee.commons.util.UuidUtils; import com.google.common.base.Stopwatch; import org.apache.commons.codec.binary.Hex; import org.junit.Assert; import org.junit.Test; import java.nio.ByteBuffer; import java.util.Base64; import java.util.concurrent.ThreadLocalRandom; public class BytesTest { @Test public void test1() { int n = 999999; byte[] data = SecureRandoms.nextBytes(ThreadLocalRandom.current().nextInt(9999)); Stopwatch watch = Stopwatch.createStarted(); for (int i = 0; i < n; i++) { Base64.getEncoder().encodeToString(data); } System.out.println("Base64.getEncoder().encodeToString " + watch.stop()); watch.reset().start(); for (int i = 0; i < n; i++) { test.utils.Base64.encodeBytes(data); } System.out.println("test.utils.Base64.encodeBytes " + watch.stop()); watch.reset().start(); for (int i = 0; i < n; i++) { Bytes.encodeHex(data); } System.out.println("hexEncode " + watch.stop()); watch.reset().start(); for (int i = 0; i < n; i++) { Hex.encodeHexString(data); } System.out.println("Hex.encodeHexString " + watch.stop()); } @Test public void test2() { for (int i = 0; i < 10000; i++) { byte b = (byte) SecureRandoms.nextInt(); if (!Integer.toBinaryString((b & 0xFF) + 0x100).equals(Integer.toBinaryString(b & 0xFF | 0x100))) { System.err.println(i+"fail"); } } } @Test public void test3() { for (int i = 0; i < 100000; i++) { byte[] data = SecureRandoms.nextBytes(ThreadLocalRandom.current().nextInt(999)); String hex = Hex.encodeHexString(data); Assert.assertArrayEquals(data, Bytes.decodeHex(hex)); } } @Test public void test4() { byte[] uuid = UuidUtils.uuid(); long a = System.nanoTime(); long b = Thread.currentThread().getId(); long c = System.identityHashCode(SecureRandoms.class); ByteBuffer buffer = ByteBuffer.allocate(40); buffer.put(uuid); buffer.putLong(a); buffer.putLong(b); buffer.putLong(c); buffer.flip(); Assert.assertEquals(Bytes.encodeHex(buffer.array()), Bytes.encodeHex(uuid) + Bytes.encodeHex(Bytes.toBytes(a)) + Bytes.encodeHex(Bytes.toBytes(b)) + Bytes.encodeHex(Bytes.toBytes(c))); } } ================================================ FILE: src/test/java/test/utils/FloatContent.java ================================================ package test.utils; import java.nio.ByteBuffer; import java.nio.ByteOrder; import cn.ponfee.commons.util.Bytes; /** * http://blog.csdn.net/yezhubenyue/article/details/7436624 * http://blog.csdn.net/xieyihua1994/article/details/51659379 * http://blog.csdn.net/gaoshuang5678/article/details/50554131 * https://blog.csdn.net/K346K346/article/details/50487127 * * 规格化的浮点数是上述的第一种情况,对于单精度来说,也就是阶码位不为0且不为255的这种情况. * * * 整数部分除2取余,直到商为0停止,从最后的余数读起,一直到最前面的余数 * 小数部分乘2取整,然后从前往后读 * * Float: * S:sign符号, * E:exponent指数(0~2^8-1=255) * e:移码(负数左移,正数右移),e=E-127 * M:mantissa尾数(0~2^23-1=8388607) * * SEEE EEEE E[1]MMM MMMM MMMM MMMM MMMM MMMM * * 1100 0001 0[1]100 1000 0000 0000 0000 0000 // -12.5 * S: 1 * E: 100 0001 0 // 130 * e: E-127 = 130-127 = 3 * M: [1]100 1000 0000 0000 0000 0000 // 即: 1.100 1000 0000 0000 0000 0000 * * 移码:如果指数e为负数,底数的小数点向左移,如果指数e为正数,底数的小数点向右移 * M(1.100 1000 0000 0000 0000 0000)向右移3位 * --> 1100. 1000 0000 0000 0000 0000 * * 小数点左边的1100 表示为 (1 × 2^3) + (1 × 2^2) + (0 × 2^1) + (0 × 2^0), 其结果为 12 * 小数点右边的 .100… 表示为 (1 × 2^-1) + (0 × 2^-2) + (0 × 2^-3) + ... ,其结果为.5 * 以上二值的和为12.5, 由于S 为1,使用为负数,即-12.5 。 * * * 将浮点数转成二进制:0.5->0.1, 0.25->0.01, 0.125->0.001, 0.0625->0.0001 * 小数的二进制算法和整数的大致相反,就是不断的拿小数部分乘以2取积的整数部分,然后正序排列,比如求0.9的二进制: * 0.9*2=1.8 取 1 * 0.8*2=1.6 取 1 * 0.6*2=1.2 取 1 * 0.2*2=0.4 取 0 * 0.4*2=0.8 取 0 * 0.8*2=1.6 取 1 * * 17.625 * 转二进制:1 0001.101 * 偏移:右移N位,直到小数点前只剩一位:1.0001 101(即右移4位),M=1.0001 101 * 则e=4,即E=127+e=131 ==> E=10000011 * S=0 * M省略小数点前面的1(向左或向右移后,最左边的数总是1) * 则其二进制为:0 10000011 000 1101 0000 0000 0000 0000 * * 指数全为0(E=0): * 1、尾数全0:则这个数的真值为±0(正负号和数符位有关) * 2、尾数不全为0:表明当前的float数是一个非规格化的数 * (小于2^-127,所以float的最小值指数位为0000 0001=1-127=-126,尾数最后一位为1,即00000...0001) * * 指数全为1(E=255): * 1、尾数全0:值为±∞,+∞:0 11111111 000 0000 0000 0000 0000。-∞:1 11111111 000 0000 0000 0000 0000 * 2、尾数不全为0:表明为NaN(所以float的最大值的指数位为1111 1110=254-127=127,尾数位全为1) * * Double: * SEEE EEEE EEEE [1]MMMM MMMM MMMM MMMM MMMM MMMM MMMM MMMM MMMM MMMM MMMM MMMM MMMM * 1+11+52 * e = E-1023 * * * 0 00000000000000000000000000000000 * Float.MIN_VALUE 00000000000000000000000000000001 // 非规格化的最小值 * Float.MIN_NORMAL 00000000100000000000000000000000 // 规格化的最小值 * Float.MAX_VALUE 01111111011111111111111111111111 * Float.NaN 01111111110000000000000000000000 * Float.NEGATIVE_INFINITY 11111111100000000000000000000000 * Float.POSITIVE_INFINITY 01111111100000000000000000000000 * * @author Ponfee */ public class FloatContent { public static void main(String[] args) { System.out.println(Bytes.toBinary(Bytes.toBytes(Float.MAX_VALUE))); System.out.println(Bytes.toBinary(Bytes.toBytes(Double.MAX_VALUE))); System.out.println(Bytes.toFloat(new byte[] {127, (byte)0xff, (byte)0xff, (byte)0xff})); System.out.println(Bytes.toBinary(Bytes.toBytes(-12.5f))); System.out.println(Bytes.toBinary(ByteBuffer.allocate(4).putFloat(-12.5f).array())); System.out.println(); System.out.println(Bytes.toBinary(Bytes.toBytes(17.625f))); System.out.println(Bytes.toBinary(ByteBuffer.allocate(4).putFloat(17.625f).array())); System.out.println(); System.out.println(Bytes.toBinary(Bytes.toBytes(6.9f))); System.out.println(Bytes.toBinary(Bytes.toBytes(0.9f))); System.out.println(); System.out.println(ByteOrder.nativeOrder()); System.out.println(Bytes.toFloat(new byte[] { 0, 0, 0, 127, 0, 0, 0, 0 })); System.out.println(0x1.0p-3f); // 0x1.0*power(2,-3) System.out.println(0x0.9p-3f); // 0x0.5*power(2,-3) } } ================================================ FILE: src/test/java/test/utils/GuavaCacheRefreshTest.java ================================================ package test.utils; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; import com.google.common.base.Preconditions; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; public class GuavaCacheRefreshTest { public class SkuCache { private String skuId; private String skuCode; private Long realQuantity; public String getSkuId() { return skuId; } public void setSkuId(String skuId) { this.skuId = skuId; } public String getSkuCode() { return skuCode; } public void setSkuCode(String skuCode) { this.skuCode = skuCode; } public Long getRealQuantity() { return realQuantity; } public void setRealQuantity(Long realQuantity) { this.realQuantity = realQuantity; } } AtomicInteger loadTimes = new AtomicInteger(0); AtomicInteger count = new AtomicInteger(0); @Test public void testCacheUse() throws Exception { LoadingCache loadingCache = CacheBuilder.newBuilder() .refreshAfterWrite(1000, TimeUnit.MILLISECONDS) //Prevent data reloading from failing, but the value of memory remains the same .expireAfterWrite(1500, TimeUnit.MILLISECONDS) .build(new CacheLoader() { @Override public SkuCache load(String key) { SkuCache skuCache = new SkuCache(); skuCache.setSkuCode(key + "---" + (loadTimes.incrementAndGet())); skuCache.setSkuId(key); skuCache.setRealQuantity(100L); System.out.println("load..." + key); return skuCache; } @Override public ListenableFuture reload(String key, SkuCache oldValue) throws Exception { Preconditions.checkNotNull(key); Preconditions.checkNotNull(oldValue); System.out.println("reload..."); //Simulate time consuming operation // Thread.sleep(1000); return Futures.immediateFuture(load(key)); } }); for (int i = 0; i < 1000; i++) { new Thread(() -> { try { getValue(loadingCache); } catch (Exception e) { e.printStackTrace(); } }).start(); } System.in.read(); System.out.println("finish"); } private void getValue(LoadingCache loadingCache) throws Exception { for (int i = 0; i < 10; i++) { Thread.sleep(300l); System.out.println(loadingCache.get("sku").toString() + " - " + count.incrementAndGet()); } } } ================================================ FILE: src/test/java/test/utils/GuavaCacheTest.java ================================================ package test.utils; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import org.junit.Test; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; public class GuavaCacheTest { @Test public void create1() throws Exception { LoadingCache cache = CacheBuilder.newBuilder() .maximumSize(1000) .weakValues() .weakKeys() .softValues() .expireAfterAccess(30L, TimeUnit.SECONDS) .build(new CacheLoader() { public @Override Long load(String key) { return -1L; // 缓存未命中,会调用此方法加载(不能为null) } } ); cache.put("abc", 123L); System.out.println(cache.get("abc")); System.out.println(cache.get("def")); System.out.println(cache.getUnchecked("123")); } @Test public void create2() throws Exception { Cache cache = CacheBuilder.newBuilder() .maximumSize(1000) .build(); // look Ma, no CacheLoader // If the key wasn't in the "easy to compute" group, we need to // do things the hard way. Long result = cache.get("123", new Callable() { public @Override Long call() { return -1L; // 缓存未命中,会调用此方法加载 } }); System.out.println(result); System.out.println(cache.getIfPresent("456")); } } ================================================ FILE: src/test/java/test/utils/Java8DateTimeTester.java ================================================ package test.utils; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.format.DateTimeFormatter; public class Java8DateTimeTester { private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); @org.junit.Test public void testFormat() { System.out.println(DATE_FORMATTER.format(LocalDateTime.now())); //System.out.println(DATE_FORMATTER.format(Instant.now())); // error System.out.println(LocalDateTime.now().format(DATE_FORMATTER)); System.out.println(DateTimeFormatter.ISO_INSTANT.format(Instant.now())); } @org.junit.Test public void testLocalDate() { System.out.println(LocalDateTime.now()); System.out.println(LocalDateTime.now().toLocalDate()); System.out.println(LocalDateTime.now().toLocalTime()); System.out.println(LocalDate.now()); System.out.println(LocalDate.now().atStartOfDay()); System.out.println(LocalTime.now()); } } ================================================ FILE: src/test/java/test/utils/MapToObjTest.java ================================================ package test.utils; import com.google.common.base.CaseFormat; import com.google.common.collect.ImmutableMap; import cn.ponfee.commons.jce.DigestAlgorithms; import cn.ponfee.commons.reflect.BeanMaps; public class MapToObjTest { public static class A { private int a_b; private String str; private DigestAlgorithms mode; public A() {} public A(int a_b, String str) { super(); this.a_b = a_b; this.str = str; } public int getA_b() { return a_b; } public void setA_b(int a_b) { this.a_b = a_b; } public String getStr() { return str; } public void setStr(String str) { this.str = str; } public DigestAlgorithms getMode() { return mode; } public void setMode(DigestAlgorithms mode) { this.mode = mode; } @Override public String toString() { return "A [a_b=" + a_b + ", str=" + str + ", mode=" +( mode == null ? "null" : mode.name()) + "]"; } } public static void main(String[] args) { System.out.println(CaseFormat.LOWER_HYPHEN.to(CaseFormat.LOWER_CAMEL, "test-data"));//testData System.out.println(CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, "test_data"));//testData System.out.println(CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, "test_data"));//TestData System.out.println(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, "testdata"));//testdata System.out.println(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, "TestData"));//test_data System.out.println(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, "testData"));//test_data System.out.println(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, "testData"));//test-data A a = new A(1, "aaa"); //Map map = ObjectUtils.bean2map(a); //System.out.println(map); //a = ObjectUtils.map2bean(map, A.class); //System.out.println(a); a = BeanMaps.PROPS.toBean(ImmutableMap.of("aB", 123, "str", "abc", "mode", "RipeMD128"), A.class); System.out.println(a); System.out.println(BeanMaps.PROPS.toMap(a)); } } ================================================ FILE: src/test/java/test/utils/NumbersTest.java ================================================ package test.utils; import org.junit.Test; import java.util.Arrays; import java.util.stream.LongStream; /** * @author ponfee.fu */ public class NumbersTest { @Test public void testSplit() { long[] oldBillsPaid = {543L, -560L, 20L, 3200L, 20L, 0L}; long[] oldBillsAmt = {100L, 3200L, 100L, -10000L}; long total = LongStream.of(oldBillsPaid).sum(); long[] split = split(oldBillsAmt, total); System.out.println("total: " + total); System.out.println("bills: " + Arrays.toString(oldBillsAmt)); System.out.println("paid: " + Arrays.toString(split)); } public static long[] split(long[] bills, long value) { long total = LongStream.of(bills).map(Math::abs).sum(); long[] result = new long[bills.length]; if (bills.length == 0 || value == 0) { return result; } double rate; int i = 0, n = bills.length - 1; for (; i < n; i++) { // rate <= 1.0 rate = value / (double) total; // 因为result[i]是ceil后的结果,所以按比率上来算value减得会更多,即rate只会递减,所以不会出现溢出(后面的费用项不够抵扣)的情况 result[i] = Math.min((int) Math.ceil(bills[i] * rate), value); value -= result[i]; total -= bills[i]; if (value == 0) { break; } } // the last bill item if (i == n) { result[i] = value; } return result; } } ================================================ FILE: src/test/java/test/utils/ObjectUtilsTest.java ================================================ package test.utils; import java.util.UUID; import org.junit.Assert; import org.junit.Test; import cn.ponfee.commons.util.Bytes; public class ObjectUtilsTest { static int n = 100000000; UUID uuid = UUID.randomUUID(); long most = uuid.getMostSignificantBits(), least = uuid.getLeastSignificantBits(); @Test public void test1() { char[] chars = new char[32]; Bytes.encodeHex(chars, 0, (byte) (most >>> 56)); Bytes.encodeHex(chars, 2, (byte) (most >>> 48)); Bytes.encodeHex(chars, 4, (byte) (most >>> 40)); Bytes.encodeHex(chars, 6, (byte) (most >>> 32)); Bytes.encodeHex(chars, 8, (byte) (most >>> 24)); Bytes.encodeHex(chars, 10, (byte) (most >>> 16)); Bytes.encodeHex(chars, 12, (byte) (most >>> 8)); Bytes.encodeHex(chars, 14, (byte) (most)); Bytes.encodeHex(chars, 16, (byte) (least >>> 56)); Bytes.encodeHex(chars, 18, (byte) (least >>> 48)); Bytes.encodeHex(chars, 20, (byte) (least >>> 40)); Bytes.encodeHex(chars, 22, (byte) (least >>> 32)); Bytes.encodeHex(chars, 24, (byte) (least >>> 24)); Bytes.encodeHex(chars, 26, (byte) (least >>> 16)); Bytes.encodeHex(chars, 28, (byte) (least >>> 8)); Bytes.encodeHex(chars, 30, (byte) (least)); String s1 = new String(chars); System.out.println(s1); byte[] bytes = new byte[] { (byte) (most >>> 56), (byte) (most >>> 48), (byte) (most >>> 40), (byte) (most >>> 32), (byte) (most >>> 24), (byte) (most >>> 16), (byte) (most >>> 8), (byte) (most), (byte) (least >>> 56), (byte) (least >>> 48), (byte) (least >>> 40), (byte) (least >>> 32), (byte) (least >>> 24), (byte) (least >>> 16), (byte) (least >>> 8), (byte) (least) }; String s2 = Bytes.encodeHex(bytes); System.out.println(s2); Assert.assertEquals(s1, s2); } @Test public void test2() { for (int i= 0; i < n; i++) { char[] chars = new char[32]; Bytes.encodeHex(chars, 0, (byte) (most >>> 56)); Bytes.encodeHex(chars, 2, (byte) (most >>> 48)); Bytes.encodeHex(chars, 4, (byte) (most >>> 40)); Bytes.encodeHex(chars, 6, (byte) (most >>> 32)); Bytes.encodeHex(chars, 8, (byte) (most >>> 24)); Bytes.encodeHex(chars, 10, (byte) (most >>> 16)); Bytes.encodeHex(chars, 12, (byte) (most >>> 8)); Bytes.encodeHex(chars, 14, (byte) (most )); Bytes.encodeHex(chars, 16, (byte) (least >>> 56)); Bytes.encodeHex(chars, 18, (byte) (least >>> 48)); Bytes.encodeHex(chars, 20, (byte) (least >>> 40)); Bytes.encodeHex(chars, 22, (byte) (least >>> 32)); Bytes.encodeHex(chars, 24, (byte) (least >>> 24)); Bytes.encodeHex(chars, 26, (byte) (least >>> 16)); Bytes.encodeHex(chars, 28, (byte) (least >>> 8)); Bytes.encodeHex(chars, 30, (byte) (least )); new String(chars); } } @Test public void test3() { for (int i= 0; i < n; i++) { byte[] bytes = new byte[] { (byte) (most >>> 56), (byte) (most >>> 48), (byte) (most >>> 40), (byte) (most >>> 32), (byte) (most >>> 24), (byte) (most >>> 16), (byte) (most >>> 8), (byte) (most ), (byte) (least >>> 56), (byte) (least >>> 48), (byte) (least >>> 40), (byte) (least >>> 32), (byte) (least >>> 24), (byte) (least >>> 16), (byte) (least >>> 8), (byte) (least ) }; Bytes.encodeHex(bytes); } } } ================================================ FILE: src/test/java/test/utils/OptionalTest.java ================================================ package test.utils; import java.util.NoSuchElementException; import java.util.Optional; public class OptionalTest { public static void main(String[] args) { //创建Optional实例,也可以通过方法返回值得到。 Optional name = Optional.of("Sanaulla"); //创建没有值的Optional实例,例如值为'null' Optional empty = Optional.ofNullable(null); //isPresent方法用来检查Optional实例是否有值。 if (name.isPresent()) { //调用get()返回Optional值。 System.out.println(name.get()); } try { //在Optional实例上调用get()抛出NoSuchElementException。 System.out.println(empty.get()); } catch (NoSuchElementException ex) { System.out.println(ex.getMessage()); } //ifPresent方法接受lambda表达式参数。 //如果Optional值不为空,lambda表达式会处理并在其上执行操作。 name.ifPresent((value) -> { System.out.println("The length of the value is: " + value.length()); }); //如果有值orElse方法会返回Optional实例,否则返回传入的错误信息。 System.out.println(empty.orElse("There is no value present!")); System.out.println(name.orElse("There is some value!")); //orElseGet与orElse类似,区别在于传入的默认值。 //orElseGet接受lambda表达式生成默认值。 System.out.println(empty.orElseGet(() -> "Default Value")); System.out.println(name.orElseGet(() -> "Default Value")); try { //orElseThrow与orElse方法类似,区别在于返回值。 //orElseThrow抛出由传入的lambda表达式/方法生成异常。 empty.orElseThrow(RuntimeException::new); } catch (Throwable ex) { System.out.println(ex.toString()); } //map方法通过传入的lambda表达式修改Optonal实例默认值。 //lambda表达式返回值会包装为Optional实例。 Optional upperName = name.map((value) -> value.toUpperCase()); System.out.println(upperName.orElse("No value found")); //flatMap与map(Funtion)非常相似,区别在于lambda表达式的返回值。 //map方法的lambda表达式返回值可以是任何类型,但是返回值会包装成Optional实例。 //但是flatMap方法的lambda返回值总是Optional类型。 upperName = name.flatMap((value) -> Optional.of(value.toUpperCase())); System.out.println(upperName.orElse("No value found")); //filter方法检查Optiona值是否满足给定条件。 //如果满足返回Optional实例值,否则返回空Optional。 Optional longName = name.filter((value) -> value.length() > 6); System.out.println(longName.orElse("The name is less than 6 characters")); //另一个示例,Optional值不满足给定条件。 Optional anotherName = Optional.of("Sana"); Optional shortName = anotherName.filter((value) -> value.length() > 6); System.out.println(shortName.orElse("The name is less than 6 characters")); } } ================================================ FILE: src/test/java/test/utils/ProjectFileUtilsTester.java ================================================ package test.utils; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.util.Scanner; import org.apache.commons.io.FilenameUtils; import org.junit.Test; import cn.ponfee.commons.util.MavenProjects; public class ProjectFileUtilsTester { @Test public void testGetMainJavaFile() { File file = MavenProjects.getMainJavaFile(MavenProjects.class); printFile(file); } @Test public void testGetTestJavaFile() { File file = MavenProjects.getTestJavaFile(ProjectFileUtilsTester.class); printFile(file); } @Test public void testGetMainJavaPath() { String Path = MavenProjects.getMainJavaPath("test", "TestUtils.java"); printFile(Path); } @Test public void testGetMainResourcesPath() { String Path = MavenProjects.getMainResourcesPath("log4j.properties"); printFile(Path); } @Test public void testGetTestResourcesPath() { String Path = MavenProjects.getTestResourcesPath("redis-script-node.lua"); printFile(Path); } private void printFile(String filepath) { printFile(new File(filepath)); } private void printFile(File file) { try { Scanner scanner = new Scanner(file); while (scanner.hasNextLine()) { System.out.println(scanner.nextLine()); } scanner.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } } public static void main(String[] args) { /*String s = new String(toByteArray(new URL("file:///d:/github/springmvc-demo/src/test/resources/ftl/macro.ftl").openStream())); System.out.println(s);*/ String filename = "D:\\github\\jedis-clients\\src\\main\\java\\test\\TestUtils.java"; System.out.println(FilenameUtils.getBaseName(filename)); System.out.println(FilenameUtils.getExtension(filename)); System.out.println(FilenameUtils.getFullPath(filename)); System.out.println(FilenameUtils.getFullPathNoEndSeparator(filename)); System.out.println(FilenameUtils.getName(filename)); System.out.println(FilenameUtils.getPath(filename)); System.out.println(FilenameUtils.getPathNoEndSeparator(filename)); System.out.println(FilenameUtils.getPrefix(filename)); System.out.println(FilenameUtils.getPrefixLength(filename)); } public static byte[] toByteArray(InputStream in) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); byte[] buff = new byte[4096]; int n; while (-1 != (n = in.read(buff))) { out.write(buff, 0, n); } return out.toByteArray(); } } ================================================ FILE: src/test/java/test/utils/RepeatableAnn.java ================================================ package test.utils; import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.Arrays; public class RepeatableAnn { public static void main(String[] args) { Annotation[] annotations = RepeatAnn.class.getAnnotations(); System.out.println(annotations.length); //1 Arrays.stream(annotations).forEach(System.out::println); Annotation[] annotations2 = Annotations.class.getAnnotations(); System.out.println(annotations2.length);//1 Arrays.stream(annotations2).forEach(System.out::println); } /** * The same annotation can be applied to a declaration or type more than * once, given that each annotation is marked as @Repeatable. In the * following code, the @Repeatable annotation is used to develop an * annotation that can be repeated, rather than grouped together as in * previous releases of Java. In this situation, an annotation named Role is * being created, and it will be used to signify a role for an annotated * class or method. */ @Repeatable(value = Roles.class) public static @interface Role { String name() default "doctor"; } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public static @interface Roles { Role[] value(); } @Role(name = "doctor") @Role(name = "who") public static class RepeatAnn{ } @Roles({@Role(name="doctor"), @Role(name="who")}) public static class Annotations{ } } ================================================ FILE: src/test/java/test/utils/SimpleXmlHandlerTest.java ================================================ package test.utils; import cn.ponfee.commons.resource.ResourceLoaderFacade; import cn.ponfee.commons.util.MavenProjects; import cn.ponfee.commons.xml.SimpleXmlHandler; import org.apache.commons.io.IOUtils; import java.io.FileReader; import java.io.IOException; public class SimpleXmlHandlerTest { public static void main(String[] args) throws IOException { System.out.println(SimpleXmlHandlerTest.class.getResourceAsStream("signer.xsd")); System.out.println(SimpleXmlHandlerTest.class.getClassLoader().getResourceAsStream("/signer.xsd")); System.out.println(ClassLoader.getSystemResourceAsStream("/signer.xsd")); System.out.println(Thread.currentThread().getContextClassLoader().getResourceAsStream("/signer.xsd")); System.out.println(SimpleXmlHandlerTest.class.getResourceAsStream("/signer.xsd")); System.out.println(SimpleXmlHandlerTest.class.getClassLoader().getResourceAsStream("signer.xsd")); System.out.println(ClassLoader.getSystemResourceAsStream("signer.xsd")); System.out.println(Thread.currentThread().getContextClassLoader().getResourceAsStream("signers.xml")); System.out.println(ResourceLoaderFacade.getResource("classpath:/signer.xsd")); System.out.println(ResourceLoaderFacade.getResource("classpath:/signers.xml")); System.out.println("----validate"); SimpleXmlHandler.validate( IOUtils.toString(new FileReader(MavenProjects.getTestResourcesPath("signers.xml"))), IOUtils.toString(new FileReader(MavenProjects.getTestResourcesPath("signer.xsd"))) ); System.out.println("----validate done"); } } ================================================ FILE: src/test/java/test/utils/TempTest.java ================================================ package test.utils; import cn.ponfee.commons.jce.digest.DigestUtils; import cn.ponfee.commons.reflect.ClassUtils; import cn.ponfee.commons.reflect.Fields; import cn.ponfee.commons.reflect.GenericUtils; import cn.ponfee.commons.resource.ResourceScanner; import cn.ponfee.commons.util.Bytes; import cn.ponfee.commons.util.ObjectUtils; import cn.ponfee.commons.util.SecureRandoms; import cn.ponfee.commons.util.UuidUtils; import com.google.common.base.Stopwatch; import org.apache.commons.codec.binary.Hex; import org.joda.time.DateTime; import org.junit.Assert; import java.io.File; import java.io.IOException; import java.lang.reflect.Method; import java.math.BigInteger; import java.nio.ByteBuffer; import java.util.Calendar; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; public class TempTest { @org.junit.Test public void test0() throws IOException { //Files.touch("d:/xampp"); //Files.touch("d:/123.xlsx"); //Files.touch("d:/1233.xlsx"); com.google.common.io.Files.touch(new File("d:/12345.xlsx")); com.google.common.io.Files.touch(new File("d:/xampp")); } @org.junit.Test public void test2() { System.out.println(DigestUtils.sha256Hex(DigestUtils.sha256("cn.ponfee.commons.jce.hash.HashUtils".getBytes()))); } @org.junit.Test public void test3() { String str = "123"; String str2 = "123"; Fields.put(str, "value", "abc".toCharArray()); System.out.println(str2); char[] chars = (char[]) Fields.get(str, "value"); chars[0] = '1'; System.out.println(str2); } @org.junit.Test public void test4() { for (Class clazz : new ResourceScanner("/cn/ponfee/commons/base/**/*.class").scan4class()) { for (Method method : clazz.getMethods()) { try { Class c = GenericUtils.getActualArgTypeArgument(method, 0); if (c != Object.class) { System.out.println(clazz.getSimpleName() + "-->" + method.getName() + "-->" + c); } } catch (Exception e) { } } } } @org.junit.Test public void test41() { //new ResourceScanner("/**/rmid_fr.properties").scan4text().forEach((k, v) -> System.out.println(k + " -> " + v)); //new ResourceScanner("log4j2.xml.template").scan4text().forEach((k, v) -> System.out.println(k + " -> " + v)); //new ResourceScanner("/cn/ponfee/commons/jce/*.class").scan4bytes().forEach((k, v) -> System.out.println(k + " -> " + v)); //new ResourceScanner("/cn/ponfee/commons/jce/**/*.class").scan4bytes().forEach((k, v) -> System.out.println(k + " -> " + v)); //new ResourceScanner("/*.template").scan4text().forEach((k, v) -> System.out.println(k + " -> " + v)); //new ResourceScanner("/log4j2.xml.template").scan4text().forEach((k, v) -> System.out.println(k + " -> " + v)); //new ResourceScanner("log4j2.xml.template").scan4text().forEach((k, v) -> System.out.println(k + " -> " + v)); //new ResourceScanner("/**/*.xml").scan4text().forEach((k, v) -> System.out.println(k + " -> " + v)); //new ResourceScanner("/cn/ponfee/commons/base/**/*.class").scan4class().forEach(System.out::println); //new ResourceScanner("/cn/ponfee/commons/**/*.class").scan4class(null, new Class[] {Service.class}).forEach(System.out::println); //new ResourceScanner("/cn/ponfee/commons/**/*.class").scan4class(null, new Class[] {Component.class}).forEach(System.out::println); //new ResourceScanner("/cn/ponfee/commons/**/*.class").scan4class(new Class[]{Tuple.class}, null).forEach(System.out::println); //new ResourceScanner("/*.template").scan4text().forEach((k, v) -> System.out.println(k + " -> " + v)); //new ResourceScanner("/log4j2.xml.template").scan4text().forEach((k, v) -> System.out.println(k + " -> " + v)); //new ResourceScanner("log4j2.xml.template").scan4text().forEach((k, v) -> System.out.println(k + " -> " + v)); //new ResourceScanner("/**/tika*.xml").scan4text().forEach((k, v) -> System.out.println(k + " -> " + v)); //new ResourceScanner("/cn/ponfee/commons/jce/*.class").scan4bytes().forEach((k, v) -> System.out.println(k + " -> " + v)); //new ResourceScanner("/cn/ponfee/commons/jce/**/*.class").scan4bytes().forEach((k, v) -> System.out.println(k + " -> " + v)); //new ResourceScanner("/cn/ponfee/commons/base/**/*.class").scan4class().forEach(System.out::println); //new ResourceScanner("/cn/ponfee/commons/**/*.class").scan4class(null, new Class[] {Service.class}).forEach(System.out::println); //new ResourceScanner("/cn/ponfee/commons/**/*.class").scan4class(null, new Class[] {Component.class}).forEach(System.out::println); //new ResourceScanner("/cn/ponfee/commons/**/*.class").scan4class(new Class[]{Tuple.class}, null).forEach(System.out::println); //new ResourceScanner("*").scan4bytes().entrySet().stream().filter(e -> e.getKey().endsWith(".xml")).forEach(e -> System.out.println(e.getKey() + " -> " + e.getValue())); //new ResourceScanner("/*.xml").scan4bytes().entrySet().stream().filter(e -> e.getKey().endsWith(".xml")).forEach(e -> System.out.println(e.getKey() + " -> " + e.getValue())); //new ResourceScanner("**/*.xml").scan4bytes().entrySet().stream().filter(e -> e.getKey().endsWith(".xml")).forEach(e -> System.out.println(e.getKey() + " -> " + e.getValue())); new ResourceScanner("/**/*.xml").scan4bytes().entrySet().stream().filter(e -> e.getKey().endsWith(".xml")).forEach(e -> System.out.println(e.getKey() + " -> " + e.getValue())); } @org.junit.Test public void test5() { for (Method method : ClassUtils.class.getMethods()) { System.out.println(ClassUtils.getMethodSignature(method) + " --> "+method.toGenericString()); } } @org.junit.Test public void test6() { byte[] bytes = SecureRandoms.nextBytes(32); BigInteger big = new BigInteger(1, bytes); long start = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { big.toString(16); } System.out.println(System.currentTimeMillis()-start); } @org.junit.Test public void test7() { byte[] bytes = SecureRandoms.nextBytes(32); BigInteger big = new BigInteger(1, bytes); long start = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { Hex.encodeHexString(big.toByteArray()); } System.out.println(System.currentTimeMillis()-start); } @org.junit.Test public void test8() { System.out.println(new DateTime().millisOfDay().withMinimumValue()); System.out.println(new DateTime().withTimeAtStartOfDay()); Calendar calendar = Calendar.getInstance(); DateTime dateTime = new DateTime(2000, 1, 1, 0, 0, 0, 0); System.out.println(dateTime.plusDays(45).plusMonths(1).dayOfWeek() .withMaximumValue().toString("E MM/dd/yyyy HH:mm:ss.SSS")); calendar.setTime(dateTime.toDate()); System.out.println(calendar.getTime()); } @org.junit.Test public void test9() { short svalue; for (int i = 0; i < 10000; i++) { svalue = (short) SecureRandoms.nextInt(); Assert.assertArrayEquals(Bytes.toBytes(svalue), fromShort(svalue)); } byte[] bytes; for (int i = 0; i < 10000; i++) { bytes = SecureRandoms.nextBytes(2); Assert.assertEquals(Bytes.toShort(bytes), toShort(bytes, 0)); } int ivalue; for (int i = 0; i < 10000; i++) { ivalue = SecureRandoms.nextInt(); Assert.assertArrayEquals(Bytes.toBytes(ivalue), fromInt(ivalue)); } for (int i = 0; i < 10000; i++) { bytes = SecureRandoms.nextBytes(4); Assert.assertEquals(Bytes.toInt(bytes), toInt(bytes, 0)); } long lvalue; for (int i = 0; i < 10000; i++) { lvalue = SecureRandoms.nextLong(); Assert.assertArrayEquals(Bytes.toBytes(lvalue), fromLong(lvalue)); } for (int i = 0; i < 10000; i++) { bytes = SecureRandoms.nextBytes(8); Assert.assertEquals(Bytes.toLong(bytes), toLong(bytes, 0)); } } @org.junit.Test public void test10() { long value = SecureRandoms.nextLong(); byte[] bytes = SecureRandoms.nextBytes(8); long round = 999999999L; Stopwatch watch = Stopwatch.createStarted(); for (long i = 0; i < round; i++) { Bytes.toBytes(value); } System.out.println("Bytes.fromLong: " + watch.stop()); watch.reset().start(); for (long i = 0; i < round; i++) { fromLong(value); } System.out.println("fromLong: " + watch.stop()); watch.reset().start(); for (long i = 0; i < round; i++) { Bytes.toLong(bytes); } System.out.println("Bytes.toLong: " + watch.stop()); watch.reset().start(); for (long i = 0; i < round; i++) { toLong(bytes, 0); } System.out.println("toLong: " + watch.stop()); } @org.junit.Test public void test100() { System.out.println(UuidUtils.uuid32()); UUID uuid = UUID.randomUUID(); long most = uuid.getMostSignificantBits(); long least = uuid.getLeastSignificantBits(); long round = 99999999L; String s; Stopwatch watch = Stopwatch.createStarted(); for (long i = 0; i < round; i++) { s = Long.toHexString(most) + Long.toHexString(least); } System.out.println("Long.toHexString: " + watch.stop()); watch.reset().start(); for (long i = 0; i < round; i++) { s = Bytes.encodeHex(Bytes.toBytes(most))+Bytes.encodeHex(Bytes.toBytes(least)); } System.out.println("Bytes.hexEncode: " + watch.stop()); watch.reset().start(); for (long i = 0; i < round; i++) { s = Bytes.encodeHex(new byte[] { (byte) (most >>> 56), (byte) (most >>> 48), (byte) (most >>> 40), (byte) (most >>> 32), (byte) (most >>> 24), (byte) (most >>> 16), (byte) (most >>> 8), (byte) (most ), (byte) (least >>> 56), (byte) (least >>> 48), (byte) (least >>> 40), (byte) (least >>> 32), (byte) (least >>> 24), (byte) (least >>> 16), (byte) (least >>> 8), (byte) (least ) }); } System.out.println("uuid32: " + watch.stop()); } @org.junit.Test public void test11() { int value = tableSizeFor(ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE)); System.out.println(Integer.toBinaryString(value)); } @org.junit.Test public void test12() { System.out.println(Bytes.toBinary(Bytes.toBytes(MAXIMUM_CAPACITY))); System.out.println(Bytes.toBinary(Bytes.toBytes(Integer.MAX_VALUE))); } static final int MAXIMUM_CAPACITY = 1 << 30; static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; } public static byte[] fromShort(short value) { return ByteBuffer.allocate(Short.BYTES).putShort(value).array(); } public static short toShort(byte[] bytes, int fromIdx) { ByteBuffer buffer = ByteBuffer.allocate(Short.BYTES); buffer.put(bytes, fromIdx, Short.BYTES).flip(); return buffer.getShort(); } public static byte[] fromInt(int value) { return ByteBuffer.allocate(Integer.BYTES).putInt(value).array(); } public static int toInt(byte[] bytes, int fromIdx) { ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); buffer.put(bytes, fromIdx, Integer.BYTES).flip(); return buffer.getInt(); } public static byte[] fromLong(long number) { return ByteBuffer.allocate(Long.BYTES).putLong(number).array(); } public static long toLong(byte[] bytes, int fromIdx) { ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); buffer.put(bytes, fromIdx, Long.BYTES).flip(); return buffer.getLong(); } } ================================================ FILE: src/test/java/test/utils/Test1.java ================================================ package test.utils; import cn.ponfee.commons.util.SecureRandoms; import org.apache.commons.codec.binary.Base32; import org.apache.commons.codec.binary.Hex; import java.util.ArrayList; import java.util.concurrent.ThreadLocalRandom; public class Test1 { private int age; private class Nested { } public boolean compare(Test1 t) { return this.age > t.age; } public static void main(String[] args) { Test1 t1 = new Test1(); Test1 t2 = new Test1(); t1.compare(t2); Nested n = t1.new Nested(); System.out.println(new ArrayList<>().equals(null)); System.out.println(3&0x01); System.out.println(2&0x01); System.out.println(1&0x01); System.out.println(0&0x01); System.out.println(new Double((double)Long.MAX_VALUE).longValue() == Long.MAX_VALUE); System.out.println(new Double((double)Long.MIN_VALUE).longValue() == Long.MIN_VALUE); byte[] b = SecureRandoms.nextBytes(11); System.out.println(new Base32().encodeToString(b)); System.out.println(Hex.encodeHexString(b)); System.out.println(Integer.toBinaryString(tableSizeFor(1 << 30))); for (int i = 0; i < 1; i++) { System.out.println(Integer.toBinaryString(tableSizeFor(ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE)))); } } static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= 1 << 30) ? 1 << 30 : n + 1; } } ================================================ FILE: src/test/java/test/utils/Test2.java ================================================ package test.utils; import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.LinkedBlockingQueue; import java.util.stream.Collectors; import java.util.stream.Stream; import org.junit.Assert; import org.junit.Test; import org.springframework.util.Base64Utils; import cn.ponfee.commons.collect.Collects; import cn.ponfee.commons.io.Files; import cn.ponfee.commons.util.Base64UrlSafe; import cn.ponfee.commons.util.SecureRandoms; /** * @author Ponfee */ public class Test2 { private static final byte[] data = SecureRandoms.nextBytes(10001); @Test public void test1() { for (int i = 0; i < 999999; i++) { java.util.Base64.getEncoder().encodeToString(data); } } @Test public void test2() { for (int i = 0; i < 999999; i++) { Base64Utils.encodeToString(data); } } @Test public void test3() { String s = Base64UrlSafe.encode(data); System.out.println(Base64UrlSafe.decode(s)); } @Test public void test4() { String[] s1 = {"1","2"}; String[] s2 = {"2","3"}; String[] s3 = Collects.intersect(s1, s2); System.out.println(Arrays.toString(s3)); } @Test public void test5() throws InterruptedException { System.out.println(Stream.of(1,2,3,4).flatMap(i-> Stream.of(i*i, i*i*i)).collect(Collectors.toList())); System.out.println(Arrays.asList("word", "count").stream().map(w -> w.split("")).collect(Collectors.toList())); System.out.println(Arrays.asList("word", "count").stream().flatMap(w -> Stream.of(w.split(""))).collect(Collectors.toList())); /*SynchronousQueue queue = new SynchronousQueue<>(); System.out.println(queue.size()); queue.put("xx"); System.out.println(queue.size());*/ LinkedBlockingQueue queue = new LinkedBlockingQueue<>(1); System.out.println(queue.size()); queue.put("xx"); queue.put("xx"); System.out.println(queue.size()); } @Test public void test6() { new ConcurrentHashMap<>(); StringBuilder builder = new StringBuilder(); builder.append("xfdeafds"); builder.deleteCharAt(builder.length()-1); StringBuilder builder2 = new StringBuilder(); builder2.append("XX"); builder2.append(builder); System.out.println(builder2.toString()); builder = new StringBuilder(); builder.append("xfdeafds"); builder.setLength(builder.length()-1); builder2 = new StringBuilder(); builder2.append("XX"); builder2.append(builder); System.out.println(builder2.toString()); System.out.println("|\0|"); System.out.println("|\u0000|"); Assert.assertEquals('\0', '\u0000'); } @Test public void test7() throws IOException { System.out.println(Files.toString(new File("src/test/resources/test.txt"))); System.out.println(Files.toString(new File("src/test/java/test/test1.java"))); System.out.println(Files.toString(new File("src/main/resources/log4j2.xml"))); System.out.println(Files.toString(new File("src/main/java/code/ponfee/commons/util/Asserts.java"))); } public static int leastFire(int num, int shotDegrade, int remDegrade, int health) { assert shotDegrade >= remDegrade; int[] healths = new int[num]; Arrays.fill(healths, health); return fire(healths, shotDegrade, remDegrade); } private static int fire(int[] healths, int shotDegrade, int remDegrade) { Arrays.sort(healths); if (healths[healths.length - 1] == 0) { return 0; } for (int i = 0; i < healths.length; i++) { if (i == healths.length - 1) { healths[i] = Math.max(0, healths[i] - shotDegrade); } else { healths[i] = Math.max(0, healths[i] - remDegrade); } } return 1 + fire(healths, shotDegrade, remDegrade); } } ================================================ FILE: src/test/java/test/utils/TestBeanCopy.java ================================================ package test.utils; import cn.ponfee.commons.json.Jsons; import cn.ponfee.commons.model.Result; import cn.ponfee.commons.reflect.BeanMaps; import cn.ponfee.commons.reflect.BeanCopiers; import cn.ponfee.commons.reflect.Fields; import org.junit.Test; import org.springframework.cglib.beans.BeanCopier; import java.util.Map; public class TestBeanCopy { public static void main(String[] args) { Object obj = new Object(); System.out.println(Fields.addressOf(obj)); System.out.println(System.identityHashCode(obj)); } static int round = 9999999; @Test public void test0() { Result result1 = Result.failure(-1, "error"); Result result2 = new Result<>(); for (int i = 0; i < round; i++) { org.springframework.beans.BeanUtils.copyProperties(result1, result2); } } @Test public void test1() { Result result1 = Result.failure(-1, "error"); Result result2 = new Result<>(); for (int i = 0; i < round; i++) { BeanCopiers.copy(result1, result2); } } @Test public void test2() { Result result1 = Result.failure(-1, "error"); Result result2 = new Result<>(); for (int i = 0; i < round; i++) { BeanCopier.create(Result.class, Result.class, false).copy(result1, result2, null); } } @Test public void test3() { BeanCopier copier = BeanCopier.create(Result.class, Result.class, false); Result result1 = Result.failure(-1, "error"); Result result2 = new Result<>(); for (int i = 0; i < round; i++) { copier.copy(result1, result2, null); } } // ------------------------------------------------------toMap @Test public void test4() { Result result1 = Result.failure(-1, "error"); System.out.println(BeanMaps.PROPS.toMap(result1)); for (int i = 0; i < round; i++) { BeanMaps.FIELDS.toMap(result1); } } @Test public void test5() { Result result1 = Result.failure(-1, "error"); System.out.println(Jsons.toJson(BeanMaps.CGLIB.toMap(result1))); for (int i = 0; i < round; i++) { BeanMaps.CGLIB.toMap(result1); } } @Test public void test6() { Result result1 = Result.failure(-1, "error"); System.out.println(BeanMaps.PROPS.toMap(result1)); for (int i = 0; i < round; i++) { BeanMaps.PROPS.toMap(result1); } } @Test public void test7() { TestBean bean = new TestBean(); Map map = BeanMaps.PROPS.toMap(bean); map.remove("failure"); map.remove("success"); System.out.println(map); System.out.println(Jsons.toJson(BeanMaps.PROPS.toBean(map, TestBean.class))); for (int i = 0; i < round; i++) { BeanMaps.CGLIB.toBean(map, TestBean.class); } } @Test public void test8() { TestBean bean = new TestBean(); Map map = BeanMaps.CGLIB.toMap(bean); System.out.println(map); System.out.println(Jsons.toJson(BeanMaps.CGLIB.toBean(map, TestBean.class))); for (int i = 0; i < round; i++) { BeanMaps.CGLIB.toBean(map, TestBean.class); } } @Test public void test9() { TestBean bean = new TestBean(); bean.setAge(20); bean.set_id("xxx"); Map map = BeanMaps.PROPS.toMap(bean); System.out.println(map); map.put("_ip", "yyyy"); System.out.println(Jsons.toJson(BeanMaps.CGLIB.toBean(map, TestBean.class))); } public static class TestBean { private int age; private String _id; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String get_id() { return _id; } public void set_id(String _id) { this._id = _id; } } } ================================================ FILE: src/test/java/test/utils/TestCache.java ================================================ package test.utils; import java.util.Date; import java.util.Random; import java.util.concurrent.atomic.AtomicBoolean; import cn.ponfee.commons.cache.Cache; import cn.ponfee.commons.cache.CacheBuilder; import cn.ponfee.commons.util.ObjectUtils; import cn.ponfee.commons.util.UuidUtils; public class TestCache { public static void main(String[] args) throws InterruptedException { Random random = new Random(); Cache cache = CacheBuilder.newBuilder() .caseSensitiveKey(false).compressKey(true).autoReleaseInSeconds(2).build(); AtomicBoolean flag = new AtomicBoolean(true); int n = 10; Thread[] threads = new Thread[n]; for (int i = 0; i < n; i++) { threads[i] = new Thread(() -> { while (flag.get()) { if (cache.isDestroy()) break; cache.put(UuidUtils.uuid32(), null, new Date().getTime() + random.nextInt(1000)); } }); } for (Thread thread : threads) { thread.start(); } for (int i = 0; i < 15; i++) { System.out.println(cache.size()); Thread.sleep(1000); } flag.set(false); for (Thread thread : threads) { thread.join(); } cache.destroy(); System.out.println(cache.size()); } } ================================================ FILE: src/test/java/test/utils/TestCost.java ================================================ package test.utils; import java.security.SecureRandom; import org.junit.Test; import cn.ponfee.commons.util.SecureRandoms; public class TestCost { @Test public void test1() { byte b1 = 127, b2 = 127; long start = System.currentTimeMillis(); for (long i = 0; i < 99999999999L; i++) { if (b1 != b2) { System.out.println("fail!"); break; } } System.out.println("test1 cost: " + (System.currentTimeMillis() - start) / 1000); } @Test public void test2() { byte b1 = 127, b2 = 127; long start = System.currentTimeMillis(); for (long i = 0; i < 99999999999L; i++) { if ((b1 ^ b2) != 0) { System.out.println("fail!"); break; } } System.out.println("test2 cost: " + (System.currentTimeMillis() - start) / 1000); } @Test public void test3() { long start = System.currentTimeMillis(); for (long i = 0; i < 99999999999L; i++) { } System.out.println("test2 cost: " + (System.currentTimeMillis() - start) / 1000); } @Test public void test4() { long start = System.currentTimeMillis(); for (long i = 0; i != 99999999999L; i++) { } System.out.println("test2 cost: " + (System.currentTimeMillis() - start) / 1000); } @Test public void test6() { byte[] b = SecureRandoms.nextBytes(1024); long start = System.currentTimeMillis(); for (long i = 0; i < 999999L; i++) { org.apache.commons.codec.binary.Base64.encodeBase64String(b); //java.util.Base64.getEncoder().encodeToString(b); } System.out.println("test6 cost: " + (System.currentTimeMillis() - start) / 1000); } @Test public void test5() { SecureRandom random = new SecureRandom(); byte[] bytes = new byte[16]; random.nextBytes(bytes); long start = System.currentTimeMillis(); for (long i = 0; i != 99999999L; i++) { //org.apache.commons.codec.binary.Hex.encodeHex(bytes); org.bouncycastle.util.encoders.Hex.toHexString(bytes); } System.out.println("test2 cost: " + (System.currentTimeMillis() - start) / 1000); } } ================================================ FILE: src/test/java/test/utils/TestInterrupt.java ================================================ package test.utils; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.ReentrantLock; public class TestInterrupt { public static void main(String[] args) throws InterruptedException { Lock lock = new ReentrantLock(); Thread t1 = new Thread(() -> { lock.lock(); try { System.out.println("t1 start.."); Thread.sleep(3000); System.out.println("t1 end.."); } catch (InterruptedException e) { e.printStackTrace(); } finally { //lock.unlock(); } }); Thread t2 = new Thread(() -> { System.out.println("t2 start..."+Thread.currentThread().isInterrupted()); try { //lock.lockInterruptibly(); // 会感知中断,会抛出InterruptedException,会重置中断状态(false) LockSupport.park(); // 会感知中断,不会抛异常,不会重置中断状态(true) /*long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start < 2000) { // 正常执行情况(非阻塞) }*/ } catch (Exception e) { e.printStackTrace(); } System.out.println("t2 end..."+Thread.currentThread().isInterrupted()); }); t1.start(); Thread.sleep(100); t2.start(); Thread.sleep(100); t2.interrupt(); t1.join(); t2.join(); System.out.println(t2.isInterrupted()); } } ================================================ FILE: src/test/java/test/utils/TestLock.java ================================================ package test.utils; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class TestLock { public static void main(String[] args) throws InterruptedException { Lock lock = new ReentrantLock(); Thread t1 = new Thread(() -> { lock.lock(); try { System.out.println("t1..."); //Thread.sleep(150); Thread.sleep(60000); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }, "t1"); Thread t2 = new Thread(() -> { try { Thread.sleep(100); lock.lock(); System.out.println("t2..."); Thread.sleep(60000); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }, "t2"); Thread t3 = new Thread(() -> { try { Thread.sleep(200); lock.lock(); System.out.println("t3..."); Thread.sleep(60000); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }, "t3"); Thread t4 = new Thread(() -> { try { Thread.sleep(300); lock.lock(); System.out.println("t4..."); Thread.sleep(60000); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }, "t4"); Thread t5 = new Thread(() -> { try { Thread.sleep(400); lock.lock(); System.out.println("t5..."); Thread.sleep(60000); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }, "t5"); Thread t6 = new Thread(() -> { try { Thread.sleep(500); lock.lock(); System.out.println("t6..."); Thread.sleep(60000); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }, "t6"); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); t6.start(); t1.join(); t2.join(); t3.join(); t4.join(); t5.join(); t6.join(); } } ================================================ FILE: src/test/java/test/utils/TestXmlReader.java ================================================ package test.utils; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import cn.ponfee.commons.xml.XmlReader; public class TestXmlReader { public static void main(String[] args) throws InterruptedException { XmlReader reader = XmlReader.create("T2015031800001000970051218861UTF-8single_trade_query20884112935393640515abcdbcd3831256abcd1034792318@qq.com20882125606099710.0002015-03-18 15:22:262015-03-18 15:43:112015-03-18 15:22:362015-03-18 15:43:11FB2015031815141294480.1010.03197622780097REFUND_SUCCESSpiaokuan03@dfasfds.com2088411293539364abcd全国汽车票0.030.100.102015031800001000970051218861TRADE_SUCCESSF39350dc1dd1a85815bfc2f153ae436e1MD5"); System.out.println(reader.getNodeFloat("price")); AtomicInteger flag = new AtomicInteger(); List list = new ArrayList<>(); for (int i = 0; i < 1; i++) { list.add(new Thread(() -> { while (flag.getAndIncrement() < 50000) { reader.evaluate("//alipay/request/param/@name"); reader.evaluate("//alipay/request/param[@name='trade_no']"); reader.evaluate("//alipay/response/trade/seller_email"); //System.out.println(reader.evaluate("//alipay/request/param/@name")); //System.out.println(reader.evaluate("//alipay/request/param[@name='trade_no']")); //System.out.println(reader.evaluate("//alipay/response/trade/seller_email")); } })); } long start = System.currentTimeMillis(); for (Thread thread : list) { thread.start(); } for (Thread thread : list) { thread.join(); } System.out.println(System.currentTimeMillis() - start); } } ================================================ FILE: src/test/java/test/utils/Ztzip.java ================================================ package test.utils; import cn.ponfee.commons.util.MavenProjects; import cn.ponfee.commons.util.ZipUtils; public class Ztzip { public static void main(String[] args) throws Exception { //org.zeroturnaround.zip.ZipUtil.pack(new File("D:\\tmp"), new File("d:/demo.zip")); //org.zeroturnaround.zip.ZipUtil.unexplode(new File("D:\\Recv Files.zip")); //org.zeroturnaround.zip.ZipUtil.addOrReplaceEntries(new File("d:/demo.zip"), new ZipEntrySource[] {new ByteSource("README.md", "readme!!!!!!!!!!!!!!!!!!!".getBytes())}); //jodd.io.ZipUtil.unzip("D:\\sql script", "d:/demo1.zip"); //jodd.io.ZipUtil.zip("D:\\sql script"); //jodd.io.ZipUtil.gzip("D:\\demo.zip"); ZipUtils.zip(MavenProjects.getProjectBaseDir(), MavenProjects.getProjectBaseDir() + "\\..\\commons-code.zip", true, "123456", "test123"); //ZipUtils.unzip("E:\\common、s-code\\commons-code.zip", "d:\\commons-code", "123456"); } } ================================================ FILE: src/test/resources/cacert.pem ================================================ -----BEGIN CERTIFICATE----- MIIEgDCCA2igAwIBAgIJAKtU1mIQGJmSMA0GCSqGSIb3DQEBBAUAMIGGMQswCQYD VQQGEwJDTjESMBAGA1UECBMJR1VBTkdET05HMREwDwYDVQQHEwhTSEVOWkhFTjEQ MA4GA1UEChMHVEVOQ0VOVDEMMAoGA1UECxMDRklUMQwwCgYDVQQDEwNDRlQxIjAg BgkqhkiG9w0BCQEWE3BlbmdsaXVAdGVuY2VudC5jb20wHhcNMTUxMjIxMDIxNzM2 WhcNMzUxMjE2MDIxNzM2WjCBhjELMAkGA1UEBhMCQ04xEjAQBgNVBAgTCUdVQU5H RE9ORzERMA8GA1UEBxMIU0hFTlpIRU4xEDAOBgNVBAoTB1RFTkNFTlQxDDAKBgNV BAsTA0ZJVDEMMAoGA1UEAxMDQ0ZUMSIwIAYJKoZIhvcNAQkBFhNwZW5nbGl1QHRl bmNlbnQuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu49T5R5F FjVHJSZ38QUn6EyU24W4jDWCkiJvaheJcMSGhA6X/UYNRFnqwvcZORrofy6AsFRX icVgYAdMniHe2d1YobZF/l+85y7grELn/4chuZUNHfVdNBvIo1R4tC9kcdkfoArk f7g8mFvcJ/iUryP6M3KlXLVS2HD7NnsQ6wYp4/Z0CWQ2fBZMZQPeMGfDRC/TXy/K UdpBu5vQao4cJabMWJA++w+TbFsM8r3RN8A/hidWKBCDThmDYzExolSXB6Bl2BWS KMxCTgRB+Bz9ekLxE3ZdyYU0pCw3nSFv35REY/v7b5gM7f8lSZM3FpNOmnhIf1zK 63HDDOTqjEZRIQIDAQABo4HuMIHrMB0GA1UdDgQWBBRSd12VBD61NL12eknBIPo3 Y7ikBTCBuwYDVR0jBIGzMIGwgBRSd12VBD61NL12eknBIPo3Y7ikBaGBjKSBiTCB hjELMAkGA1UEBhMCQ04xEjAQBgNVBAgTCUdVQU5HRE9ORzERMA8GA1UEBxMIU0hF TlpIRU4xEDAOBgNVBAoTB1RFTkNFTlQxDDAKBgNVBAsTA0ZJVDEMMAoGA1UEAxMD Q0ZUMSIwIAYJKoZIhvcNAQkBFhNwZW5nbGl1QHRlbmNlbnQuY29tggkAq1TWYhAY mZIwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQQFAAOCAQEAS26cvtHrXksD4wfs O2C912AdngV303lUcLjtQpyfUFEJwtfHiIHqCw+gJfedJxYLyhA5twTE1RY1bym3 FJEnKkIHcj7sid3bHbco0caryP+praHLpQPWkOLggfUy+c28fFZb1DXtkPAhzQs0 NscBnxqf6Cd+6uqbplg1nzTX8CeMXD2lajY1g12BM0f47bc3vP1/j3ab7Qa//0iJ cnnFbHOJ8qRLO4/B4Wwi7EeYPTJwSfc28Rj/TXYxODEDE22IaNO/kv6F8SAKNbQe L7h6QKbL+Gbhl6OkZH456MVflKK3uCiKe2dChE3D4JS4UbXSTbfuZtQY+tZlQf0M Uy5hlw== -----END CERTIFICATE----- ================================================ FILE: src/test/resources/copy-right.txt ================================================ /* __________ _____ *\ ** \______ \____ _____/ ____\____ ____ Copyright (c) 2017-2023 Ponfee ** ** | ___/ _ \ / \ __\/ __ \_/ __ \ http://www.ponfee.cn ** ** | | ( <_> ) | \ | \ ___/\ ___/ Apache License Version 2.0 ** ** |____| \____/|___| /__| \___ >\___ > http://www.apache.org/licenses/ ** ** \/ \/ \/ ** \* */ ================================================ FILE: src/test/resources/csv.csv ================================================ 区域,分公司,项目数,项目应收(元),成交套数,套均收入(元),团购项目数,导客项目数,代收项目数,线上项目数,应收(万),实收(万),成交套数,套均收入(元),团购项目应收(万),团购项目成交套数,团购项目经服成交套数,团购项目套均收入(元),团购项目经服成交应收(万),团购项目中介应付外佣(万),团购项目经服成交套数占比,团购项目中介分佣比例,导客项目应收(万),导客项目成交套数,导客项目套均收入(元),导客项目中介应付外佣(万),导客项目中介分佣比例,代收项目应收(万),代收项目成交套数,线上项目应收(万),线上项目成交套数,月指标(万),指标完成率 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd 1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd ,合计,abd111111,abd111111,ab1111111d,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd ================================================ FILE: src/test/resources/log/log4j.properties ================================================ ################################################################################ # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ################################################################################ log4j.rootLogger=WARN, console, file log4j.logger.cn.ponfee.flink=INFO # \u65e5\u5fd7\u6253\u5370\u5230\u63a7\u5236\u53f0 log4j.appender.console=org.apache.log4j.ConsoleAppender log4j.appender.console.Target=System.out log4j.appender.console.layout=org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern=%d{HH:mm:ss} %-5p %-60c %x - %m%n # \u65e5\u5fd7\u5199\u5165\u6587\u4ef6 log4j.appender.file=org.apache.log4j.RollingFileAppender # log4j\u4e0d\u652f\u6301\u201c${variable_name:-default_value}\u201d\u7684\u9ed8\u8ba4\u503c\u914d\u7f6e\u65b9\u5f0f #log4j.appender.file.File=${log.home:-.}/logs/log4j/flink.log # System.getProperty("log.home") #log4j.appender.file.File=${log.home}/logs/log4j/flink.log log4j.appender.file.File=logs/log4j/flink.log log4j.appender.file.MaxFileSize=100MB log4j.appender.file.MaxBackupIndex=10 log4j.appender.file.Threshold=WARN log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss} %l%n%m%n log4j.appender.file.Encoding=UTF-8 ================================================ FILE: src/test/resources/log/log4j.properties.bak ================================================ log4j.rootLogger=warn,console,file log4j.logger.base=warn log4j.logger.org.apache.commons=warn log4j.logger.org.springframework=warn log4j.logger.org.apache.ibatis=warn log4j.logger.org.mybatis.spring=warn log4j.logger.java.sql=warn log4j.logger.java.sql.Connection=warn log4j.logger.java.sql.PreparedStatement=warn log4j.logger.java.sql.ResultSet=warn log4j.logger.java.sql.Statement=warn log4j.logger.org.quartz=warn # \u63a7\u5236\u53f0 log4j.appender.console=org.apache.log4j.ConsoleAppender log4j.appender.console.target=System.out log4j.appender.console.Threshold=debug log4j.appender.console.layout=org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern=%-4r %-5p [%t] %37c %3x - %m%n # \u6587\u4ef6 log4j.appender.file=org.apache.log4j.RollingFileAppender log4j.appender.file.File=../commons-code.log log4j.appender.file.MaxFileSize=100KB log4j.appender.file.MaxBackupIndex=5 log4j.appender.file.Threshold=warn log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss} %l%n%m%n log4j.appender.file.Encoding=UTF-8 ================================================ FILE: src/test/resources/log/log4j2.xml ================================================ ${sys:catalina.home}/WebAppLogs/HHServices %5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%n %5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%n %5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%n %5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%n %5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%n ================================================ FILE: src/test/resources/log/logback.xml ================================================ FLINK-LOG %d{yyyy-MM-dd HH:mm:ss}| %-5level|%thread| %logger{50}| %msg%n DEBUG ACCEPT DENY ${Log_Home}/debug/debug.log ${Log_Home}/debug/debug.%d{yyyy-MM-dd}.%i.log 30 200MB %d{yyyy-MM-dd HH:mm:ss}| %-5level|%thread| %logger{50}| %msg%n INFO ACCEPT DENY ${Log_Home}/info/info.log ${Log_Home}/info/info.%d{yyyy-MM-dd}.%i.log 30 200MB %d{yyyy-MM-dd HH:mm:ss}| %-5level|%thread| %logger{50}| %msg%n WARN ACCEPT DENY ${Log_Home}/warn/warn.log ${Log_Home}/warn/warn.%d{yyyy-MM-dd}.%i.log 30 200MB %d{yyyy-MM-dd HH:mm:ss}| %-5level|%thread| %logger{50}| %msg%n ERROR ACCEPT DENY ${Log_Home}/error/error.log ${Log_Home}/error/error.%d{yyyy-MM-dd}.%i.log 30 200MB %d{yyyy-MM-dd HH:mm:ss}| %-5level|%thread| %logger{50}| %msg%n ================================================ FILE: src/test/resources/signer.xsd ================================================ ================================================ FILE: src/test/resources/signers.xml ================================================ 99 1234 1234 pfx 45e4ea70a3589e96cd670c8e5c8c7be5_28766470-a40c-4c9e-b312-d7a5618db23b 1 1234 1234 2 1234 1234 3 1234 1234 4 1234 1234 5 1234 1234 6 1234 1234 7 1234 1234 8 1234 1234 9 1234 1234 ================================================ FILE: src/test/resources/sm2-crypto.cer ================================================ -----BEGIN CERTIFICATE----- MIIDYjCCAwagAwIBAgIIYGX/NheSnJ4wDAYIKoEcz1UBg3UFADCBgjELMAkGA1UE BhMCQ04xEjAQBgNVBAgMCUd1YW5nZG9uZzERMA8GA1UEBwwIU2hlbnpoZW4xJzAl BgNVBAoMHlNoZW5aaGVuIENlcnRpZmljYXRlIEF1dGhvcml0eTENMAsGA1UECwwE c3pjYTEUMBIGA1UEAwwLU1pDQSBTTTIgQ0EwHhcNMTQxMjI0MDg1NDQ1WhcNMTUx MjI0MDg1NDQ1WjB5MQswCQYDVQQGEwJDTjESMBAGA1UECAwJ5bm/5Lic55yBMRIw EAYDVQQHDAnkuK3lsbHluIIxEjAQBgNVBAsMCeadjuWbm+WbmzEUMBIGA1UECwwL NTU1NTU1NTU1NTUxGDAWBgNVBAMMD+a1i+ivleS4k+eUqDg4NzBZMBMGByqGSM49 AgEGCCqBHM9VAYItA0IABGbrwB3NL0ABrqJ2cmsPzSfmb4RZzy7Bv7pB41i+AsMP Csa0cGGbFI/JrBf4voyvwQklC1J3KQ6KGMMIociSul+jggFqMIIBZjAMBgNVHRME BTADAQEAMFwGCCsGAQUFBwEBBFAwTjAoBggrBgEFBQcwAoYcaHR0cDovLzEyNy4w LjAuMS9jYWlzc3VlLmh0bTAiBggrBgEFBQcwAYYWaHR0cDovLzEyNy4wLjAuMToy MDQ0MzAfBggqVgsHg8zpfwQTDBE2MzU0NDQ0NDQ1MjI1Nzc3NTALBgNVHQ8EBAMC AzgwHQYDVR0OBBYEFInkTvIZtFp4lXBGViPI3slWaqL4MBAGCCpWCweDzOl8BAQM AjU0MBEGCCpWCweDzOl5BAUMAzQyMjAfBgNVHSMEGDAWgBRF6jeNcj+1pgRvwyrJ rd3u5stOjTA5BgNVHSAEMjAwMC4GBFUdIAAwJjAkBggrBgEFBQcCARYYaHR0cDov LzEyNy4wLjAuMS9jcHMuaHRtMCoGA1UdHwQjMCEwH6AdoBuGGWh0dHA6Ly8xMjcu MC4wLjEvY3JsMS5jcmwwDAYIKoEcz1UBg3UFAANIADBFAiAE+O+vf8ICszfBamzk JloHZ7CoC2jNnImNQcEJ3grr4AIhALRyg2keou896I22NEQiZhrtToYT9WxEKJBz BdK8TGIJ -----END CERTIFICATE----- ================================================ FILE: src/test/resources/test.txt ================================================ \xAC\xED\x00\x05t\x00\x0A2018-02-09

     *  异步数据批处理
     *
     *  execute:直接抛出异常,在线程外部无法捕获异常,想要捕获该异常,可以实现UncaughtExceptionHandler接口
     *  submit :不会抛出异常,需要调用返回值Future对象的get方法
     *
     *  JCTools
     *  disruptor
     *