Compare commits
53 Commits
Version_1.
...
master
Author | SHA1 | Date |
---|---|---|
Robinson | 23b9a4f7ff | |
Robinson | bc5c28ef08 | |
Robinson | 81588e2a8e | |
Robinson | da9c34b538 | |
Robinson | efcdbf9559 | |
Robinson | e13205166a | |
Robinson | 0ce4bb2e2e | |
Robinson | dc1bfb8371 | |
Robinson | 3bf845f355 | |
Robinson | 0ff5f377d1 | |
Robinson | 5fa2e2aff6 | |
Robinson | db0be638b4 | |
Robinson | ed5d2d2f74 | |
Robinson | ff21f4353e | |
Robinson | 0561f66e8e | |
Robinson | 869b3b4167 | |
Robinson | a0d02c83fc | |
Robinson | 11c30aed96 | |
Robinson | d3a767806b | |
Robinson | 27921e22ff | |
Robinson | be2dfae824 | |
Robinson | 9c8f6c97dc | |
Robinson | f874a0da68 | |
Robinson | 26a05099e9 | |
Robinson | f2782248db | |
Robinson | 298e760ee5 | |
Robinson | 52495cbe48 | |
Robinson | b12a86d9bc | |
Robinson | c4a6c98540 | |
Robinson | 61fa96539f | |
Robinson | 1b5827e33a | |
Robinson | c1f1ead3fb | |
Robinson | 18f7b2a2b8 | |
Robinson | 1db6417333 | |
Robinson | b3f94b28c9 | |
Robinson | ca30e648a8 | |
Robinson | b53df2ba32 | |
Robinson | 3b5a0482e1 | |
Robinson | 11b3fe0d1f | |
Robinson | d74b89cf92 | |
Robinson | d19bd8bbff | |
Robinson | 7fefbb9a7d | |
Robinson | 9808c02f57 | |
Robinson | 69a65ac109 | |
Robinson | 7e2ee15f31 | |
Robinson | 175a3e06a6 | |
Robinson | 63ae2fe26b | |
Robinson | ff0dc51666 | |
Robinson | f9ddf39cdf | |
Robinson | a044df0999 | |
Robinson | 391de3b011 | |
Robinson | bf925c10d3 | |
Robinson | 858927509d |
445
LICENSE
445
LICENSE
|
@ -12,23 +12,6 @@
|
|||
Sean Luke
|
||||
Michael Lecuyer (portions Copyright 1993
|
||||
|
||||
- FileUtil (code from FilenameUtils.java for normalize + dependencies) -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Utilities
|
||||
http://commons.apache.org/proper/commons-io/
|
||||
Copyright 2013
|
||||
The Apache Software Foundation
|
||||
Kevin A. Burton
|
||||
Scott Sanders
|
||||
Daniel Rall
|
||||
Christoph.Reck
|
||||
Peter Donald
|
||||
Jeff Turner
|
||||
Matthew Hawthorne
|
||||
Martin Cooper
|
||||
Jeremias Maerki
|
||||
Stephen Colebourne
|
||||
|
||||
- FastThreadLocal -
|
||||
[BSD 3-Clause License]
|
||||
https://git.dorkbox.com/dorkbox/Utilities
|
||||
|
@ -38,28 +21,6 @@
|
|||
Lightweight Java Game Library Project
|
||||
Riven
|
||||
|
||||
- Base64Fast -
|
||||
[BSD 3-Clause License]
|
||||
https://git.dorkbox.com/dorkbox/Utilities
|
||||
http://migbase64.sourceforge.net/
|
||||
Copyright 2004
|
||||
Mikael Grev, MiG InfoCom AB. (base64@miginfocom.com)
|
||||
|
||||
- BCrypt -
|
||||
[BSD 2-Clause "Simplified" or "FreeBSD" license]
|
||||
https://git.dorkbox.com/dorkbox/Utilities
|
||||
http://www.mindrot.org/projects/jBCrypt
|
||||
Copyright 2006
|
||||
Damien Miller (djm@mindrot.org)
|
||||
GWT modified version
|
||||
|
||||
- Modified hex conversion utility methods -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Utilities
|
||||
https://netty.io
|
||||
Copyright 2014
|
||||
The Netty Project
|
||||
|
||||
- Retrofit - A type-safe HTTP client for Android and Java
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/square/retrofit
|
||||
|
@ -68,7 +29,7 @@
|
|||
|
||||
- Resource Listing - Listing the contents of a resource directory
|
||||
[The Apache Software License, Version 2.0]
|
||||
http://www.uofr.net/~greg/java/get-resource-listing.html
|
||||
https://www.uofr.net/~greg/java/get-resource-listing.html
|
||||
Copyright 2017
|
||||
Greg Briggs
|
||||
|
||||
|
@ -79,11 +40,12 @@
|
|||
Pronghorn Technology LLC
|
||||
Dorkbox LLC
|
||||
|
||||
- UrlRewriteFilter - UrlRewriteFilter is a Java Web Filter for any J2EE compliant web application server
|
||||
[BSD 3-Clause License]
|
||||
https://github.com/paultuckey/urlrewritefilter
|
||||
Copyright 2022
|
||||
Paul Tuckey
|
||||
- Kotlin Coroutine CountDownLatch -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/Kotlin/kotlinx.coroutines/issues/59
|
||||
https://github.com/venkatperi/kotlin-coroutines-lib
|
||||
Copyright 2018
|
||||
Venkat Peri
|
||||
|
||||
- kotlinx.coroutines - Library support for Kotlin coroutines with multiplatform support
|
||||
[The Apache Software License, Version 2.0]
|
||||
|
@ -98,25 +60,6 @@
|
|||
Tatu Saloranta (tatu.saloranta@iki.fi)
|
||||
Contributors. See source release-notes/CREDITS
|
||||
|
||||
- kotlin-logging - Lightweight logging framework for Kotlin
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/MicroUtils/kotlin-logging
|
||||
Copyright 2023
|
||||
Ohad Shai
|
||||
|
||||
- SLF4J - Simple facade or abstraction for various logging frameworks
|
||||
[MIT License]
|
||||
http://www.slf4j.org
|
||||
Copyright 2023
|
||||
QOS.ch
|
||||
|
||||
- XZ for Java - Complete implementation of XZ data compression in pure Java
|
||||
[Public Domain, per Creative Commons CC0]
|
||||
https://tukaani.org/xz/java.html
|
||||
Copyright 2023
|
||||
Lasse Collin
|
||||
Igor Pavlov
|
||||
|
||||
- Kotlin -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/JetBrains/kotlin
|
||||
|
@ -125,380 +68,6 @@
|
|||
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
|
||||
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
|
||||
|
||||
- Netty - An event-driven asynchronous network application framework
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://netty.io
|
||||
Copyright 2023
|
||||
The Netty Project
|
||||
Contributors. See source NOTICE
|
||||
|
||||
- Bouncy Castle Crypto - Lightweight cryptography API and JCE Extension
|
||||
[The Apache Software License, Version 2.0]
|
||||
http://www.bouncycastle.org
|
||||
Copyright 2023
|
||||
The Legion of the Bouncy Castle Inc
|
||||
|
||||
- Lightweight Java Game Library - Java library that enables cross-platform access to popular native APIs
|
||||
[BSD 3-Clause License]
|
||||
https://github.com/LWJGL/lwjgl3
|
||||
Copyright 2023
|
||||
Lightweight Java Game Library
|
||||
|
||||
- TypeTools - A simple, zero-dependency library for working with types. Supports Java 1.6+ and Android.
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/jhalterman/typetools
|
||||
Copyright 2023
|
||||
Jonathan Halterman and friends
|
||||
|
||||
- Collections - Niche collections to augment what is already available.
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Collections
|
||||
Copyright 2022
|
||||
Dorkbox LLC
|
||||
|
||||
Extra license information
|
||||
- AhoCorasickDoubleArrayTrie - Niche collections to augment what is already available.
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/hankcs/AhoCorasickDoubleArrayTrie
|
||||
Copyright 2018
|
||||
hankcs <me@hankcs.com>
|
||||
|
||||
- Bias, BinarySearch -
|
||||
[MIT License]
|
||||
https://git.dorkbox.com/dorkbox/Collections
|
||||
https://github.com/timboudreau/util
|
||||
Copyright 2013
|
||||
Tim Boudreau
|
||||
|
||||
- ConcurrentEntry -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Collections
|
||||
Copyright 2016
|
||||
bennidi
|
||||
dorkbox
|
||||
|
||||
- Collection Utilities (Array, ArrayMap, BooleanArray, ByteArray, CharArray, FloatArray, IdentityMap, IntArray, IntFloatMap, IntIntMap, IntMap, IntSet, LongArray, LongMap, ObjectFloatMap, ObjectIntMap, ObjectMap, ObjectSet, OrderedMap, OrderedSet) -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Collections
|
||||
https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils
|
||||
Copyright 2011
|
||||
LibGDX
|
||||
Mario Zechner (badlogicgames@gmail.com)
|
||||
Nathan Sweet (nathan.sweet@gmail.com)
|
||||
|
||||
- Predicate -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Collections
|
||||
https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils
|
||||
Copyright 2011
|
||||
LibGDX
|
||||
Mario Zechner (badlogicgames@gmail.com)
|
||||
Nathan Sweet (nathan.sweet@gmail.com)
|
||||
xoppa
|
||||
|
||||
- Select, QuickSelect -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Collections
|
||||
https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils
|
||||
Copyright 2011
|
||||
LibGDX
|
||||
Mario Zechner (badlogicgames@gmail.com)
|
||||
Nathan Sweet (nathan.sweet@gmail.com)
|
||||
Jon Renner
|
||||
|
||||
- TimSort, ComparableTimSort -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Collections
|
||||
https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils
|
||||
Copyright 2008
|
||||
The Android Open Source Project
|
||||
|
||||
- ConcurrentWeakIdentityHashMap - Concurrent WeakIdentity HashMap
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/spring-projects/spring-loaded/blob/master/springloaded/src/main/java/org/springsource/loaded/support/ConcurrentWeakIdentityHashMap.java
|
||||
Copyright 2016
|
||||
zhanhb
|
||||
|
||||
- Kotlin -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/JetBrains/kotlin
|
||||
Copyright 2020
|
||||
JetBrains s.r.o. and Kotlin Programming Language contributors
|
||||
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
|
||||
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
|
||||
|
||||
- Updates - Software Update Management
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Updates
|
||||
Copyright 2021
|
||||
Dorkbox LLC
|
||||
|
||||
Extra license information
|
||||
- Kotlin -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/JetBrains/kotlin
|
||||
Copyright 2020
|
||||
JetBrains s.r.o. and Kotlin Programming Language contributors
|
||||
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
|
||||
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
|
||||
|
||||
- Executor - Shell, JVM, and SSH command execution on Linux, MacOS, or Windows for Java 8+
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Executor
|
||||
Copyright 2022
|
||||
Dorkbox LLC
|
||||
|
||||
Extra license information
|
||||
- ZT Process Executor -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/zeroturnaround/zt-exec
|
||||
Copyright 2014
|
||||
ZeroTurnaround LLC
|
||||
|
||||
- Apache Commons Exec -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://commons.apache.org/proper/commons-exec/
|
||||
Copyright 2014
|
||||
The Apache Software Foundation
|
||||
|
||||
- Kotlin -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/JetBrains/kotlin
|
||||
Copyright 2020
|
||||
JetBrains s.r.o. and Kotlin Programming Language contributors
|
||||
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
|
||||
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
|
||||
|
||||
- kotlinx.coroutines - Library support for Kotlin coroutines with multiplatform support
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/Kotlin/kotlinx.coroutines
|
||||
Copyright 2022
|
||||
JetBrains s.r.o.
|
||||
|
||||
- SLF4J - Simple facade or abstraction for various logging frameworks
|
||||
[MIT License]
|
||||
http://www.slf4j.org
|
||||
Copyright 2022
|
||||
QOS.ch
|
||||
|
||||
- Logback - Logback is a logging framework for Java applications
|
||||
[The Apache Software License, Version 2.0]
|
||||
http://logback.qos.ch
|
||||
Copyright 2022
|
||||
QOS.ch
|
||||
|
||||
- SSHJ - SSHv2 library for Java
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/hierynomus/sshj
|
||||
Copyright 2022
|
||||
Jeroen van Erp
|
||||
SSHJ Contributors
|
||||
|
||||
Extra license information
|
||||
- Apache MINA -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://mina.apache.org/sshd-project/
|
||||
The Apache Software Foundation
|
||||
|
||||
- Apache Commons-Net -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://commons.apache.org/proper/commons-net/
|
||||
The Apache Software Foundation
|
||||
|
||||
- JZlib -
|
||||
[The Apache Software License, Version 2.0]
|
||||
http://www.jcraft.com/jzlib
|
||||
Atsuhiko Yamanaka
|
||||
JCraft, Inc.
|
||||
|
||||
- Bouncy Castle Crypto -
|
||||
[The Apache Software License, Version 2.0]
|
||||
http://www.bouncycastle.org
|
||||
The Legion of the Bouncy Castle Inc
|
||||
|
||||
- ed25519-java -
|
||||
[Public Domain, per Creative Commons CC0]
|
||||
https://github.com/str4d/ed25519-java
|
||||
https://github.com/str4d
|
||||
|
||||
- Updates - Software Update Management
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Updates
|
||||
Copyright 2021
|
||||
Dorkbox LLC
|
||||
|
||||
Extra license information
|
||||
- Kotlin -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/JetBrains/kotlin
|
||||
Copyright 2020
|
||||
JetBrains s.r.o. and Kotlin Programming Language contributors
|
||||
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
|
||||
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
|
||||
|
||||
- NetworkUtils - Utilities for managing network configurations, IP/MAC address conversion, and ping (via OS native commands)
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/NetworkUtils
|
||||
Copyright 2022
|
||||
Dorkbox LLC
|
||||
|
||||
Extra license information
|
||||
- Netty -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://netty.io/
|
||||
Copyright 2014
|
||||
The Netty Project
|
||||
This product contains a modified portion of Netty Network Utils
|
||||
|
||||
- Apache Harmony -
|
||||
[The Apache Software License, Version 2.0]
|
||||
http://archive.apache.org/dist/harmony/
|
||||
Copyright 2010
|
||||
The Apache Software Foundation
|
||||
This product contains a modified portion of 'Apache Harmony', an open source Java SE
|
||||
|
||||
- Apache HTTP Utils -
|
||||
[The Apache Software License, Version 2.0]
|
||||
http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/psl/
|
||||
Copyright 2010
|
||||
The Apache Software Foundation
|
||||
This product contains a modified portion of 'PublicSuffixDomainFilter.java'
|
||||
|
||||
- SLF4J - Simple facade or abstraction for various logging frameworks
|
||||
[MIT License]
|
||||
http://www.slf4j.org
|
||||
Copyright 2022
|
||||
QOS.ch
|
||||
|
||||
- JNA - Simplified native library access for Java.
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/twall/jna
|
||||
Copyright 2022
|
||||
Timothy Wall
|
||||
|
||||
- JNA-Platform - Mappings for a number of commonly used platform functions
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/twall/jna
|
||||
Copyright 2022
|
||||
Timothy Wall
|
||||
|
||||
- Kotlin -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/JetBrains/kotlin
|
||||
Copyright 2020
|
||||
JetBrains s.r.o. and Kotlin Programming Language contributors
|
||||
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
|
||||
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
|
||||
|
||||
- Executor - Shell, JVM, and SSH command execution on Linux, MacOS, or Windows for Java 8+
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Executor
|
||||
Copyright 2022
|
||||
Dorkbox LLC
|
||||
|
||||
Extra license information
|
||||
- ZT Process Executor -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/zeroturnaround/zt-exec
|
||||
Copyright 2014
|
||||
ZeroTurnaround LLC
|
||||
|
||||
- Apache Commons Exec -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://commons.apache.org/proper/commons-exec/
|
||||
Copyright 2014
|
||||
The Apache Software Foundation
|
||||
|
||||
- Kotlin -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/JetBrains/kotlin
|
||||
Copyright 2020
|
||||
JetBrains s.r.o. and Kotlin Programming Language contributors
|
||||
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
|
||||
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
|
||||
|
||||
- kotlinx.coroutines - Library support for Kotlin coroutines with multiplatform support
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/Kotlin/kotlinx.coroutines
|
||||
Copyright 2022
|
||||
JetBrains s.r.o.
|
||||
|
||||
- SLF4J - Simple facade or abstraction for various logging frameworks
|
||||
[MIT License]
|
||||
http://www.slf4j.org
|
||||
Copyright 2022
|
||||
QOS.ch
|
||||
|
||||
- Logback - Logback is a logging framework for Java applications
|
||||
[The Apache Software License, Version 2.0]
|
||||
http://logback.qos.ch
|
||||
Copyright 2022
|
||||
QOS.ch
|
||||
|
||||
- SSHJ - SSHv2 library for Java
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/hierynomus/sshj
|
||||
Copyright 2022
|
||||
Jeroen van Erp
|
||||
SSHJ Contributors
|
||||
|
||||
Extra license information
|
||||
- Apache MINA -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://mina.apache.org/sshd-project/
|
||||
The Apache Software Foundation
|
||||
|
||||
- Apache Commons-Net -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://commons.apache.org/proper/commons-net/
|
||||
The Apache Software Foundation
|
||||
|
||||
- JZlib -
|
||||
[The Apache Software License, Version 2.0]
|
||||
http://www.jcraft.com/jzlib
|
||||
Atsuhiko Yamanaka
|
||||
JCraft, Inc.
|
||||
|
||||
- Bouncy Castle Crypto -
|
||||
[The Apache Software License, Version 2.0]
|
||||
http://www.bouncycastle.org
|
||||
The Legion of the Bouncy Castle Inc
|
||||
|
||||
- ed25519-java -
|
||||
[Public Domain, per Creative Commons CC0]
|
||||
https://github.com/str4d/ed25519-java
|
||||
https://github.com/str4d
|
||||
|
||||
- Updates - Software Update Management
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Updates
|
||||
Copyright 2021
|
||||
Dorkbox LLC
|
||||
|
||||
Extra license information
|
||||
- Kotlin -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/JetBrains/kotlin
|
||||
Copyright 2020
|
||||
JetBrains s.r.o. and Kotlin Programming Language contributors
|
||||
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
|
||||
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
|
||||
|
||||
- Updates - Software Update Management
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Updates
|
||||
Copyright 2021
|
||||
Dorkbox LLC
|
||||
|
||||
Extra license information
|
||||
- Kotlin -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/JetBrains/kotlin
|
||||
Copyright 2020
|
||||
JetBrains s.r.o. and Kotlin Programming Language contributors
|
||||
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
|
||||
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
|
||||
|
||||
- OS - Information about the system, Java runtime, OS, Window Manager, and Desktop Environment.
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/OS
|
||||
|
|
22
LICENSE.BSD2
22
LICENSE.BSD2
|
@ -1,22 +0,0 @@
|
|||
BSD License
|
||||
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL <ORGANIZATION> BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
121
LICENSE.CC0
121
LICENSE.CC0
|
@ -1,121 +0,0 @@
|
|||
Creative Commons Legal Code
|
||||
|
||||
CC0 1.0 Universal
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||
HEREUNDER.
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for
|
||||
the purpose of contributing to a commons of creative, cultural and
|
||||
scientific works ("Commons") that the public can reliably and without fear
|
||||
of later claims of infringement build upon, modify, incorporate in other
|
||||
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||
and for any purposes, including without limitation commercial purposes.
|
||||
These owners may contribute to the Commons to promote the ideal of a free
|
||||
culture and the further production of creative, cultural and scientific
|
||||
works, or to gain reputation or greater distribution for their Work in
|
||||
part through the use and efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any
|
||||
expectation of additional consideration or compensation, the person
|
||||
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not
|
||||
limited to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display,
|
||||
communicate, and translate a Work;
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
iii. publicity and privacy rights pertaining to a person's image or
|
||||
likeness depicted in a Work;
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||
in a Work;
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation
|
||||
thereof, including any amended or successor version of such
|
||||
directive); and
|
||||
vii. other similar, equivalent or corresponding rights throughout the
|
||||
world based on applicable law or treaty, and any national
|
||||
implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||
of action, whether now known or unknown (including existing as well as
|
||||
future claims and causes of action), in the Work (i) in all territories
|
||||
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||
treaty (including future time extensions), (iii) in any current or future
|
||||
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||
including without limitation commercial, advertising or promotional
|
||||
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||
member of the public at large and to the detriment of Affirmer's heirs and
|
||||
successors, fully intending that such Waiver shall not be subject to
|
||||
revocation, rescission, cancellation, termination, or any other legal or
|
||||
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||
as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||
be judged legally invalid or ineffective under applicable law, then the
|
||||
Waiver shall be preserved to the maximum extent permitted taking into
|
||||
account Affirmer's express Statement of Purpose. In addition, to the
|
||||
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||
maximum duration provided by applicable law or treaty (including future
|
||||
time extensions), (iii) in any current or future medium and for any number
|
||||
of copies, and (iv) for any purpose whatsoever, including without
|
||||
limitation commercial, advertising or promotional purposes (the
|
||||
"License"). The License shall be deemed effective as of the date CC0 was
|
||||
applied by Affirmer to the Work. Should any part of the License for any
|
||||
reason be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||
of the License, and in such case Affirmer hereby affirms that he or she
|
||||
will not (i) exercise any of his or her remaining Copyright and Related
|
||||
Rights in the Work or (ii) assert any associated claims and causes of
|
||||
action with respect to the Work, in either case contrary to Affirmer's
|
||||
express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
b. Affirmer offers the Work as-is and makes no representations or
|
||||
warranties of any kind concerning the Work, express, implied,
|
||||
statutory or otherwise, including without limitation warranties of
|
||||
title, merchantability, fitness for a particular purpose, non
|
||||
infringement, or the absence of latent or other defects, accuracy, or
|
||||
the present or absence of errors, whether or not discoverable, all to
|
||||
the greatest extent permissible under applicable law.
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without
|
||||
limitation any person's Copyright and Related Rights in the Work.
|
||||
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||
consents, permissions or other rights required for any use of the
|
||||
Work.
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to
|
||||
this CC0 or use of the Work.
|
21
LICENSE.MIT
21
LICENSE.MIT
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
|
||||
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.
|
12
README.md
12
README.md
|
@ -11,25 +11,25 @@ Please see the header of each file for the specific license that applies to it.
|
|||
|
||||
Maven Info
|
||||
---------
|
||||
````
|
||||
```
|
||||
<dependencies>
|
||||
...
|
||||
<dependency>
|
||||
<groupId>com.dorkbox</groupId>
|
||||
<artifactId>Utilities</artifactId>
|
||||
<version>1.36</version>
|
||||
<version>1.48</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
````
|
||||
```
|
||||
|
||||
Gradle Info
|
||||
---------
|
||||
````
|
||||
```
|
||||
dependencies {
|
||||
...
|
||||
compile "com.dorkbox:Utilities:1.36"
|
||||
compile "com.dorkbox:Utilities:1.48"
|
||||
}
|
||||
````
|
||||
```
|
||||
|
||||
|
||||
License
|
||||
|
|
118
build.gradle.kts
118
build.gradle.kts
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2021 dorkbox, llc
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -14,8 +14,6 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.time.Instant
|
||||
|
||||
///////////////////////////////
|
||||
////// PUBLISH TO SONATYPE / MAVEN CENTRAL
|
||||
////// TESTING : (to local maven repo) <'publish and release' - 'publishToMavenLocal'>
|
||||
|
@ -25,19 +23,19 @@ import java.time.Instant
|
|||
gradle.startParameter.showStacktrace = ShowStacktrace.ALWAYS // always show the stacktrace!
|
||||
|
||||
plugins {
|
||||
id("com.dorkbox.GradleUtils") version "3.6"
|
||||
id("com.dorkbox.Licensing") version "2.17"
|
||||
id("com.dorkbox.VersionUpdate") version "2.5"
|
||||
id("com.dorkbox.GradlePublish") version "1.15"
|
||||
id("com.dorkbox.GradleUtils") version "3.18"
|
||||
id("com.dorkbox.Licensing") version "2.28"
|
||||
id("com.dorkbox.VersionUpdate") version "2.8"
|
||||
id("com.dorkbox.GradlePublish") version "1.20"
|
||||
|
||||
kotlin("jvm") version "1.7.20"
|
||||
kotlin("jvm") version "1.9.0"
|
||||
}
|
||||
|
||||
object Extras {
|
||||
// set for the project
|
||||
const val description = "Utilities for use within Java projects"
|
||||
const val group = "com.dorkbox"
|
||||
const val version = "1.38"
|
||||
const val version = "1.48"
|
||||
|
||||
// set as project.ext
|
||||
const val name = "Utilities"
|
||||
|
@ -45,8 +43,6 @@ object Extras {
|
|||
const val vendor = "Dorkbox LLC"
|
||||
const val vendorUrl = "https://dorkbox.com"
|
||||
const val url = "https://git.dorkbox.com/dorkbox/Utilities"
|
||||
|
||||
val buildDate = Instant.now().toString()
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
|
@ -70,22 +66,6 @@ licensing {
|
|||
author("Sean Luke")
|
||||
author("Michael Lecuyer (portions Copyright 1993")
|
||||
}
|
||||
extra("FileUtil (code from FilenameUtils.java for normalize + dependencies)", License.APACHE_2) {
|
||||
url(Extras.url)
|
||||
url("http://commons.apache.org/proper/commons-io/")
|
||||
copyright(2013)
|
||||
author("The Apache Software Foundation")
|
||||
author("Kevin A. Burton")
|
||||
author("Scott Sanders")
|
||||
author("Daniel Rall")
|
||||
author("Christoph.Reck")
|
||||
author("Peter Donald")
|
||||
author("Jeff Turner")
|
||||
author("Matthew Hawthorne")
|
||||
author("Martin Cooper")
|
||||
author("Jeremias Maerki")
|
||||
author("Stephen Colebourne")
|
||||
}
|
||||
extra("FastThreadLocal", License.BSD_3) {
|
||||
url(Extras.url)
|
||||
url("https://github.com/LWJGL/lwjgl3/blob/5819c9123222f6ce51f208e022cb907091dd8023/modules/core/src/main/java/org/lwjgl/system/FastThreadLocal.java")
|
||||
|
@ -94,25 +74,7 @@ licensing {
|
|||
author("Lightweight Java Game Library Project")
|
||||
author("Riven")
|
||||
}
|
||||
extra("Base64Fast", License.BSD_3) {
|
||||
url(Extras.url)
|
||||
url("http://migbase64.sourceforge.net/")
|
||||
copyright(2004)
|
||||
author("Mikael Grev, MiG InfoCom AB. (base64@miginfocom.com)")
|
||||
}
|
||||
extra("BCrypt", License.BSD_2) {
|
||||
url(Extras.url)
|
||||
url("http://www.mindrot.org/projects/jBCrypt")
|
||||
copyright(2006)
|
||||
author("Damien Miller (djm@mindrot.org)")
|
||||
note("GWT modified version")
|
||||
}
|
||||
extra("Modified hex conversion utility methods", License.APACHE_2) {
|
||||
url(Extras.url)
|
||||
url("https://netty.io")
|
||||
copyright(2014)
|
||||
author("The Netty Project")
|
||||
}
|
||||
|
||||
extra("Retrofit", License.APACHE_2) {
|
||||
copyright(2020)
|
||||
description("A type-safe HTTP client for Android and Java")
|
||||
|
@ -123,7 +85,7 @@ licensing {
|
|||
copyright(2017)
|
||||
description("Listing the contents of a resource directory")
|
||||
author("Greg Briggs")
|
||||
url("http://www.uofr.net/~greg/java/get-resource-listing.html")
|
||||
url("https://www.uofr.net/~greg/java/get-resource-listing.html")
|
||||
}
|
||||
extra("CommonUtils", License.APACHE_2) {
|
||||
copyright(2017)
|
||||
|
@ -132,11 +94,11 @@ licensing {
|
|||
author("Dorkbox LLC")
|
||||
url("https://www.pronghorn.tech ")
|
||||
}
|
||||
extra("UrlRewriteFilter", License.BSD_3) {
|
||||
description("UrlRewriteFilter is a Java Web Filter for any J2EE compliant web application server")
|
||||
url("https://github.com/paultuckey/urlrewritefilter")
|
||||
copyright(2022)
|
||||
author("Paul Tuckey")
|
||||
extra("Kotlin Coroutine CountDownLatch", License.APACHE_2) {
|
||||
url("https://github.com/Kotlin/kotlinx.coroutines/issues/59")
|
||||
url("https://github.com/venkatperi/kotlin-coroutines-lib")
|
||||
copyright(2018)
|
||||
author("Venkat Peri")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -151,7 +113,7 @@ tasks.jar.get().apply {
|
|||
attributes["Specification-Vendor"] = Extras.vendor
|
||||
|
||||
attributes["Implementation-Title"] = "${Extras.group}.${Extras.id}"
|
||||
attributes["Implementation-Version"] = Extras.buildDate
|
||||
attributes["Implementation-Version"] = GradleUtils.now()
|
||||
attributes["Implementation-Vendor"] = Extras.vendor
|
||||
}
|
||||
}
|
||||
|
@ -159,61 +121,31 @@ tasks.jar.get().apply {
|
|||
// NOTE: compileOnly is used because there are some classes/dependencies that ARE NOT necessary to be included, UNLESS the user
|
||||
// is actually using that part of the library. If this happens, they will (or should) already be using the dependency)
|
||||
dependencies {
|
||||
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
|
||||
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
|
||||
|
||||
api("com.dorkbox:Collections:1.2")
|
||||
api("com.dorkbox:Executor:3.11")
|
||||
api("com.dorkbox:NetworkUtils:2.19.1")
|
||||
api("com.dorkbox:OS:1.6")
|
||||
api("com.dorkbox:OS:1.8")
|
||||
api("com.dorkbox:Updates:1.1")
|
||||
|
||||
|
||||
// https://github.com/cowtowncoder/java-uuid-generator
|
||||
// Java UUID class doesn't expose time/location versions, has a flawed compareTo() on 64bit, and is slow. This one is also thread safe.
|
||||
api("com.fasterxml.uuid:java-uuid-generator:4.0.1")
|
||||
api("com.fasterxml.uuid:java-uuid-generator:4.2.0")
|
||||
|
||||
// https://github.com/MicroUtils/kotlin-logging
|
||||
api("io.github.microutils:kotlin-logging:3.0.4")
|
||||
api("org.slf4j:slf4j-api:2.0.6")
|
||||
// // https://github.com/MicroUtils/kotlin-logging NO JPMS SUPPORT!
|
||||
// api("io.github.microutils:kotlin-logging:3.0.4")
|
||||
// api("org.slf4j:slf4j-api:2.0.7")
|
||||
|
||||
api("org.tukaani:xz:1.9")
|
||||
compileOnly("com.fasterxml.uuid:java-uuid-generator:4.1.0")
|
||||
|
||||
// api "com.koloboke:koloboke-api-jdk8:1.0.0"
|
||||
// runtime "com.koloboke:koloboke-impl-jdk8:1.0.0"
|
||||
|
||||
// compileOnly("com.esotericsoftware:kryo:5.3.0")
|
||||
// compileOnly("de.javakaffee:kryo-serializers:0.45")
|
||||
|
||||
compileOnly("io.netty:netty-buffer:4.1.86.Final")
|
||||
|
||||
val bcVersion = "1.70"
|
||||
compileOnly("org.bouncycastle:bcprov-jdk15on:$bcVersion")
|
||||
compileOnly("org.bouncycastle:bcpg-jdk15on:$bcVersion")
|
||||
compileOnly("org.bouncycastle:bcmail-jdk15on:$bcVersion")
|
||||
compileOnly("org.bouncycastle:bctls-jdk15on:$bcVersion")
|
||||
|
||||
compileOnly("org.lwjgl:lwjgl-xxhash:3.3.1")
|
||||
|
||||
compileOnly("net.jodah:typetools:0.6.3")
|
||||
// compileOnly("io.netty:netty-buffer:4.1.96.Final")
|
||||
|
||||
|
||||
// testing
|
||||
testImplementation("org.bouncycastle:bcprov-jdk15on:$bcVersion")
|
||||
testImplementation("org.bouncycastle:bcpg-jdk15on:$bcVersion")
|
||||
testImplementation("org.bouncycastle:bcmail-jdk15on:$bcVersion")
|
||||
testImplementation("org.bouncycastle:bctls-jdk15on:$bcVersion")
|
||||
|
||||
testImplementation("com.esotericsoftware:kryo:5.4.0")
|
||||
testImplementation("de.javakaffee:kryo-serializers:0.45")
|
||||
|
||||
testImplementation("com.dorkbox:Serializers:2.7")
|
||||
|
||||
testImplementation("com.dorkbox:Executor:3.13")
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
testImplementation("ch.qos.logback:logback-classic:1.4.5")
|
||||
}
|
||||
repositories {
|
||||
mavenCentral()
|
||||
// testImplementation("ch.qos.logback:logback-classic:1.4.5")
|
||||
// implementation(kotlin("stdlib-jdk8"))
|
||||
}
|
||||
|
||||
publishToSonatype {
|
||||
|
|
Binary file not shown.
|
@ -1,5 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
|
@ -80,10 +80,10 @@ do
|
|||
esac
|
||||
done
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
@ -143,12 +143,16 @@ fi
|
|||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
|
|
|
@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal
|
|||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2018 dorkbox, llc
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -13,3 +13,4 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
rootProject.name = "Utilities"
|
||||
|
|
|
@ -1,454 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Locale;
|
||||
|
||||
import dorkbox.os.OS;
|
||||
|
||||
public
|
||||
class CacheUtil {
|
||||
|
||||
private static final ThreadLocal<MessageDigest> digestLocal = ThreadLocal.withInitial(()->{
|
||||
try {
|
||||
return MessageDigest.getInstance("SHA1");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("Unable to initialize hash algorithm. SHA1 digest doesn't exist?!? (This should not happen");
|
||||
}
|
||||
});
|
||||
|
||||
private final String tempDir;
|
||||
|
||||
public static
|
||||
void clear(String tempDir) {
|
||||
new CacheUtil(tempDir).clear();
|
||||
}
|
||||
|
||||
public CacheUtil() {
|
||||
this("cache");
|
||||
}
|
||||
|
||||
public CacheUtil(String tempDir) {
|
||||
this.tempDir = tempDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears ALL saved files in the cache
|
||||
*/
|
||||
public
|
||||
void clear() {
|
||||
// deletes all of the files (recursively) in the specified location. If the directory is empty (no locked files), then the
|
||||
// directory is also deleted.
|
||||
FileUtil.delete(new File(OS.INSTANCE.getTEMP_DIR(), tempDir));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks to see if the specified file is in the cache. NULL if it is not, otherwise specifies a location on disk.
|
||||
* <p>
|
||||
* This cache is not persisted across runs.
|
||||
*/
|
||||
public
|
||||
File check(final File file) {
|
||||
if (file == null) {
|
||||
throw new NullPointerException("file");
|
||||
}
|
||||
|
||||
// if we already have this fileName, reuse it
|
||||
return check(file.getAbsolutePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the specified file is in the cache. NULL if it is not, otherwise specifies a location on disk.
|
||||
*/
|
||||
public
|
||||
File check(final String fileName) {
|
||||
if (fileName == null) {
|
||||
throw new NullPointerException("fileName");
|
||||
}
|
||||
|
||||
// if we already have this fileName, reuse it
|
||||
File newFile = makeCacheFile(fileName);
|
||||
|
||||
// if this file already exists (via HASH), we just reuse what is saved on disk.
|
||||
if (newFile.canRead() && newFile.isFile()) {
|
||||
return newFile;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the specified URL is in the cache. NULL if it is not, otherwise specifies a location on disk.
|
||||
*/
|
||||
public
|
||||
File check(final URL fileResource) {
|
||||
if (fileResource == null) {
|
||||
throw new NullPointerException("fileResource");
|
||||
}
|
||||
|
||||
return check(fileResource.getPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the specified stream (based on the hash of the input stream) is in the cache. NULL if it is not, otherwise
|
||||
* specifies a location on disk.
|
||||
*/
|
||||
public
|
||||
File check(final InputStream fileStream) throws IOException {
|
||||
if (fileStream == null) {
|
||||
throw new NullPointerException("fileStream");
|
||||
}
|
||||
|
||||
return check(null, fileStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the specified name is in the cache. NULL if it is not, otherwise specifies a location on disk. If the
|
||||
* cacheName is NULL, it will use a HASH of the fileStream
|
||||
*/
|
||||
public
|
||||
File check(String cacheName, final InputStream fileStream) throws IOException {
|
||||
if (fileStream == null) {
|
||||
throw new NullPointerException("fileStream");
|
||||
}
|
||||
|
||||
if (cacheName == null) {
|
||||
cacheName = createNameAsHash(fileStream);
|
||||
}
|
||||
|
||||
// if we already have this fileName, reuse it
|
||||
File newFile = makeCacheFile(cacheName);
|
||||
|
||||
// if this file already exists (via HASH), we just reuse what is saved on disk.
|
||||
if (newFile.canRead() && newFile.isFile()) {
|
||||
return newFile;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Saves the name of the file in a cache, based on the file's name.
|
||||
*/
|
||||
public
|
||||
File save(final File file) throws IOException {
|
||||
return save(file.getAbsolutePath(), file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the name of the file in a cache, based on the specified name. If cacheName is NULL, it will use the file's name.
|
||||
*/
|
||||
public
|
||||
File save(String cacheName, final File file) throws IOException {
|
||||
if (cacheName == null) {
|
||||
cacheName = file.getAbsolutePath();
|
||||
}
|
||||
return save(cacheName, file.getAbsolutePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the name of the file in a cache, based on the specified name.
|
||||
*/
|
||||
public
|
||||
File save(final String fileName) throws IOException {
|
||||
return save(null, fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the name of the file in a cache, based on name. If cacheName is NULL, it will use the file's name.
|
||||
*
|
||||
* @return the newly create cache file, or an IOException if there were problems
|
||||
*/
|
||||
public
|
||||
File save(String cacheName, final String fileName) throws IOException {
|
||||
if (cacheName == null) {
|
||||
cacheName = fileName;
|
||||
}
|
||||
|
||||
// if we already have this fileName, reuse it
|
||||
File newFile = makeCacheFile(cacheName);
|
||||
|
||||
// if this file already exists (via HASH), we just reuse what is saved on disk.
|
||||
if (newFile.canRead() && newFile.isFile()) {
|
||||
return newFile;
|
||||
}
|
||||
|
||||
|
||||
// is file sitting on drive
|
||||
File iconTest = new File(fileName);
|
||||
if (iconTest.isFile()) {
|
||||
if (!iconTest.canRead()) {
|
||||
throw new IOException("File exists but unable to read source file " + fileName);
|
||||
}
|
||||
|
||||
// have to copy the resource to the cache
|
||||
FileUtil.copyFile(iconTest, newFile);
|
||||
|
||||
return newFile;
|
||||
}
|
||||
else {
|
||||
// suck it out of a URL/Resource (with debugging if necessary)
|
||||
final URL systemResource = LocationResolver.getResource(fileName);
|
||||
|
||||
if (systemResource == null) {
|
||||
throw new IOException("Unable to load URL resource " + fileName);
|
||||
}
|
||||
|
||||
InputStream inStream = systemResource.openStream();
|
||||
|
||||
// saves the file into our temp location, uses HASH of cacheName
|
||||
return makeFileViaStream(cacheName, inStream);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the name of the URL in a cache, based on it's path.
|
||||
*/
|
||||
public
|
||||
File save(final URL fileResource) throws IOException {
|
||||
return save(null, fileResource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the name of the URL in a cache, based on the specified name. If cacheName is NULL, it will use the URL's path.
|
||||
*/
|
||||
public
|
||||
File save(String cacheName, final URL fileResource) throws IOException {
|
||||
if (cacheName == null) {
|
||||
cacheName = fileResource.getPath();
|
||||
}
|
||||
|
||||
// if we already have this fileName, reuse it
|
||||
File newFile = makeCacheFile(cacheName);
|
||||
|
||||
// if this file already exists (via HASH), we just reuse what is saved on disk.
|
||||
if (newFile.canRead() && newFile.isFile()) {
|
||||
return newFile;
|
||||
}
|
||||
|
||||
InputStream inStream = fileResource.openStream();
|
||||
|
||||
// saves the file into our temp location, uses HASH of cacheName
|
||||
return makeFileViaStream(cacheName, inStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* This caches the data based on the HASH of the input stream.
|
||||
*/
|
||||
public
|
||||
File save(final InputStream fileStream) throws IOException {
|
||||
if (fileStream == null) {
|
||||
throw new NullPointerException("fileStream");
|
||||
}
|
||||
|
||||
return save(null, fileStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the name of the file in a cache, based on the cacheName. If the cacheName is NULL, it will use a HASH of the fileStream
|
||||
* as the name.
|
||||
*/
|
||||
public
|
||||
File save(String cacheName, final InputStream fileStream) throws IOException {
|
||||
if (cacheName == null) {
|
||||
cacheName = createNameAsHash(fileStream);
|
||||
}
|
||||
|
||||
// if we already have this fileName, reuse it
|
||||
File newFile = makeCacheFile(cacheName);
|
||||
|
||||
// if this file already exists (via HASH), we just reuse what is saved on disk.
|
||||
if (newFile.canRead() && newFile.isFile()) {
|
||||
return newFile;
|
||||
}
|
||||
|
||||
return makeFileViaStream(cacheName, fileStream);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* must be called from synchronized block!
|
||||
*
|
||||
* @param cacheName needs name+extension for the resource
|
||||
* @param resourceStream the resource to copy to a file on disk
|
||||
*
|
||||
* @return the full path of the resource copied to disk, or NULL if invalid
|
||||
*/
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
private
|
||||
File makeFileViaStream(final String cacheName, final InputStream resourceStream) throws IOException {
|
||||
if (resourceStream == null) {
|
||||
throw new NullPointerException("resourceStream");
|
||||
}
|
||||
|
||||
if (cacheName == null) {
|
||||
throw new NullPointerException("cacheName");
|
||||
}
|
||||
|
||||
File newFile = makeCacheFile(cacheName);
|
||||
|
||||
// if this file already exists (via HASH), we just reuse what is saved on disk.
|
||||
if (newFile.canRead() && newFile.isFile()) {
|
||||
return newFile.getAbsoluteFile();
|
||||
}
|
||||
|
||||
OutputStream outStream = null;
|
||||
try {
|
||||
int read;
|
||||
byte[] buffer = new byte[2048];
|
||||
outStream = new FileOutputStream(newFile);
|
||||
|
||||
while ((read = resourceStream.read(buffer)) > 0) {
|
||||
outStream.write(buffer, 0, read);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Send up exception
|
||||
String message = "Unable to copy '" + cacheName + "' to temporary location: '" + newFile.getAbsolutePath() + "'";
|
||||
throw new IOException(message, e);
|
||||
} finally {
|
||||
try {
|
||||
resourceStream.close();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
try {
|
||||
if (outStream != null) {
|
||||
outStream.close();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
//get the name of the new file
|
||||
return newFile.getAbsoluteFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cacheName the name of the file to use in the cache. This file name can use invalid file name characters
|
||||
*
|
||||
* @return the file on disk represented by the file name
|
||||
*/
|
||||
public
|
||||
File create(final String cacheName) {
|
||||
return makeCacheFile(cacheName);
|
||||
}
|
||||
|
||||
// creates the file that will be cached. It may, or may not already exist
|
||||
// must be called from synchronized block!
|
||||
// never returns null
|
||||
private
|
||||
File makeCacheFile(final String cacheName) {
|
||||
if (cacheName == null) {
|
||||
throw new NullPointerException("cacheName");
|
||||
}
|
||||
|
||||
File saveDir = new File(OS.INSTANCE.getTEMP_DIR(), tempDir);
|
||||
|
||||
// can be wimpy, only one at a time
|
||||
String hash = hashName(cacheName);
|
||||
String extension = FileUtil.INSTANCE.getExtension(cacheName);
|
||||
if (extension.isEmpty()) {
|
||||
extension = "cache";
|
||||
}
|
||||
|
||||
File newFile = new File(saveDir, hash + '.' + extension).getAbsoluteFile();
|
||||
// make whatever dirs we need to.
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
newFile.getParentFile().mkdirs();
|
||||
|
||||
return newFile;
|
||||
}
|
||||
|
||||
// hashed name to prevent invalid file names from being used
|
||||
private static
|
||||
String hashName(final String name) {
|
||||
// figure out the fileName
|
||||
byte[] bytes = name.getBytes(StandardCharsets.UTF_8);
|
||||
MessageDigest digest = digestLocal.get();
|
||||
|
||||
digest.reset();
|
||||
digest.update(bytes);
|
||||
|
||||
// convert to alpha-numeric. see https://stackoverflow.com/questions/29183818/why-use-tostring32-and-not-tostring36
|
||||
return new BigInteger(1, digest.digest()).toString(32).toUpperCase(Locale.US);
|
||||
}
|
||||
|
||||
// this is if we DO NOT have a file name. We hash the resourceStream bytes to base the name on that. The extension will be ".cache"
|
||||
public static
|
||||
String createNameAsHash(final InputStream resourceStream) throws IOException {
|
||||
MessageDigest digest = digestLocal.get();
|
||||
|
||||
digest.reset();
|
||||
|
||||
try {
|
||||
// we have to set the cache name based on the hash of the input stream ONLY...
|
||||
final ByteArrayOutputStream outStream = new ByteArrayOutputStream(4096); // will resize if necessary
|
||||
|
||||
int read;
|
||||
byte[] buffer = new byte[2048];
|
||||
|
||||
while ((read = resourceStream.read(buffer)) > 0) {
|
||||
digest.update(buffer, 0, read);
|
||||
outStream.write(buffer, 0, read);
|
||||
}
|
||||
|
||||
// convert to alpha-numeric. see https://stackoverflow.com/questions/29183818/why-use-tostring32-and-not-tostring36
|
||||
return new BigInteger(1, digest.digest()).toString(32).toUpperCase(Locale.US) + ".cache";
|
||||
} catch (IOException e) {
|
||||
// Send up exception
|
||||
String message = "Unable to copy InputStream to memory.";
|
||||
throw new IOException(message, e);
|
||||
} finally {
|
||||
try {
|
||||
resourceStream.close();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,359 @@
|
|||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util
|
||||
|
||||
import dorkbox.os.OS.TEMP_DIR
|
||||
import dorkbox.util.FileUtil.copyFile
|
||||
import dorkbox.util.FileUtil.delete
|
||||
import java.io.*
|
||||
import java.math.BigInteger
|
||||
import java.net.URL
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
|
||||
class CacheUtil(private val tempDir: String = "cache") {
|
||||
/**
|
||||
* Clears ALL saved files in the cache
|
||||
*/
|
||||
fun clear() {
|
||||
// deletes all the files (recursively) in the specified location. If the directory is empty (no locked files), then the
|
||||
// directory is also deleted.
|
||||
delete(File(TEMP_DIR, tempDir))
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the specified file is in the cache. NULL if it is not, otherwise specifies a location on disk.
|
||||
*
|
||||
*
|
||||
* This cache is not persisted across runs.
|
||||
*/
|
||||
fun check(file: File?): File? {
|
||||
if (file == null) {
|
||||
throw NullPointerException("file")
|
||||
}
|
||||
|
||||
// if we already have this fileName, reuse it
|
||||
return check(file.absolutePath)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the specified file is in the cache. NULL if it is not, otherwise specifies a location on disk.
|
||||
*/
|
||||
fun check(fileName: String?): File? {
|
||||
if (fileName == null) {
|
||||
throw NullPointerException("fileName")
|
||||
}
|
||||
|
||||
// if we already have this fileName, reuse it
|
||||
val newFile = makeCacheFile(fileName)
|
||||
|
||||
// if this file already exists (via HASH), we just reuse what is saved on disk.
|
||||
return if (newFile.canRead() && newFile.isFile) {
|
||||
newFile
|
||||
} else null
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the specified URL is in the cache. NULL if it is not, otherwise specifies a location on disk.
|
||||
*/
|
||||
fun check(fileResource: URL): File? {
|
||||
return check(fileResource.path)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the specified stream (based on the hash of the input stream) is in the cache. NULL if it is not, otherwise
|
||||
* specifies a location on disk.
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
fun check(fileStream: InputStream): File? {
|
||||
return check(null, fileStream)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the specified name is in the cache. NULL if it is not, otherwise specifies a location on disk. If the
|
||||
* cacheName is NULL, it will use a HASH of the fileStream
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
fun check(cacheName: String?, fileStream: InputStream): File? {
|
||||
// if we already have this fileName, reuse it
|
||||
val newFile = if (cacheName == null) {
|
||||
makeCacheFile(createNameAsHash(fileStream))
|
||||
} else {
|
||||
makeCacheFile(cacheName)
|
||||
}
|
||||
|
||||
// if this file already exists (via HASH), we just reuse what is saved on disk.
|
||||
return if (newFile.canRead() && newFile.isFile) {
|
||||
newFile
|
||||
} else null
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the name of the file in a cache, based on the file's name.
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
fun save(file: File): File {
|
||||
return save(file.absolutePath, file)
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the name of the file in a cache, based on the specified name. If cacheName is NULL, it will use the file's name.
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
fun save(cacheName: String?, file: File): File {
|
||||
return if (cacheName == null) {
|
||||
save(file.absolutePath, file.absolutePath)
|
||||
} else {
|
||||
save(cacheName, file.absolutePath)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the name of the file in a cache, based on the specified name.
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
fun save(fileName: String): File {
|
||||
return save(null, fileName)
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the name of the file in a cache, based on name. If cacheName is NULL, it will use the file's name.
|
||||
*
|
||||
* @return the newly create cache file, or an IOException if there were problems
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
fun save(cacheName: String?, fileName: String): File {
|
||||
// if we already have this fileName, reuse it
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val cacheName = cacheName ?: fileName
|
||||
|
||||
val newFile = makeCacheFile(cacheName)
|
||||
|
||||
// if this file already exists (via HASH), we just reuse what is saved on disk.
|
||||
if (newFile.canRead() && newFile.isFile) {
|
||||
return newFile
|
||||
}
|
||||
|
||||
|
||||
// is file sitting on drive
|
||||
val iconTest = File(fileName)
|
||||
return if (iconTest.isFile) {
|
||||
if (!iconTest.canRead()) {
|
||||
throw IOException("File exists but unable to read source file $fileName")
|
||||
}
|
||||
|
||||
// have to copy the resource to the cache
|
||||
copyFile(iconTest, newFile)
|
||||
newFile
|
||||
} else {
|
||||
// suck it out of a URL/Resource (with debugging if necessary)
|
||||
val systemResource = LocationResolver.getResource(fileName) ?: throw IOException("Unable to load URL resource $fileName")
|
||||
val inStream = systemResource.openStream()
|
||||
|
||||
// saves the file into our temp location, uses HASH of cacheName
|
||||
makeFileViaStream(cacheName, inStream)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the name of the URL in a cache, based on it's path.
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
fun save(fileResource: URL): File {
|
||||
return save(null, fileResource)
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the name of the URL in a cache, based on the specified name. If cacheName is NULL, it will use the URL's path.
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
fun save(cacheName: String?, fileResource: URL): File {
|
||||
// if we already have this fileName, reuse it
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val cacheName = cacheName ?: fileResource.path
|
||||
|
||||
val newFile = makeCacheFile(cacheName)
|
||||
|
||||
// if this file already exists (via HASH), we just reuse what is saved on disk.
|
||||
if (newFile.canRead() && newFile.isFile) {
|
||||
return newFile
|
||||
}
|
||||
val inStream = fileResource.openStream()
|
||||
|
||||
// saves the file into our temp location, uses HASH of cacheName
|
||||
return makeFileViaStream(cacheName, inStream)
|
||||
}
|
||||
|
||||
/**
|
||||
* This caches the data based on the HASH of the input stream.
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
fun save(fileStream: InputStream?): File {
|
||||
if (fileStream == null) {
|
||||
throw NullPointerException("fileStream")
|
||||
}
|
||||
return save(null, fileStream)
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the name of the file in a cache, based on the cacheName. If the cacheName is NULL, it will use a HASH of the fileStream
|
||||
* as the name.
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
fun save(cacheName: String?, fileStream: InputStream): File {
|
||||
// if we already have this fileName, reuse it
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val cacheName = cacheName ?: createNameAsHash(fileStream)
|
||||
|
||||
val newFile = makeCacheFile(cacheName)
|
||||
|
||||
// if this file already exists (via HASH), we just reuse what is saved on disk.
|
||||
return if (newFile.canRead() && newFile.isFile) {
|
||||
newFile
|
||||
} else makeFileViaStream(cacheName, fileStream)
|
||||
}
|
||||
|
||||
/**
|
||||
* must be called from synchronized block!
|
||||
*
|
||||
* @param cacheName needs name+extension for the resource
|
||||
* @param resourceStream the resource to copy to a file on disk
|
||||
*
|
||||
* @return the full path of the resource copied to disk, or NULL if invalid
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
private fun makeFileViaStream(cacheName: String, resourceStream: InputStream): File {
|
||||
val newFile = makeCacheFile(cacheName)
|
||||
|
||||
// if this file already exists (via HASH), we just reuse what is saved on disk.
|
||||
if (newFile.canRead() && newFile.isFile) {
|
||||
return newFile.absoluteFile
|
||||
}
|
||||
var outStream: OutputStream? = null
|
||||
try {
|
||||
var read: Int
|
||||
val buffer = ByteArray(2048)
|
||||
outStream = FileOutputStream(newFile)
|
||||
while (resourceStream.read(buffer).also { read = it } > 0) {
|
||||
outStream.write(buffer, 0, read)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
// Send up exception
|
||||
val message = "Unable to copy '" + cacheName + "' to temporary location: '" + newFile.absolutePath + "'"
|
||||
throw IOException(message, e)
|
||||
} finally {
|
||||
try {
|
||||
resourceStream.close()
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
try {
|
||||
outStream?.close()
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
}
|
||||
|
||||
//get the name of the new file
|
||||
return newFile.absoluteFile
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cacheName the name of the file to use in the cache. This file name can use invalid file name characters
|
||||
*
|
||||
* @return the file on disk represented by the file name
|
||||
*/
|
||||
fun create(cacheName: String): File {
|
||||
return makeCacheFile(cacheName)
|
||||
}
|
||||
|
||||
// creates the file that will be cached. It may, or may not already exist
|
||||
// must be called from synchronized block!
|
||||
// never returns null
|
||||
private fun makeCacheFile(cacheName: String): File {
|
||||
val saveDir = File(TEMP_DIR, tempDir)
|
||||
|
||||
// can be wimpy, only one at a time
|
||||
val hash = hashName(cacheName)
|
||||
var extension = Sys.getExtension(cacheName)
|
||||
if (extension.isEmpty()) {
|
||||
extension = "cache"
|
||||
}
|
||||
val newFile = File(saveDir, "$hash.$extension").absoluteFile
|
||||
// make whatever dirs we need to.
|
||||
newFile.parentFile.mkdirs()
|
||||
return newFile
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = Sys.version
|
||||
|
||||
private val digestLocal = ThreadLocal.withInitial {
|
||||
try {
|
||||
return@withInitial MessageDigest.getInstance("SHA1")
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
throw RuntimeException("Unable to initialize hash algorithm. SHA1 digest doesn't exist?!? (This should not happen")
|
||||
}
|
||||
}
|
||||
|
||||
fun clear(tempDir: String) {
|
||||
CacheUtil(tempDir).clear()
|
||||
}
|
||||
|
||||
// hashed name to prevent invalid file names from being used
|
||||
private fun hashName(name: String): String {
|
||||
// figure out the fileName
|
||||
val bytes = name.toByteArray(StandardCharsets.UTF_8)
|
||||
val digest = digestLocal.get()
|
||||
digest.reset()
|
||||
digest.update(bytes)
|
||||
|
||||
// convert to alpha-numeric. see https://stackoverflow.com/questions/29183818/why-use-tostring32-and-not-tostring36
|
||||
return BigInteger(1, digest.digest()).toString(32).uppercase()
|
||||
}
|
||||
|
||||
// this is if we DO NOT have a file name. We hash the resourceStream bytes to base the name on that. The extension will be ".cache"
|
||||
@Throws(IOException::class)
|
||||
fun createNameAsHash(resourceStream: InputStream): String {
|
||||
val digest = digestLocal.get()
|
||||
digest.reset()
|
||||
return try {
|
||||
// we have to set the cache name based on the hash of the input stream ONLY...
|
||||
val outStream = ByteArrayOutputStream(4096) // will resize if necessary
|
||||
var read: Int
|
||||
val buffer = ByteArray(2048)
|
||||
while (resourceStream.read(buffer).also { read = it } > 0) {
|
||||
digest.update(buffer, 0, read)
|
||||
outStream.write(buffer, 0, read)
|
||||
}
|
||||
|
||||
// convert to alpha-numeric. see https://stackoverflow.com/questions/29183818/why-use-tostring32-and-not-tostring36
|
||||
BigInteger(1, digest.digest()).toString(32).uppercase() + ".cache"
|
||||
} catch (e: IOException) {
|
||||
// Send up exception
|
||||
val message = "Unable to copy InputStream to memory."
|
||||
throw IOException(message, e)
|
||||
} finally {
|
||||
try {
|
||||
resourceStream.close()
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2017 Pronghorn Technology LLC
|
||||
*
|
||||
|
@ -16,25 +32,14 @@
|
|||
|
||||
package dorkbox.util
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import mu.KLogger
|
||||
import mu.KotlinLogging
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
inline fun <reified T> T.logger(name: String = T::class.java.simpleName): KLogger {
|
||||
return KotlinLogging.logger(name)
|
||||
}
|
||||
|
||||
fun Exception.stackTraceToString(): String {
|
||||
val exceptionWriter = StringWriter()
|
||||
|
@ -61,20 +66,12 @@ inline fun ignoreExceptions(vararg blocks: () -> Unit) {
|
|||
}
|
||||
}
|
||||
|
||||
fun async(dispatcher: CoroutineDispatcher, action: suspend CoroutineScope.() -> Unit): Job {
|
||||
return GlobalScope.launch(dispatcher) {
|
||||
action()
|
||||
fun Mutex.safeUnlock() {
|
||||
if (isLocked) {
|
||||
unlock()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun asyncIO(action: suspend CoroutineScope.() -> Unit): Job {
|
||||
return GlobalScope.launch(Dispatchers.IO) {
|
||||
action()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// From: https://elizarov.medium.com/phantom-of-the-coroutine-afc63b03a131
|
||||
suspend inline fun <T> Mutex.withReentrantLock(crossinline block: suspend () -> T): T {
|
||||
val key = ReentrantMutexContextKey(this)
|
||||
|
|
|
@ -1,105 +0,0 @@
|
|||
/*
|
||||
* Copyright 2014 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
|
||||
|
||||
public
|
||||
class CountingLatch {
|
||||
private final Sync sync;
|
||||
|
||||
|
||||
public
|
||||
CountingLatch() {
|
||||
this.sync = new Sync();
|
||||
}
|
||||
|
||||
public
|
||||
CountingLatch(final int initialCount) {
|
||||
this.sync = new Sync(initialCount);
|
||||
}
|
||||
|
||||
public
|
||||
void increment() {
|
||||
this.sync.releaseShared(1);
|
||||
}
|
||||
|
||||
public
|
||||
int getCount() {
|
||||
return this.sync.getCount();
|
||||
}
|
||||
|
||||
public
|
||||
void decrement() {
|
||||
this.sync.releaseShared(-1);
|
||||
}
|
||||
|
||||
public
|
||||
void await() throws InterruptedException {
|
||||
this.sync.acquireSharedInterruptibly(1);
|
||||
}
|
||||
|
||||
public
|
||||
boolean await(final long timeout) throws InterruptedException {
|
||||
return this.sync.tryAcquireSharedNanos(1, TimeUnit.MILLISECONDS.toNanos(timeout));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Synchronization control for CountingLatch. Uses AQS state to represent
|
||||
* count.
|
||||
*/
|
||||
private static final
|
||||
class Sync extends AbstractQueuedSynchronizer {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private
|
||||
Sync() {
|
||||
}
|
||||
|
||||
private
|
||||
Sync(final int initialState) {
|
||||
setState(initialState);
|
||||
}
|
||||
|
||||
int getCount() {
|
||||
return getState();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected
|
||||
int tryAcquireShared(final int acquires) {
|
||||
return getState() == 0 ? 1 : -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected
|
||||
boolean tryReleaseShared(final int delta) {
|
||||
// Decrement count; signal when transition to zero
|
||||
for (; ; ) {
|
||||
final int c = getState();
|
||||
final int nextc = c + delta;
|
||||
if (nextc < 0) {
|
||||
return false;
|
||||
}
|
||||
if (compareAndSetState(c, nextc)) {
|
||||
return nextc == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public
|
||||
class DelayTimer {
|
||||
private final String name;
|
||||
private final boolean isDaemon;
|
||||
private final Runnable listener;
|
||||
private volatile Timer timer;
|
||||
private long delay;
|
||||
|
||||
public
|
||||
DelayTimer(Runnable listener) {
|
||||
this(null, true, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sometimes you want to make sure that this timer will complete, even if the calling thread has terminated.
|
||||
*
|
||||
* @param name the name of the thread (if you want to specify one)
|
||||
* @param isDaemon true if you want this timer to be run on a daemon thread
|
||||
* @param listener the callback listener to execute
|
||||
*/
|
||||
public
|
||||
DelayTimer(String name, boolean isDaemon, Runnable listener) {
|
||||
this.name = name;
|
||||
this.listener = listener;
|
||||
this.isDaemon = isDaemon;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this timer is still waiting to run.
|
||||
*/
|
||||
public synchronized
|
||||
boolean isWaiting() {
|
||||
return this.timer != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the delay timer!
|
||||
*/
|
||||
public synchronized
|
||||
void cancel() {
|
||||
if (this.timer != null) {
|
||||
this.timer.cancel();
|
||||
this.timer.purge();
|
||||
this.timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param delay milliseconds to wait
|
||||
*/
|
||||
public synchronized
|
||||
void delay(long delay) {
|
||||
this.delay = delay;
|
||||
cancel();
|
||||
|
||||
if (delay > 0) {
|
||||
if (this.name != null) {
|
||||
this.timer = new Timer(this.name, this.isDaemon);
|
||||
}
|
||||
else {
|
||||
this.timer = new Timer(this.isDaemon);
|
||||
}
|
||||
|
||||
TimerTask t = new TimerTask() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
// timer can change if the callback calls delay() or cancel()
|
||||
Timer origTimer = DelayTimer.this.timer;
|
||||
|
||||
DelayTimer.this.listener.run();
|
||||
|
||||
if (origTimer != null) {
|
||||
origTimer.cancel();
|
||||
origTimer.purge();
|
||||
|
||||
if (origTimer == DelayTimer.this.timer) {
|
||||
DelayTimer.this.timer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
this.timer.schedule(t, delay);
|
||||
}
|
||||
else {
|
||||
this.listener.run();
|
||||
this.timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized
|
||||
long getDelay() {
|
||||
return this.delay;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util
|
||||
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Sometimes you want to make sure that this timer will complete, even if the calling thread has terminated.
|
||||
*/
|
||||
class DelayTimer(
|
||||
/**
|
||||
* the name of the thread (if you want to specify one)
|
||||
*/
|
||||
private val name: String,
|
||||
/**
|
||||
* true if you want this timer to be run on a daemon thread
|
||||
*/
|
||||
private val isDaemon: Boolean,
|
||||
/**
|
||||
* the callback listener to execute
|
||||
*/
|
||||
private val listener: Runnable) {
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = Sys.version
|
||||
}
|
||||
|
||||
@Volatile
|
||||
private var timer: Timer? = null
|
||||
|
||||
@get:Synchronized
|
||||
var delay: Long = 0
|
||||
private set
|
||||
|
||||
constructor(listener: Runnable) : this("Timer-", true, listener)
|
||||
|
||||
@get:Synchronized
|
||||
val isWaiting: Boolean
|
||||
/**
|
||||
* @return true if this timer is still waiting to run.
|
||||
*/
|
||||
get() = timer != null
|
||||
|
||||
/**
|
||||
* Cancel the delay timer!
|
||||
*/
|
||||
@Synchronized
|
||||
fun cancel() {
|
||||
if (timer != null) {
|
||||
timer!!.cancel()
|
||||
timer!!.purge()
|
||||
timer = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param delay milliseconds to wait
|
||||
*/
|
||||
@Synchronized
|
||||
fun delay(delay: Long) {
|
||||
this.delay = delay
|
||||
cancel()
|
||||
|
||||
if (delay > 0) {
|
||||
timer = Timer(name, isDaemon)
|
||||
val t: TimerTask = object : TimerTask() {
|
||||
override fun run() {
|
||||
// timer can change if the callback calls delay() or cancel()
|
||||
val origTimer = timer
|
||||
listener.run()
|
||||
if (origTimer != null) {
|
||||
origTimer.cancel()
|
||||
origTimer.purge()
|
||||
if (origTimer === timer) {
|
||||
timer = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
timer!!.schedule(t, delay)
|
||||
} else {
|
||||
listener.run()
|
||||
timer = null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
/*
|
||||
* Copyright © 2012-2014 Lightweight Java Game Library Project
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are
|
||||
* permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright notice, this list
|
||||
* of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright notice, this
|
||||
* list of conditions and the following disclaimer in the documentation and/or
|
||||
* other materials provided with the distribution.
|
||||
* - Neither the name of 'Light Weight Java Game Library' nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
|
||||
* OF SUCH DAMAGE.
|
||||
*/
|
||||
package dorkbox.util;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Fast {@code ThreadLocal} implementation, adapted from the
|
||||
* <a href="https://github.com/riven8192/LibStruct/blob/master/src/net/indiespot/struct/runtime/FastThreadLocal.java">LibStruct</a> library.
|
||||
*
|
||||
* <p>This implementation replaces the {@code ThreadLocalMap} lookup in {@link ThreadLocal} with a simple array access. The big advantage of this method is
|
||||
* that thread-local accesses are identified as invariant by the JVM, which enables significant code-motion optimizations.</p>
|
||||
*
|
||||
* <p>The underlying array contains a slot for each thread that uses the {@link FastThreadLocal} instance. The slot is indexed by {@link Thread#getId()}. The
|
||||
* array grows if necessary when the {@link #set} method is called.</p>
|
||||
*
|
||||
* <p>It is assumed that usages of this class will be read heavy, so any contention/false-sharing issues caused by the {@link #set} method are ignored.</p>
|
||||
*
|
||||
* @param <T> the thread-local value type
|
||||
*
|
||||
* @author Riven
|
||||
* @see java.lang.ThreadLocal
|
||||
*/
|
||||
public class FastThreadLocal<T> {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private T[] threadIDMap = (T[])new Object[1];
|
||||
|
||||
/** Creates a thread local variable. */
|
||||
public FastThreadLocal() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current thread's "initial value" for this thread-local variable.
|
||||
*/
|
||||
public T initialValue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current thread's copy of this thread-local variable to the specified value.
|
||||
*
|
||||
* @param value the value to be stored in the current thread's copy of this thread-local.
|
||||
*
|
||||
* @see ThreadLocal#set(T)
|
||||
*/
|
||||
public void set(T value) {
|
||||
int id = (int)Thread.currentThread().getId();
|
||||
|
||||
synchronized ( this ) {
|
||||
int len = threadIDMap.length;
|
||||
if (len <= id) {
|
||||
threadIDMap = Arrays.copyOf(threadIDMap, id + 1);
|
||||
}
|
||||
|
||||
threadIDMap[id] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value in the current thread's copy of this thread-local variable.
|
||||
*
|
||||
* @see ThreadLocal#get()
|
||||
*/
|
||||
public final T get() {
|
||||
int id = (int)Thread.currentThread().getId();
|
||||
|
||||
T[] threadIDMap = this.threadIDMap; // It's OK if the array is resized after this access, will just use the old array.
|
||||
|
||||
T value = threadIDMap.length <= id ? null : threadIDMap[id];
|
||||
|
||||
if ( value == null ) {
|
||||
value = initialValue();
|
||||
set(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the current thread's value for this thread-local variable.
|
||||
*
|
||||
* @see ThreadLocal#remove()
|
||||
*/
|
||||
public void remove() {
|
||||
set(null);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright © 2012-2014 Lightweight Java Game Library Project
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are
|
||||
* permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright notice, this list
|
||||
* of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright notice, this
|
||||
* list of conditions and the following disclaimer in the documentation and/or
|
||||
* other materials provided with the distribution.
|
||||
* - Neither the name of 'Light Weight Java Game Library' nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
|
||||
* OF SUCH DAMAGE.
|
||||
*/
|
||||
package dorkbox.util
|
||||
|
||||
/**
|
||||
* Fast `ThreadLocal` implementation, adapted from the
|
||||
* [LibStruct](https://github.com/riven8192/LibStruct/blob/master/src/net/indiespot/struct/runtime/FastThreadLocal.java) library.
|
||||
*
|
||||
*
|
||||
* This implementation replaces the `ThreadLocalMap` lookup in [ThreadLocal] with a simple array access. The big advantage of this method is
|
||||
* that thread-local accesses are identified as invariant by the JVM, which enables significant code-motion optimizations.
|
||||
*
|
||||
*
|
||||
* The underlying array contains a slot for each thread that uses the [FastThreadLocal] instance. The slot is indexed by [Thread.getId]. The
|
||||
* array grows if necessary when the [.set] method is called.
|
||||
*
|
||||
*
|
||||
* It is assumed that usages of this class will be read heavy, so any contention/false-sharing issues caused by the [.set] method are ignored.
|
||||
*
|
||||
* @param <T> the thread-local value type
|
||||
*
|
||||
* @author Riven
|
||||
* @see java.lang.ThreadLocal
|
||||
</T> */
|
||||
abstract class FastThreadLocal<T> {
|
||||
/** Creates a thread local variable. */
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private var threadIDMap = arrayOfNulls<Any>(1) as Array<T?>
|
||||
|
||||
/**
|
||||
* Returns the current thread's "initial value" for this thread-local variable.
|
||||
*/
|
||||
abstract fun initialValue(): T
|
||||
|
||||
/**
|
||||
* Sets the current thread's copy of this thread-local variable to the specified value.
|
||||
*
|
||||
* @param value the value to be stored in the current thread's copy of this thread-local.
|
||||
*
|
||||
* @see ThreadLocal.set
|
||||
*/
|
||||
fun set(value: T?) {
|
||||
val id = Thread.currentThread().id.toInt()
|
||||
|
||||
synchronized(this) {
|
||||
val len = threadIDMap.size
|
||||
if (len <= id) {
|
||||
threadIDMap = threadIDMap.copyOf(id + 1)
|
||||
}
|
||||
threadIDMap[id] = value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value in the current thread's copy of this thread-local variable.
|
||||
*
|
||||
* @see ThreadLocal.get
|
||||
*/
|
||||
fun get(): T {
|
||||
val id = Thread.currentThread().id.toInt()
|
||||
val threadIDMap: Array<T?> = threadIDMap // It's OK if the array is resized after this access, will just use the old array.
|
||||
var value = if (threadIDMap.size <= id) null else threadIDMap[id]
|
||||
|
||||
if (value == null) {
|
||||
value = initialValue()
|
||||
set(value)
|
||||
}
|
||||
|
||||
return value!!
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the current thread's value for this thread-local variable.
|
||||
*
|
||||
* @see ThreadLocal.remove
|
||||
*/
|
||||
fun remove() {
|
||||
set(null)
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -16,21 +16,9 @@
|
|||
package dorkbox.util
|
||||
|
||||
import dorkbox.os.OS
|
||||
import mu.KotlinLogging
|
||||
import java.io.BufferedInputStream
|
||||
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.io.InputStream
|
||||
import java.io.PrintWriter
|
||||
import java.io.RandomAccessFile
|
||||
import java.io.Reader
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.*
|
||||
import java.nio.file.DirectoryIteratorException
|
||||
import java.util.*
|
||||
import java.util.zip.*
|
||||
|
@ -56,9 +44,9 @@ object FileUtil {
|
|||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = "1.38"
|
||||
val version = Sys.version
|
||||
|
||||
private val log = KotlinLogging.logger(FileUtil::class.java.name)
|
||||
private val log: Logger = LoggerFactory.getLogger(FileUtil::class.java)
|
||||
|
||||
private const val DEBUG = false
|
||||
|
||||
|
@ -568,12 +556,11 @@ object FileUtil {
|
|||
/**
|
||||
* Copies a files from one location to another. Overwriting any existing file at the destination.
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(IOException::class)
|
||||
fun copyFile(`in`: File, out: File): File {
|
||||
val normalizedIn = normalize(`in`)!!.absolutePath
|
||||
val normalizedout = normalize(out)!!.absolutePath
|
||||
if (normalizedIn.equals(normalizedout, ignoreCase = true)) {
|
||||
val normalizedIn = `in`.normalize().absolutePath
|
||||
val normalizedOut = out.normalize().absolutePath
|
||||
if (normalizedIn.equals(normalizedOut, ignoreCase = true)) {
|
||||
if (DEBUG) {
|
||||
System.err.println("Source equals destination! $normalizedIn")
|
||||
}
|
||||
|
@ -661,8 +648,8 @@ object FileUtil {
|
|||
*/
|
||||
@Throws(IOException::class)
|
||||
fun copyDirectory(src_: File, dest_: File, vararg namesToIgnore: String) {
|
||||
val src = normalize(src_)
|
||||
val dest = normalize(dest_)
|
||||
val src = src_.normalize()
|
||||
val dest = dest_.normalize()
|
||||
|
||||
requireNotNull(src) { "Source must be valid" }
|
||||
requireNotNull(dest) { "Destination must be valid" }
|
||||
|
@ -768,7 +755,6 @@ object FileUtil {
|
|||
*
|
||||
* @return true IFF the file/dir was deleted or didn't exist at first
|
||||
*/
|
||||
@JvmStatic
|
||||
fun delete(file: File, vararg namesToIgnore: String): Boolean {
|
||||
if (!file.exists()) {
|
||||
return true
|
||||
|
@ -784,7 +770,7 @@ object FileUtil {
|
|||
var delete = true
|
||||
val file2 = files[i]
|
||||
val name2 = file2.name
|
||||
val name2Full = normalize(file2)!!.absolutePath
|
||||
val name2Full = file2.normalize().absolutePath
|
||||
if (file2.isDirectory) {
|
||||
for (name in namesToIgnore) {
|
||||
if (name[0] == UNIX_SEPARATOR && name == name2) {
|
||||
|
@ -860,7 +846,7 @@ object FileUtil {
|
|||
/**
|
||||
* @return the contents of the file as a byte array
|
||||
*/
|
||||
fun toBytes(file: File): ByteArray? {
|
||||
fun toBytes(file: File): ByteArray {
|
||||
return file.readBytes()
|
||||
}
|
||||
|
||||
|
@ -868,7 +854,7 @@ object FileUtil {
|
|||
* Creates the directories in the specified location.
|
||||
*/
|
||||
fun mkdir(location: File): String {
|
||||
val path = normalize(location)!!.absoluteFile
|
||||
val path = location.normalize().absoluteFile
|
||||
if (location.mkdirs()) {
|
||||
if (DEBUG) {
|
||||
System.err.println("Created directory: $path")
|
||||
|
@ -889,7 +875,7 @@ object FileUtil {
|
|||
*/
|
||||
@Throws(IOException::class)
|
||||
fun tempFile(fileName: String): File {
|
||||
return normalize(File.createTempFile(fileName, null))!!.absoluteFile
|
||||
return File.createTempFile(fileName, null).normalize().absoluteFile
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -904,7 +890,7 @@ object FileUtil {
|
|||
if (!file.mkdir()) {
|
||||
throw IOException("Unable to create temp directory: $file")
|
||||
}
|
||||
return normalize(file)!!.absolutePath
|
||||
return file.normalize().absolutePath
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1044,16 +1030,15 @@ object FileUtil {
|
|||
*/
|
||||
@Throws(IOException::class)
|
||||
private fun unjarzip1(inputStream: ZipInputStream, outputDir: File, extractManifest: Boolean) {
|
||||
|
||||
inputStream.use {
|
||||
var entry: ZipEntry
|
||||
var entry: ZipEntry?
|
||||
while (inputStream.nextEntry.also { entry = it } != null) {
|
||||
val name = entry.name
|
||||
val name = entry!!.name
|
||||
if (!extractManifest && name.startsWith("META-INF/")) {
|
||||
continue
|
||||
}
|
||||
val file = File(outputDir, name)
|
||||
if (entry.isDirectory) {
|
||||
if (entry!!.isDirectory) {
|
||||
mkdir(file.path)
|
||||
continue
|
||||
}
|
||||
|
@ -1067,7 +1052,7 @@ object FileUtil {
|
|||
}
|
||||
|
||||
/**
|
||||
* Parses the specified root directory for **ALL** files that are in it. All of the sub-directories are searched as well.
|
||||
* Parses the specified root directory for **ALL** files that are in it. All the sub-directories are searched as well.
|
||||
*
|
||||
*
|
||||
* *This is different, in that it returns ALL FILES, instead of ones that just match a specific extension.*
|
||||
|
@ -1080,7 +1065,7 @@ object FileUtil {
|
|||
}
|
||||
|
||||
/**
|
||||
* Parses the specified root directory for **ALL** files that are in it. All of the sub-directories are searched as well.
|
||||
* Parses the specified root directory for **ALL** files that are in it. All the sub-directories are searched as well.
|
||||
*
|
||||
*
|
||||
* *This is different, in that it returns ALL FILES, instead of ones that just match a specific extension.*
|
||||
|
@ -1093,7 +1078,7 @@ object FileUtil {
|
|||
}
|
||||
|
||||
/**
|
||||
* Parses the specified root directory for files that end in the extension to match. All of the sub-directories are searched as well.
|
||||
* Parses the specified root directory for files that end in the extension to match. All the sub-directories are searched as well.
|
||||
*
|
||||
* @return the list of all files in the root+sub-dirs that match the given extension.
|
||||
*/
|
||||
|
@ -1103,7 +1088,7 @@ object FileUtil {
|
|||
val directories = LinkedList<File?>()
|
||||
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val rootDirectory = normalize(rootDirectory) ?: throw IOException("Root directory was invalid!")
|
||||
val rootDirectory = rootDirectory.normalize()
|
||||
|
||||
if (!rootDirectory.exists()) {
|
||||
throw IOException("Location does not exist: " + rootDirectory.absolutePath)
|
||||
|
@ -1347,625 +1332,4 @@ object FileUtil {
|
|||
}
|
||||
return file.setLastModified(timestamp)
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/*
|
||||
* FilenameUtils.java (normalize + dependencies) - Apache 2.0 License
|
||||
* http://commons.apache.org/proper/commons-io/
|
||||
* Copyright 2013 ASF
|
||||
* Authors: Kevin A. Burton, Scott Sanders, Daniel Rall, Christoph.Reck,
|
||||
* Peter Donald, Jeff Turner, Matthew Hawthorne, Martin Cooper,
|
||||
* Jeremias Maerki, Stephen Colebourne
|
||||
*/
|
||||
/**
|
||||
* Normalizes a path, removing double and single dot path steps.
|
||||
*
|
||||
*
|
||||
* THIS IS DIFFERENT in that it might not be a path that resolves to anything
|
||||
*
|
||||
*
|
||||
* This method normalizes a path to a standard format.
|
||||
* The input may contain separators in either Unix or Windows format.
|
||||
* The output will contain separators in the format of the system.
|
||||
*
|
||||
*
|
||||
* A trailing slash will be retained.
|
||||
* A double slash will be merged to a single slash (but UNC names are handled).
|
||||
* A single dot path segment will be removed.
|
||||
* A double dot will cause that path segment and the one before to be removed.
|
||||
* If the double dot has no parent path segment to work with, `null`
|
||||
* is returned.
|
||||
*
|
||||
*
|
||||
* The output will be the same on both Unix and Windows except
|
||||
* for the separator character.
|
||||
* <pre>
|
||||
* /foo// --> /foo/
|
||||
* /foo/./ --> /foo/
|
||||
* /foo/../bar --> /bar
|
||||
* /foo/../bar/ --> /bar/
|
||||
* /foo/../bar/../baz --> /baz
|
||||
* //foo//./bar --> /foo/bar
|
||||
* /../ --> null
|
||||
* ../foo --> null
|
||||
* foo/bar/.. --> foo/
|
||||
* foo/../../bar --> null
|
||||
* foo/../bar --> bar
|
||||
* //server/foo/../bar --> //server/bar
|
||||
* //server/../bar --> null
|
||||
* C:\foo\..\bar --> C:\bar
|
||||
* C:\..\bar --> null
|
||||
* ~/foo/../bar/ --> ~/bar/
|
||||
* ~/../bar --> null
|
||||
</pre> *
|
||||
* (Note the file separator returned will be correct for Windows/Unix)
|
||||
*
|
||||
* @param filename the filename to normalize, null returns null
|
||||
*
|
||||
* @return the normalized filename, or null if invalid
|
||||
*/
|
||||
fun normalizeRaw(filename: String): String? {
|
||||
return doNormalize(filename, SYSTEM_SEPARATOR, true)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* FilenameUtils.java (normalize + dependencies) - Apache 2.0 License
|
||||
* http://commons.apache.org/proper/commons-io/
|
||||
* Copyright 2013 ASF
|
||||
* Authors: Kevin A. Burton, Scott Sanders, Daniel Rall, Christoph.Reck,
|
||||
* Peter Donald, Jeff Turner, Matthew Hawthorne, Martin Cooper,
|
||||
* Jeremias Maerki, Stephen Colebourne
|
||||
*/
|
||||
/**
|
||||
* Normalizes a path, removing double and single dot path steps.
|
||||
*
|
||||
*
|
||||
* This method normalizes a path to a standard format.
|
||||
* The input may contain separators in either Unix or Windows format.
|
||||
* The output will contain separators in the format of the system.
|
||||
*
|
||||
*
|
||||
* A trailing slash will be retained.
|
||||
* A double slash will be merged to a single slash (but UNC names are handled).
|
||||
* A single dot path segment will be removed.
|
||||
* A double dot will cause that path segment and the one before to be removed.
|
||||
* If the double dot has no parent path segment to work with, `null`
|
||||
* is returned.
|
||||
*
|
||||
*
|
||||
* The output will be the same on both Unix and Windows except
|
||||
* for the separator character.
|
||||
* <pre>
|
||||
* /foo// --> /foo/
|
||||
* /foo/./ --> /foo/
|
||||
* /foo/../bar --> /bar
|
||||
* /foo/../bar/ --> /bar/
|
||||
* /foo/../bar/../baz --> /baz
|
||||
* //foo//./bar --> /foo/bar
|
||||
* /../ --> null
|
||||
* ../foo --> null
|
||||
* foo/bar/.. --> foo/
|
||||
* foo/../../bar --> null
|
||||
* foo/../bar --> bar
|
||||
* //server/foo/../bar --> //server/bar
|
||||
* //server/../bar --> null
|
||||
* C:\foo\..\bar --> C:\bar
|
||||
* C:\..\bar --> null
|
||||
* ~/foo/../bar/ --> ~/bar/
|
||||
* ~/../bar --> null
|
||||
</pre> *
|
||||
* (Note the file separator returned will be correct for Windows/Unix)
|
||||
*
|
||||
* @param filename the file to normalize, null returns null
|
||||
* @return the normalized file, or null if invalid
|
||||
*/
|
||||
fun normalize(filename: String): File? {
|
||||
val asString = doNormalize(File(filename).absolutePath, SYSTEM_SEPARATOR, true) ?: return null
|
||||
return File(asString).absoluteFile
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* FilenameUtils.java (normalize + dependencies) - Apache 2.0 License
|
||||
* http://commons.apache.org/proper/commons-io/
|
||||
* Copyright 2013 ASF
|
||||
* Authors: Kevin A. Burton, Scott Sanders, Daniel Rall, Christoph.Reck,
|
||||
* Peter Donald, Jeff Turner, Matthew Hawthorne, Martin Cooper,
|
||||
* Jeremias Maerki, Stephen Colebourne
|
||||
*/
|
||||
/**
|
||||
* Normalizes a path, removing double and single dot path steps.
|
||||
*
|
||||
*
|
||||
* This method normalizes a path to a standard format.
|
||||
* The input may contain separators in either Unix or Windows format.
|
||||
* The output will contain separators in the format of the system.
|
||||
*
|
||||
*
|
||||
* A trailing slash will be retained.
|
||||
* A double slash will be merged to a single slash (but UNC names are handled).
|
||||
* A single dot path segment will be removed.
|
||||
* A double dot will cause that path segment and the one before to be removed.
|
||||
* If the double dot has no parent path segment to work with, `null`
|
||||
* is returned.
|
||||
*
|
||||
*
|
||||
* The output will be the same on both Unix and Windows except
|
||||
* for the separator character.
|
||||
* <pre>
|
||||
* /foo// --> /foo/
|
||||
* /foo/./ --> /foo/
|
||||
* /foo/../bar --> /bar
|
||||
* /foo/../bar/ --> /bar/
|
||||
* /foo/../bar/../baz --> /baz
|
||||
* //foo//./bar --> /foo/bar
|
||||
* /../ --> null
|
||||
* ../foo --> null
|
||||
* foo/bar/.. --> foo/
|
||||
* foo/../../bar --> null
|
||||
* foo/../bar --> bar
|
||||
* //server/foo/../bar --> //server/bar
|
||||
* //server/../bar --> null
|
||||
* C:\foo\..\bar --> C:\bar
|
||||
* C:\..\bar --> null
|
||||
* ~/foo/../bar/ --> ~/bar/
|
||||
* ~/../bar --> null
|
||||
</pre> *
|
||||
* (Note the file separator returned will be correct for Windows/Unix)
|
||||
*
|
||||
* @param file the file to normalize, null returns null
|
||||
* @return the normalized file, or null if invalid
|
||||
*/
|
||||
fun normalize(file: File): File? {
|
||||
val asString = doNormalize(file.absolutePath, SYSTEM_SEPARATOR, true) ?: return null
|
||||
return File(asString).absoluteFile
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* FilenameUtils.java (normalize + dependencies) - Apache 2.0 License
|
||||
* http://commons.apache.org/proper/commons-io/
|
||||
* Copyright 2013 ASF
|
||||
* Authors: Kevin A. Burton, Scott Sanders, Daniel Rall, Christoph.Reck,
|
||||
* Peter Donald, Jeff Turner, Matthew Hawthorne, Martin Cooper,
|
||||
* Jeremias Maerki, Stephen Colebourne
|
||||
*/
|
||||
/**
|
||||
* Normalizes a path, removing double and single dot path steps.
|
||||
*
|
||||
*
|
||||
* This method normalizes a path to a standard format.
|
||||
* The input may contain separators in either Unix or Windows format.
|
||||
* The output will contain separators in the format specified.
|
||||
*
|
||||
*
|
||||
* A trailing slash will be retained.
|
||||
* A double slash will be merged to a single slash (but UNC names are handled).
|
||||
* A single dot path segment will be removed.
|
||||
* A double dot will cause that path segment and the one before to be removed.
|
||||
* If the double dot has no parent path segment to work with, `null`
|
||||
* is returned.
|
||||
*
|
||||
*
|
||||
* The output will be the same on both Unix and Windows except
|
||||
* for the separator character.
|
||||
* <pre>
|
||||
* /foo// --> /foo/
|
||||
* /foo/./ --> /foo/
|
||||
* /foo/../bar --> /bar
|
||||
* /foo/../bar/ --> /bar/
|
||||
* /foo/../bar/../baz --> /baz
|
||||
* //foo//./bar --> /foo/bar
|
||||
* /../ --> null
|
||||
* ../foo --> null
|
||||
* foo/bar/.. --> foo/
|
||||
* foo/../../bar --> null
|
||||
* foo/../bar --> bar
|
||||
* //server/foo/../bar --> //server/bar
|
||||
* //server/../bar --> null
|
||||
* C:\foo\..\bar --> C:\bar
|
||||
* C:\..\bar --> null
|
||||
* ~/foo/../bar/ --> ~/bar/
|
||||
* ~/../bar --> null
|
||||
</pre> *
|
||||
* The output will be the same on both Unix and Windows including
|
||||
* the separator character.
|
||||
*
|
||||
* @param filename the filename to normalize, null returns null
|
||||
* @param unixSeparator `true` if a unix separator should
|
||||
* be used or `false` if a windows separator should be used.
|
||||
* @return the normalized filename, or null if invalid
|
||||
*/
|
||||
fun normalize(filename: String, unixSeparator: Boolean): String? {
|
||||
val separator = if (unixSeparator) UNIX_SEPARATOR else WINDOWS_SEPARATOR
|
||||
return doNormalize(filename, separator, true)
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/*
|
||||
* FilenameUtils.java (normalize + dependencies) - Apache 2.0 License
|
||||
* http://commons.apache.org/proper/commons-io/
|
||||
* Copyright 2013 ASF
|
||||
* Authors: Kevin A. Burton, Scott Sanders, Daniel Rall, Christoph.Reck,
|
||||
* Peter Donald, Jeff Turner, Matthew Hawthorne, Martin Cooper,
|
||||
* Jeremias Maerki, Stephen Colebourne
|
||||
*/
|
||||
/**
|
||||
* Normalizes a path, removing double and single dot path steps,
|
||||
* and removing any final directory separator.
|
||||
*
|
||||
*
|
||||
* This method normalizes a path to a standard format.
|
||||
* The input may contain separators in either Unix or Windows format.
|
||||
* The output will contain separators in the format of the system.
|
||||
*
|
||||
*
|
||||
* A trailing slash will be removed.
|
||||
* A double slash will be merged to a single slash (but UNC names are handled).
|
||||
* A single dot path segment will be removed.
|
||||
* A double dot will cause that path segment and the one before to be removed.
|
||||
* If the double dot has no parent path segment to work with, `null`
|
||||
* is returned.
|
||||
*
|
||||
*
|
||||
* The output will be the same on both Unix and Windows except
|
||||
* for the separator character.
|
||||
* <pre>
|
||||
* /foo// --> /foo
|
||||
* /foo/./ --> /foo
|
||||
* /foo/../bar --> /bar
|
||||
* /foo/../bar/ --> /bar
|
||||
* /foo/../bar/../baz --> /baz
|
||||
* //foo//./bar --> /foo/bar
|
||||
* /../ --> null
|
||||
* ../foo --> null
|
||||
* foo/bar/.. --> foo
|
||||
* foo/../../bar --> null
|
||||
* foo/../bar --> bar
|
||||
* //server/foo/../bar --> //server/bar
|
||||
* //server/../bar --> null
|
||||
* C:\foo\..\bar --> C:\bar
|
||||
* C:\..\bar --> null
|
||||
* ~/foo/../bar/ --> ~/bar
|
||||
* ~/../bar --> null
|
||||
</pre> *
|
||||
* (Note the file separator returned will be correct for Windows/Unix)
|
||||
*
|
||||
* @param filename the filename to normalize, null returns null
|
||||
* @return the normalized filename, or null if invalid
|
||||
*/
|
||||
fun normalizeNoEndSeparator(filename: String): String? {
|
||||
return doNormalize(filename, SYSTEM_SEPARATOR, false)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* FilenameUtils.java (normalize + dependencies) - Apache 2.0 License
|
||||
* http://commons.apache.org/proper/commons-io/
|
||||
* Copyright 2013 ASF
|
||||
* Authors: Kevin A. Burton, Scott Sanders, Daniel Rall, Christoph.Reck,
|
||||
* Peter Donald, Jeff Turner, Matthew Hawthorne, Martin Cooper,
|
||||
* Jeremias Maerki, Stephen Colebourne
|
||||
*/
|
||||
/**
|
||||
* Normalizes a path, removing double and single dot path steps,
|
||||
* and removing any final directory separator.
|
||||
*
|
||||
*
|
||||
* This method normalizes a path to a standard format.
|
||||
* The input may contain separators in either Unix or Windows format.
|
||||
* The output will contain separators in the format specified.
|
||||
*
|
||||
*
|
||||
* A trailing slash will be removed.
|
||||
* A double slash will be merged to a single slash (but UNC names are handled).
|
||||
* A single dot path segment will be removed.
|
||||
* A double dot will cause that path segment and the one before to be removed.
|
||||
* If the double dot has no parent path segment to work with, `null`
|
||||
* is returned.
|
||||
*
|
||||
*
|
||||
* The output will be the same on both Unix and Windows including
|
||||
* the separator character.
|
||||
* <pre>
|
||||
* /foo// --> /foo
|
||||
* /foo/./ --> /foo
|
||||
* /foo/../bar --> /bar
|
||||
* /foo/../bar/ --> /bar
|
||||
* /foo/../bar/../baz --> /baz
|
||||
* //foo//./bar --> /foo/bar
|
||||
* /../ --> null
|
||||
* ../foo --> null
|
||||
* foo/bar/.. --> foo
|
||||
* foo/../../bar --> null
|
||||
* foo/../bar --> bar
|
||||
* //server/foo/../bar --> //server/bar
|
||||
* //server/../bar --> null
|
||||
* C:\foo\..\bar --> C:\bar
|
||||
* C:\..\bar --> null
|
||||
* ~/foo/../bar/ --> ~/bar
|
||||
* ~/../bar --> null
|
||||
</pre> *
|
||||
*
|
||||
* @param filename the filename to normalize, null returns null
|
||||
* @param unixSeparator `true` if a unix separator should
|
||||
* be used or `false` if a windows separtor should be used.
|
||||
* @return the normalized filename, or null if invalid
|
||||
*/
|
||||
fun normalizeNoEndSeparator(filename: String, unixSeparator: Boolean): String? {
|
||||
val separator = if (unixSeparator) UNIX_SEPARATOR else WINDOWS_SEPARATOR
|
||||
return doNormalize(filename, separator, false)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* FilenameUtils.java (normalize + dependencies) - Apache 2.0 License
|
||||
* http://commons.apache.org/proper/commons-io/
|
||||
* Copyright 2013 ASF
|
||||
* Authors: Kevin A. Burton, Scott Sanders, Daniel Rall, Christoph.Reck,
|
||||
* Peter Donald, Jeff Turner, Matthew Hawthorne, Martin Cooper,
|
||||
* Jeremias Maerki, Stephen Colebourne
|
||||
*/
|
||||
/**
|
||||
* Internal method to perform the normalization.
|
||||
*
|
||||
* @param filename the filename
|
||||
* @param separator The separator character to use
|
||||
* @param keepSeparator true to keep the final separator
|
||||
* @return the normalized filename
|
||||
*/
|
||||
private fun doNormalize(filename: String, separator: Char, keepSeparator: Boolean): String? {
|
||||
var size = filename.length
|
||||
if (size == 0) {
|
||||
return filename
|
||||
}
|
||||
|
||||
val prefix = getPrefixLength(filename)
|
||||
if (prefix < 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
val array = CharArray(size + 2) // +1 for possible extra slash, +2 for arraycopy
|
||||
filename.toCharArray(array, 0, 0, filename.length)
|
||||
|
||||
// fix separators throughout
|
||||
val otherSeparator = if (separator == SYSTEM_SEPARATOR) OTHER_SEPARATOR else SYSTEM_SEPARATOR
|
||||
for (i in array.indices) {
|
||||
if (array[i] == otherSeparator) {
|
||||
array[i] = separator
|
||||
}
|
||||
}
|
||||
|
||||
// add extra separator on the end to simplify code below
|
||||
var lastIsDirectory = true
|
||||
if (array[size - 1] != separator) {
|
||||
array[size++] = separator
|
||||
lastIsDirectory = false
|
||||
}
|
||||
|
||||
// adjoining slashes
|
||||
run {
|
||||
var i = prefix + 1
|
||||
while (i < size) {
|
||||
if (array[i] == separator && array[i - 1] == separator) {
|
||||
System.arraycopy(array, i, array, i - 1, size - i)
|
||||
size--
|
||||
i--
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
// dot slash
|
||||
var i = prefix + 1
|
||||
while (i < size) {
|
||||
if (array[i] == separator && array[i - 1] == '.' && (i == prefix + 1 || array[i - 2] == separator)) {
|
||||
if (i == size - 1) {
|
||||
lastIsDirectory = true
|
||||
}
|
||||
System.arraycopy(array, i + 1, array, i - 1, size - i)
|
||||
size -= 2
|
||||
i--
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
i = prefix + 2
|
||||
outer@ while (i < size) {
|
||||
if (array[i] == separator && array[i - 1] == '.' && array[i - 2] == '.' && (i == prefix + 2 || array[i - 3] == separator)) {
|
||||
if (i == prefix + 2) {
|
||||
return null
|
||||
}
|
||||
if (i == size - 1) {
|
||||
lastIsDirectory = true
|
||||
}
|
||||
var j: Int
|
||||
j = i - 4
|
||||
while (j >= prefix) {
|
||||
if (array[j] == separator) {
|
||||
// remove b/../ from a/b/../c
|
||||
System.arraycopy(array, i + 1, array, j + 1, size - i)
|
||||
size -= i - j
|
||||
i = j + 1
|
||||
i++
|
||||
continue@outer
|
||||
}
|
||||
j--
|
||||
}
|
||||
// remove a/../ from a/../c
|
||||
System.arraycopy(array, i + 1, array, prefix, size - i)
|
||||
size -= i + 1 - prefix
|
||||
i = prefix + 1
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
if (size <= 0) { // should never be less than 0
|
||||
return ""
|
||||
}
|
||||
|
||||
if (size <= prefix) { // should never be less than prefix
|
||||
return String(array, 0, size)
|
||||
}
|
||||
|
||||
return if (lastIsDirectory && keepSeparator) {
|
||||
String(array, 0, size) // keep trailing separator
|
||||
} else String(array, 0, size - 1)
|
||||
// lose trailing separator
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/*
|
||||
* FilenameUtils.java (normalize + dependencies) - Apache 2.0 License
|
||||
* http://commons.apache.org/proper/commons-io/
|
||||
* Copyright 2013 ASF
|
||||
* Authors: Kevin A. Burton, Scott Sanders, Daniel Rall, Christoph.Reck,
|
||||
* Peter Donald, Jeff Turner, Matthew Hawthorne, Martin Cooper,
|
||||
* Jeremias Maerki, Stephen Colebourne
|
||||
*/
|
||||
/**
|
||||
* Returns the length of the filename prefix, such as `C:/` or `~/`.
|
||||
*
|
||||
*
|
||||
* This method will handle a file in either Unix or Windows format.
|
||||
*
|
||||
*
|
||||
* The prefix length includes the first slash in the full filename
|
||||
* if applicable. Thus, it is possible that the length returned is greater
|
||||
* than the length of the input string.
|
||||
````
|
||||
Windows:
|
||||
a\b\c.txt --> "" --> relative
|
||||
\a\b\c.txt --> "\" --> current drive absolute
|
||||
C:a\b\c.txt --> "C:" --> drive relative
|
||||
C:\a\b\c.txt --> "C:\" --> absolute
|
||||
\\server\a\b\c.txt --> "\\server\" --> UNC
|
||||
|
||||
Unix:
|
||||
a/b/c.txt --> "" --> relative
|
||||
/a/b/c.txt --> "/" --> absolute
|
||||
~/a/b/c.txt --> "~/" --> current user
|
||||
~ --> "~/" --> current user (slash added)
|
||||
~user/a/b/c.txt --> "~user/" --> named user
|
||||
~user --> "~user/" --> named user (slash added)
|
||||
````
|
||||
*
|
||||
*
|
||||
*
|
||||
* The output will be the same irrespective of the machine that the code is running on.
|
||||
* ie. both Unix and Windows prefixes are matched regardless.
|
||||
*
|
||||
* @param filename the filename to find the prefix in, null returns -1
|
||||
* @return the length of the prefix, -1 if invalid or null
|
||||
*/
|
||||
fun getPrefixLength(filename: String): Int {
|
||||
val len = filename.length
|
||||
if (len == 0) {
|
||||
return 0
|
||||
}
|
||||
|
||||
var ch0 = filename[0]
|
||||
if (ch0 == ':') {
|
||||
return -1
|
||||
}
|
||||
|
||||
return if (len == 1) {
|
||||
if (ch0 == '~') {
|
||||
return 2 // return a length greater than the input
|
||||
}
|
||||
if (isSeparator(ch0)) 1 else 0
|
||||
} else {
|
||||
if (ch0 == '~') {
|
||||
var posUnix = filename.indexOf(UNIX_SEPARATOR, 1)
|
||||
var posWin = filename.indexOf(WINDOWS_SEPARATOR, 1)
|
||||
if (posUnix == -1 && posWin == -1) {
|
||||
return len + 1 // return a length greater than the input
|
||||
}
|
||||
posUnix = if (posUnix == -1) posWin else posUnix
|
||||
posWin = if (posWin == -1) posUnix else posWin
|
||||
return Math.min(posUnix, posWin) + 1
|
||||
}
|
||||
val ch1 = filename[1]
|
||||
if (ch1 == ':') {
|
||||
ch0 = ch0.uppercaseChar()
|
||||
if (ch0 >= 'A' && ch0 <= 'Z') {
|
||||
return if (len == 2 || isSeparator(filename[2]) == false) {
|
||||
2
|
||||
} else 3
|
||||
}
|
||||
-1
|
||||
} else if (isSeparator(ch0) && isSeparator(ch1)) {
|
||||
var posUnix = filename.indexOf(UNIX_SEPARATOR, 2)
|
||||
var posWin = filename.indexOf(WINDOWS_SEPARATOR, 2)
|
||||
if (posUnix == -1 && posWin == -1 || posUnix == 2 || posWin == 2) {
|
||||
return -1
|
||||
}
|
||||
posUnix = if (posUnix == -1) posWin else posUnix
|
||||
posWin = if (posWin == -1) posUnix else posWin
|
||||
Math.min(posUnix, posWin) + 1
|
||||
} else {
|
||||
if (isSeparator(ch0)) 1 else 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/*
|
||||
* FilenameUtils.java (normalize + dependencies) - Apache 2.0 License
|
||||
* http://commons.apache.org/proper/commons-io/
|
||||
* Copyright 2013 ASF
|
||||
* Authors: Kevin A. Burton, Scott Sanders, Daniel Rall, Christoph.Reck,
|
||||
* Peter Donald, Jeff Turner, Matthew Hawthorne, Martin Cooper,
|
||||
* Jeremias Maerki, Stephen Colebourne
|
||||
*/
|
||||
/**
|
||||
* Checks if the character is a separator.
|
||||
*
|
||||
* @param ch the character to check
|
||||
* @return true if it is a separator character
|
||||
*/
|
||||
private fun isSeparator(ch: Char): Boolean {
|
||||
return ch == UNIX_SEPARATOR || ch == WINDOWS_SEPARATOR
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the extension of a file (text after the last '.')
|
||||
*
|
||||
* @return "" if there is no extension
|
||||
*/
|
||||
fun getExtension(fileName: String): String {
|
||||
val dot = fileName.lastIndexOf('.')
|
||||
return if (dot > -1) {
|
||||
fileName.substring(dot + 1)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of a file that is before the extension (text before the last '.')
|
||||
*
|
||||
* @return non-null
|
||||
*/
|
||||
fun getNameWithoutExtension(fileName: String): String {
|
||||
val dot = fileName.lastIndexOf('.')
|
||||
return if (dot > -1) {
|
||||
fileName.substring(0, dot)
|
||||
} else {
|
||||
fileName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,296 +0,0 @@
|
|||
/*
|
||||
* Copyright 2014 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.FontFormatException;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.font.FontRenderContext;
|
||||
import java.awt.font.GlyphVector;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.Enumeration;
|
||||
|
||||
import dorkbox.os.OS;
|
||||
|
||||
/**
|
||||
* Java Font utilities
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public
|
||||
class FontUtil {
|
||||
/** Default location where all the fonts are stored */
|
||||
public static volatile String FONTS_LOCATION = OS.INSTANCE.getProperty(FontUtil.class.getCanonicalName() + ".FONTS_LOCATION", "resources/fonts");
|
||||
|
||||
|
||||
/** All of the fonts in the {@link #FONTS_LOCATION} will be loaded by the Font manager */
|
||||
public static
|
||||
void loadAllFonts() {
|
||||
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||
Enumeration<URL> fonts = LocationResolver.getResources(FONTS_LOCATION);
|
||||
|
||||
if (fonts.hasMoreElements()) {
|
||||
// skip the FIRST one, since we always know that the first one is the directory we asked for
|
||||
fonts.nextElement();
|
||||
|
||||
while (fonts.hasMoreElements()) {
|
||||
URL url = fonts.nextElement();
|
||||
InputStream is = null;
|
||||
|
||||
//noinspection TryWithIdenticalCatches
|
||||
try {
|
||||
String path = url.toURI()
|
||||
.getPath();
|
||||
|
||||
// only support TTF and OTF fonts (java 7+).
|
||||
if (path.endsWith(".ttf") || path.endsWith(".otf")) {
|
||||
is = url.openStream();
|
||||
|
||||
java.awt.Font newFont = java.awt.Font.createFont(java.awt.Font.TRUETYPE_FONT, is);
|
||||
// fonts that ALREADY exist are not re-registered
|
||||
ge.registerFont(newFont);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} catch (URISyntaxException e) {
|
||||
e.printStackTrace();
|
||||
} catch (FontFormatException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (is != null) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets (or creates) a Font based on a specific system property. Remember: the FontManager caches system/loaded fonts, so we don't need
|
||||
* to ALSO cache them as well. see: https://stackoverflow.com/questions/6102602/java-awt-is-font-a-lightweight-object
|
||||
* <p>
|
||||
* Also remember that if requesting a BOLD hint for a font, the system will look for a font that is BOLD. If none are found, it
|
||||
* will then apply transforms to the specified font to create a font that is bold. Specifying a bold name AND a bold hint will not
|
||||
* "double bold" the font
|
||||
* <p></p>
|
||||
* For example:
|
||||
* <p>
|
||||
*
|
||||
* Font titleTextFont = FontUtil.parseFont("Source Code Pro Bold 16");
|
||||
*
|
||||
* @param fontInfo This is the font "name style size", as a string. For example "Source Code Pro Bold BOLD 16"
|
||||
*
|
||||
* @return the specified font
|
||||
*/
|
||||
public static
|
||||
java.awt.Font parseFont(final String fontInfo) {
|
||||
try {
|
||||
final int sizeIndex = fontInfo.lastIndexOf(" ");
|
||||
|
||||
String size = fontInfo.substring(sizeIndex + 1);
|
||||
|
||||
// hint is at most 6 (ITALIC) before sizeIndex - we can use this to our benefit.
|
||||
int styleIndex = fontInfo.indexOf(" ", sizeIndex - 7);
|
||||
String styleString = fontInfo.substring(styleIndex + 1, sizeIndex);
|
||||
int style = java.awt.Font.PLAIN;
|
||||
|
||||
if (styleString.equalsIgnoreCase("bold")) {
|
||||
style = java.awt.Font.BOLD;
|
||||
}
|
||||
else if (styleString.equalsIgnoreCase("italic")) {
|
||||
style = java.awt.Font.ITALIC;
|
||||
}
|
||||
|
||||
String fontName = fontInfo.substring(0, styleIndex);
|
||||
|
||||
// this can be WRONG, in which case it will just error out
|
||||
//noinspection MagicConstant
|
||||
return new java.awt.Font(fontName, style, Integer.parseInt(size));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unable to load font info from '" + fontInfo + "'", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the correct font for a specified pixel height. This measures the maximum font height possible for the specified font. This
|
||||
* can be different than the alpha-numeric height.
|
||||
*
|
||||
* @param font the font we are checking
|
||||
* @param height the height in pixels we want to get as close as possible to
|
||||
*
|
||||
* @return the font (derived from the specified font) that is as close as possible to the requested height. If our font-size is less
|
||||
* than the height, then the approach is from the low size (so the returned font will always fit inside the box)
|
||||
*/
|
||||
public static
|
||||
java.awt.Font getFontForSpecificHeight(final java.awt.Font font, final int height) {
|
||||
int size = font.getSize();
|
||||
Boolean lastAction = null;
|
||||
|
||||
while (true) {
|
||||
java.awt.Font fontCheck = new java.awt.Font(font.getName(), java.awt.Font.PLAIN, size);
|
||||
int maxFontHeight = getMaxFontHeight(fontCheck);
|
||||
|
||||
if (maxFontHeight < height && lastAction != Boolean.FALSE) {
|
||||
size++;
|
||||
lastAction = Boolean.TRUE;
|
||||
} else if (maxFontHeight > height && lastAction != Boolean.TRUE) {
|
||||
size--;
|
||||
lastAction = Boolean.FALSE;
|
||||
} else {
|
||||
// either we are the exact size, or we are ONE font size to big/small (depending on what our initial guess was)
|
||||
return fontCheck;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the specified font height for a specific string, as rendered on the screen.
|
||||
*
|
||||
* @param font the font to use for rendering the string
|
||||
* @param string the string used to get the height
|
||||
*
|
||||
* @return the height of the string
|
||||
*/
|
||||
public static
|
||||
int getFontHeight(final java.awt.Font font, final String string) {
|
||||
BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
|
||||
|
||||
Graphics2D g = image.createGraphics();
|
||||
FontRenderContext frc = g.getFontRenderContext();
|
||||
|
||||
GlyphVector gv = font.createGlyphVector(frc, string);
|
||||
int height = gv.getPixelBounds(null, 0, 0).height;
|
||||
g.dispose();
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the specified font width for a specific string, as rendered on the screen.
|
||||
*
|
||||
* @param font the font to use for rendering the string
|
||||
* @param string the string used to get the width
|
||||
*
|
||||
* @return the width of the string
|
||||
*/
|
||||
public static
|
||||
int getFontWidth(final java.awt.Font font, final String string) {
|
||||
BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
|
||||
|
||||
Graphics2D g = image.createGraphics();
|
||||
FontRenderContext frc = g.getFontRenderContext();
|
||||
|
||||
GlyphVector gv = font.createGlyphVector(frc, string);
|
||||
int width = gv.getPixelBounds(null, 0, 0).width;
|
||||
g.dispose();
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum font height used by alpha-numeric characters ONLY, as recorded by the font.
|
||||
*/
|
||||
public static
|
||||
int getAlphaNumericFontHeight(final java.awt.Font font) {
|
||||
// Because font metrics is based on a graphics context, we need to create a small, temporary image to determine the width and height
|
||||
BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g = image.createGraphics();
|
||||
|
||||
FontMetrics metrics = g.getFontMetrics(font);
|
||||
int height = metrics.getAscent() + metrics.getDescent();
|
||||
g.dispose();
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum font height used by of ALL characters, as recorded by the font.
|
||||
*/
|
||||
public static
|
||||
int getMaxFontHeight(final java.awt.Font font) {
|
||||
// Because font metrics is based on a graphics context, we need to create a small, temporary image to determine the width and height
|
||||
BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g = image.createGraphics();
|
||||
|
||||
FontMetrics metrics = g.getFontMetrics(font);
|
||||
int height = metrics.getMaxAscent() + metrics.getMaxDescent();
|
||||
|
||||
g.dispose();
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the specified text (with a font) and as an image
|
||||
*
|
||||
* @param font the specified font to render the image
|
||||
* @return a BufferedImage of the specified text, font, and color
|
||||
*/
|
||||
public static
|
||||
BufferedImage getFontAsImage(final java.awt.Font font, String text, Color foregroundColor) {
|
||||
// Because font metrics is based on a graphics context, we need to create a small, temporary image to determine the width and height
|
||||
BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2d = img.createGraphics();
|
||||
g2d.setFont(font);
|
||||
|
||||
FontMetrics fm = g2d.getFontMetrics();
|
||||
int width = fm.stringWidth(text);
|
||||
int height = fm.getHeight();
|
||||
g2d.dispose();
|
||||
|
||||
// make it square
|
||||
if (width > height) {
|
||||
height = width;
|
||||
} else {
|
||||
width = height;
|
||||
}
|
||||
|
||||
img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
||||
g2d = img.createGraphics();
|
||||
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
|
||||
|
||||
g2d.setFont(font);
|
||||
fm = g2d.getFontMetrics();
|
||||
|
||||
g2d.setColor(foregroundColor);
|
||||
|
||||
// width/4 centers the text in the image
|
||||
g2d.drawString(text, width/4.0f, fm.getAscent());
|
||||
g2d.dispose();
|
||||
|
||||
return img;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,257 @@
|
|||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util
|
||||
|
||||
import dorkbox.os.OS.getProperty
|
||||
import java.awt.*
|
||||
import java.awt.image.BufferedImage
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.net.URISyntaxException
|
||||
|
||||
/**
|
||||
* Java Font utilities
|
||||
*/
|
||||
@Suppress("unused")
|
||||
object FontUtil {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = Sys.version
|
||||
|
||||
/** Default location where all the fonts are stored */
|
||||
@Volatile
|
||||
var FONTS_LOCATION = getProperty(FontUtil::class.java.canonicalName + ".FONTS_LOCATION", "resources/fonts")
|
||||
|
||||
/** All of the fonts in the [.FONTS_LOCATION] will be loaded by the Font manager */
|
||||
fun loadAllFonts() {
|
||||
val ge = GraphicsEnvironment.getLocalGraphicsEnvironment()
|
||||
val fonts = LocationResolver.getResources(FONTS_LOCATION) ?: return
|
||||
if (fonts.hasMoreElements()) {
|
||||
// skip the FIRST one, since we always know that the first one is the directory we asked for
|
||||
fonts.nextElement()
|
||||
while (fonts.hasMoreElements()) {
|
||||
val url = fonts.nextElement()
|
||||
var `is`: InputStream? = null
|
||||
try {
|
||||
val path = url.toURI().path
|
||||
|
||||
// only support TTF and OTF fonts (java 7+).
|
||||
if (path.endsWith(".ttf") || path.endsWith(".otf")) {
|
||||
`is` = url.openStream()
|
||||
val newFont = Font.createFont(Font.TRUETYPE_FONT, `is`)
|
||||
// fonts that ALREADY exist are not re-registered
|
||||
ge.registerFont(newFont)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: URISyntaxException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: FontFormatException) {
|
||||
e.printStackTrace()
|
||||
} finally {
|
||||
if (`is` != null) {
|
||||
try {
|
||||
`is`.close()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets (or creates) a Font based on a specific system property. Remember: the FontManager caches system/loaded fonts, so we don't need
|
||||
* to ALSO cache them as well. see: https://stackoverflow.com/questions/6102602/java-awt-is-font-a-lightweight-object
|
||||
*
|
||||
*
|
||||
* Also remember that if requesting a BOLD hint for a font, the system will look for a font that is BOLD. If none are found, it
|
||||
* will then apply transforms to the specified font to create a font that is bold. Specifying a bold name AND a bold hint will not
|
||||
* "double bold" the font
|
||||
*
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
*
|
||||
*
|
||||
* Font titleTextFont = FontUtil.parseFont("Source Code Pro Bold 16");
|
||||
*
|
||||
* @param fontInfo This is the font "name style size", as a string. For example "Source Code Pro Bold BOLD 16"
|
||||
*
|
||||
* @return the specified font
|
||||
*/
|
||||
fun parseFont(fontInfo: String): Font {
|
||||
return try {
|
||||
val sizeIndex = fontInfo.lastIndexOf(" ")
|
||||
val size = fontInfo.substring(sizeIndex + 1)
|
||||
|
||||
// hint is at most 6 (ITALIC) before sizeIndex - we can use this to our benefit.
|
||||
val styleIndex = fontInfo.indexOf(" ", sizeIndex - 7)
|
||||
val styleString = fontInfo.substring(styleIndex + 1, sizeIndex)
|
||||
var style = Font.PLAIN
|
||||
if (styleString.equals("bold", ignoreCase = true)) {
|
||||
style = Font.BOLD
|
||||
} else if (styleString.equals("italic", ignoreCase = true)) {
|
||||
style = Font.ITALIC
|
||||
}
|
||||
val fontName = fontInfo.substring(0, styleIndex)
|
||||
|
||||
// this can be WRONG, in which case it will just error out
|
||||
Font(fontName, style, size.toInt())
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException("Unable to load font info from '$fontInfo'", e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the correct font for a specified pixel height. This measures the maximum font height possible for the specified font. This
|
||||
* can be different than the alpha-numeric height.
|
||||
*
|
||||
* @param font the font we are checking
|
||||
* @param height the height in pixels we want to get as close as possible to
|
||||
*
|
||||
* @return the font (derived from the specified font) that is as close as possible to the requested height. If our font-size is less
|
||||
* than the height, then the approach is from the low size (so the returned font will always fit inside the box)
|
||||
*/
|
||||
fun getFontForSpecificHeight(font: Font, height: Int): Font {
|
||||
var size = font.size
|
||||
var lastAction: Boolean? = null
|
||||
while (true) {
|
||||
val fontCheck = Font(font.name, Font.PLAIN, size)
|
||||
val maxFontHeight = getMaxFontHeight(fontCheck)
|
||||
lastAction = if (maxFontHeight < height && lastAction !== java.lang.Boolean.FALSE) {
|
||||
size++
|
||||
java.lang.Boolean.TRUE
|
||||
} else if (maxFontHeight > height && lastAction !== java.lang.Boolean.TRUE) {
|
||||
size--
|
||||
java.lang.Boolean.FALSE
|
||||
} else {
|
||||
// either we are the exact size, or we are ONE font size to big/small (depending on what our initial guess was)
|
||||
return fontCheck
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the specified font height for a specific string, as rendered on the screen.
|
||||
*
|
||||
* @param font the font to use for rendering the string
|
||||
* @param string the string used to get the height
|
||||
*
|
||||
* @return the height of the string
|
||||
*/
|
||||
fun getFontHeight(font: Font, string: String): Int {
|
||||
val image = BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB)
|
||||
val g = image.createGraphics()
|
||||
val frc = g.fontRenderContext
|
||||
val gv = font.createGlyphVector(frc, string)
|
||||
val height = gv.getPixelBounds(null, 0f, 0f).height
|
||||
g.dispose()
|
||||
return height
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the specified font width for a specific string, as rendered on the screen.
|
||||
*
|
||||
* @param font the font to use for rendering the string
|
||||
* @param string the string used to get the width
|
||||
*
|
||||
* @return the width of the string
|
||||
*/
|
||||
fun getFontWidth(font: Font, string: String): Int {
|
||||
val image = BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB)
|
||||
val g = image.createGraphics()
|
||||
val frc = g.fontRenderContext
|
||||
val gv = font.createGlyphVector(frc, string)
|
||||
val width = gv.getPixelBounds(null, 0f, 0f).width
|
||||
g.dispose()
|
||||
return width
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum font height used by alpha-numeric characters ONLY, as recorded by the font.
|
||||
*/
|
||||
fun getAlphaNumericFontHeight(font: Font): Int {
|
||||
// Because font metrics is based on a graphics context, we need to create a small, temporary image to determine the width and height
|
||||
val image = BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB)
|
||||
val g = image.createGraphics()
|
||||
val metrics = g.getFontMetrics(font)
|
||||
val height = metrics.ascent + metrics.descent
|
||||
g.dispose()
|
||||
return height
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum font height used by of ALL characters, as recorded by the font.
|
||||
*/
|
||||
fun getMaxFontHeight(font: Font): Int {
|
||||
// Because font metrics is based on a graphics context, we need to create a small, temporary image to determine the width and height
|
||||
val image = BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB)
|
||||
val g = image.createGraphics()
|
||||
val metrics = g.getFontMetrics(font)
|
||||
val height = metrics.maxAscent + metrics.maxDescent
|
||||
g.dispose()
|
||||
return height
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the specified text (with a font) and as an image
|
||||
*
|
||||
* @param font the specified font to render the image
|
||||
* @return a BufferedImage of the specified text, font, and color
|
||||
*/
|
||||
fun getFontAsImage(font: Font, text: String, foregroundColor: Color): BufferedImage {
|
||||
// Because font metrics is based on a graphics context, we need to create a small, temporary image to determine the width and height
|
||||
var img = BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB)
|
||||
var g2d = img.createGraphics()
|
||||
g2d.font = font
|
||||
|
||||
var fm = g2d.fontMetrics
|
||||
var width = fm.stringWidth(text)
|
||||
var height = fm.height
|
||||
g2d.dispose()
|
||||
|
||||
// make it square
|
||||
if (width > height) {
|
||||
height = width
|
||||
} else {
|
||||
width = height
|
||||
}
|
||||
|
||||
img = BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB)
|
||||
g2d = img.createGraphics()
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY)
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
|
||||
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON)
|
||||
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY)
|
||||
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE)
|
||||
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON)
|
||||
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR)
|
||||
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY)
|
||||
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE)
|
||||
|
||||
g2d.font = font
|
||||
fm = g2d.fontMetrics
|
||||
g2d.color = foregroundColor
|
||||
|
||||
// width/4 centers the text in the image
|
||||
g2d.drawString(text, width / 4.0f, fm.ascent.toFloat())
|
||||
g2d.dispose()
|
||||
return img
|
||||
}
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util;
|
||||
|
||||
import org.bouncycastle.crypto.digests.SHA256Digest;
|
||||
|
||||
/**
|
||||
* Bouncycastle hashes!
|
||||
*/
|
||||
public
|
||||
class HashUtil {
|
||||
/**
|
||||
* gets the SHA256 hash + SALT of the specified username, as UTF-16
|
||||
*/
|
||||
public static
|
||||
byte[] getSha256WithSalt(String username, byte[] saltBytes) {
|
||||
if (username == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] charToBytes = Sys.charToBytes16(username.toCharArray());
|
||||
byte[] userNameWithSalt = Sys.concatBytes(charToBytes, saltBytes);
|
||||
|
||||
|
||||
SHA256Digest sha256 = new SHA256Digest();
|
||||
byte[] usernameHashBytes = new byte[sha256.getDigestSize()];
|
||||
sha256.update(userNameWithSalt, 0, userNameWithSalt.length);
|
||||
sha256.doFinal(usernameHashBytes, 0);
|
||||
|
||||
return usernameHashBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* gets the SHA256 hash of the specified string, as UTF-16
|
||||
*/
|
||||
public static
|
||||
byte[] getSha256(String string) {
|
||||
byte[] charToBytes = Sys.charToBytes16(string.toCharArray());
|
||||
|
||||
SHA256Digest sha256 = new SHA256Digest();
|
||||
byte[] usernameHashBytes = new byte[sha256.getDigestSize()];
|
||||
sha256.update(charToBytes, 0, charToBytes.length);
|
||||
sha256.doFinal(usernameHashBytes, 0);
|
||||
|
||||
return usernameHashBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* gets the SHA256 hash of the specified byte array
|
||||
*/
|
||||
public static
|
||||
byte[] getSha256(byte[] bytes) {
|
||||
|
||||
SHA256Digest sha256 = new SHA256Digest();
|
||||
byte[] hashBytes = new byte[sha256.getDigestSize()];
|
||||
sha256.update(bytes, 0, bytes.length);
|
||||
sha256.doFinal(hashBytes, 0);
|
||||
|
||||
return hashBytes;
|
||||
}
|
||||
|
||||
public static
|
||||
byte[] getSha256WithSalt(byte[] bytes, byte[] saltBytes) {
|
||||
if (bytes == null || saltBytes == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] bytesWithSalt = dorkbox.util.Sys.concatBytes(bytes, saltBytes);
|
||||
|
||||
SHA256Digest sha256 = new SHA256Digest();
|
||||
byte[] usernameHashBytes = new byte[sha256.getDigestSize()];
|
||||
sha256.update(bytesWithSalt, 0, bytesWithSalt.length);
|
||||
sha256.doFinal(usernameHashBytes, 0);
|
||||
|
||||
return usernameHashBytes;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -25,13 +25,13 @@ import javax.imageio.stream.ImageInputStream;
|
|||
|
||||
@SuppressWarnings({"unused", "Duplicates"})
|
||||
// deprecated. use kotlin.
|
||||
@Deprecated
|
||||
public
|
||||
class IO {
|
||||
/**
|
||||
* Convenient close for a Closeable.
|
||||
*/
|
||||
@SuppressWarnings("Duplicates")
|
||||
@Deprecated
|
||||
public static
|
||||
void close(final Closeable closeable) {
|
||||
if (closeable != null) {
|
||||
|
@ -48,6 +48,7 @@ class IO {
|
|||
* Convenient close for a Closeable.
|
||||
*/
|
||||
@SuppressWarnings("Duplicates")
|
||||
@Deprecated
|
||||
public static
|
||||
void closeQuietly(final Closeable closeable) {
|
||||
if (closeable != null) {
|
||||
|
@ -61,8 +62,9 @@ class IO {
|
|||
/**
|
||||
* Copy the contents of the input stream to the output stream.
|
||||
* <p>
|
||||
* DOES NOT CLOSE THE STEAMS!
|
||||
* DOES NOT CLOSE THE STREAMS!
|
||||
*/
|
||||
@Deprecated
|
||||
public static
|
||||
<T extends OutputStream> T copyStream(final InputStream inputStream, final T outputStream) throws IOException {
|
||||
byte[] buffer = new byte[4096];
|
||||
|
@ -79,8 +81,9 @@ class IO {
|
|||
/**
|
||||
* Copy the contents of the input stream to the output stream.
|
||||
* <p>
|
||||
* DOES NOT CLOSE THE STEAMS!
|
||||
* DOES NOT CLOSE THE STREAMS!
|
||||
*/
|
||||
@Deprecated
|
||||
public static
|
||||
<T extends OutputStream> T copyStream(final ImageInputStream inputStream, final T outputStream) throws IOException {
|
||||
return copyStream(4096, inputStream, outputStream);
|
||||
|
@ -89,8 +92,9 @@ class IO {
|
|||
/**
|
||||
* Copy the contents of the input stream to the output stream.
|
||||
* <p>
|
||||
* DOES NOT CLOSE THE STEAMS!
|
||||
* DOES NOT CLOSE THE STREAMS!
|
||||
*/
|
||||
@Deprecated
|
||||
public static
|
||||
<T extends OutputStream> T copyStream(final int bufferSize, final ImageInputStream inputStream, final T outputStream) throws IOException {
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
|
@ -107,8 +111,9 @@ class IO {
|
|||
/**
|
||||
* Copy the contents of the input stream to a new output stream.
|
||||
* <p>
|
||||
* DOES NOT CLOSE THE STEAMS!
|
||||
* DOES NOT CLOSE THE STREAMS!
|
||||
*/
|
||||
@Deprecated
|
||||
public static
|
||||
ByteArrayOutputStream copyStream(final InputStream inputStream) throws IOException {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(4096);
|
||||
|
@ -126,8 +131,9 @@ class IO {
|
|||
/**
|
||||
* Copy the contents of the input stream to a new output stream.
|
||||
* <p>
|
||||
* DOES NOT CLOSE THE STEAMS!
|
||||
* DOES NOT CLOSE THE STREAMS!
|
||||
*/
|
||||
@Deprecated
|
||||
public static
|
||||
ByteArrayOutputStream copyStream(final ImageInputStream inputStream) throws IOException {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(4096);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2016 dorkbox, llc
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -145,7 +145,7 @@ class ImageUtil {
|
|||
|
||||
// have to resize the file (and return the new path)
|
||||
|
||||
String extension = FileUtil.INSTANCE.getExtension(fileName);
|
||||
String extension = Sys.INSTANCE.getExtension(fileName);
|
||||
if (extension.isEmpty()) {
|
||||
extension = "png"; // made up
|
||||
}
|
||||
|
@ -162,7 +162,7 @@ class ImageUtil {
|
|||
}
|
||||
else {
|
||||
// suck it out of a URL/Resource (with debugging if necessary)
|
||||
final URL systemResource = LocationResolver.getResource(fileName);
|
||||
final URL systemResource = LocationResolver.Companion.getResource(fileName);
|
||||
image = new ImageIcon(systemResource).getImage();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util
|
||||
|
||||
import org.tukaani.xz.LZMA2Options
|
||||
import org.tukaani.xz.LZMAInputStream
|
||||
import org.tukaani.xz.LZMAOutputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
object LZMA {
|
||||
// https://tukaani.org/xz/java.html
|
||||
@Throws(IOException::class)
|
||||
fun encode(input: InputStream, output: OutputStream) {
|
||||
LZMAOutputStream(output, LZMA2Options(3), true).use { compressionStream ->
|
||||
input.copyTo(compressionStream)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun decode(input: InputStream): ByteArrayOutputStream {
|
||||
val byteArrayOutputStream = ByteArrayOutputStream(8192)
|
||||
LZMAInputStream(input).use { compressedStream -> compressedStream.copyTo(byteArrayOutputStream) }
|
||||
return byteArrayOutputStream
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun decode(input: InputStream, output: OutputStream) {
|
||||
LZMAInputStream(input).use { compressedStream -> compressedStream.copyTo(output) }
|
||||
}
|
||||
}
|
|
@ -1,648 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.net.URLDecoder;
|
||||
import java.security.CodeSource;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.Vector;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import dorkbox.os.OS;
|
||||
|
||||
/**
|
||||
* Convenience methods for working with resource/file/class locations
|
||||
*/
|
||||
public
|
||||
class LocationResolver {
|
||||
private static final Pattern SLASH_PATTERN = Pattern.compile("\\\\");
|
||||
|
||||
private static
|
||||
void log(String message) {
|
||||
System.err.println(prefix() + message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the path. fixes %20 as spaces (in winxp at least). Converts \ -> / (windows slash -> unix slash)
|
||||
*
|
||||
* @return a string pointing to the cleaned path
|
||||
* @throws IOException
|
||||
*/
|
||||
private static
|
||||
String normalizePath(String path) throws IOException {
|
||||
// make sure the slashes are in unix format.
|
||||
path = SLASH_PATTERN.matcher(path)
|
||||
.replaceAll("/");
|
||||
|
||||
// Can have %20 as spaces (in winxp at least). need to convert to proper path from URL
|
||||
return URLDecoder.decode(path, "UTF-8");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the location of the currently loaded jar, or possibly null if it was compiled on the fly
|
||||
*/
|
||||
public static
|
||||
File get() {
|
||||
return get(LocationResolver.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the location that this classfile was loaded from, or possibly null if the class was compiled on the fly
|
||||
*/
|
||||
public static
|
||||
File get(Class<?> clazz) {
|
||||
// Get the location of this class
|
||||
ProtectionDomain pDomain = clazz.getProtectionDomain();
|
||||
CodeSource cSource = pDomain.getCodeSource();
|
||||
|
||||
// file:/X:/workspace/XYZ/classes/ when it's in ide/flat
|
||||
// jar:/X:/workspace/XYZ/jarname.jar when it's jar
|
||||
URL loc = cSource.getLocation();
|
||||
|
||||
// we don't always have a protection domain (for example, when we compile classes on the fly, from memory)
|
||||
if (loc == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Can have %20 as spaces (in winxp at least). need to convert to proper path from URL
|
||||
try {
|
||||
File file = new File(normalizePath(loc.getFile())).getAbsoluteFile()
|
||||
.getCanonicalFile();
|
||||
return file;
|
||||
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException("Unable to decode file path!", e);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unable to get canonical file path!", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a URL of a given resourceName. If the resourceName is a directory, the returned URL will be the URL for the directory.
|
||||
* </p>
|
||||
* This method searches the disk first (via new {@link File#File(String)}, then by {@link ClassLoader#getResource(String)}, then by
|
||||
* {@link ClassLoader#getSystemResource(String)}.
|
||||
*
|
||||
* @param resourceName the resource name to search for
|
||||
*
|
||||
* @return the URL for that given resource name
|
||||
*/
|
||||
public static
|
||||
URL getResource(String resourceName) {
|
||||
try {
|
||||
resourceName = normalizePath(resourceName);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
URL resource = null;
|
||||
|
||||
// 1) maybe it's on disk? priority is disk
|
||||
File file = new File(resourceName);
|
||||
if (file.canRead()) {
|
||||
try {
|
||||
resource = file.toURI()
|
||||
.toURL();
|
||||
} catch (MalformedURLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// 2) is it in the context classloader
|
||||
if (resource == null) {
|
||||
resource = Thread.currentThread()
|
||||
.getContextClassLoader()
|
||||
.getResource(resourceName);
|
||||
}
|
||||
|
||||
// 3) is it in the system classloader
|
||||
if (resource == null) {
|
||||
// maybe it's in the system classloader?
|
||||
resource = ClassLoader.getSystemResource(resourceName);
|
||||
}
|
||||
|
||||
// 4) look for it, and log the output (so we can find or debug it)
|
||||
if (resource == null) {
|
||||
try {
|
||||
searchResource(resourceName);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an enumeration of URLs of a given resourceName. If the resourceName is a directory, the returned list will be the URLs
|
||||
* of the contents of that directory. The first URL will always be the directory URL, as returned by {@link #getResource(String)}.
|
||||
* </p>
|
||||
* This method searches the disk first (via new {@link File#File(String)}, then by {@link ClassLoader#getResources(String)}, then by
|
||||
* {@link ClassLoader#getSystemResources(String)}.
|
||||
*
|
||||
* @param resourceName the resource name to search for
|
||||
*
|
||||
* @return the enumeration of URLs for that given resource name
|
||||
*/
|
||||
public static
|
||||
Enumeration<URL> getResources(String resourceName) {
|
||||
try {
|
||||
resourceName = normalizePath(resourceName);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
Enumeration<URL> resources = null;
|
||||
try {
|
||||
// 1) maybe it's on disk? priority is disk
|
||||
File file = new File(resourceName);
|
||||
if (file.canRead()) {
|
||||
ArrayDeque<URL> urlList = new ArrayDeque<URL>(4);
|
||||
// add self always
|
||||
urlList.add(file.toURI()
|
||||
.toURL());
|
||||
|
||||
if (file.isDirectory()) {
|
||||
// add urls of all children
|
||||
File[] files = file.listFiles();
|
||||
if (files != null) {
|
||||
for (int i = 0, n = files.length; i < n; i++) {
|
||||
urlList.add(files[i].toURI()
|
||||
.toURL());
|
||||
}
|
||||
}
|
||||
}
|
||||
resources = new Vector<URL>(urlList).elements();
|
||||
}
|
||||
|
||||
// 2) is it in the context classloader
|
||||
if (resources == null) {
|
||||
resources = Thread.currentThread()
|
||||
.getContextClassLoader()
|
||||
.getResources(resourceName);
|
||||
}
|
||||
|
||||
// 3) is it in the system classloader
|
||||
if (resources == null) {
|
||||
// maybe it's in the system classloader?
|
||||
resources = ClassLoader.getSystemResources(resourceName);
|
||||
|
||||
}
|
||||
|
||||
// 4) look for it, and log the output (so we can find or debug it)
|
||||
if (resources == null) {
|
||||
searchResource(resourceName);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return resources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the resource as a stream.
|
||||
* <p>
|
||||
* 1) checks the disk in the relative location to the executing app<br/>
|
||||
* 2) Checks the current thread context classloader <br/>
|
||||
* 3) Checks the Classloader system resource
|
||||
*
|
||||
* @param resourceName the name, including path information (Only '\' is valid as the path separator)
|
||||
*
|
||||
* @return the resource stream, if it could be found, otherwise null.
|
||||
*/
|
||||
public static
|
||||
InputStream getResourceAsStream(String resourceName) {
|
||||
try {
|
||||
resourceName = normalizePath(resourceName);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
InputStream resourceAsStream = null;
|
||||
|
||||
// 1) maybe it's on disk? priority is disk
|
||||
if (new File(resourceName).canRead()) {
|
||||
try {
|
||||
resourceAsStream = new FileInputStream(resourceName);
|
||||
} catch (FileNotFoundException e) {
|
||||
// shouldn't happen, but if there is something wonky...
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// 2) maybe it's in the context classloader
|
||||
if (resourceAsStream == null) {
|
||||
resourceAsStream = Thread.currentThread()
|
||||
.getContextClassLoader()
|
||||
.getResourceAsStream(resourceName);
|
||||
}
|
||||
|
||||
// 3) maybe it's in the system classloader
|
||||
if (resourceAsStream == null) {
|
||||
resourceAsStream = ClassLoader.getSystemResourceAsStream(resourceName);
|
||||
}
|
||||
|
||||
|
||||
// 4) look for it, and log the output (so we can find or debug it)
|
||||
if (resourceAsStream == null) {
|
||||
try {
|
||||
searchResource(resourceName);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return resourceAsStream;
|
||||
}
|
||||
|
||||
|
||||
// via RIVEN at JGO. CC0 as far as I can tell.
|
||||
public static
|
||||
void searchResource(String path) throws IOException {
|
||||
try {
|
||||
path = normalizePath(path);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
List<Root> roots = new ArrayList<Root>();
|
||||
|
||||
ClassLoader contextClassLoader = Thread.currentThread()
|
||||
.getContextClassLoader();
|
||||
|
||||
if (contextClassLoader instanceof URLClassLoader) {
|
||||
URL[] urLs = ((URLClassLoader) contextClassLoader).getURLs();
|
||||
for (URL url : urLs) {
|
||||
roots.add(new Root(url));
|
||||
}
|
||||
|
||||
System.err.println();
|
||||
log("SEARCHING: \"" + path + "\"");
|
||||
|
||||
for (int attempt = 1; attempt <= 6; attempt++) {
|
||||
for (Root root : roots) {
|
||||
if (root.search(path, attempt)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log("FAILED: failed to find anything like");
|
||||
log(" \"" + path + "\"");
|
||||
log(" in all classpath entries:");
|
||||
|
||||
for (Root root : roots) {
|
||||
final File entry = root.entry;
|
||||
if (entry != null) {
|
||||
log(" \"" + entry.getAbsolutePath() + "\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IOException("Unable to search for '" + path + "' in the context classloader of type '" + contextClassLoader.getClass() +
|
||||
"'. Please report this issue with as many specific details as possible (OS, Java version, application version");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List directory contents for a resource folder. Not recursive.
|
||||
* This is basically a brute-force implementation.
|
||||
* Works for regular files and also JARs.
|
||||
*
|
||||
* @author Greg Briggs
|
||||
* @param clazz Any java class that lives in the same place as the resources you want.
|
||||
* @param path Should end with "/", but not start with one.
|
||||
* @return Just the name of each member item, not the full paths.
|
||||
*
|
||||
*
|
||||
* @throws URISyntaxException
|
||||
* @throws IOException
|
||||
*/
|
||||
String[] getDirectoryContents(Class clazz, String path) throws URISyntaxException, IOException {
|
||||
URL dirURL = clazz.getClassLoader().getResource(path);
|
||||
if (dirURL != null && dirURL.getProtocol().equals("file")) {
|
||||
/* A file path: easy enough */
|
||||
return new File(dirURL.toURI()).list();
|
||||
}
|
||||
|
||||
if (dirURL == null) {
|
||||
/*
|
||||
* In case of a jar file, we can't actually find a directory.
|
||||
* Have to assume the same jar as clazz.
|
||||
*/
|
||||
String me = clazz.getName().replace(".", "/") + ".class";
|
||||
dirURL = clazz.getClassLoader().getResource(me);
|
||||
}
|
||||
|
||||
if (dirURL.getProtocol()
|
||||
.equals("jar")) {
|
||||
/* A JAR path */
|
||||
String jarPath = dirURL.getPath().substring(5, dirURL.getPath().indexOf("!")); //strip out only the JAR file
|
||||
|
||||
JarFile jar = new JarFile(URLDecoder.decode(jarPath, "UTF-8"));
|
||||
Enumeration<JarEntry> entries = jar.entries(); //gives ALL entries in jar
|
||||
Set<String> result = new HashSet<String>(); //avoid duplicates in case it is a subdirectory
|
||||
|
||||
while (entries.hasMoreElements()) {
|
||||
String name = entries.nextElement().getName();
|
||||
if (name.startsWith(path)) { //filter according to the path
|
||||
String entry = name.substring(path.length());
|
||||
int checkSubdir = entry.indexOf("/");
|
||||
if (checkSubdir >= 0) {
|
||||
// if it is a subdirectory, we just return the directory name
|
||||
entry = entry.substring(0, checkSubdir);
|
||||
}
|
||||
result.add(entry);
|
||||
}
|
||||
}
|
||||
return result.toArray(new String[result.size()]);
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException("Cannot list files for URL " + dirURL);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
private static
|
||||
class Root {
|
||||
final File entry;
|
||||
final List<String> resources = new ArrayList<String>();
|
||||
|
||||
public
|
||||
Root(URL entry) throws IOException {
|
||||
this.entry = visitRoot(entry, resources);
|
||||
}
|
||||
|
||||
public
|
||||
boolean search(String path, int attempt) {
|
||||
try {
|
||||
path = normalizePath(path);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
switch (attempt) {
|
||||
case 1: {
|
||||
for (String resource : resources) {
|
||||
if (path.equals(resource)) {
|
||||
log("SUCCESS: found resource \"" + path + "\" in root: " + entry);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case 2: {
|
||||
for (String resource : resources) {
|
||||
if (path.toLowerCase()
|
||||
.equals(resource.toLowerCase())) {
|
||||
log("FOUND: similarly named resource:");
|
||||
log(" \"" + resource + "\"");
|
||||
log(" in classpath entry:");
|
||||
log(" \"" + entry + "\"");
|
||||
log(" for access use:");
|
||||
log(" getResourceAsStream(\"/" + resource + "\");");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case 3: {
|
||||
for (String resource : resources) {
|
||||
String r1 = path;
|
||||
String r2 = resource;
|
||||
|
||||
if (r1.contains("/")) {
|
||||
r1 = r1.substring(r1.lastIndexOf('/') + 1);
|
||||
}
|
||||
if (r2.contains("/")) {
|
||||
r2 = r2.substring(r2.lastIndexOf('/') + 1);
|
||||
}
|
||||
|
||||
if (r1.equals(r2)) {
|
||||
log("FOUND: mislocated resource:");
|
||||
log(" \"" + resource + "\"");
|
||||
log(" in classpath entry:");
|
||||
log(" \"" + entry + "\"");
|
||||
log(" for access use:");
|
||||
log(" getResourceAsStream(\"/" + resource + "\");");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case 4: {
|
||||
for (String resource : resources) {
|
||||
String r1 = path.toLowerCase();
|
||||
String r2 = resource.toLowerCase();
|
||||
|
||||
if (r1.contains("/")) {
|
||||
r1 = r1.substring(r1.lastIndexOf('/') + 1);
|
||||
}
|
||||
if (r2.contains("/")) {
|
||||
r2 = r2.substring(r2.lastIndexOf('/') + 1);
|
||||
}
|
||||
|
||||
if (r1.equals(r2)) {
|
||||
log("FOUND: mislocated, similarly named resource:");
|
||||
log(" \"" + resource + "\"");
|
||||
log(" in classpath entry:");
|
||||
log(" \"" + entry + "\"");
|
||||
log(" for access use:");
|
||||
log(" getResourceAsStream(\"/" + resource + "\");");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case 5: {
|
||||
for (String resource : resources) {
|
||||
String r1 = path;
|
||||
String r2 = resource;
|
||||
|
||||
if (r1.contains("/")) {
|
||||
r1 = r1.substring(r1.lastIndexOf('/') + 1);
|
||||
}
|
||||
if (r2.contains("/")) {
|
||||
r2 = r2.substring(r2.lastIndexOf('/') + 1);
|
||||
}
|
||||
if (r1.contains(".")) {
|
||||
r1 = r1.substring(0, r1.lastIndexOf('.'));
|
||||
}
|
||||
if (r2.contains(".")) {
|
||||
r2 = r2.substring(0, r2.lastIndexOf('.'));
|
||||
}
|
||||
|
||||
if (r1.equals(r2)) {
|
||||
log("FOUND: resource with different extension:");
|
||||
log(" \"" + resource + "\"");
|
||||
log(" in classpath entry:");
|
||||
log(" \"" + entry + "\"");
|
||||
log(" for access use:");
|
||||
log(" getResourceAsStream(\"/" + resource + "\");");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case 6: {
|
||||
for (String resource : resources) {
|
||||
String r1 = path.toLowerCase();
|
||||
String r2 = resource.toLowerCase();
|
||||
|
||||
if (r1.contains("/")) {
|
||||
r1 = r1.substring(r1.lastIndexOf('/') + 1);
|
||||
}
|
||||
if (r2.contains("/")) {
|
||||
r2 = r2.substring(r2.lastIndexOf('/') + 1);
|
||||
}
|
||||
if (r1.contains(".")) {
|
||||
r1 = r1.substring(0, r1.lastIndexOf('.'));
|
||||
}
|
||||
if (r2.contains(".")) {
|
||||
r2 = r2.substring(0, r2.lastIndexOf('.'));
|
||||
}
|
||||
|
||||
if (r1.equals(r2)) {
|
||||
log("FOUND: similarly named resource with different extension:");
|
||||
log(" \"" + resource + "\"");
|
||||
log(" in classpath entry:");
|
||||
log(" \"" + entry + "\"");
|
||||
log(" for access use:");
|
||||
log(" getResourceAsStream(\"/" + resource + "\");");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static
|
||||
File visitRoot(URL url, List<String> resources) throws IOException {
|
||||
if (!url.getProtocol()
|
||||
.equals("file")) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
String path = url.getPath();
|
||||
|
||||
if (OS.INSTANCE.isWindows()) {
|
||||
if (path.startsWith("/")) {
|
||||
path = path.substring(1);
|
||||
}
|
||||
}
|
||||
|
||||
File root = new File(path);
|
||||
if (!root.exists()) {
|
||||
log("failed to find classpath entry in filesystem: " + path);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (root.isDirectory()) {
|
||||
visitDir(normalizePath(root.getAbsolutePath()), root, resources);
|
||||
}
|
||||
else {
|
||||
final String s = root.getName()
|
||||
.toLowerCase();
|
||||
|
||||
if (s.endsWith(".zip")) {
|
||||
visitZip(root, resources);
|
||||
}
|
||||
else if (s.endsWith(".jar")) {
|
||||
visitZip(root, resources);
|
||||
}
|
||||
else {
|
||||
log("unknown classpath entry type: " + path);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
private static
|
||||
void visitDir(String root, File dir, Collection<String> out) {
|
||||
final File[] files = dir.listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
visitDir(root, file, out);
|
||||
}
|
||||
|
||||
out.add(file.getAbsolutePath()
|
||||
.replace('\\', '/')
|
||||
.substring(root.length() + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static
|
||||
void visitZip(File jar, Collection<String> out) throws IOException {
|
||||
ZipInputStream zis = new ZipInputStream(new FileInputStream(jar));
|
||||
while (true) {
|
||||
ZipEntry entry = zis.getNextEntry();
|
||||
if (entry == null) {
|
||||
break;
|
||||
}
|
||||
out.add(entry.getName()
|
||||
.replace('\\', '/'));
|
||||
}
|
||||
zis.close();
|
||||
}
|
||||
|
||||
private static
|
||||
String prefix() {
|
||||
return "[" + LocationResolver.class.getSimpleName() + "] ";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,560 @@
|
|||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util
|
||||
|
||||
import dorkbox.os.OS.isWindows
|
||||
import java.io.*
|
||||
import java.net.*
|
||||
import java.util.*
|
||||
import java.util.jar.*
|
||||
import java.util.regex.*
|
||||
import java.util.zip.*
|
||||
|
||||
/**
|
||||
* Convenience methods for working with resource/file/class locations
|
||||
*/
|
||||
class LocationResolver {
|
||||
/**
|
||||
* List directory contents for a resource folder. Not recursive.
|
||||
* This is basically a brute-force implementation.
|
||||
* Works for regular files and also JARs.
|
||||
*
|
||||
* @author Greg Briggs
|
||||
* @param clazz Any java class that lives in the same place as the resources you want.
|
||||
* @param path Should end with "/", but not start with one.
|
||||
* @return Just the name of each member item, not the full paths.
|
||||
*
|
||||
*
|
||||
* @throws URISyntaxException
|
||||
* @throws IOException
|
||||
*/
|
||||
@Throws(URISyntaxException::class, IOException::class)
|
||||
fun getDirectoryContents(clazz: Class<*>, path: String): Array<String> {
|
||||
var dirURL = clazz.classLoader.getResource(path)
|
||||
if (dirURL != null && dirURL.protocol == "file") {/* A file path: easy enough */
|
||||
return File(dirURL.toURI()).list()!!
|
||||
}
|
||||
if (dirURL == null) {/*
|
||||
* In case of a jar file, we can't actually find a directory.
|
||||
* Have to assume the same jar as clazz.
|
||||
*/
|
||||
val me = clazz.name.replace(".", "/") + ".class"
|
||||
dirURL = clazz.classLoader.getResource(me)
|
||||
}
|
||||
if (dirURL!!.protocol == "jar") {/* A JAR path */
|
||||
val jarPath = dirURL.path.substring(5, dirURL.path.indexOf("!")) //strip out only the JAR file
|
||||
val jar = JarFile(URLDecoder.decode(jarPath, "UTF-8"))
|
||||
val entries = jar.entries() //gives ALL entries in jar
|
||||
val result: MutableSet<String> = HashSet() //avoid duplicates in case it is a subdirectory
|
||||
while (entries.hasMoreElements()) {
|
||||
val name = entries.nextElement().name
|
||||
if (name.startsWith(path)) { //filter according to the path
|
||||
var entry = name.substring(path.length)
|
||||
val checkSubdir = entry.indexOf("/")
|
||||
if (checkSubdir >= 0) {
|
||||
// if it is a subdirectory, we just return the directory name
|
||||
entry = entry.substring(0, checkSubdir)
|
||||
}
|
||||
result.add(entry)
|
||||
}
|
||||
}
|
||||
return result.toTypedArray<String>()
|
||||
}
|
||||
throw UnsupportedOperationException("Cannot list files for URL $dirURL")
|
||||
}
|
||||
|
||||
private class Root(entry: URL) {
|
||||
val entry: File?
|
||||
val resources: MutableList<String> = ArrayList()
|
||||
|
||||
init {
|
||||
this.entry = visitRoot(entry, resources)
|
||||
}
|
||||
|
||||
fun search(path: String, attempt: Int): Boolean {
|
||||
var path = path
|
||||
try {
|
||||
path = normalizePath(path)
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
when (attempt) {
|
||||
1 -> {
|
||||
for (resource in resources) {
|
||||
if (path == resource) {
|
||||
log("SUCCESS: found resource \"$path\" in root: $entry")
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2 -> {
|
||||
for (resource in resources) {
|
||||
if (path.lowercase(Locale.getDefault()) == resource.lowercase(Locale.getDefault())) {
|
||||
log("FOUND: similarly named resource:")
|
||||
log(" \"$resource\"")
|
||||
log(" in classpath entry:")
|
||||
log(" \"$entry\"")
|
||||
log(" for access use:")
|
||||
log(" getResourceAsStream(\"/$resource\");")
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3 -> {
|
||||
for (resource in resources) {
|
||||
var r1 = path
|
||||
var r2 = resource
|
||||
if (r1.contains("/")) {
|
||||
r1 = r1.substring(r1.lastIndexOf('/') + 1)
|
||||
}
|
||||
if (r2.contains("/")) {
|
||||
r2 = r2.substring(r2.lastIndexOf('/') + 1)
|
||||
}
|
||||
if (r1 == r2) {
|
||||
log("FOUND: mislocated resource:")
|
||||
log(" \"$resource\"")
|
||||
log(" in classpath entry:")
|
||||
log(" \"$entry\"")
|
||||
log(" for access use:")
|
||||
log(" getResourceAsStream(\"/$resource\");")
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4 -> {
|
||||
for (resource in resources) {
|
||||
var r1 = path.lowercase(Locale.getDefault())
|
||||
var r2 = resource.lowercase(Locale.getDefault())
|
||||
if (r1.contains("/")) {
|
||||
r1 = r1.substring(r1.lastIndexOf('/') + 1)
|
||||
}
|
||||
if (r2.contains("/")) {
|
||||
r2 = r2.substring(r2.lastIndexOf('/') + 1)
|
||||
}
|
||||
if (r1 == r2) {
|
||||
log("FOUND: mislocated, similarly named resource:")
|
||||
log(" \"$resource\"")
|
||||
log(" in classpath entry:")
|
||||
log(" \"$entry\"")
|
||||
log(" for access use:")
|
||||
log(" getResourceAsStream(\"/$resource\");")
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
5 -> {
|
||||
for (resource in resources) {
|
||||
var r1 = path
|
||||
var r2 = resource
|
||||
if (r1.contains("/")) {
|
||||
r1 = r1.substring(r1.lastIndexOf('/') + 1)
|
||||
}
|
||||
if (r2.contains("/")) {
|
||||
r2 = r2.substring(r2.lastIndexOf('/') + 1)
|
||||
}
|
||||
if (r1.contains(".")) {
|
||||
r1 = r1.substring(0, r1.lastIndexOf('.'))
|
||||
}
|
||||
if (r2.contains(".")) {
|
||||
r2 = r2.substring(0, r2.lastIndexOf('.'))
|
||||
}
|
||||
if (r1 == r2) {
|
||||
log("FOUND: resource with different extension:")
|
||||
log(" \"$resource\"")
|
||||
log(" in classpath entry:")
|
||||
log(" \"$entry\"")
|
||||
log(" for access use:")
|
||||
log(" getResourceAsStream(\"/$resource\");")
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
6 -> {
|
||||
for (resource in resources) {
|
||||
var r1 = path.lowercase(Locale.getDefault())
|
||||
var r2 = resource.lowercase(Locale.getDefault())
|
||||
if (r1.contains("/")) {
|
||||
r1 = r1.substring(r1.lastIndexOf('/') + 1)
|
||||
}
|
||||
if (r2.contains("/")) {
|
||||
r2 = r2.substring(r2.lastIndexOf('/') + 1)
|
||||
}
|
||||
if (r1.contains(".")) {
|
||||
r1 = r1.substring(0, r1.lastIndexOf('.'))
|
||||
}
|
||||
if (r2.contains(".")) {
|
||||
r2 = r2.substring(0, r2.lastIndexOf('.'))
|
||||
}
|
||||
if (r1 == r2) {
|
||||
log("FOUND: similarly named resource with different extension:")
|
||||
log(" \"$resource\"")
|
||||
log(" in classpath entry:")
|
||||
log(" \"$entry\"")
|
||||
log(" for access use:")
|
||||
log(" getResourceAsStream(\"/$resource\");")
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = Sys.version
|
||||
|
||||
private val SLASH_PATTERN = Pattern.compile("\\\\")
|
||||
|
||||
private fun log(message: String) {
|
||||
System.err.println(prefix() + message)
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the path. fixes %20 as spaces (in winxp at least). Converts \ -> / (windows slash -> unix slash)
|
||||
*
|
||||
* @return a string pointing to the cleaned path
|
||||
* @throws IOException
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
private fun normalizePath(path: String): String {
|
||||
// make sure the slashes are in unix format.
|
||||
val path = SLASH_PATTERN.matcher(path).replaceAll("/")
|
||||
|
||||
// Can have %20 as spaces (in winxp at least). need to convert to proper path from URL
|
||||
return URLDecoder.decode(path, "UTF-8")
|
||||
}
|
||||
/**
|
||||
* Retrieve the location that this classfile was loaded from, or possibly null if the class was compiled on the fly
|
||||
*/
|
||||
/**
|
||||
* Retrieve the location of the currently loaded jar, or possibly null if it was compiled on the fly
|
||||
*/
|
||||
@JvmOverloads
|
||||
operator fun get(clazz: Class<*> = LocationResolver::class.java): File? {
|
||||
// Get the location of this class
|
||||
val pDomain = clazz.protectionDomain
|
||||
val cSource = pDomain.codeSource
|
||||
|
||||
// file:/X:/workspace/XYZ/classes/ when it's in ide/flat
|
||||
// jar:/X:/workspace/XYZ/jarname.jar when it's jar
|
||||
val loc = cSource.location ?: return null
|
||||
|
||||
// we don't always have a protection domain (for example, when we compile classes on the fly, from memory)
|
||||
|
||||
// Can have %20 as spaces (in winxp at least). need to convert to proper path from URL
|
||||
return try {
|
||||
File(normalizePath(loc.file)).absoluteFile.canonicalFile
|
||||
} catch (e: UnsupportedEncodingException) {
|
||||
throw RuntimeException("Unable to decode file path!", e)
|
||||
} catch (e: IOException) {
|
||||
throw RuntimeException("Unable to get canonical file path!", e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a URL of a given resourceName. If the resourceName is a directory, the returned URL will be the URL for the directory.
|
||||
*
|
||||
* This method searches the disk first (via new [File.File], then by [ClassLoader.getResource], then by
|
||||
* [ClassLoader.getSystemResource].
|
||||
*
|
||||
* @param resourceName the resource name to search for
|
||||
*
|
||||
* @return the URL for that given resource name
|
||||
*/
|
||||
fun getResource(resourceName: String): URL? {
|
||||
var resourceName = resourceName
|
||||
try {
|
||||
resourceName = normalizePath(resourceName)
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
var resource: URL? = null
|
||||
|
||||
// 1) maybe it's on disk? priority is disk
|
||||
val file = File(resourceName)
|
||||
if (file.canRead()) {
|
||||
try {
|
||||
resource = file.toURI().toURL()
|
||||
} catch (e: MalformedURLException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
// 2) is it in the context classloader
|
||||
if (resource == null) {
|
||||
resource = Thread.currentThread().contextClassLoader.getResource(resourceName)
|
||||
}
|
||||
|
||||
// 3) is it in the system classloader
|
||||
if (resource == null) {
|
||||
// maybe it's in the system classloader?
|
||||
resource = ClassLoader.getSystemResource(resourceName)
|
||||
}
|
||||
|
||||
// 4) look for it, and log the output (so we can find or debug it)
|
||||
if (resource == null) {
|
||||
try {
|
||||
searchResource(resourceName)
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
return resource
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an enumeration of URLs of a given resourceName. If the resourceName is a directory, the returned list will be the URLs
|
||||
* of the contents of that directory. The first URL will always be the directory URL, as returned by [.getResource].
|
||||
*
|
||||
* This method searches the disk first (via new [File.File], then by [ClassLoader.getResources], then by
|
||||
* [ClassLoader.getSystemResources].
|
||||
*
|
||||
* @param resourceName the resource name to search for
|
||||
*
|
||||
* @return the enumeration of URLs for that given resource name
|
||||
*/
|
||||
fun getResources(resourceName: String): Enumeration<URL>? {
|
||||
var resourceName = resourceName
|
||||
|
||||
try {
|
||||
resourceName = normalizePath(resourceName)
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
var resources: Enumeration<URL>? = null
|
||||
try {
|
||||
// 1) maybe it's on disk? priority is disk
|
||||
val file = File(resourceName)
|
||||
if (file.canRead()) {
|
||||
val urlList = ArrayDeque<URL>(4)
|
||||
// add self always
|
||||
urlList.add(
|
||||
file.toURI().toURL()
|
||||
)
|
||||
if (file.isDirectory) {
|
||||
// add urls of all children
|
||||
val files = file.listFiles()
|
||||
if (files != null) {
|
||||
var i = 0
|
||||
val n = files.size
|
||||
while (i < n) {
|
||||
urlList.add(
|
||||
files[i].toURI().toURL()
|
||||
)
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
resources = Vector(urlList).elements()
|
||||
}
|
||||
|
||||
// 2) is it in the context classloader
|
||||
if (resources == null) {
|
||||
resources = Thread.currentThread().contextClassLoader.getResources(resourceName)
|
||||
}
|
||||
|
||||
// 3) is it in the system classloader
|
||||
if (resources == null) {
|
||||
// maybe it's in the system classloader?
|
||||
resources = ClassLoader.getSystemResources(resourceName)
|
||||
}
|
||||
|
||||
// 4) look for it, and log the output (so we can find or debug it)
|
||||
if (resources == null) {
|
||||
searchResource(resourceName) // can throw an exception
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
return resources
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the resource as a stream.
|
||||
*
|
||||
*
|
||||
* 1) checks the disk in the relative location to the executing app<br></br>
|
||||
* 2) Checks the current thread context classloader <br></br>
|
||||
* 3) Checks the Classloader system resource
|
||||
*
|
||||
* @param resourceName the name, including path information (Only '\' is valid as the path separator)
|
||||
*
|
||||
* @return the resource stream, if it could be found, otherwise null.
|
||||
*/
|
||||
fun getResourceAsStream(resourceName: String): InputStream? {
|
||||
var resourceName = resourceName
|
||||
try {
|
||||
resourceName = normalizePath(resourceName)
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
var resourceAsStream: InputStream? = null
|
||||
|
||||
// 1) maybe it's on disk? priority is disk
|
||||
if (File(resourceName).canRead()) {
|
||||
try {
|
||||
resourceAsStream = FileInputStream(resourceName)
|
||||
} catch (e: FileNotFoundException) {
|
||||
// shouldn't happen, but if there is something wonky...
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
// 2) maybe it's in the context classloader
|
||||
if (resourceAsStream == null) {
|
||||
resourceAsStream = Thread.currentThread().contextClassLoader.getResourceAsStream(resourceName)
|
||||
}
|
||||
|
||||
// 3) maybe it's in the system classloader
|
||||
if (resourceAsStream == null) {
|
||||
resourceAsStream = ClassLoader.getSystemResourceAsStream(resourceName)
|
||||
}
|
||||
|
||||
|
||||
// 4) look for it, and log the output (so we can find or debug it)
|
||||
if (resourceAsStream == null) {
|
||||
try {
|
||||
searchResource(resourceName)
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
return resourceAsStream
|
||||
}
|
||||
|
||||
// via RIVEN at JGO. CC0 as far as I can tell.
|
||||
@Throws(IOException::class)
|
||||
fun searchResource(path: String) {
|
||||
var path = path
|
||||
|
||||
try {
|
||||
path = normalizePath(path)
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
val roots: MutableList<Root> = ArrayList()
|
||||
val contextClassLoader = Thread.currentThread().contextClassLoader
|
||||
|
||||
if (contextClassLoader is URLClassLoader) {
|
||||
val urLs = contextClassLoader.urLs
|
||||
for (url in urLs) {
|
||||
roots.add(Root(url))
|
||||
}
|
||||
System.err.println()
|
||||
log("SEARCHING: \"$path\"")
|
||||
|
||||
for (attempt in 1..6) {
|
||||
for (root in roots) {
|
||||
if (root.search(path, attempt)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log("FAILED: failed to find anything like")
|
||||
log(" \"$path\"")
|
||||
log(" in all classpath entries:")
|
||||
for (root in roots) {
|
||||
val entry = root.entry
|
||||
if (entry != null) {
|
||||
log(" \"" + entry.absolutePath + "\"")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw IOException(
|
||||
"Unable to search for '" + path + "' in the context classloader of type '" + contextClassLoader.javaClass + "'. Please report this issue with as many specific details as possible (OS, Java version, application version"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun visitRoot(url: URL, resources: MutableList<String>): File? {
|
||||
check(url.protocol == "file")
|
||||
var path = url.path
|
||||
if (isWindows) {
|
||||
if (path.startsWith("/")) {
|
||||
path = path.substring(1)
|
||||
}
|
||||
}
|
||||
|
||||
val root = File(path)
|
||||
if (!root.exists()) {
|
||||
log("failed to find classpath entry in filesystem: $path")
|
||||
return null
|
||||
}
|
||||
|
||||
if (root.isDirectory) {
|
||||
visitDir(normalizePath(root.absolutePath), root, resources)
|
||||
} else {
|
||||
val s = root.name.lowercase(Locale.getDefault())
|
||||
if (s.endsWith(".zip")) {
|
||||
visitZip(root, resources)
|
||||
} else if (s.endsWith(".jar")) {
|
||||
visitZip(root, resources)
|
||||
} else {
|
||||
log("unknown classpath entry type: $path")
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return root
|
||||
}
|
||||
|
||||
private fun visitDir(root: String, dir: File, out: MutableCollection<String>) {
|
||||
val files = dir.listFiles()
|
||||
if (files != null) {
|
||||
for (file in files) {
|
||||
if (file.isDirectory) {
|
||||
visitDir(root, file, out)
|
||||
}
|
||||
out.add(
|
||||
file.absolutePath.replace('\\', '/').substring(root.length + 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun visitZip(jar: File, out: MutableCollection<String>) {
|
||||
val zis = ZipInputStream(FileInputStream(jar))
|
||||
while (true) {
|
||||
val entry = zis.nextEntry ?: break
|
||||
out.add(
|
||||
entry.name.replace('\\', '/')
|
||||
)
|
||||
}
|
||||
zis.close()
|
||||
}
|
||||
|
||||
private fun prefix(): String {
|
||||
return "[" + LocationResolver::class.java.simpleName + "] "
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -13,20 +13,21 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util;
|
||||
package dorkbox.util
|
||||
|
||||
|
||||
public
|
||||
class MathUtil {
|
||||
object MathUtil {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = Sys.version
|
||||
|
||||
/**
|
||||
* Checks to see if the string is an integer
|
||||
*
|
||||
* @return true if it's an integer, false otherwise
|
||||
*/
|
||||
public static
|
||||
boolean isInteger(final String string) {
|
||||
return isNumber(string, 10);
|
||||
fun isInteger(string: String): Boolean {
|
||||
return isNumber(string, 10)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -34,9 +35,8 @@ class MathUtil {
|
|||
*
|
||||
* @return true if it's a long, false otherwise
|
||||
*/
|
||||
public static
|
||||
boolean isLong(final String string) {
|
||||
return isNumber(string, 19);
|
||||
fun isLong(string: String): Boolean {
|
||||
return isNumber(string, 19)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -44,10 +44,9 @@ class MathUtil {
|
|||
*
|
||||
* @return true if it's a number, false otherwise
|
||||
*/
|
||||
public static
|
||||
boolean isNumber(final char character) {
|
||||
fun isNumber(character: Char): Boolean {
|
||||
// way faster than Character.isDigit()
|
||||
return character >= '0' && character <= '9';
|
||||
return character >= '0' && character <= '9'
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -55,41 +54,33 @@ class MathUtil {
|
|||
*
|
||||
* @return true if it's a number, false otherwise
|
||||
*/
|
||||
public static
|
||||
boolean isNumber(final String string, long sizeLimit) {
|
||||
if (string == null) {
|
||||
return false;
|
||||
}
|
||||
fun isNumber(string: String, sizeLimit: Long): Boolean {
|
||||
if (sizeLimit <= 0) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
|
||||
int length = string.length();
|
||||
val length = string.length
|
||||
if (length == 0) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
if (string.charAt(0) == '-') {
|
||||
var i = 0
|
||||
if (string[0] == '-') {
|
||||
if (length == 1) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
i = 1;
|
||||
i = 1
|
||||
}
|
||||
|
||||
if (length - i > sizeLimit) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
|
||||
for (; i < length; i++) {
|
||||
char c = string.charAt(i);
|
||||
while (i < length) {
|
||||
val c = string[i]
|
||||
// way faster than Character.isDigit()
|
||||
if (c < '0' || c > '9') {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -99,103 +90,85 @@ class MathUtil {
|
|||
*
|
||||
* @return the number of digits of the number, from 1-19.
|
||||
*/
|
||||
public static
|
||||
int numberOfDigits(long number) {
|
||||
fun numberOfDigits(number: Long): Int {
|
||||
// have to make it always positive for the following checks to pass.
|
||||
var number = number
|
||||
if (number < 0L) {
|
||||
number = -number;
|
||||
number = -number
|
||||
}
|
||||
|
||||
// Guessing 4 digit numbers will be more probable.
|
||||
// They are set in the first branch.
|
||||
if (number < 10000L) { // from 1 to 4
|
||||
return if (number < 10000L) { // from 1 to 4
|
||||
if (number < 100L) { // 1 or 2
|
||||
if (number < 10L) {
|
||||
return 1;
|
||||
1
|
||||
} else {
|
||||
2
|
||||
}
|
||||
else {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
else { // 3 or 4
|
||||
} else { // 3 or 4
|
||||
if (number < 1000L) {
|
||||
return 3;
|
||||
}
|
||||
else {
|
||||
return 4;
|
||||
3
|
||||
} else {
|
||||
4
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // from 5 to 20 (albeit longs can't have more than 18 or 19)
|
||||
} else { // from 5 to 20 (albeit longs can't have more than 18 or 19)
|
||||
if (number < 1000000000000L) { // from 5 to 12
|
||||
if (number < 100000000L) { // from 5 to 8
|
||||
if (number < 1000000L) { // 5 or 6
|
||||
if (number < 100000L) {
|
||||
return 5;
|
||||
5
|
||||
} else {
|
||||
6
|
||||
}
|
||||
else {
|
||||
return 6;
|
||||
}
|
||||
}
|
||||
else { // 7 u 8
|
||||
} else { // 7 u 8
|
||||
if (number < 10000000L) {
|
||||
return 7;
|
||||
}
|
||||
else {
|
||||
return 8;
|
||||
7
|
||||
} else {
|
||||
8
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // from 9 to 12
|
||||
} else { // from 9 to 12
|
||||
if (number < 10000000000L) { // 9 or 10
|
||||
if (number < 1000000000L) {
|
||||
return 9;
|
||||
9
|
||||
} else {
|
||||
10
|
||||
}
|
||||
else {
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
else { // 11 or 12
|
||||
} else { // 11 or 12
|
||||
if (number < 100000000000L) {
|
||||
return 11;
|
||||
}
|
||||
else {
|
||||
return 12;
|
||||
11
|
||||
} else {
|
||||
12
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // from 13 to ... (18 or 20)
|
||||
} else { // from 13 to ... (18 or 20)
|
||||
if (number < 10000000000000000L) { // from 13 to 16
|
||||
if (number < 100000000000000L) { // 13 or 14
|
||||
if (number < 10000000000000L) {
|
||||
return 13;
|
||||
13
|
||||
} else {
|
||||
14
|
||||
}
|
||||
else {
|
||||
return 14;
|
||||
}
|
||||
}
|
||||
else { // 15 or 16
|
||||
} else { // 15 or 16
|
||||
if (number < 1000000000000000L) {
|
||||
return 15;
|
||||
}
|
||||
else {
|
||||
return 16;
|
||||
15
|
||||
} else {
|
||||
16
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // from 17 to ... 20?
|
||||
} else { // from 17 to ... 20?
|
||||
if (number < 1000000000000000000L) { // 17 or 18
|
||||
if (number < 100000000000000000L) {
|
||||
return 17;
|
||||
17
|
||||
} else {
|
||||
18
|
||||
}
|
||||
else {
|
||||
return 18;
|
||||
}
|
||||
}
|
||||
else { // 19? Can it be?
|
||||
} else { // 19? Can it be?
|
||||
// 10000000000000000000L isn't a valid long.
|
||||
return 19;
|
||||
19
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -208,55 +181,46 @@ class MathUtil {
|
|||
* @param text the input text that may, or may not, contain a mix of numbers and letters
|
||||
* @return the value as an integer
|
||||
*/
|
||||
public static
|
||||
int stripTrailingNonDigits(final String text) {
|
||||
if (text == null || text.isEmpty()) {
|
||||
return 0;
|
||||
fun stripTrailingNonDigits(text: String): Int {
|
||||
if (text.isEmpty()) {
|
||||
return 0
|
||||
}
|
||||
|
||||
int numberIndex = 0;
|
||||
int length = text.length();
|
||||
|
||||
while (numberIndex < length && Character.isDigit(text.charAt(numberIndex))) {
|
||||
numberIndex++;
|
||||
var numberIndex = 0
|
||||
val length = text.length
|
||||
while (numberIndex < length && Character.isDigit(text[numberIndex])) {
|
||||
numberIndex++
|
||||
}
|
||||
|
||||
String substring = text.substring(0, numberIndex);
|
||||
val substring = text.substring(0, numberIndex)
|
||||
try {
|
||||
return Integer.parseInt(substring);
|
||||
} catch (Exception ignored) {
|
||||
return substring.toInt()
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
|
||||
return 0;
|
||||
return 0
|
||||
}
|
||||
|
||||
public static
|
||||
boolean isEven(int value) {
|
||||
return (value & 1) == 0;
|
||||
fun isEven(value: Int): Boolean {
|
||||
return value and 1 == 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next power of two. Returns the specified value if the value is already a power of two.
|
||||
*/
|
||||
public static
|
||||
int nextPowerOfTwo(int value) {
|
||||
return 1 << (32 - Integer.numberOfLeadingZeros(value - 1));
|
||||
fun nextPowerOfTwo(value: Int): Int {
|
||||
return 1 shl 32 - Integer.numberOfLeadingZeros(value - 1)
|
||||
}
|
||||
|
||||
public static
|
||||
boolean isPowerOfTwo(int value) {
|
||||
return value != 0 && (value & value - 1) == 0;
|
||||
fun isPowerOfTwo(value: Int): Boolean {
|
||||
return value != 0 && value and value - 1 == 0
|
||||
}
|
||||
|
||||
|
||||
public static
|
||||
boolean intersectRect(double x1, double y1, double w1, double h1, double x2, double y2, double w2, double h2) {
|
||||
return intersectRange(x1, x1 + w1, x2, x2 + w2) && intersectRange(y1, y1 + h1, y2, y2 + h2);
|
||||
fun intersectRect(x1: Double, y1: Double, w1: Double, h1: Double, x2: Double, y2: Double, w2: Double, h2: Double): Boolean {
|
||||
return intersectRange(x1, x1 + w1, x2, x2 + w2) && intersectRange(y1, y1 + h1, y2, y2 + h2)
|
||||
}
|
||||
|
||||
public static
|
||||
boolean intersectRange(double ax1, double ax2, double bx1, double bx2) {
|
||||
return Math.max(ax1, bx1) <= Math.min(ax2, bx2);
|
||||
fun intersectRange(ax1: Double, ax2: Double, bx1: Double, bx2: Double): Boolean {
|
||||
return Math.max(ax1, bx1) <= Math.min(ax2, bx2)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -264,9 +228,8 @@ class MathUtil {
|
|||
*
|
||||
* @return the new value, clamped
|
||||
*/
|
||||
public static
|
||||
int clamp(int value, int min, int max) {
|
||||
return Math.max(min, Math.min(max, value));
|
||||
fun clamp(value: Int, min: Int, max: Int): Int {
|
||||
return Math.max(min, Math.min(max, value))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -274,9 +237,8 @@ class MathUtil {
|
|||
*
|
||||
* @return the new value, clamped
|
||||
*/
|
||||
public static
|
||||
long clamp(long value, long min, long max) {
|
||||
return Math.max(min, Math.min(max, value));
|
||||
fun clamp(value: Long, min: Long, max: Long): Long {
|
||||
return Math.max(min, Math.min(max, value))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -284,9 +246,8 @@ class MathUtil {
|
|||
*
|
||||
* @return the new value, clamped
|
||||
*/
|
||||
public static
|
||||
float clamp(float value, float min, float max) {
|
||||
return Math.max(min, Math.min(max, value));
|
||||
fun clamp(value: Float, min: Float, max: Float): Float {
|
||||
return Math.max(min, Math.min(max, value))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -294,8 +255,7 @@ class MathUtil {
|
|||
*
|
||||
* @return the new value, clamped
|
||||
*/
|
||||
public static
|
||||
double clamp(double value, double min, double max) {
|
||||
return Math.max(min, Math.min(max, value));
|
||||
fun clamp(value: Double, min: Double, max: Double): Double {
|
||||
return Math.max(min, Math.min(max, value))
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util;
|
||||
|
||||
public interface Message {
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2022 dorkbox, llc
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -21,7 +21,7 @@ import java.util.concurrent.atomic.*
|
|||
/**
|
||||
* The default thread factory with names and daemon state
|
||||
*/
|
||||
class NamedThreadFactory constructor(
|
||||
class NamedThreadFactory(
|
||||
/** @param namePrefix what you want the subsequent threads to be named. */
|
||||
val namePrefix: String,
|
||||
|
||||
|
@ -32,12 +32,26 @@ class NamedThreadFactory constructor(
|
|||
val threadPriority: Int = Thread.NORM_PRIORITY,
|
||||
|
||||
/** @param daemon true to stop this thread automatically when the JVM shutsdown */
|
||||
val daemon: Boolean = true
|
||||
val daemon: Boolean = true,
|
||||
|
||||
/** @param actionOnNewThread the action that will run whenever a new thread is created */
|
||||
val actionOnNewThread: (Thread) -> Unit
|
||||
|
||||
) : ThreadFactory {
|
||||
constructor(poolNamePrefix: String, group: ThreadGroup) : this(poolNamePrefix, group, Thread.NORM_PRIORITY, true)
|
||||
constructor(poolNamePrefix: String, isDaemon: Boolean) : this(poolNamePrefix, Thread.currentThread().threadGroup, isDaemon)
|
||||
constructor(poolNamePrefix: String, group: ThreadGroup, isDaemon: Boolean) : this(poolNamePrefix, group, Thread.NORM_PRIORITY, isDaemon)
|
||||
constructor(poolNamePrefix: String) : this(poolNamePrefix, Thread.currentThread().threadGroup, Thread.NORM_PRIORITY, true, {})
|
||||
constructor(poolNamePrefix: String, group: ThreadGroup) : this(poolNamePrefix, group, Thread.NORM_PRIORITY, true, {})
|
||||
constructor(poolNamePrefix: String, isDaemon: Boolean) : this(poolNamePrefix, Thread.currentThread().threadGroup, isDaemon, {})
|
||||
constructor(poolNamePrefix: String, group: ThreadGroup, isDaemon: Boolean) : this(poolNamePrefix, group, Thread.NORM_PRIORITY, isDaemon, {})
|
||||
constructor(poolNamePrefix: String, group: ThreadGroup, threadPriority: Int, isDaemon: Boolean) : this(poolNamePrefix, group, threadPriority, isDaemon, {})
|
||||
constructor(poolNamePrefix: String, group: ThreadGroup, isDaemon: Boolean, actionOnNewThread: (Thread) -> Unit) : this(poolNamePrefix, group, Thread.NORM_PRIORITY, isDaemon, actionOnNewThread)
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = Sys.version
|
||||
}
|
||||
|
||||
|
||||
|
||||
private val poolId = AtomicInteger()
|
||||
|
@ -63,6 +77,8 @@ class NamedThreadFactory constructor(
|
|||
val t = Thread(group, r, namePrefix + '-' + poolId.incrementAndGet())
|
||||
t.isDaemon = daemon
|
||||
t.priority = threadPriority
|
||||
|
||||
actionOnNewThread(t)
|
||||
return t
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,100 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
|
||||
import dorkbox.os.OS;
|
||||
|
||||
/**
|
||||
* Loads the specified library, extracting it from the jar, if necessary
|
||||
*/
|
||||
public
|
||||
class NativeLoader {
|
||||
|
||||
public static
|
||||
File extractLibrary(final String sourceFileName, final String destinationDirectory, final String destinationName, String version) throws IOException {
|
||||
try {
|
||||
String suffix = OS.INSTANCE.getType().getLibraryNames()[0];
|
||||
final String outputFileName;
|
||||
if (version == null) {
|
||||
outputFileName = destinationName + suffix;
|
||||
}
|
||||
else {
|
||||
outputFileName = destinationName + "." + version + suffix;
|
||||
}
|
||||
|
||||
final File file = new File(destinationDirectory, outputFileName);
|
||||
if (!file.canRead() || file.length() == 0 || !file.canExecute()) {
|
||||
// now we copy it out
|
||||
final InputStream inputStream = LocationResolver.getResourceAsStream(sourceFileName);
|
||||
|
||||
OutputStream outStream = null;
|
||||
try {
|
||||
outStream = new FileOutputStream(file);
|
||||
|
||||
byte[] buffer = new byte[4096];
|
||||
int read;
|
||||
while ((read = inputStream.read(buffer)) > 0) {
|
||||
outStream.write(buffer, 0, read);
|
||||
}
|
||||
|
||||
outStream.flush();
|
||||
outStream.close();
|
||||
outStream = null;
|
||||
} finally {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
try {
|
||||
if (outStream != null) {
|
||||
outStream.close();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return file;
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Error extracting library: " + sourceFileName, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static
|
||||
void loadLibrary(final File file) {
|
||||
// inject into the correct classloader
|
||||
AccessController.doPrivileged(new PrivilegedAction<Object>() {
|
||||
@Override
|
||||
public
|
||||
Object run() {
|
||||
System.load(file.getAbsolutePath());
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private
|
||||
NativeLoader() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util
|
||||
|
||||
import dorkbox.os.OS.type
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.security.AccessController
|
||||
import java.security.PrivilegedAction
|
||||
|
||||
/**
|
||||
* Loads the specified library, extracting it from the jar, if necessary
|
||||
*/
|
||||
object NativeLoader {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = Sys.version
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun extractLibrary(sourceFileName: String, destinationDirectory: String?, destinationName: String, version: String?): File {
|
||||
return try {
|
||||
val suffix = type.libraryNames[0]
|
||||
|
||||
val outputFileName = if (version == null) {
|
||||
destinationName + suffix
|
||||
} else {
|
||||
"$destinationName.$version$suffix"
|
||||
}
|
||||
|
||||
val file = File(destinationDirectory, outputFileName)
|
||||
if (!file.canRead() || file.length() == 0L || !file.canExecute()) {
|
||||
// now we copy it out
|
||||
val inputStream = LocationResolver.getResourceAsStream(sourceFileName) ?: throw IllegalArgumentException("Cannot find sourceFileName!")
|
||||
|
||||
inputStream.use {
|
||||
FileOutputStream(file).use {
|
||||
inputStream.copyTo(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
file
|
||||
} catch (e: Exception) {
|
||||
throw IOException("Error extracting library: $sourceFileName", e)
|
||||
}
|
||||
}
|
||||
|
||||
fun loadLibrary(file: File) {
|
||||
// inject into the correct classloader
|
||||
AccessController.doPrivileged(PrivilegedAction<Any?> {
|
||||
System.load(file.absolutePath)
|
||||
null
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -13,93 +13,83 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util;
|
||||
|
||||
package dorkbox.util
|
||||
|
||||
/**
|
||||
* This class uses the MersenneTwisterFast, which is MOSTLY random.
|
||||
*/
|
||||
public
|
||||
class RandomUtil {
|
||||
object RandomUtil {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = Sys.version
|
||||
|
||||
private static final FastThreadLocal<MersenneTwisterFast> random = new FastThreadLocal<MersenneTwisterFast>() {
|
||||
@Override
|
||||
public
|
||||
MersenneTwisterFast initialValue() {
|
||||
return new MersenneTwisterFast();
|
||||
private val random: FastThreadLocal<MersenneTwisterFast> = object : FastThreadLocal<MersenneTwisterFast>() {
|
||||
override fun initialValue(): MersenneTwisterFast {
|
||||
return MersenneTwisterFast()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the thread local MersenneTwister (as it's not thread safe), if necessary
|
||||
*/
|
||||
public static
|
||||
MersenneTwisterFast get() {
|
||||
return random.get();
|
||||
fun get(): MersenneTwisterFast {
|
||||
return random.get()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a get integer
|
||||
*/
|
||||
public static
|
||||
int int_() {
|
||||
return get().nextInt();
|
||||
fun int(): Int {
|
||||
return get().nextInt()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a number between 0 (inclusive) and the specified value (inclusive).
|
||||
*/
|
||||
public static
|
||||
int int_(int range) {
|
||||
return get().nextInt(range + 1);
|
||||
fun int(range: Int): Int {
|
||||
return get().nextInt(range + 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a number between start (inclusive) and end (inclusive).
|
||||
*/
|
||||
public static
|
||||
int int_(int start, int end) {
|
||||
return start + get().nextInt(end - start + 1);
|
||||
fun int(start: Int, end: Int): Int {
|
||||
return start + get().nextInt(end - start + 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a boolean value.
|
||||
*/
|
||||
public static
|
||||
boolean bool() {
|
||||
return get().nextBoolean();
|
||||
fun bool(): Boolean {
|
||||
return get().nextBoolean()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number between 0.0 (inclusive) and 1.0 (exclusive).
|
||||
*/
|
||||
public static
|
||||
float float_() {
|
||||
return get().nextFloat();
|
||||
fun float(): Float {
|
||||
return get().nextFloat()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a number between 0 (inclusive) and the specified value (exclusive).
|
||||
*/
|
||||
public static
|
||||
float float_(float range) {
|
||||
return get().nextFloat() * range;
|
||||
fun float(range: Float): Float {
|
||||
return get().nextFloat() * range
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a number between start (inclusive) and end (exclusive).
|
||||
*/
|
||||
public static
|
||||
float float_(float start, float end) {
|
||||
return start + get().nextFloat() * (end - start);
|
||||
fun float(start: Float, end: Float): Float {
|
||||
return start + get().nextFloat() * (end - start)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Places random bytes in the specified byte array
|
||||
*/
|
||||
public static
|
||||
void bytes_(byte[] bytes) {
|
||||
get().nextBytes(bytes);
|
||||
fun bytes(bytes: ByteArray) {
|
||||
get().nextBytes(bytes)
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -13,11 +13,15 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util;
|
||||
package dorkbox.util
|
||||
|
||||
public class RegExp {
|
||||
private static final String whitespace_chars = "" /* dummy empty string for homogeneity */
|
||||
+ "\\u0009" // CHARACTER TABULATION
|
||||
object RegExp {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = Sys.version
|
||||
|
||||
private const val whitespace_chars = ("" /* dummy empty string for homogeneity */ + "\\u0009" // CHARACTER TABULATION
|
||||
+ "\\u000A" // LINE FEED (LF)
|
||||
+ "\\u000B" // LINE TABULATION
|
||||
+ "\\u000C" // FORM FEED (FF)
|
||||
|
@ -42,12 +46,11 @@ public class RegExp {
|
|||
+ "\\u2029" // PARAGRAPH SEPARATOR
|
||||
+ "\\u202F" // NARROW NO-BREAK SPACE
|
||||
+ "\\u205F" // MEDIUM MATHEMATICAL SPACE
|
||||
+ "\\u3000" // IDEOGRAPHIC SPACE
|
||||
;
|
||||
+ "\\u3000") // IDEOGRAPHIC SPACE
|
||||
|
||||
/* A \s that actually works for Java’s native character set: Unicode */
|
||||
public static final String whitespace_charclass = "[" + whitespace_chars + "]";
|
||||
const val WHITESPACE = "[" + whitespace_chars + "]"
|
||||
|
||||
/* A \S that actually works for Java’s native character set: Unicode */
|
||||
public static final String not_whitespace_charclass = "[^" + whitespace_chars + "]";
|
||||
const val NOT_WHITESPACE = "[^" + whitespace_chars + "]"
|
||||
}
|
|
@ -1,152 +0,0 @@
|
|||
/*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util;
|
||||
|
||||
import java.awt.Container;
|
||||
import java.awt.GraphicsConfiguration;
|
||||
import java.awt.GraphicsDevice;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.awt.MouseInfo;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
|
||||
/**
|
||||
* Screen utilities
|
||||
*/
|
||||
public final
|
||||
class ScreenUtil {
|
||||
public static
|
||||
Rectangle getScreenBoundsAt(Point pos) {
|
||||
GraphicsDevice gd = getMonitorAtLocation(pos);
|
||||
Rectangle bounds = null;
|
||||
|
||||
if (gd != null) {
|
||||
bounds = gd.getDefaultConfiguration()
|
||||
.getBounds();
|
||||
}
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
public static
|
||||
GraphicsDevice getMonitorAtMouseLocation() {
|
||||
Point mouseLocation = MouseInfo.getPointerInfo()
|
||||
.getLocation();
|
||||
|
||||
return getMonitorAtLocation(mouseLocation);
|
||||
}
|
||||
|
||||
public static
|
||||
GraphicsDevice getMonitorAtLocation(Point pos) {
|
||||
|
||||
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||
GraphicsDevice screenDevices[] = ge.getScreenDevices();
|
||||
|
||||
GraphicsDevice device = null;
|
||||
for (GraphicsDevice device1 : screenDevices) {
|
||||
GraphicsConfiguration gc = device1.getDefaultConfiguration();
|
||||
Rectangle screenBounds = gc.getBounds();
|
||||
|
||||
if (screenBounds.contains(pos)) {
|
||||
device = device1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (device == null) {
|
||||
device = ge.getDefaultScreenDevice();
|
||||
}
|
||||
|
||||
return device;
|
||||
}
|
||||
|
||||
public static
|
||||
int getMonitorNumberAtMouseLocation() {
|
||||
Point mouseLocation = MouseInfo.getPointerInfo()
|
||||
.getLocation();
|
||||
|
||||
return getMonitorNumberAtLocation(mouseLocation);
|
||||
}
|
||||
|
||||
public static
|
||||
int getMonitorNumberAtLocation(Point pos) {
|
||||
|
||||
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||
GraphicsDevice screenDevices[] = ge.getScreenDevices();
|
||||
|
||||
for (int i = 0; i < screenDevices.length; i++) {
|
||||
final GraphicsDevice device1 = screenDevices[i];
|
||||
GraphicsConfiguration gc = device1.getDefaultConfiguration();
|
||||
Rectangle screenBounds = gc.getBounds();
|
||||
|
||||
if (screenBounds.contains(pos)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// we are the primary monitor, so return 0.
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static
|
||||
void showOnSameScreenAsMouse_Center(final Container frame) {
|
||||
Point mouseLocation = MouseInfo.getPointerInfo()
|
||||
.getLocation();
|
||||
|
||||
GraphicsDevice monitorAtMouse = ScreenUtil.getMonitorAtLocation(mouseLocation);
|
||||
Rectangle bounds = monitorAtMouse.getDefaultConfiguration()
|
||||
.getBounds();
|
||||
frame.setLocation(bounds.x + bounds.width / 2 - frame.getWidth() / 2, bounds.y + bounds.height / 2 - frame.getHeight() / 2);
|
||||
}
|
||||
|
||||
public static
|
||||
void showOnSameScreenAsMouse(final Container frame) {
|
||||
Point mouseLocation = MouseInfo.getPointerInfo()
|
||||
.getLocation();
|
||||
|
||||
GraphicsDevice monitorAtMouse = ScreenUtil.getMonitorAtLocation(mouseLocation);
|
||||
Rectangle bounds = monitorAtMouse.getDefaultConfiguration()
|
||||
.getBounds();
|
||||
frame.setLocation(bounds.x, bounds.y);
|
||||
}
|
||||
|
||||
private
|
||||
ScreenUtil() {
|
||||
}
|
||||
|
||||
|
||||
// public static Rectangle getSafeScreenBounds(Point pos) {
|
||||
// Rectangle bounds = getScreenBoundsAt(pos);
|
||||
// Insets insets = getScreenInsetsAt(pos);
|
||||
//
|
||||
// bounds.x += insets.left;
|
||||
// bounds.y += insets.top;
|
||||
// bounds.width -= insets.left + insets.right;
|
||||
// bounds.height -= insets.top + insets.bottom;
|
||||
//
|
||||
// return bounds;
|
||||
// }
|
||||
|
||||
// public static Insets getScreenInsetsAt(Point pos) {
|
||||
// GraphicsDevice gd = getGraphicsDeviceAt(pos);
|
||||
// Insets insets = null;
|
||||
// if (gd != null) {
|
||||
// insets = Toolkit.getDefaultToolkit().getScreenInsets(gd.getDefaultConfiguration());
|
||||
// }
|
||||
//
|
||||
// return insets;
|
||||
// }
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util
|
||||
|
||||
import java.awt.*
|
||||
|
||||
/**
|
||||
* Screen utilities
|
||||
*/
|
||||
object ScreenUtil {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = Sys.version
|
||||
|
||||
|
||||
/**
|
||||
* @return the screen bounds for the monitor at a specific point. Will return null if there is no screen at the point
|
||||
*/
|
||||
fun getScreenBoundsAt(pos: Point): Rectangle? {
|
||||
return getMonitorAtLocation(pos)?.defaultConfiguration?.bounds
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the monitor that is presently at the current mouse location. Can never return null.
|
||||
*/
|
||||
val monitorAtMouseLocation: GraphicsDevice
|
||||
get() {
|
||||
val mouseLocation = MouseInfo.getPointerInfo().location
|
||||
return getMonitorAtLocation(mouseLocation)!! // the mouse is ALWAYS on a screen!
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the monitor at the specified location. Will return null if there is no monitor for the specified location
|
||||
*/
|
||||
fun getMonitorAtLocation(pos: Point): GraphicsDevice? {
|
||||
val ge = GraphicsEnvironment.getLocalGraphicsEnvironment()
|
||||
val screenDevices = ge.screenDevices
|
||||
|
||||
for (device1 in screenDevices) {
|
||||
val gc = device1.defaultConfiguration
|
||||
val screenBounds = gc.bounds
|
||||
if (screenBounds.contains(pos)) {
|
||||
return device1
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the monitor number that is presently at the current mouse location. Can never return null.
|
||||
*/
|
||||
val monitorNumberAtMouseLocation: Int
|
||||
get() {
|
||||
val mouseLocation = MouseInfo.getPointerInfo().location
|
||||
return getMonitorNumberAtLocation(mouseLocation)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the monitor number that is presently at the current mouse location. Can never return null.
|
||||
*/
|
||||
fun getMonitorNumberAtLocation(pos: Point): Int {
|
||||
val ge = GraphicsEnvironment.getLocalGraphicsEnvironment()
|
||||
val screenDevices = ge.screenDevices
|
||||
|
||||
for (i in screenDevices.indices) {
|
||||
val device1 = screenDevices[i]
|
||||
val gc = device1.defaultConfiguration
|
||||
val screenBounds = gc.bounds
|
||||
if (screenBounds.contains(pos)) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
// we are the primary monitor, so return 0.
|
||||
return 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the Container at the same monitor, in the CENTER, as the mouse
|
||||
*/
|
||||
fun showOnSameScreenAsMouse_Center(frame: Container) {
|
||||
val mouseLocation = MouseInfo.getPointerInfo().location
|
||||
val monitorAtMouse = getMonitorAtLocation(mouseLocation)
|
||||
val bounds = monitorAtMouse!!.defaultConfiguration.bounds
|
||||
frame.setLocation(bounds.x + bounds.width / 2 - frame.width / 2, bounds.y + bounds.height / 2 - frame.height / 2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the Container at the same monitor as the mouse, at its default location
|
||||
*/
|
||||
fun showOnSameScreenAsMouse(frame: Container) {
|
||||
val mouseLocation = MouseInfo.getPointerInfo().location
|
||||
val monitorAtMouse = getMonitorAtLocation(mouseLocation)!!
|
||||
val bounds = monitorAtMouse.defaultConfiguration.bounds
|
||||
frame.setLocation(bounds.x, bounds.y)
|
||||
}
|
||||
}
|
|
@ -1,577 +0,0 @@
|
|||
/*
|
||||
* Copyright 2014 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.Frame;
|
||||
import java.awt.HeadlessException;
|
||||
import java.awt.Image;
|
||||
import java.awt.Point;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.Window;
|
||||
import java.awt.event.HierarchyEvent;
|
||||
import java.awt.event.HierarchyListener;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.WindowListener;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.swing.AbstractButton;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JRootPane;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.UIManager;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public
|
||||
class SwingUtil {
|
||||
static {
|
||||
/*
|
||||
* hack workaround for starting the Toolkit thread before any Timer stuff javax.swing.Timer uses the Event Dispatch Thread, which is not
|
||||
* created until the Toolkit thread starts up. Using the Swing Timer before starting this stuff starts up may get unexpected
|
||||
* results (such as taking a long time before the first timer event).
|
||||
*/
|
||||
Toolkit.getDefaultToolkit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the entire L&F based on "simple" name. Null to set to the system L&F. If this is not called (or set), Swing will use the
|
||||
* default CrossPlatform L&F, which is 'Metal'.
|
||||
*
|
||||
* NOTE: On Linux + swing if the SystemLookAndFeel is the GtkLookAndFeel, this will cause GTK2 to get first which
|
||||
* will cause conflicts if one tries to use GTK3
|
||||
*
|
||||
* @param lookAndFeel the class or null for the system default
|
||||
*/
|
||||
public static
|
||||
void setLookAndFeel(final Class<?> lookAndFeel) {
|
||||
if (lookAndFeel == null) {
|
||||
setLookAndFeelByName(null);
|
||||
} else {
|
||||
setLookAndFeelByName(lookAndFeel.getName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the entire L&F based on "simple" name. Null to set to the system L&F. If this is not called (or set), Swing will use the
|
||||
* default CrossPlatform L&F, which is 'Metal'.
|
||||
*
|
||||
* NOTE: On Linux + swing if the SystemLookAndFeel is the GtkLookAndFeel, this will cause GTK2 to get first which
|
||||
* will cause conflicts if one tries to use GTK3
|
||||
*
|
||||
* @param lookAndFeel the simple name or null for the system default
|
||||
*/
|
||||
public static
|
||||
void setLookAndFeelByName(final String lookAndFeel) {
|
||||
if (lookAndFeel == null) {
|
||||
try {
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// register a different L&F
|
||||
String specified = lookAndFeel.toLowerCase(Locale.US).trim();
|
||||
String current = UIManager.getLookAndFeel().getName().toLowerCase(Locale.US);
|
||||
|
||||
if (!specified.equals(current)) {
|
||||
try {
|
||||
for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
|
||||
String name = info.getName().toLowerCase(Locale.US);
|
||||
String className = info.getClassName().toLowerCase(Locale.US);
|
||||
if (specified.equals(name) || specified.equals(className)) {
|
||||
UIManager.setLookAndFeel(info.getClassName());
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// whoops. something isn't right!
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// // display available look and feels by name
|
||||
// for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
|
||||
// System.err.println(info.getClassName());
|
||||
// }
|
||||
//
|
||||
// // display all properties for the specified look and feel
|
||||
// Set<Map.Entry<Object, Object>> entries = UIManager.getLookAndFeelDefaults()
|
||||
// .entrySet();
|
||||
// for (Map.Entry<Object, Object> e : entries) {
|
||||
// String key;
|
||||
//
|
||||
//
|
||||
// if (e.getKey() instanceof StringBuffer) {
|
||||
// StringBuffer s = (StringBuffer) e.getKey();
|
||||
// key = s.toString();
|
||||
// }
|
||||
// else {
|
||||
// key = (String) e.getKey();
|
||||
// }
|
||||
//
|
||||
// //Print all the keys available in UI manager
|
||||
// if (e.getValue() != null && (key.toLowerCase().contains("icon") || key.toLowerCase().contains("image"))) {
|
||||
// System.out.println("Key: " + key);
|
||||
// }
|
||||
//
|
||||
// if (key.endsWith("TabbedPane.font")) {
|
||||
// Font font = UIManager.getFont(key);
|
||||
// Font biggerFont = font.deriveFont(2 * font.getSize2D());
|
||||
// // change ui default to bigger font
|
||||
// UIManager.put(key, biggerFont);
|
||||
// }
|
||||
// else if (key.endsWith("Tree.rowHeight")) {
|
||||
// int rowHeight = UIManager.getInt(key);
|
||||
// int bigRowHeight = 2 * rowHeight;
|
||||
// UIManager.put(key, bigRowHeight);
|
||||
// }
|
||||
// }
|
||||
|
||||
// this means we couldn't find our L&F
|
||||
new Exception("Could not load " + lookAndFeel + ", it was not available.").printStackTrace();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the System is configured to use the System L&F. False otherwise
|
||||
*/
|
||||
public static
|
||||
boolean isDefaultLookAndFeel() {
|
||||
return UIManager.getLookAndFeel()
|
||||
.getClass()
|
||||
.getName()
|
||||
.equals(UIManager.getSystemLookAndFeelClassName());
|
||||
}
|
||||
|
||||
/** used when setting various icon components in the GUI to "nothing", since null doesn't work */
|
||||
public static final Image BLANK_ICON = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB_PRE);
|
||||
|
||||
/**
|
||||
* Adds a listener to the window parent of the given component. Can be before the component is really added to its hierarchy.
|
||||
*
|
||||
* @param source The source component
|
||||
* @param listener The listener to add to the window
|
||||
*/
|
||||
public static
|
||||
void addWindowListener(final Component source, final WindowListener listener) {
|
||||
if (source instanceof Window) {
|
||||
((Window) source).addWindowListener(listener);
|
||||
}
|
||||
else {
|
||||
source.addHierarchyListener(new HierarchyListener() {
|
||||
@Override
|
||||
public
|
||||
void hierarchyChanged(HierarchyEvent e) {
|
||||
if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) == HierarchyEvent.SHOWING_CHANGED) {
|
||||
SwingUtilities.getWindowAncestor(source)
|
||||
.addWindowListener(listener);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the largest icon/image for a button (or other JComponent that has .setIcon(image) method) without affecting the size of the
|
||||
* button. An image that is any larger will require that the button increases it's height or width.
|
||||
*
|
||||
* @param button The button (or JMenuItem, etc that has the .setIcon() method ) that you want to measure
|
||||
*
|
||||
* @return the largest height of an icon before the button will increase in size (as a result of a larger image)
|
||||
*/
|
||||
public static
|
||||
int getLargestIconHeightForButton(AbstractButton button) {
|
||||
// of note, components can ALSO have different sizes attached to them!
|
||||
// mini
|
||||
// myButton.putClientProperty("JComponent.sizeVariant", "mini");
|
||||
// small
|
||||
// mySlider.putClientProperty("JComponent.sizeVariant", "small");
|
||||
// large
|
||||
// myTextField.putClientProperty("JComponent.sizeVariant", "large");
|
||||
|
||||
// save the icon + text
|
||||
Icon icon = button.getIcon();
|
||||
String text = button.getText();
|
||||
|
||||
button.setText("`Tj|┃"); // `Tj|┃ are glyphs that are at the top/bottom of the fontset (usually));
|
||||
int minHeight = 0;
|
||||
int iconSize = 0;
|
||||
for (int i = 1; i < 128; i++) {
|
||||
ImageIcon imageIcon = new ImageIcon(new BufferedImage(1, i, BufferedImage.TYPE_BYTE_BINARY));
|
||||
button.setIcon(imageIcon);
|
||||
button.invalidate();
|
||||
int height = (int) button.getPreferredSize()
|
||||
.getHeight();
|
||||
|
||||
// System.err.println(imageIcon.getIconHeight() + "x" + imageIcon.getIconHeight() + " icon \t>>>>>>>>> " + height + "px tall item")
|
||||
|
||||
if (minHeight == 0) {
|
||||
minHeight = height;
|
||||
} else if (minHeight != height) {
|
||||
break;
|
||||
}
|
||||
|
||||
// this is the largest icon size before the size of the component changes
|
||||
iconSize = imageIcon.getIconHeight();
|
||||
}
|
||||
|
||||
// restore original values
|
||||
button.setIcon(icon);
|
||||
button.setText(text);
|
||||
|
||||
return iconSize;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Centers a component according to the window location.
|
||||
*
|
||||
* @param window The parent window
|
||||
* @param component A component, usually a dialog
|
||||
*/
|
||||
public static
|
||||
void centerInWindow(final Window window, final Component component) {
|
||||
Dimension size = window.getSize();
|
||||
Point loc = window.getLocationOnScreen();
|
||||
Dimension cmpSize = component.getSize();
|
||||
loc.x += (size.width - cmpSize.width) / 2;
|
||||
loc.y += (size.height - cmpSize.height) / 2;
|
||||
component.setBounds(loc.x, loc.y, cmpSize.width, cmpSize.height);
|
||||
}
|
||||
|
||||
public static
|
||||
void invokeLater(final Runnable runnable) {
|
||||
if (EventQueue.isDispatchThread()) {
|
||||
runnable.run();
|
||||
}
|
||||
else {
|
||||
SwingUtilities.invokeLater(runnable);
|
||||
}
|
||||
}
|
||||
|
||||
public static
|
||||
void invokeAndWait(final Runnable runnable) throws InvocationTargetException, InterruptedException {
|
||||
if (EventQueue.isDispatchThread()) {
|
||||
runnable.run();
|
||||
}
|
||||
else {
|
||||
EventQueue.invokeAndWait(runnable);
|
||||
}
|
||||
}
|
||||
|
||||
public static
|
||||
void invokeAndWaitQuietly(final Runnable runnable) {
|
||||
if (EventQueue.isDispatchThread()) {
|
||||
runnable.run();
|
||||
}
|
||||
else {
|
||||
try {
|
||||
EventQueue.invokeAndWait(runnable);
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a key character into it's corresponding VK entry, or 0 if it's 0
|
||||
*/
|
||||
public static
|
||||
int getVirtualKey(final char key) {
|
||||
switch (key) {
|
||||
case 0x08: return KeyEvent.VK_BACK_SPACE;
|
||||
case 0x09: return KeyEvent.VK_TAB;
|
||||
case 0x0a: return KeyEvent.VK_ENTER;
|
||||
case 0x1B: return KeyEvent.VK_ESCAPE;
|
||||
case 0x20AC: return KeyEvent.VK_EURO_SIGN;
|
||||
case 0x20: return KeyEvent.VK_SPACE;
|
||||
case 0x21: return KeyEvent.VK_EXCLAMATION_MARK;
|
||||
case 0x22: return KeyEvent.VK_QUOTEDBL;
|
||||
case 0x23: return KeyEvent.VK_NUMBER_SIGN;
|
||||
case 0x24: return KeyEvent.VK_DOLLAR;
|
||||
case 0x26: return KeyEvent.VK_AMPERSAND;
|
||||
case 0x27: return KeyEvent.VK_QUOTE;
|
||||
case 0x28: return KeyEvent.VK_LEFT_PARENTHESIS;
|
||||
case 0x29: return KeyEvent.VK_RIGHT_PARENTHESIS;
|
||||
case 0x2A: return KeyEvent.VK_ASTERISK;
|
||||
case 0x2B: return KeyEvent.VK_PLUS;
|
||||
case 0x2C: return KeyEvent.VK_COMMA;
|
||||
case 0x2D: return KeyEvent.VK_MINUS;
|
||||
case 0x2E: return KeyEvent.VK_PERIOD;
|
||||
case 0x2F: return KeyEvent.VK_SLASH;
|
||||
case 0x30: return KeyEvent.VK_0;
|
||||
case 0x31: return KeyEvent.VK_1;
|
||||
case 0x32: return KeyEvent.VK_2;
|
||||
case 0x33: return KeyEvent.VK_3;
|
||||
case 0x34: return KeyEvent.VK_4;
|
||||
case 0x35: return KeyEvent.VK_5;
|
||||
case 0x36: return KeyEvent.VK_6;
|
||||
case 0x37: return KeyEvent.VK_7;
|
||||
case 0x38: return KeyEvent.VK_8;
|
||||
case 0x39: return KeyEvent.VK_9;
|
||||
case 0x3A: return KeyEvent.VK_COLON;
|
||||
case 0x3B: return KeyEvent.VK_SEMICOLON;
|
||||
case 0x3C: return KeyEvent.VK_LESS;
|
||||
case 0x3D: return KeyEvent.VK_EQUALS;
|
||||
case 0x3E: return KeyEvent.VK_GREATER;
|
||||
case 0x40: return KeyEvent.VK_AT;
|
||||
case 0x41: return KeyEvent.VK_A;
|
||||
case 0x42: return KeyEvent.VK_B;
|
||||
case 0x43: return KeyEvent.VK_C;
|
||||
case 0x44: return KeyEvent.VK_D;
|
||||
case 0x45: return KeyEvent.VK_E;
|
||||
case 0x46: return KeyEvent.VK_F;
|
||||
case 0x47: return KeyEvent.VK_G;
|
||||
case 0x48: return KeyEvent.VK_H;
|
||||
case 0x49: return KeyEvent.VK_I;
|
||||
case 0x4A: return KeyEvent.VK_J;
|
||||
case 0x4B: return KeyEvent.VK_K;
|
||||
case 0x4C: return KeyEvent.VK_L;
|
||||
case 0x4D: return KeyEvent.VK_M;
|
||||
case 0x4E: return KeyEvent.VK_N;
|
||||
case 0x4F: return KeyEvent.VK_O;
|
||||
case 0x50: return KeyEvent.VK_P;
|
||||
case 0x51: return KeyEvent.VK_Q;
|
||||
case 0x52: return KeyEvent.VK_R;
|
||||
case 0x53: return KeyEvent.VK_S;
|
||||
case 0x54: return KeyEvent.VK_T;
|
||||
case 0x55: return KeyEvent.VK_U;
|
||||
case 0x56: return KeyEvent.VK_V;
|
||||
case 0x57: return KeyEvent.VK_W;
|
||||
case 0x58: return KeyEvent.VK_X;
|
||||
case 0x59: return KeyEvent.VK_Y;
|
||||
case 0x5A: return KeyEvent.VK_Z;
|
||||
case 0x5B: return KeyEvent.VK_OPEN_BRACKET;
|
||||
case 0x5C: return KeyEvent.VK_BACK_SLASH;
|
||||
case 0x5D: return KeyEvent.VK_CLOSE_BRACKET;
|
||||
case 0x5E: return KeyEvent.VK_CIRCUMFLEX;
|
||||
case 0x5F: return KeyEvent.VK_UNDERSCORE;
|
||||
case 0x60: return KeyEvent.VK_BACK_QUOTE;
|
||||
case 0x61: return KeyEvent.VK_A;
|
||||
case 0x62: return KeyEvent.VK_B;
|
||||
case 0x63: return KeyEvent.VK_C;
|
||||
case 0x64: return KeyEvent.VK_D;
|
||||
case 0x65: return KeyEvent.VK_E;
|
||||
case 0x66: return KeyEvent.VK_F;
|
||||
case 0x67: return KeyEvent.VK_G;
|
||||
case 0x68: return KeyEvent.VK_H;
|
||||
case 0x69: return KeyEvent.VK_I;
|
||||
case 0x6A: return KeyEvent.VK_J;
|
||||
case 0x6B: return KeyEvent.VK_K;
|
||||
case 0x6C: return KeyEvent.VK_L;
|
||||
case 0x6D: return KeyEvent.VK_M;
|
||||
case 0x6E: return KeyEvent.VK_N;
|
||||
case 0x6F: return KeyEvent.VK_O;
|
||||
case 0x70: return KeyEvent.VK_P;
|
||||
case 0x71: return KeyEvent.VK_Q;
|
||||
case 0x72: return KeyEvent.VK_R;
|
||||
case 0x73: return KeyEvent.VK_S;
|
||||
case 0x74: return KeyEvent.VK_T;
|
||||
case 0x75: return KeyEvent.VK_U;
|
||||
case 0x76: return KeyEvent.VK_V;
|
||||
case 0x77: return KeyEvent.VK_W;
|
||||
case 0x78: return KeyEvent.VK_X;
|
||||
case 0x79: return KeyEvent.VK_Y;
|
||||
case 0x7A: return KeyEvent.VK_Z;
|
||||
case 0x7B: return KeyEvent.VK_BRACELEFT;
|
||||
case 0x7D: return KeyEvent.VK_BRACERIGHT;
|
||||
case 0x7F: return KeyEvent.VK_DELETE;
|
||||
case 0xA1: return KeyEvent.VK_INVERTED_EXCLAMATION_MARK;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a VK key character into it's corresponding char entry
|
||||
*/
|
||||
public static
|
||||
char getFromVirtualKey(final int key) {
|
||||
switch (key) {
|
||||
case KeyEvent.VK_BACK_SPACE: return 0x08;
|
||||
case KeyEvent.VK_TAB: return 0x09;
|
||||
case KeyEvent.VK_ENTER: return 0x0a;
|
||||
case KeyEvent.VK_ESCAPE: return 0x1B;
|
||||
case KeyEvent.VK_EURO_SIGN: return 0x20AC;
|
||||
case KeyEvent.VK_SPACE: return 0x20;
|
||||
case KeyEvent.VK_EXCLAMATION_MARK: return 0x21;
|
||||
case KeyEvent.VK_QUOTEDBL: return 0x22;
|
||||
case KeyEvent.VK_NUMBER_SIGN: return 0x23;
|
||||
case KeyEvent.VK_DOLLAR: return 0x24;
|
||||
case KeyEvent.VK_AMPERSAND: return 0x26;
|
||||
case KeyEvent.VK_QUOTE: return 0x27;
|
||||
case KeyEvent.VK_LEFT_PARENTHESIS: return 0x28;
|
||||
case KeyEvent.VK_RIGHT_PARENTHESIS: return 0x29;
|
||||
case KeyEvent.VK_ASTERISK: return 0x2A;
|
||||
case KeyEvent.VK_PLUS: return 0x2B;
|
||||
case KeyEvent.VK_COMMA: return 0x2C;
|
||||
case KeyEvent.VK_MINUS: return 0x2D;
|
||||
case KeyEvent.VK_PERIOD: return 0x2E;
|
||||
case KeyEvent.VK_SLASH: return 0x2F;
|
||||
case KeyEvent.VK_0: return 0x30;
|
||||
case KeyEvent.VK_1: return 0x31;
|
||||
case KeyEvent.VK_2: return 0x32;
|
||||
case KeyEvent.VK_3: return 0x33;
|
||||
case KeyEvent.VK_4: return 0x34;
|
||||
case KeyEvent.VK_5: return 0x35;
|
||||
case KeyEvent.VK_6: return 0x36;
|
||||
case KeyEvent.VK_7: return 0x37;
|
||||
case KeyEvent.VK_8: return 0x38;
|
||||
case KeyEvent.VK_9: return 0x39;
|
||||
case KeyEvent.VK_COLON: return 0x3A;
|
||||
case KeyEvent.VK_SEMICOLON: return 0x3B;
|
||||
case KeyEvent.VK_LESS: return 0x3C;
|
||||
case KeyEvent.VK_EQUALS: return 0x3D;
|
||||
case KeyEvent.VK_GREATER: return 0x3E;
|
||||
case KeyEvent.VK_AT: return 0x40;
|
||||
case KeyEvent.VK_A: return 0x41;
|
||||
case KeyEvent.VK_B: return 0x42;
|
||||
case KeyEvent.VK_C: return 0x43;
|
||||
case KeyEvent.VK_D: return 0x44;
|
||||
case KeyEvent.VK_E: return 0x45;
|
||||
case KeyEvent.VK_F: return 0x46;
|
||||
case KeyEvent.VK_G: return 0x47;
|
||||
case KeyEvent.VK_H: return 0x48;
|
||||
case KeyEvent.VK_I: return 0x49;
|
||||
case KeyEvent.VK_J: return 0x4A;
|
||||
case KeyEvent.VK_K: return 0x4B;
|
||||
case KeyEvent.VK_L: return 0x4C;
|
||||
case KeyEvent.VK_M: return 0x4D;
|
||||
case KeyEvent.VK_N: return 0x4E;
|
||||
case KeyEvent.VK_O: return 0x4F;
|
||||
case KeyEvent.VK_P: return 0x50;
|
||||
case KeyEvent.VK_Q: return 0x51;
|
||||
case KeyEvent.VK_R: return 0x52;
|
||||
case KeyEvent.VK_S: return 0x53;
|
||||
case KeyEvent.VK_T: return 0x54;
|
||||
case KeyEvent.VK_U: return 0x55;
|
||||
case KeyEvent.VK_V: return 0x56;
|
||||
case KeyEvent.VK_W: return 0x57;
|
||||
case KeyEvent.VK_X: return 0x58;
|
||||
case KeyEvent.VK_Y: return 0x59;
|
||||
case KeyEvent.VK_Z: return 0x5A;
|
||||
case KeyEvent.VK_OPEN_BRACKET: return 0x5B;
|
||||
case KeyEvent.VK_BACK_SLASH: return 0x5C;
|
||||
case KeyEvent.VK_CLOSE_BRACKET: return 0x5D;
|
||||
case KeyEvent.VK_CIRCUMFLEX: return 0x5E;
|
||||
case KeyEvent.VK_UNDERSCORE: return 0x5F;
|
||||
case KeyEvent.VK_BACK_QUOTE: return 0x60;
|
||||
// case KeyEvent.VK_A: return 0x61;
|
||||
// case KeyEvent.VK_B: return 0x62;
|
||||
// case KeyEvent.VK_C: return 0x63;
|
||||
// case KeyEvent.VK_D: return 0x64;
|
||||
// case KeyEvent.VK_E: return 0x65;
|
||||
// case KeyEvent.VK_F: return 0x66;
|
||||
// case KeyEvent.VK_G: return 0x67;
|
||||
// case KeyEvent.VK_H: return 0x68;
|
||||
// case KeyEvent.VK_I: return 0x69;
|
||||
// case KeyEvent.VK_J: return 0x6A;
|
||||
// case KeyEvent.VK_K: return 0x6B;
|
||||
// case KeyEvent.VK_L: return 0x6C;
|
||||
// case KeyEvent.VK_M: return 0x6D;
|
||||
// case KeyEvent.VK_N: return 0x6E;
|
||||
// case KeyEvent.VK_O: return 0x6F;
|
||||
// case KeyEvent.VK_P: return 0x70;
|
||||
// case KeyEvent.VK_Q: return 0x71;
|
||||
// case KeyEvent.VK_R: return 0x72;
|
||||
// case KeyEvent.VK_S: return 0x73;
|
||||
// case KeyEvent.VK_T: return 0x74;
|
||||
// case KeyEvent.VK_U: return 0x75;
|
||||
// case KeyEvent.VK_V: return 0x76;
|
||||
// case KeyEvent.VK_W: return 0x77;
|
||||
// case KeyEvent.VK_X: return 0x78;
|
||||
// case KeyEvent.VK_Y: return 0x79;
|
||||
// case KeyEvent.VK_Z: return 0x7A;
|
||||
case KeyEvent.VK_BRACELEFT: return 0x7B;
|
||||
case KeyEvent.VK_BRACERIGHT: return 0x7D;
|
||||
case KeyEvent.VK_DELETE: return 0x7F;
|
||||
case KeyEvent.VK_INVERTED_EXCLAMATION_MARK: return 0xA1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays up a dialog in the center of the screen (where the mouse is located) that displays a message using a default icon
|
||||
* determined by the <code>messageType</code> parameter.
|
||||
*
|
||||
* @param title the title for the dialog
|
||||
* @param message the message to display
|
||||
* @param messageType the type (ERROR, QUESTION, etc)
|
||||
*
|
||||
* @return the clicked on value, if any.
|
||||
* @throws HeadlessException
|
||||
*/
|
||||
public static
|
||||
int showMessageDialog(final String title, final String message, final int messageType) throws HeadlessException {
|
||||
JOptionPane pane = new JOptionPane(message, messageType, JOptionPane.DEFAULT_OPTION, null, null, null);
|
||||
pane.setInitialValue(null);
|
||||
Frame rootFrame = JOptionPane.getRootFrame();
|
||||
pane.setComponentOrientation(rootFrame.getComponentOrientation());
|
||||
|
||||
int style;
|
||||
|
||||
switch (messageType) {
|
||||
case JOptionPane.ERROR_MESSAGE:
|
||||
style = JRootPane.ERROR_DIALOG;
|
||||
break;
|
||||
case JOptionPane.QUESTION_MESSAGE:
|
||||
style = JRootPane.QUESTION_DIALOG;
|
||||
break;
|
||||
case JOptionPane.WARNING_MESSAGE:
|
||||
style = JRootPane.WARNING_DIALOG;
|
||||
break;
|
||||
case JOptionPane.INFORMATION_MESSAGE:
|
||||
style = JRootPane.INFORMATION_DIALOG;
|
||||
break;
|
||||
case JOptionPane.PLAIN_MESSAGE:
|
||||
default:
|
||||
style = JRootPane.PLAIN_DIALOG;
|
||||
}
|
||||
|
||||
JDialog dialog = pane.createDialog(title);
|
||||
dialog.setModal(true);
|
||||
|
||||
if (JDialog.isDefaultLookAndFeelDecorated()) {
|
||||
boolean supportsWindowDecorations = UIManager.getLookAndFeel().getSupportsWindowDecorations();
|
||||
if (supportsWindowDecorations) {
|
||||
dialog.setUndecorated(true);
|
||||
dialog.getRootPane().setWindowDecorationStyle(style);
|
||||
}
|
||||
}
|
||||
|
||||
pane.selectInitialValue();
|
||||
ScreenUtil.showOnSameScreenAsMouse_Center(dialog);
|
||||
dialog.setVisible(true);
|
||||
dialog.dispose();
|
||||
|
||||
Object selectedValue = pane.getValue();
|
||||
|
||||
if (selectedValue instanceof Integer) {
|
||||
return ((Integer) selectedValue).intValue();
|
||||
}
|
||||
|
||||
return JOptionPane.CLOSED_OPTION;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,496 @@
|
|||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util
|
||||
|
||||
import dorkbox.util.ScreenUtil.showOnSameScreenAsMouse_Center
|
||||
import java.awt.*
|
||||
import java.awt.event.HierarchyEvent
|
||||
import java.awt.event.KeyEvent
|
||||
import java.awt.event.WindowListener
|
||||
import java.awt.image.BufferedImage
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import javax.swing.*
|
||||
|
||||
@Suppress("unused")
|
||||
object SwingUtil {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = Sys.version
|
||||
|
||||
init {/*
|
||||
* hack workaround for starting the Toolkit thread before any Timer stuff javax.swing.Timer uses the Event Dispatch Thread, which is not
|
||||
* created until the Toolkit thread starts up. Using the Swing Timer before starting this stuff starts up may get unexpected
|
||||
* results (such as taking a long time before the first timer event).
|
||||
*/
|
||||
Toolkit.getDefaultToolkit()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the entire L&F based on "simple" name. Null to set to the system L&F. If this is not called (or set), Swing will use the
|
||||
* default CrossPlatform L&F, which is 'Metal'.
|
||||
*
|
||||
* NOTE: On Linux + swing if the SystemLookAndFeel is the GtkLookAndFeel, this will cause GTK2 to get first which
|
||||
* will cause conflicts if one tries to use GTK3
|
||||
*
|
||||
* @param lookAndFeel the class or null for the system default
|
||||
*/
|
||||
fun setLookAndFeel(lookAndFeel: Class<*>?) {
|
||||
if (lookAndFeel == null) {
|
||||
setLookAndFeelByName(null)
|
||||
} else {
|
||||
setLookAndFeelByName(lookAndFeel.name)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the entire L&F based on "simple" name. Null to set to the system L&F. If this is not called (or set), Swing will use the
|
||||
* default CrossPlatform L&F, which is 'Metal'.
|
||||
*
|
||||
* NOTE: On Linux + swing if the SystemLookAndFeel is the GtkLookAndFeel, this will cause GTK2 to get first which
|
||||
* will cause conflicts if one tries to use GTK3
|
||||
*
|
||||
* @param lookAndFeel the simple name or null for the system default
|
||||
*/
|
||||
fun setLookAndFeelByName(lookAndFeel: String?) {
|
||||
if (lookAndFeel == null) {
|
||||
try {
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName())
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// register a different L&F
|
||||
val specified = lookAndFeel.lowercase().trim { it <= ' ' }
|
||||
val current = UIManager.getLookAndFeel().name.lowercase()
|
||||
if (specified != current) {
|
||||
try {
|
||||
for (info in UIManager.getInstalledLookAndFeels()) {
|
||||
val name = info.name.lowercase()
|
||||
val className = info.className.lowercase()
|
||||
if (specified == name || specified == className) {
|
||||
UIManager.setLookAndFeel(info.className)
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// whoops. something isn't right!
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
// // display available look and feels by name
|
||||
// for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
|
||||
// System.err.println(info.getClassName());
|
||||
// }
|
||||
//
|
||||
// // display all properties for the specified look and feel
|
||||
// Set<Map.Entry<Object, Object>> entries = UIManager.getLookAndFeelDefaults()
|
||||
// .entrySet();
|
||||
// for (Map.Entry<Object, Object> e : entries) {
|
||||
// String key;
|
||||
//
|
||||
//
|
||||
// if (e.getKey() instanceof StringBuffer) {
|
||||
// StringBuffer s = (StringBuffer) e.getKey();
|
||||
// key = s.toString();
|
||||
// }
|
||||
// else {
|
||||
// key = (String) e.getKey();
|
||||
// }
|
||||
//
|
||||
// //Print all the keys available in UI manager
|
||||
// if (e.getValue() != null && (key.toLowerCase().contains("icon") || key.toLowerCase().contains("image"))) {
|
||||
// System.out.println("Key: " + key);
|
||||
// }
|
||||
//
|
||||
// if (key.endsWith("TabbedPane.font")) {
|
||||
// Font font = UIManager.getFont(key);
|
||||
// Font biggerFont = font.deriveFont(2 * font.getSize2D());
|
||||
// // change ui default to bigger font
|
||||
// UIManager.put(key, biggerFont);
|
||||
// }
|
||||
// else if (key.endsWith("Tree.rowHeight")) {
|
||||
// int rowHeight = UIManager.getInt(key);
|
||||
// int bigRowHeight = 2 * rowHeight;
|
||||
// UIManager.put(key, bigRowHeight);
|
||||
// }
|
||||
// }
|
||||
|
||||
// this means we couldn't find our L&F
|
||||
Exception("Could not load $lookAndFeel, it was not available.").printStackTrace()
|
||||
}
|
||||
|
||||
val isDefaultLookAndFeel: Boolean
|
||||
/**
|
||||
* @return true if the System is configured to use the System L&F. False otherwise
|
||||
*/
|
||||
get() = (UIManager.getLookAndFeel().javaClass.name == UIManager.getSystemLookAndFeelClassName())
|
||||
|
||||
/** used when setting various icon components in the GUI to "nothing", since null doesn't work */
|
||||
val BLANK_ICON: Image = BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB_PRE)
|
||||
|
||||
/**
|
||||
* Adds a listener to the window parent of the given component. Can be before the component is really added to its hierarchy.
|
||||
*
|
||||
* @param source The source component
|
||||
* @param listener The listener to add to the window
|
||||
*/
|
||||
fun addWindowListener(source: Component, listener: WindowListener) {
|
||||
if (source is Window) {
|
||||
source.addWindowListener(listener)
|
||||
} else {
|
||||
source.addHierarchyListener { e ->
|
||||
if (e.changeFlags and HierarchyEvent.SHOWING_CHANGED.toLong() == HierarchyEvent.SHOWING_CHANGED.toLong()) {
|
||||
SwingUtilities.getWindowAncestor(source).addWindowListener(listener)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the largest icon/image for a button (or other JComponent that has .setIcon(image) method) without affecting the size of the
|
||||
* button. An image that is any larger will require that the button increases it's height or width.
|
||||
*
|
||||
* @param button The button (or JMenuItem, etc that has the .setIcon() method ) that you want to measure
|
||||
*
|
||||
* @return the largest height of an icon before the button will increase in size (as a result of a larger image)
|
||||
*/
|
||||
fun getLargestIconHeightForButton(button: AbstractButton): Int {
|
||||
// of note, components can ALSO have different sizes attached to them!
|
||||
// mini
|
||||
// myButton.putClientProperty("JComponent.sizeVariant", "mini");
|
||||
// small
|
||||
// mySlider.putClientProperty("JComponent.sizeVariant", "small");
|
||||
// large
|
||||
// myTextField.putClientProperty("JComponent.sizeVariant", "large");
|
||||
|
||||
// save the icon + text
|
||||
val icon = button.icon
|
||||
val text = button.text
|
||||
button.text = "`Tj|┃" // `Tj|┃ are glyphs that are at the top/bottom of the fontset (usually));
|
||||
var minHeight = 0
|
||||
var iconSize = 0
|
||||
for (i in 1..127) {
|
||||
val imageIcon = ImageIcon(BufferedImage(1, i, BufferedImage.TYPE_BYTE_BINARY))
|
||||
button.icon = imageIcon
|
||||
button.invalidate()
|
||||
val height = button.preferredSize.getHeight().toInt()
|
||||
|
||||
// System.err.println(imageIcon.getIconHeight() + "x" + imageIcon.getIconHeight() + " icon \t>>>>>>>>> " + height + "px tall item")
|
||||
if (minHeight == 0) {
|
||||
minHeight = height
|
||||
} else if (minHeight != height) {
|
||||
break
|
||||
}
|
||||
|
||||
// this is the largest icon size before the size of the component changes
|
||||
iconSize = imageIcon.iconHeight
|
||||
}
|
||||
|
||||
// restore original values
|
||||
button.icon = icon
|
||||
button.text = text
|
||||
return iconSize
|
||||
}
|
||||
|
||||
/**
|
||||
* Centers a component according to the window location.
|
||||
*
|
||||
* @param window The parent window
|
||||
* @param component A component, usually a dialog
|
||||
*/
|
||||
fun centerInWindow(window: Window, component: Component) {
|
||||
val size = window.size
|
||||
val loc = window.locationOnScreen
|
||||
val cmpSize = component.size
|
||||
loc.x += (size.width - cmpSize.width) / 2
|
||||
loc.y += (size.height - cmpSize.height) / 2
|
||||
|
||||
component.setBounds(loc.x, loc.y, cmpSize.width, cmpSize.height)
|
||||
}
|
||||
|
||||
fun invokeLater(runnable: Runnable) {
|
||||
if (EventQueue.isDispatchThread()) {
|
||||
runnable.run()
|
||||
} else {
|
||||
SwingUtilities.invokeLater(runnable)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(InvocationTargetException::class, InterruptedException::class)
|
||||
fun invokeAndWait(runnable: Runnable) {
|
||||
if (EventQueue.isDispatchThread()) {
|
||||
runnable.run()
|
||||
} else {
|
||||
EventQueue.invokeAndWait(runnable)
|
||||
}
|
||||
}
|
||||
|
||||
fun invokeAndWaitQuietly(runnable: Runnable) {
|
||||
if (EventQueue.isDispatchThread()) {
|
||||
runnable.run()
|
||||
} else {
|
||||
try {
|
||||
EventQueue.invokeAndWait(runnable)
|
||||
} catch (ignore: Exception) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a key character into it's corresponding VK entry, or 0 if it's 0
|
||||
*/
|
||||
fun getVirtualKey(key: Char): Int {
|
||||
when (key.code) {
|
||||
0x08 -> return KeyEvent.VK_BACK_SPACE
|
||||
0x09 -> return KeyEvent.VK_TAB
|
||||
0x0a -> return KeyEvent.VK_ENTER
|
||||
0x1B -> return KeyEvent.VK_ESCAPE
|
||||
0x20AC -> return KeyEvent.VK_EURO_SIGN
|
||||
0x20 -> return KeyEvent.VK_SPACE
|
||||
0x21 -> return KeyEvent.VK_EXCLAMATION_MARK
|
||||
0x22 -> return KeyEvent.VK_QUOTEDBL
|
||||
0x23 -> return KeyEvent.VK_NUMBER_SIGN
|
||||
0x24 -> return KeyEvent.VK_DOLLAR
|
||||
0x26 -> return KeyEvent.VK_AMPERSAND
|
||||
0x27 -> return KeyEvent.VK_QUOTE
|
||||
0x28 -> return KeyEvent.VK_LEFT_PARENTHESIS
|
||||
0x29 -> return KeyEvent.VK_RIGHT_PARENTHESIS
|
||||
0x2A -> return KeyEvent.VK_ASTERISK
|
||||
0x2B -> return KeyEvent.VK_PLUS
|
||||
0x2C -> return KeyEvent.VK_COMMA
|
||||
0x2D -> return KeyEvent.VK_MINUS
|
||||
0x2E -> return KeyEvent.VK_PERIOD
|
||||
0x2F -> return KeyEvent.VK_SLASH
|
||||
0x30 -> return KeyEvent.VK_0
|
||||
0x31 -> return KeyEvent.VK_1
|
||||
0x32 -> return KeyEvent.VK_2
|
||||
0x33 -> return KeyEvent.VK_3
|
||||
0x34 -> return KeyEvent.VK_4
|
||||
0x35 -> return KeyEvent.VK_5
|
||||
0x36 -> return KeyEvent.VK_6
|
||||
0x37 -> return KeyEvent.VK_7
|
||||
0x38 -> return KeyEvent.VK_8
|
||||
0x39 -> return KeyEvent.VK_9
|
||||
0x3A -> return KeyEvent.VK_COLON
|
||||
0x3B -> return KeyEvent.VK_SEMICOLON
|
||||
0x3C -> return KeyEvent.VK_LESS
|
||||
0x3D -> return KeyEvent.VK_EQUALS
|
||||
0x3E -> return KeyEvent.VK_GREATER
|
||||
0x40 -> return KeyEvent.VK_AT
|
||||
0x41 -> return KeyEvent.VK_A
|
||||
0x42 -> return KeyEvent.VK_B
|
||||
0x43 -> return KeyEvent.VK_C
|
||||
0x44 -> return KeyEvent.VK_D
|
||||
0x45 -> return KeyEvent.VK_E
|
||||
0x46 -> return KeyEvent.VK_F
|
||||
0x47 -> return KeyEvent.VK_G
|
||||
0x48 -> return KeyEvent.VK_H
|
||||
0x49 -> return KeyEvent.VK_I
|
||||
0x4A -> return KeyEvent.VK_J
|
||||
0x4B -> return KeyEvent.VK_K
|
||||
0x4C -> return KeyEvent.VK_L
|
||||
0x4D -> return KeyEvent.VK_M
|
||||
0x4E -> return KeyEvent.VK_N
|
||||
0x4F -> return KeyEvent.VK_O
|
||||
0x50 -> return KeyEvent.VK_P
|
||||
0x51 -> return KeyEvent.VK_Q
|
||||
0x52 -> return KeyEvent.VK_R
|
||||
0x53 -> return KeyEvent.VK_S
|
||||
0x54 -> return KeyEvent.VK_T
|
||||
0x55 -> return KeyEvent.VK_U
|
||||
0x56 -> return KeyEvent.VK_V
|
||||
0x57 -> return KeyEvent.VK_W
|
||||
0x58 -> return KeyEvent.VK_X
|
||||
0x59 -> return KeyEvent.VK_Y
|
||||
0x5A -> return KeyEvent.VK_Z
|
||||
0x5B -> return KeyEvent.VK_OPEN_BRACKET
|
||||
0x5C -> return KeyEvent.VK_BACK_SLASH
|
||||
0x5D -> return KeyEvent.VK_CLOSE_BRACKET
|
||||
0x5E -> return KeyEvent.VK_CIRCUMFLEX
|
||||
0x5F -> return KeyEvent.VK_UNDERSCORE
|
||||
0x60 -> return KeyEvent.VK_BACK_QUOTE
|
||||
0x61 -> return KeyEvent.VK_A
|
||||
0x62 -> return KeyEvent.VK_B
|
||||
0x63 -> return KeyEvent.VK_C
|
||||
0x64 -> return KeyEvent.VK_D
|
||||
0x65 -> return KeyEvent.VK_E
|
||||
0x66 -> return KeyEvent.VK_F
|
||||
0x67 -> return KeyEvent.VK_G
|
||||
0x68 -> return KeyEvent.VK_H
|
||||
0x69 -> return KeyEvent.VK_I
|
||||
0x6A -> return KeyEvent.VK_J
|
||||
0x6B -> return KeyEvent.VK_K
|
||||
0x6C -> return KeyEvent.VK_L
|
||||
0x6D -> return KeyEvent.VK_M
|
||||
0x6E -> return KeyEvent.VK_N
|
||||
0x6F -> return KeyEvent.VK_O
|
||||
0x70 -> return KeyEvent.VK_P
|
||||
0x71 -> return KeyEvent.VK_Q
|
||||
0x72 -> return KeyEvent.VK_R
|
||||
0x73 -> return KeyEvent.VK_S
|
||||
0x74 -> return KeyEvent.VK_T
|
||||
0x75 -> return KeyEvent.VK_U
|
||||
0x76 -> return KeyEvent.VK_V
|
||||
0x77 -> return KeyEvent.VK_W
|
||||
0x78 -> return KeyEvent.VK_X
|
||||
0x79 -> return KeyEvent.VK_Y
|
||||
0x7A -> return KeyEvent.VK_Z
|
||||
0x7B -> return KeyEvent.VK_BRACELEFT
|
||||
0x7D -> return KeyEvent.VK_BRACERIGHT
|
||||
0x7F -> return KeyEvent.VK_DELETE
|
||||
0xA1 -> return KeyEvent.VK_INVERTED_EXCLAMATION_MARK
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a VK key character into it's corresponding char entry
|
||||
*/
|
||||
fun getFromVirtualKey(key: Int): Char {
|
||||
val code = when (key) {
|
||||
KeyEvent.VK_BACK_SPACE -> 0x08
|
||||
KeyEvent.VK_TAB -> 0x09
|
||||
KeyEvent.VK_ENTER -> 0x0a
|
||||
KeyEvent.VK_ESCAPE -> 0x1B
|
||||
KeyEvent.VK_EURO_SIGN -> 0x20AC
|
||||
KeyEvent.VK_SPACE -> 0x20
|
||||
KeyEvent.VK_EXCLAMATION_MARK -> 0x21
|
||||
KeyEvent.VK_QUOTEDBL -> 0x22
|
||||
KeyEvent.VK_NUMBER_SIGN -> 0x23
|
||||
KeyEvent.VK_DOLLAR -> 0x24
|
||||
KeyEvent.VK_AMPERSAND -> 0x26
|
||||
KeyEvent.VK_QUOTE -> 0x27
|
||||
KeyEvent.VK_LEFT_PARENTHESIS -> 0x28
|
||||
KeyEvent.VK_RIGHT_PARENTHESIS -> 0x29
|
||||
KeyEvent.VK_ASTERISK -> 0x2A
|
||||
KeyEvent.VK_PLUS -> 0x2B
|
||||
KeyEvent.VK_COMMA -> 0x2C
|
||||
KeyEvent.VK_MINUS -> 0x2D
|
||||
KeyEvent.VK_PERIOD -> 0x2E
|
||||
KeyEvent.VK_SLASH -> 0x2F
|
||||
KeyEvent.VK_0 -> 0x30
|
||||
KeyEvent.VK_1 -> 0x31
|
||||
KeyEvent.VK_2 -> 0x32
|
||||
KeyEvent.VK_3 -> 0x33
|
||||
KeyEvent.VK_4 -> 0x34
|
||||
KeyEvent.VK_5 -> 0x35
|
||||
KeyEvent.VK_6 -> 0x36
|
||||
KeyEvent.VK_7 -> 0x37
|
||||
KeyEvent.VK_8 -> 0x38
|
||||
KeyEvent.VK_9 -> 0x39
|
||||
KeyEvent.VK_COLON -> 0x3A
|
||||
KeyEvent.VK_SEMICOLON -> 0x3B
|
||||
KeyEvent.VK_LESS -> 0x3C
|
||||
KeyEvent.VK_EQUALS -> 0x3D
|
||||
KeyEvent.VK_GREATER -> 0x3E
|
||||
KeyEvent.VK_AT -> 0x40
|
||||
KeyEvent.VK_A -> 0x41
|
||||
KeyEvent.VK_B -> 0x42
|
||||
KeyEvent.VK_C -> 0x43
|
||||
KeyEvent.VK_D -> 0x44
|
||||
KeyEvent.VK_E -> 0x45
|
||||
KeyEvent.VK_F -> 0x46
|
||||
KeyEvent.VK_G -> 0x47
|
||||
KeyEvent.VK_H -> 0x48
|
||||
KeyEvent.VK_I -> 0x49
|
||||
KeyEvent.VK_J -> 0x4A
|
||||
KeyEvent.VK_K -> 0x4B
|
||||
KeyEvent.VK_L -> 0x4C
|
||||
KeyEvent.VK_M -> 0x4D
|
||||
KeyEvent.VK_N -> 0x4E
|
||||
KeyEvent.VK_O -> 0x4F
|
||||
KeyEvent.VK_P -> 0x50
|
||||
KeyEvent.VK_Q -> 0x51
|
||||
KeyEvent.VK_R -> 0x52
|
||||
KeyEvent.VK_S -> 0x53
|
||||
KeyEvent.VK_T -> 0x54
|
||||
KeyEvent.VK_U -> 0x55
|
||||
KeyEvent.VK_V -> 0x56
|
||||
KeyEvent.VK_W -> 0x57
|
||||
KeyEvent.VK_X -> 0x58
|
||||
KeyEvent.VK_Y -> 0x59
|
||||
KeyEvent.VK_Z -> 0x5A
|
||||
KeyEvent.VK_OPEN_BRACKET -> 0x5B
|
||||
KeyEvent.VK_BACK_SLASH -> 0x5C
|
||||
KeyEvent.VK_CLOSE_BRACKET -> 0x5D
|
||||
KeyEvent.VK_CIRCUMFLEX -> 0x5E
|
||||
KeyEvent.VK_UNDERSCORE -> 0x5F
|
||||
KeyEvent.VK_BACK_QUOTE -> 0x60
|
||||
KeyEvent.VK_BRACELEFT -> 0x7B
|
||||
KeyEvent.VK_BRACERIGHT -> 0x7D
|
||||
KeyEvent.VK_DELETE -> 0x7F
|
||||
KeyEvent.VK_INVERTED_EXCLAMATION_MARK -> 0xA1
|
||||
else -> 0
|
||||
}
|
||||
|
||||
return code.toChar()
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays up a dialog in the center of the screen (where the mouse is located) that displays a message using a default icon
|
||||
* determined by the `messageType` parameter.
|
||||
*
|
||||
* @param title the title for the dialog
|
||||
* @param message the message to display
|
||||
* @param messageType the type (ERROR, QUESTION, etc)
|
||||
*
|
||||
* @return the clicked on value, if any.
|
||||
* @throws HeadlessException
|
||||
*/
|
||||
@Throws(HeadlessException::class)
|
||||
fun showMessageDialog(title: String, message: String, messageType: Int): Int {
|
||||
val pane = JOptionPane(message, messageType, JOptionPane.DEFAULT_OPTION, null, null, null)
|
||||
pane.initialValue = null
|
||||
|
||||
val rootFrame = JOptionPane.getRootFrame()
|
||||
pane.componentOrientation = rootFrame.componentOrientation
|
||||
val style: Int
|
||||
|
||||
style = when (messageType) {
|
||||
JOptionPane.ERROR_MESSAGE -> JRootPane.ERROR_DIALOG
|
||||
JOptionPane.QUESTION_MESSAGE -> JRootPane.QUESTION_DIALOG
|
||||
JOptionPane.WARNING_MESSAGE -> JRootPane.WARNING_DIALOG
|
||||
JOptionPane.INFORMATION_MESSAGE -> JRootPane.INFORMATION_DIALOG
|
||||
JOptionPane.PLAIN_MESSAGE -> JRootPane.PLAIN_DIALOG
|
||||
else -> JRootPane.PLAIN_DIALOG
|
||||
}
|
||||
|
||||
val dialog = pane.createDialog(title)
|
||||
dialog.isModal = true
|
||||
|
||||
if (JDialog.isDefaultLookAndFeelDecorated()) {
|
||||
val supportsWindowDecorations = UIManager.getLookAndFeel().supportsWindowDecorations
|
||||
if (supportsWindowDecorations) {
|
||||
dialog.isUndecorated = true
|
||||
dialog.rootPane.windowDecorationStyle = style
|
||||
}
|
||||
}
|
||||
|
||||
pane.selectInitialValue()
|
||||
showOnSameScreenAsMouse_Center(dialog)
|
||||
dialog.isVisible = true
|
||||
dialog.dispose()
|
||||
|
||||
val selectedValue = pane.value
|
||||
return if (selectedValue is Int) {
|
||||
selectedValue
|
||||
} else JOptionPane.CLOSED_OPTION
|
||||
}
|
||||
}
|
|
@ -1,763 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import dorkbox.os.OS;
|
||||
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
public final
|
||||
class Sys {
|
||||
public static final int KILOBYTE = 1024;
|
||||
public static final int MEGABYTE = 1024 * KILOBYTE;
|
||||
public static final int GIGABYTE = 1024 * MEGABYTE;
|
||||
public static final long TERABYTE = 1024L * GIGABYTE;
|
||||
|
||||
public static final char[] HEX_CHARS = new char[] {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
|
||||
|
||||
public static
|
||||
char[] convertStringToChars(String string) {
|
||||
char[] charArray = string.toCharArray();
|
||||
|
||||
eraseString(string);
|
||||
|
||||
return charArray;
|
||||
}
|
||||
|
||||
public static
|
||||
void eraseString(String string) {
|
||||
// You can change the value of the inner char[] using reflection.
|
||||
//
|
||||
// You must be careful to either change it with an array of the same length,
|
||||
// or to also update the count field.
|
||||
//
|
||||
// If you want to be able to use it as an entry in a set or as a value in map,
|
||||
// you will need to recalculate the hash code and set the value of the hashCode field.
|
||||
|
||||
//noinspection TryWithIdenticalCatches
|
||||
try {
|
||||
Field valueField = String.class.getDeclaredField("value");
|
||||
valueField.setAccessible(true);
|
||||
char[] chars = (char[]) valueField.get(string);
|
||||
Arrays.fill(chars, '*'); // asterisk it out in case of GC not picking up the old char array.
|
||||
|
||||
valueField.set(string, new char[0]); // replace it.
|
||||
|
||||
// set count to 0
|
||||
try {
|
||||
// newer versions of java don't have this field
|
||||
Field countField = String.class.getDeclaredField("count");
|
||||
countField.setAccessible(true);
|
||||
countField.set(string, 0);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// set hash to 0
|
||||
Field hashField = String.class.getDeclaredField("hash");
|
||||
hashField.setAccessible(true);
|
||||
hashField.set(string, 0);
|
||||
} catch (SecurityException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchFieldException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* FROM: https://www.cqse.eu/en/blog/string-replace-performance/
|
||||
* <p/>
|
||||
* Replaces all occurrences of keys of the given map in the given string with the associated value in that map.
|
||||
* <p/>
|
||||
* This method is semantically the same as calling {@link String#replace(CharSequence, CharSequence)} for each of the
|
||||
* entries in the map, but may be significantly faster for many replacements performed on a short string, since
|
||||
* {@link String#replace(CharSequence, CharSequence)} uses regular expressions internally and results in many String
|
||||
* object allocations when applied iteratively.
|
||||
* <p/>
|
||||
* The order in which replacements are applied depends on the order of the map's entry set.
|
||||
*/
|
||||
public static
|
||||
String replaceStringFast(String string, Map<String, String> replacements) {
|
||||
StringBuilder sb = new StringBuilder(string);
|
||||
for (Entry<String, String> entry : replacements.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String value = entry.getValue();
|
||||
|
||||
int start = sb.indexOf(key, 0);
|
||||
while (start > -1) {
|
||||
int end = start + key.length();
|
||||
int nextSearchStart = start + value.length();
|
||||
sb.replace(start, end, value);
|
||||
start = sb.indexOf(key, nextSearchStart);
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Quickly finds a char in a string.
|
||||
*
|
||||
* @return index if it's there, -1 if not there
|
||||
*/
|
||||
public static
|
||||
int searchStringFast(String string, char c) {
|
||||
int length = string.length();
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (string.charAt(i) == c) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
public static
|
||||
String getSizePretty(final long size) {
|
||||
if (size > TERABYTE) {
|
||||
return String.format("%2.2fTB", (double) size / TERABYTE);
|
||||
}
|
||||
if (size > GIGABYTE) {
|
||||
return String.format("%2.2fGB", (double) size / GIGABYTE);
|
||||
}
|
||||
if (size > MEGABYTE) {
|
||||
return String.format("%2.2fMB", (double) size / MEGABYTE);
|
||||
}
|
||||
if (size > KILOBYTE) {
|
||||
return String.format("%2.2fKB", (double) size / KILOBYTE);
|
||||
}
|
||||
|
||||
return String.valueOf(size) + "B";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a PRETTY string representation of the specified time.
|
||||
*/
|
||||
public static String getTimePretty(long nanoSeconds) {
|
||||
final TimeUnit unit;
|
||||
final String text;
|
||||
|
||||
if (TimeUnit.DAYS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0) {
|
||||
unit = TimeUnit.DAYS;
|
||||
text = "d";
|
||||
}
|
||||
else if (TimeUnit.HOURS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0) {
|
||||
unit = TimeUnit.HOURS;
|
||||
text = "h";
|
||||
}
|
||||
else if (TimeUnit.MINUTES.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0) {
|
||||
unit = TimeUnit.MINUTES;
|
||||
text = "min";
|
||||
}
|
||||
else if (TimeUnit.SECONDS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0) {
|
||||
unit = TimeUnit.SECONDS;
|
||||
text = "s";
|
||||
}
|
||||
else if (TimeUnit.MILLISECONDS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0) {
|
||||
unit = TimeUnit.MILLISECONDS;
|
||||
text = "ms";
|
||||
}
|
||||
else if (TimeUnit.MICROSECONDS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0) {
|
||||
unit = TimeUnit.MICROSECONDS;
|
||||
text = "\u03bcs"; // μs
|
||||
}
|
||||
else {
|
||||
unit = TimeUnit.NANOSECONDS;
|
||||
text = "ns";
|
||||
}
|
||||
|
||||
// convert the unit into the largest time unit possible (since that is often what makes sense)
|
||||
double value = (double) nanoSeconds / TimeUnit.NANOSECONDS.convert(1, unit);
|
||||
return String.format("%.4g" + text, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a PRETTY string representation of the specified time.
|
||||
*/
|
||||
public static String getTimePrettyFull(long nanoSeconds) {
|
||||
final TimeUnit unit;
|
||||
String text;
|
||||
|
||||
if (TimeUnit.DAYS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0) {
|
||||
unit = TimeUnit.DAYS;
|
||||
text = "day";
|
||||
}
|
||||
else if (TimeUnit.HOURS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0) {
|
||||
unit = TimeUnit.HOURS;
|
||||
text = "hour";
|
||||
}
|
||||
else if (TimeUnit.MINUTES.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0) {
|
||||
unit = TimeUnit.MINUTES;
|
||||
text = "minute";
|
||||
}
|
||||
else if (TimeUnit.SECONDS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0) {
|
||||
unit = TimeUnit.SECONDS;
|
||||
text = "second";
|
||||
}
|
||||
else if (TimeUnit.MILLISECONDS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0) {
|
||||
unit = TimeUnit.MILLISECONDS;
|
||||
text = "milli-second";
|
||||
}
|
||||
else if (TimeUnit.MICROSECONDS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0) {
|
||||
unit = TimeUnit.MICROSECONDS;
|
||||
text = "micro-second";
|
||||
}
|
||||
else {
|
||||
unit = TimeUnit.NANOSECONDS;
|
||||
text = "nano-second";
|
||||
}
|
||||
|
||||
// convert the unit into the largest time unit possible (since that is often what makes sense)
|
||||
double value = (double) nanoSeconds / TimeUnit.NANOSECONDS.convert(1, unit);
|
||||
if (value > 1.0D) {
|
||||
text += "s";
|
||||
}
|
||||
return String.format("%.4g " + text, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the contents of the input stream to a byte array.
|
||||
*/
|
||||
public static
|
||||
byte[] getBytesFromStream(InputStream inputStream) throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(8192);
|
||||
|
||||
byte[] buffer = new byte[4096];
|
||||
int read;
|
||||
while ((read = inputStream.read(buffer)) > 0) {
|
||||
baos.write(buffer, 0, read);
|
||||
}
|
||||
baos.flush();
|
||||
inputStream.close();
|
||||
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
public static
|
||||
byte[] copyBytes(byte[] src) {
|
||||
return copyBytes(src, 0);
|
||||
}
|
||||
|
||||
public static
|
||||
byte[] copyBytes(byte[] src, int position) {
|
||||
int length = src.length - position;
|
||||
|
||||
byte[] b = new byte[length];
|
||||
System.arraycopy(src, position, b, 0, length);
|
||||
return b;
|
||||
}
|
||||
|
||||
public static
|
||||
byte[] concatBytes(byte[]... arrayBytes) {
|
||||
int length = 0;
|
||||
for (byte[] bytes : arrayBytes) {
|
||||
length += bytes.length;
|
||||
}
|
||||
|
||||
byte[] concatBytes = new byte[length];
|
||||
|
||||
length = 0;
|
||||
for (byte[] bytes : arrayBytes) {
|
||||
System.arraycopy(bytes, 0, concatBytes, length, bytes.length);
|
||||
length += bytes.length;
|
||||
}
|
||||
|
||||
return concatBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* this saves the char array in UTF-16 format of bytes
|
||||
*/
|
||||
@SuppressWarnings("NumericCastThatLosesPrecision")
|
||||
public static
|
||||
byte[] charToBytes16(char[] text) {
|
||||
// NOTE: this saves the char array in UTF-16 format of bytes.
|
||||
byte[] bytes = new byte[text.length * 2];
|
||||
for (int i = 0; i < text.length; i++) {
|
||||
//noinspection CharUsedInArithmeticContext
|
||||
bytes[2 * i] = (byte) (text[i] >> 8);
|
||||
bytes[2 * i + 1] = (byte) text[i];
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
|
||||
public static
|
||||
byte[] intsToBytes(int[] ints) {
|
||||
int length = ints.length;
|
||||
byte[] bytes = new byte[length];
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
int intValue = ints[i];
|
||||
if (intValue < 0 || intValue > 255) {
|
||||
System.err.println("WARNING: int at index " + i + "(" + intValue + ") was not a valid byte value (0-255)");
|
||||
return new byte[length];
|
||||
}
|
||||
|
||||
//noinspection NumericCastThatLosesPrecision
|
||||
bytes[i] = (byte) intValue;
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
@SuppressWarnings("NumericCastThatLosesPrecision")
|
||||
public static
|
||||
byte[] charToBytesRaw(char[] chars) {
|
||||
int length = chars.length;
|
||||
byte[] bytes = new byte[length];
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
char charValue = chars[i];
|
||||
bytes[i] = (byte) charValue;
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static
|
||||
int[] bytesToInts(byte[] bytes, int startPosition, int length) {
|
||||
int[] ints = new int[length];
|
||||
int endPosition = startPosition + length;
|
||||
for (int i = startPosition; i < endPosition; i++) {
|
||||
ints[i] = bytes[i] & 0xFF;
|
||||
}
|
||||
|
||||
return ints;
|
||||
}
|
||||
|
||||
public static
|
||||
String bytesToHex(byte[] bytes) {
|
||||
return bytesToHex(bytes, 0, bytes.length, false);
|
||||
}
|
||||
|
||||
public static
|
||||
String bytesToHex(byte[] bytes, int startPosition, int length) {
|
||||
return bytesToHex(bytes, startPosition, length, false);
|
||||
}
|
||||
|
||||
public static
|
||||
String bytesToHex(byte[] bytes, int startPosition, int length, boolean padding) {
|
||||
int endPosition = startPosition + length;
|
||||
|
||||
if (padding) {
|
||||
char[] hexString = new char[3 * length];
|
||||
int j = 0;
|
||||
|
||||
for (int i = startPosition; i < endPosition; i++) {
|
||||
hexString[j++] = HEX_CHARS[(bytes[i] & 0xF0) >> 4];
|
||||
hexString[j++] = HEX_CHARS[bytes[i] & 0x0F];
|
||||
hexString[j++] = ' ';
|
||||
}
|
||||
|
||||
return new String(hexString);
|
||||
}
|
||||
else {
|
||||
char[] hexString = new char[2 * length];
|
||||
int j = 0;
|
||||
|
||||
for (int i = startPosition; i < endPosition; i++) {
|
||||
hexString[j++] = HEX_CHARS[(bytes[i] & 0xF0) >> 4];
|
||||
hexString[j++] = HEX_CHARS[bytes[i] & 0x0F];
|
||||
}
|
||||
|
||||
return new String(hexString);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an ASCII character representing a hexadecimal
|
||||
* value into its integer equivalent.
|
||||
*/
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
public static
|
||||
int hexByteToInt(byte b) {
|
||||
switch (b) {
|
||||
case '0':
|
||||
return 0;
|
||||
case '1':
|
||||
return 1;
|
||||
case '2':
|
||||
return 2;
|
||||
case '3':
|
||||
return 3;
|
||||
case '4':
|
||||
return 4;
|
||||
case '5':
|
||||
return 5;
|
||||
case '6':
|
||||
return 6;
|
||||
case '7':
|
||||
return 7;
|
||||
case '8':
|
||||
return 8;
|
||||
case '9':
|
||||
return 9;
|
||||
case 'A':
|
||||
case 'a':
|
||||
return 10;
|
||||
case 'B':
|
||||
case 'b':
|
||||
return 11;
|
||||
case 'C':
|
||||
case 'c':
|
||||
return 12;
|
||||
case 'D':
|
||||
case 'd':
|
||||
return 13;
|
||||
case 'E':
|
||||
case 'e':
|
||||
return 14;
|
||||
case 'F':
|
||||
case 'f':
|
||||
return 15;
|
||||
default:
|
||||
throw new IllegalArgumentException("Error decoding byte");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an ASCII character representing a hexadecimal
|
||||
* value into its integer equivalent.
|
||||
*/
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
public static
|
||||
int hexCharToInt(char b) {
|
||||
switch (b) {
|
||||
case '0':
|
||||
return 0;
|
||||
case '1':
|
||||
return 1;
|
||||
case '2':
|
||||
return 2;
|
||||
case '3':
|
||||
return 3;
|
||||
case '4':
|
||||
return 4;
|
||||
case '5':
|
||||
return 5;
|
||||
case '6':
|
||||
return 6;
|
||||
case '7':
|
||||
return 7;
|
||||
case '8':
|
||||
return 8;
|
||||
case '9':
|
||||
return 9;
|
||||
case 'A':
|
||||
case 'a':
|
||||
return 10;
|
||||
case 'B':
|
||||
case 'b':
|
||||
return 11;
|
||||
case 'C':
|
||||
case 'c':
|
||||
return 12;
|
||||
case 'D':
|
||||
case 'd':
|
||||
return 13;
|
||||
case 'E':
|
||||
case 'e':
|
||||
return 14;
|
||||
case 'F':
|
||||
case 'f':
|
||||
return 15;
|
||||
default:
|
||||
throw new IllegalArgumentException("Error decoding byte");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A 4-digit hex result.
|
||||
*/
|
||||
@SuppressWarnings("CharUsedInArithmeticContext")
|
||||
public static
|
||||
void hex4(final char c, final StringBuilder sb) {
|
||||
sb.append(HEX_CHARS[(c & 0xF000) >> 12]);
|
||||
sb.append(HEX_CHARS[(c & 0x0F00) >> 8]);
|
||||
sb.append(HEX_CHARS[(c & 0x00F0) >> 4]);
|
||||
sb.append(HEX_CHARS[c & 0x000F]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the byte array as a series of
|
||||
* hexadecimal characters.
|
||||
*
|
||||
* @param bytes byte array to convert
|
||||
* @return a string representation of the byte array as a series of
|
||||
* hexadecimal characters
|
||||
*/
|
||||
public static
|
||||
String toHexString(byte[] bytes) {
|
||||
char[] hexString = new char[2 * bytes.length];
|
||||
int j = 0;
|
||||
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
hexString[j++] = HEX_CHARS[(bytes[i] & 0xF0) >> 4];
|
||||
hexString[j++] = HEX_CHARS[bytes[i] & 0x0F];
|
||||
}
|
||||
|
||||
return new String(hexString);
|
||||
}
|
||||
|
||||
/**
|
||||
* from netty 4.1, apache 2.0, https://netty.io
|
||||
*/
|
||||
public static byte hexToByte(CharSequence s, int pos) {
|
||||
int hi = hexCharToInt(s.charAt(pos));
|
||||
int lo = hexCharToInt(s.charAt(pos + 1));
|
||||
if (hi == -1 || lo == -1) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"invalid hex byte '%s' at index %d of '%s'", s.subSequence(pos, pos + 2), pos, s));
|
||||
}
|
||||
return (byte) ((hi << 4) + lo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a string with <a href="http://en.wikipedia.org/wiki/Hex_dump">hex dump</a>
|
||||
*
|
||||
* @param hex a {@link CharSequence} which contains the hex dump
|
||||
*/
|
||||
public static byte[] hexToBytes(CharSequence hex) {
|
||||
return hexToBytes(hex, 0, hex.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes part of a string with <a href="http://en.wikipedia.org/wiki/Hex_dump">hex dump</a>
|
||||
*
|
||||
* from netty 4.1, apache 2.0, https://netty.io
|
||||
*
|
||||
* @param hexDump a {@link CharSequence} which contains the hex dump
|
||||
* @param fromIndex start of hex dump in {@code hexDump}
|
||||
* @param length hex string length
|
||||
*/
|
||||
public static byte[] hexToBytes(CharSequence hexDump, int fromIndex, int length) {
|
||||
if (length < 0 || (length & 1) != 0) {
|
||||
throw new IllegalArgumentException("length: " + length);
|
||||
}
|
||||
|
||||
if (length == 0) {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
byte[] bytes = new byte[length >>> 1];
|
||||
for (int i = 0; i < length; i += 2) {
|
||||
bytes[i >>> 1] = hexToByte(hexDump, fromIndex + i);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* XOR two byte arrays together, and save result in originalArray
|
||||
*
|
||||
* @param originalArray this is the base of the XOR operation.
|
||||
* @param keyArray this is XOR'd into the original array, repeats if necessary.
|
||||
*/
|
||||
@SuppressWarnings("NumericCastThatLosesPrecision")
|
||||
public static
|
||||
void xorArrays(byte[] originalArray, byte[] keyArray) {
|
||||
int keyIndex = 0;
|
||||
int keyLength = keyArray.length;
|
||||
|
||||
for (int i = 0; i < originalArray.length; i++) {
|
||||
//XOR the data and start over if necessary
|
||||
originalArray[i] = (byte) (originalArray[i] ^ keyArray[keyIndex++ % keyLength]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static
|
||||
byte[] encodeStringArray(List<String> array) {
|
||||
int length = 0;
|
||||
for (String s : array) {
|
||||
byte[] bytes = s.getBytes();
|
||||
length += bytes.length;
|
||||
}
|
||||
|
||||
if (length == 0) {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
byte[] bytes = new byte[length + array.size()];
|
||||
|
||||
length = 0;
|
||||
for (String s : array) {
|
||||
byte[] sBytes = s.getBytes();
|
||||
System.arraycopy(sBytes, 0, bytes, length, sBytes.length);
|
||||
length += sBytes.length;
|
||||
bytes[length++] = (byte) 0x01;
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static
|
||||
ArrayList<String> decodeStringArray(byte[] bytes) {
|
||||
int length = bytes.length;
|
||||
int position = 0;
|
||||
byte token = (byte) 0x01;
|
||||
ArrayList<String> list = new ArrayList<String>(0);
|
||||
|
||||
int last = 0;
|
||||
while (last + position < length) {
|
||||
byte b = bytes[last + position++];
|
||||
if (b == token) {
|
||||
byte[] xx = new byte[position - 1];
|
||||
System.arraycopy(bytes, last, xx, 0, position - 1);
|
||||
list.add(new String(xx));
|
||||
last += position;
|
||||
position = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public static
|
||||
String printArrayRaw(final byte[] bytes) {
|
||||
return printArrayRaw(bytes, 0);
|
||||
}
|
||||
|
||||
public static
|
||||
String printArrayRaw(final byte[] bytes, final int lineLength) {
|
||||
if (lineLength > 0) {
|
||||
int length = bytes.length;
|
||||
int comma = length - 1;
|
||||
|
||||
StringBuilder builder = new StringBuilder(length + length / lineLength);
|
||||
for (int i = 0; i < length; i++) {
|
||||
builder.append(bytes[i]);
|
||||
if (i < comma) {
|
||||
builder.append(",");
|
||||
}
|
||||
if (i > 0 && i % lineLength == 0) {
|
||||
builder.append(OS.INSTANCE.getLINE_SEPARATOR());
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
|
||||
}
|
||||
else {
|
||||
int length = bytes.length;
|
||||
int comma = length - 1;
|
||||
|
||||
StringBuilder builder = new StringBuilder(length + length);
|
||||
for (int i = 0; i < length; i++) {
|
||||
builder.append(bytes[i]);
|
||||
if (i < comma) {
|
||||
builder.append(",");
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public static
|
||||
void printArray(byte[] bytes) {
|
||||
printArray(bytes, bytes.length, true);
|
||||
}
|
||||
|
||||
public static
|
||||
void printArray(byte[] bytes, int length, boolean includeByteCount) {
|
||||
printArray(bytes, 0, length, includeByteCount, 40, null);
|
||||
}
|
||||
|
||||
public static
|
||||
void printArray(byte[] bytes, int inputOffset, int length, boolean includeByteCount) {
|
||||
printArray(bytes, inputOffset, length, includeByteCount, 40, null);
|
||||
}
|
||||
|
||||
public static
|
||||
void printArray(byte[] bytes, int inputOffset, int length, boolean includeByteCount, int lineLength, String header) {
|
||||
int comma = length - 1;
|
||||
|
||||
int builderLength = length + comma + 2;
|
||||
if (includeByteCount) {
|
||||
builderLength += 7 + Integer.toString(length)
|
||||
.length();
|
||||
}
|
||||
if (lineLength > 0) {
|
||||
builderLength += length / lineLength;
|
||||
}
|
||||
if (header != null) {
|
||||
builderLength += header.length() + 2;
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder(builderLength);
|
||||
|
||||
if (header != null) {
|
||||
builder.append(header)
|
||||
.append(OS.INSTANCE.getLINE_SEPARATOR());
|
||||
}
|
||||
|
||||
if (includeByteCount) {
|
||||
builder.append("Bytes: ").append(length).append(OS.INSTANCE.getLINE_SEPARATOR());
|
||||
}
|
||||
|
||||
builder.append("{");
|
||||
|
||||
for (int i = inputOffset; i < length; i++) {
|
||||
builder.append(bytes[i]);
|
||||
if (i < comma) {
|
||||
builder.append(",");
|
||||
}
|
||||
if (i > inputOffset && lineLength > 0 && i % lineLength == 0) {
|
||||
builder.append(OS.INSTANCE.getLINE_SEPARATOR());
|
||||
}
|
||||
}
|
||||
|
||||
builder.append("}");
|
||||
System.err.println(builder.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Raises an exception, but bypasses the compiler checks for the checked exception. This uses type erasure to work
|
||||
*/
|
||||
public static void throwException(Throwable t) {
|
||||
Sys.<RuntimeException>throwException0(t);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <E extends Throwable> void throwException0(Throwable t) throws E {
|
||||
throw (E) t;
|
||||
}
|
||||
|
||||
private
|
||||
Sys() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,312 @@
|
|||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util
|
||||
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
import java.util.concurrent.*
|
||||
|
||||
@Suppress("unused")
|
||||
object Sys {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = "1.48"
|
||||
|
||||
init {
|
||||
// Add this project to the updates system, which verifies this class + UUID + version information
|
||||
dorkbox.updates.Updates.add(Sys::class.java, "aebbb926aeb144739e9f3cab90ffaa72", version)
|
||||
}
|
||||
|
||||
|
||||
const val KILOBYTE = 1024
|
||||
const val MEGABYTE = 1024 * KILOBYTE
|
||||
const val GIGABYTE = 1024 * MEGABYTE
|
||||
const val TERABYTE = 1024L * GIGABYTE
|
||||
|
||||
|
||||
/**
|
||||
* FROM: https://www.cqse.eu/en/blog/string-replace-performance/
|
||||
*
|
||||
*
|
||||
* Replaces all occurrences of keys of the given map in the given string with the associated value in that map.
|
||||
*
|
||||
*
|
||||
* This method is semantically the same as calling [String.replace] for each of the
|
||||
* entries in the map, but may be significantly faster for many replacements performed on a short string, since
|
||||
* [String.replace] uses regular expressions internally and results in many String
|
||||
* object allocations when applied iteratively.
|
||||
*
|
||||
*
|
||||
* The order in which replacements are applied depends on the order of the map's entry set.
|
||||
*/
|
||||
fun replaceStringFast(string: String?, replacements: Map<String, String>): String {
|
||||
val sb = StringBuilder(string)
|
||||
for ((key, value) in replacements) {
|
||||
var start = sb.indexOf(key, 0)
|
||||
while (start > -1) {
|
||||
val end = start + key.length
|
||||
val nextSearchStart = start + value.length
|
||||
sb.replace(start, end, value)
|
||||
start = sb.indexOf(key, nextSearchStart)
|
||||
}
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Quickly finds a char in a string.
|
||||
*
|
||||
* @return index if it's there, -1 if not there
|
||||
*/
|
||||
fun searchStringFast(string: String, c: Char): Int {
|
||||
val length = string.length
|
||||
for (i in 0 until length) {
|
||||
if (string[i] == c) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
fun getSizePretty(size: Long): String {
|
||||
if (size > TERABYTE) {
|
||||
return String.format("%2.2fTB", size.toDouble() / TERABYTE)
|
||||
}
|
||||
if (size > GIGABYTE) {
|
||||
return String.format("%2.2fGB", size.toDouble() / GIGABYTE)
|
||||
}
|
||||
if (size > MEGABYTE) {
|
||||
return String.format("%2.2fMB", size.toDouble() / MEGABYTE)
|
||||
}
|
||||
return if (size > KILOBYTE) {
|
||||
String.format("%2.2fKB", size.toDouble() / KILOBYTE)
|
||||
} else size.toString() + "B"
|
||||
}
|
||||
|
||||
fun getSizePretty(size: Int): String {
|
||||
if (size > GIGABYTE) {
|
||||
return String.format("%2.2fGB", size.toDouble() / GIGABYTE)
|
||||
}
|
||||
if (size > MEGABYTE) {
|
||||
return String.format("%2.2fMB", size.toDouble() / MEGABYTE)
|
||||
}
|
||||
return if (size > KILOBYTE) {
|
||||
String.format("%2.2fKB", size.toDouble() / KILOBYTE)
|
||||
} else size.toString() + "B"
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a PRETTY string representation of the specified time.
|
||||
*/
|
||||
fun getTimePretty(nanoSeconds: Long): String {
|
||||
val unit: TimeUnit
|
||||
val text: String
|
||||
if (TimeUnit.DAYS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0L) {
|
||||
unit = TimeUnit.DAYS
|
||||
text = "d"
|
||||
} else if (TimeUnit.HOURS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0L) {
|
||||
unit = TimeUnit.HOURS
|
||||
text = "h"
|
||||
} else if (TimeUnit.MINUTES.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0L) {
|
||||
unit = TimeUnit.MINUTES
|
||||
text = "m"
|
||||
} else if (TimeUnit.SECONDS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0L) {
|
||||
unit = TimeUnit.SECONDS
|
||||
text = "s"
|
||||
} else if (TimeUnit.MILLISECONDS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0L) {
|
||||
unit = TimeUnit.MILLISECONDS
|
||||
text = "ms"
|
||||
} else if (TimeUnit.MICROSECONDS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0L) {
|
||||
unit = TimeUnit.MICROSECONDS
|
||||
text = "\u03bcs" // μs
|
||||
} else {
|
||||
unit = TimeUnit.NANOSECONDS
|
||||
text = "ns"
|
||||
}
|
||||
|
||||
// convert the unit into the largest time unit possible (since that is often what makes sense)
|
||||
val value = nanoSeconds.toDouble() / TimeUnit.NANOSECONDS.convert(1, unit)
|
||||
|
||||
return if (value < 10) {
|
||||
String.format("%.1g $text", value)
|
||||
} else if (value < 100) {
|
||||
String.format("%.2g $text", value)
|
||||
} else if (value < 1000) {
|
||||
String.format("%.3g $text", value)
|
||||
} else {
|
||||
String.format("%.4g $text", value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a PRETTY string representation of the specified time.
|
||||
*/
|
||||
fun getTimePrettyFull(nanoSeconds: Long): String {
|
||||
val unit: TimeUnit
|
||||
var text: String
|
||||
if (TimeUnit.DAYS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0L) {
|
||||
unit = TimeUnit.DAYS
|
||||
text = "day"
|
||||
} else if (TimeUnit.HOURS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0L) {
|
||||
unit = TimeUnit.HOURS
|
||||
text = "hour"
|
||||
} else if (TimeUnit.MINUTES.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0L) {
|
||||
unit = TimeUnit.MINUTES
|
||||
text = "minute"
|
||||
} else if (TimeUnit.SECONDS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0L) {
|
||||
unit = TimeUnit.SECONDS
|
||||
text = "second"
|
||||
} else if (TimeUnit.MILLISECONDS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0L) {
|
||||
unit = TimeUnit.MILLISECONDS
|
||||
text = "milli-second"
|
||||
} else if (TimeUnit.MICROSECONDS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0L) {
|
||||
unit = TimeUnit.MICROSECONDS
|
||||
text = "micro-second"
|
||||
} else {
|
||||
unit = TimeUnit.NANOSECONDS
|
||||
text = "nano-second"
|
||||
}
|
||||
|
||||
// convert the unit into the largest time unit possible (since that is often what makes sense)
|
||||
val value = nanoSeconds.toDouble() / TimeUnit.NANOSECONDS.convert(1, unit)
|
||||
if (value > 1.0) {
|
||||
text += "s"
|
||||
}
|
||||
|
||||
return if (value < 10) {
|
||||
String.format("%.1g $text", value)
|
||||
} else if (value < 100) {
|
||||
String.format("%.2g $text", value)
|
||||
} else if (value < 1000) {
|
||||
String.format("%.3g $text", value)
|
||||
} else {
|
||||
String.format("%.4g $text", value)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T : Throwable> throwException0(t: Throwable) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
throw t as T
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Thrown exception, to bypasses the compiler checks for the checked exception. This uses type erasure to work.
|
||||
*/
|
||||
fun Throwable.unchecked() {
|
||||
throwException0<RuntimeException>(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the extension of a file (text after the last '.')
|
||||
*
|
||||
* @return "" if there is no extension
|
||||
*/
|
||||
@Deprecated("Use kotlin")
|
||||
fun getExtension(fileName: String): String {
|
||||
val dot = fileName.lastIndexOf('.')
|
||||
return if (dot > -1) {
|
||||
fileName.substring(dot + 1)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the contents of the input stream to a byte array.
|
||||
*/
|
||||
@Deprecated("Use kotlin")
|
||||
@Throws(IOException::class)
|
||||
fun getBytesFromStream(inputStream: InputStream): ByteArray {
|
||||
val baos = ByteArrayOutputStream(8192)
|
||||
val buffer = ByteArray(4096)
|
||||
var read: Int
|
||||
while (inputStream.read(buffer).also { read = it } > 0) {
|
||||
baos.write(buffer, 0, read)
|
||||
}
|
||||
baos.flush()
|
||||
inputStream.close()
|
||||
return baos.toByteArray()
|
||||
}
|
||||
|
||||
@Deprecated("Use kotlin")
|
||||
@JvmOverloads
|
||||
fun copyBytes(src: ByteArray, position: Int = 0): ByteArray {
|
||||
val length = src.size - position
|
||||
val b = ByteArray(length)
|
||||
System.arraycopy(src, position, b, 0, length)
|
||||
return b
|
||||
}
|
||||
|
||||
@Deprecated("Use kotlin")
|
||||
fun concatBytes(vararg arrayBytes: ByteArray): ByteArray {
|
||||
var length = 0
|
||||
for (bytes in arrayBytes) {
|
||||
length += bytes.size
|
||||
}
|
||||
val concatBytes = ByteArray(length)
|
||||
length = 0
|
||||
for (bytes in arrayBytes) {
|
||||
System.arraycopy(bytes, 0, concatBytes, length, bytes.size)
|
||||
length += bytes.size
|
||||
}
|
||||
return concatBytes
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Erase the contents of a string, in-memory. This has no effect if the string has been interned.
|
||||
*/
|
||||
fun String.eraseString() {
|
||||
// You can change the value of the inner char[] using reflection.
|
||||
//
|
||||
// You must be careful to either change it with an array of the same length,
|
||||
// or to also update the count field.
|
||||
//
|
||||
// If you want to be able to use it as an entry in a set or as a value in map,
|
||||
// you will need to recalculate the hash code and set the value of the hashCode field.
|
||||
try {
|
||||
val valueField = String::class.java.getDeclaredField("value")
|
||||
valueField.isAccessible = true
|
||||
val chars = valueField[this] as CharArray
|
||||
Arrays.fill(chars, '*') // asterisk it out in case of GC not picking up the old char array.
|
||||
valueField[this] = CharArray(0) // replace it.
|
||||
|
||||
// set count to 0
|
||||
try {
|
||||
// newer versions of java don't have this field
|
||||
val countField = String::class.java.getDeclaredField("count")
|
||||
countField.isAccessible = true
|
||||
countField[this] = 0
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
|
||||
// set hash to 0
|
||||
val hashField = String::class.java.getDeclaredField("hash")
|
||||
hashField.isAccessible = true
|
||||
hashField[this] = 0
|
||||
} catch (e: SecurityException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util;
|
||||
|
||||
public
|
||||
interface Tool {
|
||||
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Allows for different objects to be reused in the system directly
|
||||
*/
|
||||
public
|
||||
class ToolBox {
|
||||
|
||||
private final ConcurrentHashMap<Class<?>, dorkbox.util.Tool> toolMap = new ConcurrentHashMap<Class<?>, dorkbox.util.Tool>();
|
||||
|
||||
/**
|
||||
* Registers a tool with the server, to be used by other services.
|
||||
*/
|
||||
public
|
||||
<Tool extends dorkbox.util.Tool> void register(Tool toolClass) {
|
||||
if (toolClass == null) {
|
||||
throw new IllegalArgumentException("Tool must not be null! Unable to add tool");
|
||||
}
|
||||
|
||||
dorkbox.util.Tool put = this.toolMap.put(toolClass.getClass(), toolClass);
|
||||
if (put != null) {
|
||||
throw new IllegalArgumentException("Tool must be unique! Unable to add tool '" + toolClass + "'");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Only get the tools in the ModuleStart (ie: load) methods. If used in the constructor, the tool might not be available yet
|
||||
*/
|
||||
public
|
||||
<Tool extends dorkbox.util.Tool> Tool get(Class<Tool> toolClass) {
|
||||
if (toolClass == null) {
|
||||
throw new IllegalArgumentException("Tool must not be null! Unable to add tool");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Tool tool = (Tool) this.toolMap.get(toolClass);
|
||||
return tool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only get the tools in the ModuleStart (ie: load) methods. If done in the constructor, the tool might not be available yet
|
||||
*/
|
||||
public
|
||||
<Tool extends dorkbox.util.Tool> void remove(Class<Tool> toolClass) {
|
||||
if (toolClass == null) {
|
||||
throw new IllegalArgumentException("Tool must not be null! Unable to remove tool");
|
||||
}
|
||||
|
||||
this.toolMap.remove(toolClass);
|
||||
}
|
||||
}
|
|
@ -1,160 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2005-2012, Paul Tuckey
|
||||
* All rights reserved.
|
||||
* ====================================================================
|
||||
* Licensed under the BSD License. Text as follows.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following
|
||||
* disclaimer in the documentation and/or other materials provided
|
||||
* with the distribution.
|
||||
* - Neither the name tuckey.org nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
* ====================================================================
|
||||
*
|
||||
* https://www.talisman.org/%7Eerlkonig/misc/lunatech%5Ewhat-every-webdev-must-know-about-url-encoding/
|
||||
*/
|
||||
/*
|
||||
* Copyright 2021 dorkbox, llc
|
||||
*
|
||||
* Copyright (C) 2016 Tres Finocchiaro, QZ Industries, LLC
|
||||
* Derivative code has been released as Apache 2.0, used with permission.
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.util
|
||||
|
||||
|
||||
import java.io.UnsupportedEncodingException
|
||||
import java.net.URISyntaxException
|
||||
import java.nio.charset.Charset
|
||||
|
||||
object URLDecoder {
|
||||
private const val byte_0 = '0'.code.toByte()
|
||||
private const val byte_1 = '1'.code.toByte()
|
||||
private const val byte_2 = '2'.code.toByte()
|
||||
private const val byte_3 = '3'.code.toByte()
|
||||
private const val byte_4 = '4'.code.toByte()
|
||||
private const val byte_5 = '5'.code.toByte()
|
||||
private const val byte_6 = '6'.code.toByte()
|
||||
private const val byte_7= '7'.code.toByte()
|
||||
private const val byte_8 = '8'.code.toByte()
|
||||
private const val byte_9 = '9'.code.toByte()
|
||||
private const val byte_a = 'a'.code.toByte()
|
||||
private const val byte_b = 'b'.code.toByte()
|
||||
private const val byte_c = 'c'.code.toByte()
|
||||
private const val byte_d = 'd'.code.toByte()
|
||||
private const val byte_e = 'e'.code.toByte()
|
||||
private const val byte_f = 'f'.code.toByte()
|
||||
private const val byte_A = 'A'.code.toByte()
|
||||
private const val byteB = 'B'.code.toByte()
|
||||
private const val byteC = 'C'.code.toByte()
|
||||
private const val byte_D= 'D'.code.toByte()
|
||||
private const val byte_E= 'E'.code.toByte()
|
||||
private const val byte_F = 'F'.code.toByte()
|
||||
|
||||
@Throws(URISyntaxException::class)
|
||||
fun decodeURL(url: String, charset: Charset): String {
|
||||
val queryPart = url.indexOf('?')
|
||||
var query: String? = null
|
||||
var path = url
|
||||
if (queryPart != -1) {
|
||||
query = url.substring(queryPart + 1)
|
||||
path = url.substring(0, queryPart)
|
||||
}
|
||||
val decodedPath = decodePath(path, charset)
|
||||
return if (query != null) decodedPath + '?' + decodeQuery(query, charset) else decodedPath
|
||||
}
|
||||
|
||||
@Throws(URISyntaxException::class)
|
||||
fun decodePath(path: String, charset: Charset): String {
|
||||
return decodeURLEncoded(path, false, charset)
|
||||
}
|
||||
|
||||
@Throws(URISyntaxException::class)
|
||||
fun decodeQuery(query: String, charset: Charset): String {
|
||||
return decodeURLEncoded(query, true, charset)
|
||||
}
|
||||
|
||||
@Throws(URISyntaxException::class)
|
||||
fun decodeURLEncoded(part: String, query: Boolean, charset: Charset): String {
|
||||
return try {
|
||||
val ascii = part.toByteArray(Charsets.US_ASCII)
|
||||
val decoded = ByteArray(ascii.size)
|
||||
var j = 0
|
||||
var i = 0
|
||||
while (i < ascii.size) {
|
||||
if (ascii[i] == '%'.code.toByte()) {
|
||||
if (i + 2 >= ascii.size) throw URISyntaxException(part, "Invalid URL-encoded string at char $i")
|
||||
// get the next two bytes
|
||||
val first = ascii[++i]
|
||||
val second = ascii[++i]
|
||||
decoded[j] = (hexToByte(first) * 16 + hexToByte(second)).toByte()
|
||||
} else if (query && ascii[i] == '+'.code.toByte()) decoded[j] = ' '.code.toByte() else decoded[j] = ascii[i]
|
||||
i++
|
||||
j++
|
||||
}
|
||||
// now decode
|
||||
String(decoded, 0, j, charset)
|
||||
} catch (x: UnsupportedEncodingException) {
|
||||
throw URISyntaxException(part, "Invalid encoding: $charset")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Throws(URISyntaxException::class)
|
||||
private fun hexToByte(b: Byte): Byte {
|
||||
when (b) {
|
||||
byte_0 -> return 0
|
||||
byte_1 -> return 1
|
||||
byte_2 -> return 2
|
||||
byte_3 -> return 3
|
||||
byte_4 -> return 4
|
||||
byte_5 -> return 5
|
||||
byte_6 -> return 6
|
||||
byte_7 -> return 7
|
||||
byte_8 -> return 8
|
||||
byte_9 -> return 9
|
||||
byte_a, byte_A -> return 10
|
||||
byte_b, byteB -> return 11
|
||||
byte_c, byteC -> return 12
|
||||
byte_d, byte_D -> return 13
|
||||
byte_e, byte_E -> return 14
|
||||
byte_f, byte_F -> return 15
|
||||
}
|
||||
throw URISyntaxException(b.toString(), "Invalid URL-encoded string")
|
||||
}
|
||||
}
|
|
@ -1,248 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2005-2012, Paul Tuckey
|
||||
* All rights reserved.
|
||||
* ====================================================================
|
||||
* Licensed under the BSD License. Text as follows.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following
|
||||
* disclaimer in the documentation and/or other materials provided
|
||||
* with the distribution.
|
||||
* - Neither the name tuckey.org nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
* ====================================================================
|
||||
*
|
||||
* https://www.talisman.org/%7Eerlkonig/misc/lunatech%5Ewhat-every-webdev-must-know-about-url-encoding/
|
||||
*/
|
||||
|
||||
package dorkbox.util
|
||||
|
||||
import java.io.UnsupportedEncodingException
|
||||
import java.nio.charset.Charset
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* URL-encoding utility for each URL part according to the RFC specs
|
||||
* see the rfc at http://www.ietf.org/rfc/rfc2396.txt
|
||||
*
|
||||
* @author stephane
|
||||
*/
|
||||
object URLEncoder {
|
||||
/**
|
||||
* mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
|
||||
*/
|
||||
val MARK = BitSet()
|
||||
|
||||
init {
|
||||
MARK.set('-'.code)
|
||||
MARK.set('_'.code)
|
||||
MARK.set('.'.code)
|
||||
MARK.set('!'.code)
|
||||
MARK.set('~'.code)
|
||||
MARK.set('*'.code)
|
||||
MARK.set('\''.code)
|
||||
MARK.set('('.code)
|
||||
MARK.set(')'.code)
|
||||
}
|
||||
|
||||
/**
|
||||
* lowalpha = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" |
|
||||
* "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z"
|
||||
*/
|
||||
val LOW_ALPHA = BitSet()
|
||||
|
||||
init {
|
||||
LOW_ALPHA.set('a'.code)
|
||||
LOW_ALPHA.set('b'.code)
|
||||
LOW_ALPHA.set('c'.code)
|
||||
LOW_ALPHA.set('d'.code)
|
||||
LOW_ALPHA.set('e'.code)
|
||||
LOW_ALPHA.set('f'.code)
|
||||
LOW_ALPHA.set('g'.code)
|
||||
LOW_ALPHA.set('h'.code)
|
||||
LOW_ALPHA.set('i'.code)
|
||||
LOW_ALPHA.set('j'.code)
|
||||
LOW_ALPHA.set('k'.code)
|
||||
LOW_ALPHA.set('l'.code)
|
||||
LOW_ALPHA.set('m'.code)
|
||||
LOW_ALPHA.set('n'.code)
|
||||
LOW_ALPHA.set('o'.code)
|
||||
LOW_ALPHA.set('p'.code)
|
||||
LOW_ALPHA.set('q'.code)
|
||||
LOW_ALPHA.set('r'.code)
|
||||
LOW_ALPHA.set('s'.code)
|
||||
LOW_ALPHA.set('t'.code)
|
||||
LOW_ALPHA.set('u'.code)
|
||||
LOW_ALPHA.set('v'.code)
|
||||
LOW_ALPHA.set('w'.code)
|
||||
LOW_ALPHA.set('x'.code)
|
||||
LOW_ALPHA.set('y'.code)
|
||||
LOW_ALPHA.set('z'.code)
|
||||
}
|
||||
|
||||
/**
|
||||
* upalpha = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" |
|
||||
* "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z"
|
||||
*/
|
||||
val UP_ALPHA = BitSet()
|
||||
|
||||
init {
|
||||
UP_ALPHA.set('A'.code)
|
||||
UP_ALPHA.set('B'.code)
|
||||
UP_ALPHA.set('C'.code)
|
||||
UP_ALPHA.set('D'.code)
|
||||
UP_ALPHA.set('E'.code)
|
||||
UP_ALPHA.set('F'.code)
|
||||
UP_ALPHA.set('G'.code)
|
||||
UP_ALPHA.set('H'.code)
|
||||
UP_ALPHA.set('I'.code)
|
||||
UP_ALPHA.set('J'.code)
|
||||
UP_ALPHA.set('K'.code)
|
||||
UP_ALPHA.set('L'.code)
|
||||
UP_ALPHA.set('M'.code)
|
||||
UP_ALPHA.set('N'.code)
|
||||
UP_ALPHA.set('O'.code)
|
||||
UP_ALPHA.set('P'.code)
|
||||
UP_ALPHA.set('Q'.code)
|
||||
UP_ALPHA.set('R'.code)
|
||||
UP_ALPHA.set('S'.code)
|
||||
UP_ALPHA.set('T'.code)
|
||||
UP_ALPHA.set('U'.code)
|
||||
UP_ALPHA.set('V'.code)
|
||||
UP_ALPHA.set('W'.code)
|
||||
UP_ALPHA.set('X'.code)
|
||||
UP_ALPHA.set('Y'.code)
|
||||
UP_ALPHA.set('Z'.code)
|
||||
}
|
||||
|
||||
/**
|
||||
* alpha = lowalpha | upalpha
|
||||
*/
|
||||
val ALPHA = BitSet()
|
||||
|
||||
init {
|
||||
ALPHA.or(LOW_ALPHA)
|
||||
ALPHA.or(UP_ALPHA)
|
||||
}
|
||||
|
||||
/**
|
||||
* digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
|
||||
*/
|
||||
val DIGIT = BitSet()
|
||||
|
||||
init {
|
||||
DIGIT.set('0'.code)
|
||||
DIGIT.set('1'.code)
|
||||
DIGIT.set('2'.code)
|
||||
DIGIT.set('3'.code)
|
||||
DIGIT.set('4'.code)
|
||||
DIGIT.set('5'.code)
|
||||
DIGIT.set('6'.code)
|
||||
DIGIT.set('7'.code)
|
||||
DIGIT.set('8'.code)
|
||||
DIGIT.set('9'.code)
|
||||
}
|
||||
|
||||
/**
|
||||
* alphanum = alpha | digit
|
||||
*/
|
||||
val ALPHANUM = BitSet()
|
||||
|
||||
init {
|
||||
ALPHANUM.or(ALPHA)
|
||||
ALPHANUM.or(DIGIT)
|
||||
}
|
||||
|
||||
/**
|
||||
* unreserved = alphanum | mark
|
||||
*/
|
||||
val UNRESERVED = BitSet()
|
||||
|
||||
init {
|
||||
UNRESERVED.or(ALPHANUM)
|
||||
UNRESERVED.or(MARK)
|
||||
}
|
||||
|
||||
/**
|
||||
* pchar = unreserved | escaped | ":" | "@" | "&" | "=" | "+" | "$" | ","
|
||||
*
|
||||
*
|
||||
* Note: we don't allow escaped here since we will escape it ourselves, so we don't want to allow them in the
|
||||
* unescaped sequences
|
||||
*/
|
||||
val PCHAR = BitSet()
|
||||
|
||||
init {
|
||||
PCHAR.or(UNRESERVED)
|
||||
PCHAR.set(':'.code)
|
||||
PCHAR.set('@'.code)
|
||||
PCHAR.set('&'.code)
|
||||
PCHAR.set('='.code)
|
||||
PCHAR.set('+'.code)
|
||||
PCHAR.set('$'.code)
|
||||
PCHAR.set(','.code)
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a string to be a valid path parameter URL, which means it can contain PCHAR* only (do not put the leading
|
||||
* ";" or it will be escaped.
|
||||
*
|
||||
* @throws UnsupportedEncodingException
|
||||
*/
|
||||
@Throws(UnsupportedEncodingException::class)
|
||||
fun encodePathParam(pathParam: String, charset: Charset): String {
|
||||
return encodePathSegment(pathParam, charset)
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a string to be a valid path segment URL, which means it can contain PCHAR* only (do not put path
|
||||
* parameters or they will be escaped.
|
||||
*
|
||||
* @throws UnsupportedEncodingException
|
||||
*/
|
||||
@Throws(UnsupportedEncodingException::class)
|
||||
fun encodePathSegment(pathSegment: String, charset: Charset): String {
|
||||
// start at *3 for the worst case when everything is %encoded on one byte
|
||||
val encoded = StringBuffer(pathSegment.length * 3)
|
||||
val toEncode = pathSegment.toCharArray()
|
||||
|
||||
for (i in toEncode.indices) {
|
||||
val c = toEncode[i]
|
||||
if (PCHAR[c.code]) {
|
||||
encoded.append(c)
|
||||
} else {
|
||||
val bytes = c.toString().toByteArray(charset)
|
||||
for (j in bytes.indices) {
|
||||
val b = bytes[j]
|
||||
// make it unsigned (safe, since we only goto max 255, but makes conversion to hex easier)
|
||||
val u8: Int = b.toInt() and 0xFF
|
||||
encoded.append("%")
|
||||
if (u8 < 16) encoded.append("0")
|
||||
encoded.append(Integer.toHexString(u8))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return encoded.toString()
|
||||
}
|
||||
}
|
|
@ -1,693 +0,0 @@
|
|||
/*
|
||||
* Copyright 2022 dorkbox, llc
|
||||
*
|
||||
* Copyright (C) 2016 Tres Finocchiaro, QZ Industries, LLC
|
||||
* Derivative code has been released as Apache 2.0, used with permission.
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.util
|
||||
|
||||
|
||||
import dorkbox.netUtil.Dns
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.InputStream
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.net.URLDecoder
|
||||
import java.net.UnknownHostException
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.regex.*
|
||||
import javax.net.ssl.HostnameVerifier
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.TrustManager
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
@Suppress("unused")
|
||||
object WebUtil {
|
||||
private val SECOND_LEVEL_DOMAIN_PATTERN = Pattern.compile("^(https?:\\/\\/)?([\\dA-Za-z\\.-]+)\\.([a-z\\.]{2,6})([\\w \\.-]*)*$")
|
||||
|
||||
/**
|
||||
* Regular expression to match all IANA top-level domains.
|
||||
* List accurate as of 2010/02/05. List taken from:
|
||||
* http://data.iana.org/TLD/tlds-alpha-by-domain.txt
|
||||
* This pattern is auto-generated by frameworks/base/common/tools/make-iana-tld-pattern.py
|
||||
*/
|
||||
@Volatile
|
||||
private var TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL = ("((aaa|aarp|abarth|abb|abbott|abbvie|abc|able|abogado|abudhabi|academy|accenture|accountant|accountants|aco|actor|adac|ads|adult|aeg|aero|aetna|afamilycompany|afl|africa|agakhan|agency|aig|airbus|airforce|airtel|akdn|alfaromeo|alibaba|alipay|allfinanz|allstate|ally|alsace|alstom|amazon|americanexpress|americanfamily|amex|amfam|amica|amsterdam|analytics|android|anquan|anz|aol|apartments|app|apple|aquarelle|arab|aramco|archi|army|arpa|art|arte|asda|asia|associates|athleta|attorney|auction|audi|audible|audio|auspost|author|auto|autos|avianca|aws|axa|azure|a[cdefgilmoqrstuwxz])"
|
||||
+ "|(baby|baidu|banamex|bananarepublic|band|bank|bar|barcelona|barclaycard|barclays|barefoot|bargains|baseball|basketball|bauhaus|bayern|bbc|bbt|bbva|bcg|bcn|beats|beauty|beer|bentley|berlin|best|bestbuy|bet|bharti|bible|bid|bike|bing|bingo|bio|biz|black|blackfriday|blockbuster|blog|bloomberg|blue|bms|bmw|bnpparibas|boats|boehringer|bofa|bom|bond|boo|book|booking|bosch|bostik|boston|bot|boutique|box|bradesco|bridgestone|broadway|broker|brother|brussels|budapest|bugatti|build|builders|business|buy|buzz|bzh|b[abdefghijmnorstvwyz])"
|
||||
+ "|(cab|cafe|cal|call|calvinklein|cam|camera|camp|cancerresearch|canon|capetown|capital|capitalone|car|caravan|cards|care|career|careers|cars|casa|case|caseih|cash|casino|cat|catering|catholic|cba|cbn|cbre|cbs|ceb|center|ceo|cern|cfa|cfd|chanel|channel|charity|chase|chat|cheap|chintai|christmas|chrome|church|cipriani|circle|cisco|citadel|citi|citic|city|cityeats|claims|cleaning|click|clinic|clinique|clothing|cloud|club|clubmed|coach|codes|coffee|college|cologne|com|comcast|commbank|community|company|compare|computer|comsec|condos|construction|consulting|contact|contractors|cooking|cookingchannel|cool|coop|corsica|country|coupon|coupons|courses|cpa|credit|creditcard|creditunion|cricket|crown|crs|cruise|cruises|csc|cuisinella|cymru|cyou|c[acdfghiklmnoruvwxyz])"
|
||||
+ "|(dabur|dad|dance|data|date|dating|datsun|day|dclk|dds|deal|dealer|deals|degree|delivery|dell|deloitte|delta|democrat|dental|dentist|desi|design|dev|dhl|diamonds|diet|digital|direct|directory|discount|discover|dish|diy|dnp|docs|doctor|dog|domains|dot|download|drive|dtv|dubai|duck|dunlop|dupont|durban|dvag|dvr|d[ejkmoz])"
|
||||
+ "|(earth|eat|eco|edeka|edu|education|email|emerck|energy|engineer|engineering|enterprises|epson|equipment|ericsson|erni|esq|estate|etisalat|eurovision|eus|events|exchange|expert|exposed|express|extraspace|e[cegrstu])"
|
||||
+ "|(fage|fail|fairwinds|faith|family|fan|fans|farm|farmers|fashion|fast|fedex|feedback|ferrari|ferrero|fiat|fidelity|fido|film|final|finance|financial|fire|firestone|firmdale|fish|fishing|fit|fitness|flickr|flights|flir|florist|flowers|fly|foo|food|foodnetwork|football|ford|forex|forsale|forum|foundation|fox|free|fresenius|frl|frogans|frontdoor|frontier|ftr|fujitsu|fujixerox|fun|fund|furniture|futbol|fyi|f[ijkmor])"
|
||||
+ "|(gal|gallery|gallo|gallup|game|games|gap|garden|gay|gbiz|gdn|gea|gent|genting|george|ggee|gift|gifts|gives|giving|glade|glass|gle|global|globo|gmail|gmbh|gmo|gmx|godaddy|gold|goldpoint|golf|goo|goodyear|goog|google|gop|got|gov|grainger|graphics|gratis|green|gripe|grocery|group|guardian|gucci|guge|guide|guitars|guru|g[abdefghilmnpqrstuwy])"
|
||||
+ "|(hair|hamburg|hangout|haus|hbo|hdfc|hdfcbank|health|healthcare|help|helsinki|here|hermes|hgtv|hiphop|hisamitsu|hitachi|hiv|hkt|hockey|holdings|holiday|homedepot|homegoods|homes|homesense|honda|horse|hospital|host|hosting|hot|hoteles|hotels|hotmail|house|how|hsbc|hughes|hyatt|hyundai|h[kmnrtu])"
|
||||
+ "|(ibm|icbc|ice|icu|ieee|ifm|ikano|imamat|imdb|immo|immobilien|inc|industries|infiniti|info|ing|ink|institute|insurance|insure|int|intel|international|intuit|investments|ipiranga|irish|ismaili|ist|istanbul|itau|itv|iveco|i[delmnoqrst])"
|
||||
+ "|(jaguar|java|jcb|jcp|jeep|jetzt|jewelry|jio|jll|jmp|jnj|jobs|joburg|jot|joy|jpmorgan|jprs|juegos|juniper|j[emop])"
|
||||
+ "|(kaufen|kddi|kerryhotels|kerrylogistics|kerryproperties|kfh|kia|kim|kinder|kindle|kitchen|kiwi|koeln|komatsu|kosher|kpmg|kpn|krd|kred|kuokgroup|kyoto|k[eghimnprwyz])"
|
||||
+ "|(lacaixa|lamborghini|lamer|lancaster|lancia|land|landrover|lanxess|lasalle|lat|latino|latrobe|law|lawyer|lds|lease|leclerc|lefrak|legal|lego|lexus|lgbt|lidl|life|lifeinsurance|lifestyle|lighting|like|lilly|limited|limo|lincoln|linde|link|lipsy|live|living|lixil|llc|llp|loan|loans|locker|locus|loft|lol|london|lotte|lotto|love|lpl|lplfinancial|ltd|ltda|lundbeck|lupin|luxe|luxury|l[abcikrstuvy])"
|
||||
+ "|(macys|madrid|maif|maison|makeup|man|management|mango|map|market|marketing|markets|marriott|marshalls|maserati|mattel|mba|mckinsey|med|media|meet|melbourne|meme|memorial|men|menu|merckmsd|miami|microsoft|mil|mini|mint|mit|mitsubishi|mlb|mls|mma|mobi|mobile|moda|moe|moi|mom|monash|money|monster|mormon|mortgage|moscow|moto|motorcycles|mov|movie|msd|mtn|mtr|museum|mutual|m[acdeghklmnopqrstuvwxyz])"
|
||||
+ "|(nab|nagoya|name|nationwide|natura|navy|nba|nec|net|netbank|netflix|network|neustar|new|newholland|news|next|nextdirect|nexus|nfl|ngo|nhk|nico|nike|nikon|ninja|nissan|nissay|nokia|northwesternmutual|norton|now|nowruz|nowtv|nra|nrw|ntt|nyc|n[acefgilopruz])"
|
||||
+ "|(obi|observer|off|office|okinawa|olayan|olayangroup|oldnavy|ollo|omega|one|ong|onl|online|onyourside|ooo|open|oracle|orange|org|organic|origins|osaka|otsuka|ott|ovh|om)"
|
||||
+ "|(page|panasonic|paris|pars|partners|parts|party|passagens|pay|pccw|pet|pfizer|pharmacy|phd|philips|phone|photo|photography|photos|physio|pics|pictet|pictures|pid|pin|ping|pink|pioneer|pizza|place|play|playstation|plumbing|plus|pnc|pohl|poker|politie|porn|post|pramerica|praxi|press|prime|pro|prod|productions|prof|progressive|promo|properties|property|protection|pru|prudential|pub|pwc|p[aefghklmnrstwy])"
|
||||
+ "|(qpon|quebec|quest|qvc|qa)"
|
||||
+ "|(racing|radio|raid|read|realestate|realtor|realty|recipes|red|redstone|redumbrella|rehab|reise|reisen|reit|reliance|ren|rent|rentals|repair|report|republican|rest|restaurant|review|reviews|rexroth|rich|richardli|ricoh|ril|rio|rip|rmit|rocher|rocks|rodeo|rogers|room|rsvp|rugby|ruhr|run|rwe|ryukyu|r[eosuw])"
|
||||
+ "|(saarland|safe|safety|sakura|sale|salon|samsclub|samsung|sandvik|sandvikcoromant|sanofi|sap|sarl|sas|save|saxo|sbi|sbs|sca|scb|schaeffler|schmidt|scholarships|school|schule|schwarz|science|scjohnson|scot|search|seat|secure|security|seek|select|sener|services|ses|seven|sew|sex|sexy|sfr|shangrila|sharp|shaw|shell|shia|shiksha|shoes|shop|shopping|shouji|show|showtime|shriram|silk|sina|singles|site|ski|skin|sky|skype|sling|smart|smile|sncf|soccer|social|softbank|software|sohu|solar|solutions|song|sony|soy|space|sport|spot|spreadbetting|srl|stada|staples|star|statebank|statefarm|stc|stcgroup|stockholm|storage|store|stream|studio|study|style|sucks|supplies|supply|support|surf|surgery|suzuki|swatch|swiftcover|swiss|sydney|systems|s[abcdeghijklmnorstuvxyz])"
|
||||
+ "|(tab|taipei|talk|taobao|target|tatamotors|tatar|tattoo|tax|taxi|tci|tdk|team|tech|technology|tel|temasek|tennis|teva|thd|theater|theatre|tiaa|tickets|tienda|tiffany|tips|tires|tirol|tjmaxx|tjx|tkmaxx|tmall|today|tokyo|tools|top|toray|toshiba|total|tours|town|toyota|toys|trade|trading|training|travel|travelchannel|travelers|travelersinsurance|trust|trv|tube|tui|tunes|tushu|tvs|t[cdfghjklmnortvwz])"
|
||||
+ "|(ubank|ubs|unicom|university|uno|uol|ups|u[agksyz])"
|
||||
+ "|(vacations|vana|vanguard|vegas|ventures|verisign|versicherung|vet|viajes|video|vig|viking|villas|vin|vip|virgin|visa|vision|viva|vivo|vlaanderen|vodka|volkswagen|volvo|vote|voting|voto|voyage|vuelos|v[aceginu])"
|
||||
+ "|(wales|walmart|walter|wang|wanggou|watch|watches|weather|weatherchannel|webcam|weber|website|wed|wedding|weibo|weir|whoswho|wien|wiki|williamhill|win|windows|wine|winners|wme|wolterskluwer|woodside|work|works|world|wow|wtc|wtf|w[fs])"
|
||||
+ "|(xbox|xerox|xfinity|xihuan|xin|xn\\-\\-11b4c3d|xn\\-\\-1ck2e1b|xn\\-\\-1qqw23a|xn\\-\\-2scrj9c|xn\\-\\-30rr7y|xn\\-\\-3bst00m|xn\\-\\-3ds443g|xn\\-\\-3e0b707e|xn\\-\\-3hcrj9c|xn\\-\\-3oq18vl8pn36a|xn\\-\\-3pxu8k|xn\\-\\-42c2d9a|xn\\-\\-45br5cyl|xn\\-\\-45brj9c|xn\\-\\-45q11c|xn\\-\\-4gbrim|xn\\-\\-54b7fta0cc|xn\\-\\-55qw42g|xn\\-\\-55qx5d|xn\\-\\-5su34j936bgsg|xn\\-\\-5tzm5g|xn\\-\\-6frz82g|xn\\-\\-6qq986b3xl|xn\\-\\-80adxhks|xn\\-\\-80ao21a|xn\\-\\-80aqecdr1a|xn\\-\\-80asehdb|xn\\-\\-80aswg|xn\\-\\-8y0a063a|xn\\-\\-90a3ac|xn\\-\\-90ae|xn\\-\\-90ais|xn\\-\\-9dbq2a|xn\\-\\-9et52u|xn\\-\\-9krt00a|xn\\-\\-b4w605ferd|xn\\-\\-bck1b9a5dre4c|xn\\-\\-c1avg|xn\\-\\-c2br7g|xn\\-\\-cck2b3b|xn\\-\\-cckwcxetd|xn\\-\\-cg4bki|xn\\-\\-clchc0ea0b2g2a9gcd|xn\\-\\-czr694b|xn\\-\\-czrs0t|xn\\-\\-czru2d|xn\\-\\-d1acj3b|xn\\-\\-d1alf|xn\\-\\-e1a4c|xn\\-\\-eckvdtc9d|xn\\-\\-efvy88h|xn\\-\\-fct429k|xn\\-\\-fhbei|xn\\-\\-fiq228c5hs|xn\\-\\-fiq64b|xn\\-\\-fiqs8s|xn\\-\\-fiqz9s|xn\\-\\-fjq720a|xn\\-\\-flw351e|xn\\-\\-fpcrj9c3d|xn\\-\\-fzc2c9e2c|xn\\-\\-fzys8d69uvgm|xn\\-\\-g2xx48c|xn\\-\\-gckr3f0f|xn\\-\\-gecrj9c|xn\\-\\-gk3at1e|xn\\-\\-h2breg3eve|xn\\-\\-h2brj9c|xn\\-\\-h2brj9c8c|xn\\-\\-hxt814e|xn\\-\\-i1b6b1a6a2e|xn\\-\\-imr513n|xn\\-\\-io0a7i|xn\\-\\-j1aef|xn\\-\\-j1amh|xn\\-\\-j6w193g|xn\\-\\-jlq480n2rg|xn\\-\\-jlq61u9w7b|xn\\-\\-jvr189m|xn\\-\\-kcrx77d1x4a|xn\\-\\-kprw13d|xn\\-\\-kpry57d|xn\\-\\-kput3i|xn\\-\\-l1acc|xn\\-\\-lgbbat1ad8j|xn\\-\\-mgb9awbf|xn\\-\\-mgba3a3ejt|xn\\-\\-mgba3a4f16a|xn\\-\\-mgba7c0bbn0a|xn\\-\\-mgbaakc7dvf|xn\\-\\-mgbaam7a8h|xn\\-\\-mgbab2bd|xn\\-\\-mgbah1a3hjkrd|xn\\-\\-mgbai9azgqp6j|xn\\-\\-mgbayh7gpa|xn\\-\\-mgbbh1a|xn\\-\\-mgbbh1a71e|xn\\-\\-mgbc0a9azcg|xn\\-\\-mgbca7dzdo|xn\\-\\-mgbcpq6gpa1a|xn\\-\\-mgberp4a5d4ar|xn\\-\\-mgbgu82a|xn\\-\\-mgbi4ecexp|xn\\-\\-mgbpl2fh|xn\\-\\-mgbt3dhd|xn\\-\\-mgbtx2b|xn\\-\\-mgbx4cd0ab|xn\\-\\-mix891f|xn\\-\\-mk1bu44c|xn\\-\\-mxtq1m|xn\\-\\-ngbc5azd|xn\\-\\-ngbe9e0a|xn\\-\\-ngbrx|xn\\-\\-node|xn\\-\\-nqv7f|xn\\-\\-nqv7fs00ema|xn\\-\\-nyqy26a|xn\\-\\-o3cw4h|xn\\-\\-ogbpf8fl|xn\\-\\-otu796d|xn\\-\\-p1acf|xn\\-\\-p1ai|xn\\-\\-pgbs0dh|xn\\-\\-pssy2u|xn\\-\\-q7ce6a|xn\\-\\-q9jyb4c|xn\\-\\-qcka1pmc|xn\\-\\-qxa6a|xn\\-\\-qxam|xn\\-\\-rhqv96g|xn\\-\\-rovu88b|xn\\-\\-rvc1e0am3e|xn\\-\\-s9brj9c|xn\\-\\-ses554g|xn\\-\\-t60b56a|xn\\-\\-tckwe|xn\\-\\-tiq49xqyj|xn\\-\\-unup4y|xn\\-\\-vermgensberater\\-ctb|xn\\-\\-vermgensberatung\\-pwb|xn\\-\\-vhquv|xn\\-\\-vuq861b|xn\\-\\-w4r85el8fhu5dnra|xn\\-\\-w4rs40l|xn\\-\\-wgbh1c|xn\\-\\-wgbl6a|xn\\-\\-xhq521b|xn\\-\\-xkc2al3hye2a|xn\\-\\-xkc2dl3a5ee0h|xn\\-\\-y9a3aq|xn\\-\\-yfro4i67o|xn\\-\\-ygbi2ammx|xn\\-\\-zfr164b|xxx|xyz)"
|
||||
+ "|(yachts|yahoo|yamaxun|yandex|yodobashi|yoga|yokohama|you|youtube|yun|y[et])"
|
||||
+ "|(zappos|zara|zero|zip|zone|zuerich|z[amw])))")
|
||||
|
||||
|
||||
/**
|
||||
* Good characters for Internationalized Resource Identifiers (IRI).
|
||||
* This comprises most common used Unicode characters allowed in IRI
|
||||
* as detailed in RFC 3987.
|
||||
* Specifically, those two byte Unicode characters are not included.
|
||||
*/
|
||||
const val GOOD_IRI_CHAR = "a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF"
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Marks the WEB_URL pattern as dirty, and will recompile it on its next usage
|
||||
*/
|
||||
@Volatile
|
||||
private var MARK_URL_PATTERN_DIRTY = false
|
||||
|
||||
/**
|
||||
* Regular expression pattern to match most part of RFC 3987
|
||||
* Internationalized URLs, aka IRIs. Commonly used Unicode characters are
|
||||
* added.
|
||||
*/
|
||||
@Volatile
|
||||
private var WEB_URL = compileWebUrl()
|
||||
|
||||
/**
|
||||
* Updates the web URL mega-regex, and marks usages as dirty (so they are updated)
|
||||
*/
|
||||
fun updateWebUrlRegex(topLeveDomainUrls: String) {
|
||||
TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL = topLeveDomainUrls
|
||||
MARK_URL_PATTERN_DIRTY = true // update the next time we use it.
|
||||
}
|
||||
|
||||
|
||||
private fun compileWebUrl(): Pattern {
|
||||
return Pattern.compile(
|
||||
"((?:(http|https|Http|Https|rtsp|Rtsp):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)"
|
||||
+ "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_"
|
||||
+ "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?"
|
||||
+ "((?:(?:[" + GOOD_IRI_CHAR + "][" + GOOD_IRI_CHAR + "\\-]{0,64}\\.)+" // named host
|
||||
+ TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL
|
||||
+ "|(?:(?:25[0-5]|2[0-4]" // or ip address
|
||||
+ "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(?:25[0-5]|2[0-4][0-9]"
|
||||
+ "|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1]"
|
||||
+ "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
|
||||
+ "|[1-9][0-9]|[0-9])))"
|
||||
+ "(?:\\:\\d{1,5})?)" // plus option port number
|
||||
+ "(\\/(?:(?:[a-zA-Z0-9\\;\\/\\?\\:\\@\\&\\=\\#\\~" // plus option query params
|
||||
+ "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?"
|
||||
+ "(?:\\b|$)")
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Only removes the path and query parameters. Only the transport + domain remain.
|
||||
* ie:
|
||||
* http://foo.com/index.php --> http://foo.com
|
||||
* https://www.aa.foo.com/index.php --> https://www.aa.foo.com
|
||||
* https://www.aa.foo.com/index&foo%bar --> https://www.aa.foo.com
|
||||
* https://www.aa.foo.com%foobar --> https://www.aa.foo.com
|
||||
*/
|
||||
fun cleanupAndRemovePath(fullDomainName: String): String {
|
||||
var start = fullDomainName.indexOf("://")
|
||||
if (start == -1) {
|
||||
start = 0
|
||||
}
|
||||
else {
|
||||
start += 3 // 3 is the length of ://
|
||||
}
|
||||
|
||||
var end = fullDomainName.length
|
||||
|
||||
val slash = fullDomainName.indexOf("/", start + 3)
|
||||
if (slash > -1 && slash < end) {
|
||||
end = slash
|
||||
}
|
||||
|
||||
val colon = fullDomainName.indexOf(":", start + 3)
|
||||
if (colon > -1 && colon < end) {
|
||||
end = colon
|
||||
}
|
||||
|
||||
val percent = fullDomainName.indexOf("%", start)
|
||||
if (percent > -1 && percent < end) {
|
||||
end = percent
|
||||
}
|
||||
|
||||
val ampersand = fullDomainName.indexOf("&", start)
|
||||
if (ampersand > -1 && ampersand < end) {
|
||||
end = ampersand
|
||||
}
|
||||
|
||||
val question = fullDomainName.indexOf("?", start)
|
||||
if (question > -1 && question < end) {
|
||||
end = question
|
||||
}
|
||||
|
||||
|
||||
return fullDomainName.substring(0, end)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Only removes http?s:// and the path (if it's present) and www. (if it's present). Also removes *. (if it's present)
|
||||
* ie:
|
||||
* http://foo.com/index.php --> foo.com
|
||||
* https://www.aa.foo.com/index.php --> aa.foo.com
|
||||
* https://www.aa.foo.com/index&foo%bar --> aa.foo.com
|
||||
* https://www.aa.foo.com%foobar --> aa.foo.com
|
||||
*/
|
||||
fun cleanupAndRemoveWwwAndPath(fullDomainName: String): String {
|
||||
var start = fullDomainName.indexOf("://")
|
||||
if (start == -1) {
|
||||
start = 0
|
||||
}
|
||||
else {
|
||||
start += 3 // 3 is the length of ://
|
||||
}
|
||||
|
||||
// get rid of the www. part if it exists.
|
||||
val www = fullDomainName.indexOf("www.", start)
|
||||
if (www > -1 && www <= 8) {
|
||||
start = www + 4 // 4 is the length of www.
|
||||
}
|
||||
|
||||
val star = fullDomainName.indexOf("*.", start)
|
||||
if (star > -1) {
|
||||
start = star + 2 // 2 is the length of *.
|
||||
}
|
||||
|
||||
var end = fullDomainName.length
|
||||
|
||||
val slash = fullDomainName.indexOf("/", start + 3)
|
||||
if (slash > -1 && slash < end) {
|
||||
end = slash
|
||||
}
|
||||
|
||||
val colon = fullDomainName.indexOf(":", start + 3)
|
||||
if (colon > -1 && colon < end) {
|
||||
end = colon
|
||||
}
|
||||
|
||||
val percent = fullDomainName.indexOf("%", start)
|
||||
if (percent > -1 && percent < end) {
|
||||
end = percent
|
||||
}
|
||||
|
||||
val ampersand = fullDomainName.indexOf("&", start)
|
||||
if (ampersand > -1 && ampersand < end) {
|
||||
end = ampersand
|
||||
}
|
||||
|
||||
val question = fullDomainName.indexOf("?", start)
|
||||
if (question > -1 && question < end) {
|
||||
end = question
|
||||
}
|
||||
|
||||
|
||||
return fullDomainName.substring(start, end)
|
||||
}
|
||||
|
||||
/**
|
||||
* Only removes http?s:// and www. (if it's present). Also removes *. (if it's present)
|
||||
* ie:
|
||||
* http://foo.com/index.php --> foo.com/index.php
|
||||
* https://www.aa.foo.com/index.php --> aa.foo.com/index.php
|
||||
* https://www.aa.foo.com/index&foo%bar --> aa.foo.com/index&foo%bar
|
||||
* https://www.aa.foo.com%foobar --> aa.foo.com%foobar
|
||||
*/
|
||||
fun cleanupAndPreservePath(fullDomainName: String, removeQueryString: Boolean = true): String {
|
||||
var start = fullDomainName.indexOf("://")
|
||||
if (start == -1) {
|
||||
start = 0
|
||||
}
|
||||
else {
|
||||
start += 3 // 3 is the length of ://
|
||||
}
|
||||
|
||||
// get rid of the www. part if it exists.
|
||||
val www = fullDomainName.indexOf("www.", start)
|
||||
if (www > -1 && www <= 8) {
|
||||
start = www + 4 // 4 is the length of www.
|
||||
}
|
||||
|
||||
val star = fullDomainName.indexOf("*.", start)
|
||||
if (star > -1) {
|
||||
start = star + 2 // 2 is the length of *.
|
||||
}
|
||||
|
||||
var end = if (removeQueryString) {
|
||||
var end = fullDomainName.length
|
||||
|
||||
val percent = fullDomainName.indexOf("%", start)
|
||||
if (percent > -1 && percent < end) {
|
||||
end = percent
|
||||
}
|
||||
|
||||
val ampersand = fullDomainName.indexOf("&", start)
|
||||
if (ampersand > -1 && ampersand < end) {
|
||||
end = ampersand
|
||||
}
|
||||
|
||||
val question = fullDomainName.indexOf("?", start)
|
||||
if (question > -1 && question < end) {
|
||||
end = question
|
||||
}
|
||||
|
||||
end
|
||||
} else {
|
||||
fullDomainName.length
|
||||
}
|
||||
|
||||
// If the last char is a /, remove it
|
||||
if (end -1 >= 0 && fullDomainName[end - 1] == '/') {
|
||||
end--
|
||||
}
|
||||
|
||||
return fullDomainName.substring(start, end)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Only removes www. (if it's present). Also removes *. (if it's present)
|
||||
*
|
||||
*
|
||||
* ie:
|
||||
* foo.com/index.php --> foo.com
|
||||
* www.aa.foo.com/index.php --> aa.foo.com
|
||||
* www.aa.foo.com/index&foo%bar --> aa.foo.com
|
||||
* www.aa.foo.com%foobar --> aa.foo.com
|
||||
*
|
||||
*
|
||||
* NOTE: ONLY use this if you can GUARANTEE that there is no http?s://
|
||||
*/
|
||||
fun removeWww(fullDomainName: String?): String? {
|
||||
if (fullDomainName == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
// get rid of the www. part if it exists.
|
||||
var start = fullDomainName.indexOf("www.")
|
||||
if (start > -1) {
|
||||
start += 4 // 4 is the length of www.
|
||||
}
|
||||
else {
|
||||
start = 0
|
||||
}
|
||||
|
||||
val star = fullDomainName.indexOf("*.", start)
|
||||
if (star > -1) {
|
||||
start = star + 2 // 2 is the length of *.
|
||||
}
|
||||
|
||||
var end = fullDomainName.indexOf("/", start + 3)
|
||||
if (end == -1) {
|
||||
if (start == 0) {
|
||||
// it was already clean.
|
||||
return fullDomainName
|
||||
}
|
||||
|
||||
end = fullDomainName.length
|
||||
}
|
||||
|
||||
val percent = fullDomainName.indexOf("%", start)
|
||||
if (percent > -1 && percent < end) {
|
||||
end = percent
|
||||
}
|
||||
|
||||
return fullDomainName.substring(start, end)
|
||||
}
|
||||
|
||||
fun isValidUrl(url: String?): Boolean {
|
||||
return if (url.isNullOrEmpty()) {
|
||||
false // Don't even need to check, not a valid domain
|
||||
}
|
||||
else {
|
||||
if (MARK_URL_PATTERN_DIRTY) {
|
||||
// race conditions don't matter, this just guarantees that it's updated.
|
||||
WEB_URL = compileWebUrl()
|
||||
MARK_URL_PATTERN_DIRTY = false
|
||||
}
|
||||
|
||||
val m = WEB_URL.matcher(url)
|
||||
m.matches()
|
||||
}
|
||||
}
|
||||
|
||||
fun isSubDomain(fullDomainName: String): Boolean {
|
||||
var start = fullDomainName.indexOf("://")
|
||||
if (start == -1) {
|
||||
start = 0
|
||||
}
|
||||
else {
|
||||
start += 3
|
||||
}
|
||||
|
||||
if (fullDomainName.contains("www.")) {
|
||||
start += 4 // 4 is the length of www.
|
||||
}
|
||||
|
||||
var end = fullDomainName.indexOf("/", start + 3)
|
||||
if (end == -1) {
|
||||
end = fullDomainName.length
|
||||
}
|
||||
|
||||
val substring = fullDomainName.substring(start, end)
|
||||
|
||||
val dots = substring.count { it == '.' }
|
||||
|
||||
return dots > 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Only remove http?s://www and the path (if it's present).
|
||||
* Get the next level domain after cleanup if next level domain is not top level domain.
|
||||
* ie:
|
||||
* http://www.a.b.foo.com -> b.foo.com
|
||||
* https://www.foo.com -> foo.com
|
||||
* foo.com -> foo.com
|
||||
*/
|
||||
|
||||
fun cleanupAndGetNextLevelDomain(fullDomainName: String): String? {
|
||||
var start = fullDomainName.indexOf("://")
|
||||
if (start == -1) {
|
||||
start = 0
|
||||
}
|
||||
else {
|
||||
start += 3
|
||||
}
|
||||
|
||||
if (fullDomainName.contains("www.")) {
|
||||
start += 4 // 4 is the length of www.
|
||||
}
|
||||
|
||||
var end = fullDomainName.indexOf("/", start + 3)
|
||||
if (end == -1) {
|
||||
end = fullDomainName.length
|
||||
}
|
||||
|
||||
var substring = fullDomainName.substring(start, end)
|
||||
val last = substring
|
||||
|
||||
val nextDot = substring.indexOf(".")
|
||||
if (nextDot == -1) {
|
||||
return null
|
||||
}
|
||||
|
||||
substring = substring.substring(nextDot + 1)
|
||||
|
||||
if (Dns.isTLD(substring)) {
|
||||
substring = last
|
||||
}
|
||||
|
||||
return substring
|
||||
}
|
||||
|
||||
fun getNextLevelDomain(fullDomainName: String): String? {
|
||||
val nextDot = fullDomainName.indexOf(".")
|
||||
if (nextDot == -1) {
|
||||
return null
|
||||
}
|
||||
|
||||
return fullDomainName.substring(nextDot + 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Only removes http?s:// and the path (if it's present).
|
||||
* ie:
|
||||
* http://foo.com/index.php --> foo.com
|
||||
* https://www.aa.foo.com/index.php --> foo.com
|
||||
*/
|
||||
fun cleanupAndGetSecondLevelDomain(fullDomainName: String): String? {
|
||||
// File URLs will return null at the extractSLD step, so this case is explicitly for logging purposes.
|
||||
// We want to know when the returned value is null because it's a file, vs returning null for other reasons.
|
||||
if (fullDomainName.startsWith("file://", true)){
|
||||
return null
|
||||
}
|
||||
|
||||
var start = fullDomainName.indexOf("://")
|
||||
if (start == -1) {
|
||||
start = 0
|
||||
}
|
||||
else {
|
||||
start += 3
|
||||
}
|
||||
|
||||
var end = fullDomainName.indexOf("/", start + 3)
|
||||
if (end == -1) {
|
||||
if (start == 0) {
|
||||
// it was already clean.
|
||||
return Dns.extractSLD(fullDomainName)
|
||||
}
|
||||
|
||||
end = fullDomainName.length
|
||||
}
|
||||
|
||||
// for now, get the SLD as well
|
||||
val substring = fullDomainName.substring(start, end)
|
||||
return Dns.extractSLD(substring)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the third level domain of google domains if it has one.
|
||||
* ie:
|
||||
* http://google.com/index.php -> google.com
|
||||
* http://docs.google.com/index.php -> docs.google.com
|
||||
* https://32.32.432.fdsa.docs.google.com/index.php -> docs.google.com
|
||||
*/
|
||||
|
||||
fun cleanupAndGetThirdLevelDomain(fullDomainName: String): String {
|
||||
var cleanDomain = cleanupAndRemoveWwwAndPath(fullDomainName)
|
||||
|
||||
val periodCount = cleanDomain.count { it == '.'}
|
||||
|
||||
if (periodCount <= 2) {
|
||||
return cleanDomain
|
||||
}
|
||||
|
||||
|
||||
for (x in periodCount downTo 3) {
|
||||
val nextDot = cleanDomain.indexOf(".")
|
||||
|
||||
cleanDomain = cleanDomain.substring(nextDot + 1)
|
||||
}
|
||||
|
||||
return cleanDomain
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last portion of the file uri, the file name itself.
|
||||
* ie:
|
||||
* file://Downloads/example.pdf -> example.pdf
|
||||
* file:///media.jpg -> media.jpg
|
||||
*/
|
||||
fun cleanupFileUri(domain: String): String {
|
||||
val lastSlashIndex = domain.lastIndexOf("/")
|
||||
|
||||
if (lastSlashIndex == -1) {
|
||||
return domain
|
||||
}
|
||||
|
||||
return domain.substring(lastSlashIndex + 1)
|
||||
}
|
||||
|
||||
|
||||
fun forceAcceptAllTlsCertificates() {
|
||||
/*
|
||||
* fix for
|
||||
* Exception in thread "main" javax.net.ssl.SSLHandshakeException:
|
||||
* sun.security.validator.ValidatorException:
|
||||
* PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException:
|
||||
* unable to find valid certification path to requested target
|
||||
*/
|
||||
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
|
||||
override fun getAcceptedIssuers(): Array<X509Certificate>? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun checkClientTrusted(certs: Array<X509Certificate>, authType: String) {}
|
||||
|
||||
override fun checkServerTrusted(certs: Array<X509Certificate>, authType: String) {}
|
||||
})
|
||||
|
||||
|
||||
val sc = SSLContext.getInstance("SSL")
|
||||
sc.init(null, trustAllCerts, java.security.SecureRandom())
|
||||
HttpsURLConnection.setDefaultSSLSocketFactory(sc.socketFactory)
|
||||
|
||||
// Create all-trusting host name verifier
|
||||
val allHostsValid = HostnameVerifier { _, _ -> true }
|
||||
|
||||
// Install the all-trusting host verifier
|
||||
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid)
|
||||
}
|
||||
|
||||
|
||||
|
||||
// @JvmStatic
|
||||
// fun main(args: Array<String>) {
|
||||
// println(cleanupAndPreservePath("https://www.youtube.com/watch?v=YP6EaIDlmEg&t=1s", removeQueryString = true))
|
||||
// println(cleanupAndPreservePath("https://www.khanacademy.org/", removeQueryString = true))
|
||||
// println(cleanupAndRemoveWwwAndPath("https://sat184.cloud1.tds.airast.org/student/V746/Pages/TestShell.aspx"))
|
||||
// println(cleanupAndRemoveWwwAndPath("https://sat184.cloud1.tds.airast.org/student/V746/Pages/TestShell.aspx"))
|
||||
//
|
||||
// }
|
||||
|
||||
// println(WEB_URL.matcher("https://www.youtube.com/watch?v=WEVctuQTeaI").matches())
|
||||
// println(WEB_URL.matcher("www.youtube.com/watch?v=WEVctuQTeaI").matches())
|
||||
// println(WEB_URL.matcher("youtube.com/watch?v=WEVctuQTeaI").matches())
|
||||
// println(WEB_URL.matcher("youtube.com").matches())
|
||||
// println(WEB_URL.matcher("https://www.espn.com/nba/").matches())
|
||||
// println(WEB_URL.matcher("https://www.espn.com/nba").matches())
|
||||
// println(getNextLevelDomain("admin.regression.net-ref.com"))
|
||||
// println(cleanupAndGetGoogleDomain("https://www.google.com/search?rlz=1CAZGSZ_enUS848&tbm=isch&q=pretty+backgrounds&chips=q:pretty+backgrounds,g_1:iphone:lJzZkCc6kg8%3D&usg=AI4_-kSfq6w5oVz33oUhcFfHeJC-MtmIww&sa=X&ved=0ahUKEwi0hP-Sk4riAhUUpJ4KHaWJDi0Q4lYIJigA&biw=1517&bih=695&dpr=0.9&safe=active&ssui=on"));
|
||||
// println(cleanupAndRemoveWww("http://fasttmath.capousd.org:55880/fmng/loader/"))
|
||||
// println(cleanupAndRemoveWww("http://fasttmath.capousd.org:55880/fmng/loader/"))
|
||||
// println(cleanupAndRemoveWww("http://fasttmath.capousd.org:55880/fmng/loader/"))
|
||||
// println(cleanupAndRemoveWww("https://clever.com/oauth/authorize?channel=clever-portal&client_id=8c54ced0462a3fe2da0a&confirmed=true&district_id=556cc0739496cf01000003f2" +
|
||||
// "&redirect_uri=https%3A%2F%2Fapp.typingagent.com%2Fclever%2Findex%3Foauth%3Dtrue&response_type=code"))
|
||||
// println(cleanupAndRemoveWww(
|
||||
// "https://www.clever.com/oauth/authorize?channel=clever-portal&client_id=ae17f3b6f000d1bb4f2c&confirmed=true&district_id=556cc0739496cf01000003f2&redirect_uri=https%3A%2F%2Fwww" +
|
||||
// ".khanacademy.org%2Flogin%2Fclever&response_type=code"))
|
||||
// println(cleanupAndRemoveWww(cleanupAndRemoveWww("https://sat184.cloud1.tds.airast.org/student/V746/Pages/TestShell.aspx")))
|
||||
//
|
||||
// println(cleanupAndPreservePath("http://fasttmath.capousd.org:55880/fmng/loader/"))
|
||||
// println(cleanupAndPreservePath(
|
||||
// "https://www.clever.com/oauth/authorize?channel=clever-portal&client_id=ae17f3b6f000d1bb4f2c&confirmed=true&district_id=556cc0739496cf01000003f2&redirect_uri=https%3A%2F%2Fwww" +
|
||||
// ".khanacademy.org%2Flogin%2Fclever&response_type=code"))
|
||||
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
* Runs the 'action' function when the scheme+domain+path(s) when it was successful. Runs the 'onError' function when it fails.
|
||||
*/
|
||||
suspend fun fetchData(scheme: String, domain: String, vararg paths: String, retryCount: Int = 10,
|
||||
onError: (String) ->Unit,
|
||||
onSuccess: suspend (InputStream)->Unit) = withContext(Dispatchers.IO) {
|
||||
val encodedPath = paths.joinToString(separator = "/") { URLEncoder.encodePathSegment(it, Charsets.UTF_8) }
|
||||
var location = "$scheme://$domain/$encodedPath"
|
||||
var alreadyTriedOtherScheme = false
|
||||
|
||||
// logger.trace{ "Getting data: $location" }
|
||||
|
||||
// We DO want to support redirects, in case OLD code is running in the wild.
|
||||
var base: URL
|
||||
var next: URL
|
||||
var visitedCount = 0
|
||||
|
||||
while (true) {
|
||||
visitedCount += 1
|
||||
if (visitedCount > retryCount) {
|
||||
onError("Stuck in a loop for '$location' --- more than $visitedCount attempts")
|
||||
return@withContext
|
||||
}
|
||||
|
||||
try {
|
||||
base = URL(location)
|
||||
with(base.openConnection() as HttpURLConnection) {
|
||||
useCaches = false
|
||||
instanceFollowRedirects = true
|
||||
|
||||
// if (logger.isTraceEnabled) {
|
||||
// logger.trace { "Requesting URL : $url" }
|
||||
// logger.trace { "Response Code : $responseCode" }
|
||||
// }
|
||||
|
||||
when (responseCode) {
|
||||
HttpURLConnection.HTTP_MOVED_PERM, HttpURLConnection.HTTP_MOVED_TEMP -> {
|
||||
location = getHeaderField("Location")
|
||||
// java.net.URLDecoder is only valid for query parameters/headers -- NOT FOR ACTUAL URLS!
|
||||
location = URLDecoder.decode(location, "US-ASCII")
|
||||
|
||||
|
||||
// logger.trace { "Response to '$url' redirected to '$location'" }
|
||||
|
||||
next = URL(base, location) // Deal with relative URLs
|
||||
location = next.toExternalForm()
|
||||
|
||||
// loop again with the new location
|
||||
return@with
|
||||
}
|
||||
HttpURLConnection.HTTP_OK -> {
|
||||
inputStream.use {
|
||||
onSuccess(it)
|
||||
}
|
||||
|
||||
// done
|
||||
return@withContext
|
||||
}
|
||||
HttpsURLConnection.HTTP_NOT_FOUND -> {
|
||||
if (alreadyTriedOtherScheme) {
|
||||
onError("Error '$responseCode' getting location '$location' HTTPS option exhausted.")
|
||||
|
||||
// done
|
||||
return@withContext
|
||||
}
|
||||
|
||||
// if we are HTTPS, retry again as HTTP.
|
||||
alreadyTriedOtherScheme = true
|
||||
visitedCount = 0
|
||||
|
||||
location = if (location.startsWith("https")) {
|
||||
"http://$domain/$encodedPath"
|
||||
} else {
|
||||
"https://$domain/$encodedPath"
|
||||
}
|
||||
|
||||
// loop again with the new location
|
||||
return@with
|
||||
}
|
||||
else -> {
|
||||
onError("Error '$responseCode' getting location '$location'")
|
||||
|
||||
// done
|
||||
return@withContext
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e: UnknownHostException) {
|
||||
// TMI for what's going on. We just can't, so leave it at that.
|
||||
onError("Failed to retrieve or write icon for location: '${location}'")
|
||||
return@withContext
|
||||
}
|
||||
catch (e: Exception) {
|
||||
onError("Failed to retrieve or write icon for location: '${location}'. ${e.message}")
|
||||
return@withContext
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNREACHABLE_CODE")
|
||||
null
|
||||
}
|
||||
}
|
|
@ -1,199 +0,0 @@
|
|||
/*
|
||||
* Copyright 2018 dorkbox, llc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.classes;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.GenericArrayType;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.TypeVariable;
|
||||
import java.lang.reflect.WildcardType;
|
||||
import java.util.Objects;
|
||||
|
||||
import net.jodah.typetools.TypeResolver;
|
||||
|
||||
public final
|
||||
class ClassHelper {
|
||||
|
||||
/**
|
||||
* Retrieves the generic type parameter for the PARENT (super) class of the specified class or lambda expression.
|
||||
*
|
||||
* Because of how type erasure works in java, this will work on lambda expressions and ONLY parent/super classes.
|
||||
*
|
||||
* @param genericTypeClass this class is what you are looking for
|
||||
* @param classToCheck class to actually get the parameter from
|
||||
* @param genericParameterToGet 0-based index of parameter as class to get
|
||||
*
|
||||
* @return null if the generic type could not be found.
|
||||
*/
|
||||
@SuppressWarnings({"StatementWithEmptyBody", "UnnecessaryLocalVariable"})
|
||||
public static
|
||||
Class<?> getGenericParameterAsClassForSuperClass(Class<?> genericTypeClass, Class<?> classToCheck, int genericParameterToGet) {
|
||||
Class<?> loopClassCheck = classToCheck;
|
||||
|
||||
// this will ALWAYS return something, if it is unknown, it will return TypeResolver.Unknown.class
|
||||
Class<?>[] classes = TypeResolver.resolveRawArguments(genericTypeClass, loopClassCheck);
|
||||
if (classes.length > genericParameterToGet && classes[genericParameterToGet] != TypeResolver.Unknown.class) {
|
||||
return classes[genericParameterToGet];
|
||||
}
|
||||
|
||||
// case of multiple inheritance, we are trying to get the first available generic info
|
||||
// don't check for Object.class (this is where superclass is null)
|
||||
while (loopClassCheck != Object.class) {
|
||||
// check to see if we have what we are looking for on our CURRENT class
|
||||
Type superClassGeneric = loopClassCheck.getGenericSuperclass();
|
||||
|
||||
classes = TypeResolver.resolveRawArguments(superClassGeneric, loopClassCheck);
|
||||
if (classes.length > genericParameterToGet) {
|
||||
Class<?> aClass = classes[genericParameterToGet];
|
||||
if (aClass != TypeResolver.Unknown.class) {
|
||||
return classes[genericParameterToGet];
|
||||
}
|
||||
}
|
||||
|
||||
// NO MATCH, so walk up.
|
||||
loopClassCheck = loopClassCheck.getSuperclass();
|
||||
}
|
||||
|
||||
// NOTHING! now check interfaces!
|
||||
loopClassCheck = classToCheck;
|
||||
while (loopClassCheck != Object.class) {
|
||||
// check to see if we have what we are looking for on our CURRENT class interfaces
|
||||
Type[] genericInterfaces = loopClassCheck.getGenericInterfaces();
|
||||
|
||||
for (Type genericInterface : genericInterfaces) {
|
||||
classes = TypeResolver.resolveRawArguments(genericInterface, loopClassCheck);
|
||||
if (classes.length > genericParameterToGet) {
|
||||
Class<?> aClass = classes[genericParameterToGet];
|
||||
if (aClass != TypeResolver.Unknown.class) {
|
||||
return aClass;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NO MATCH, so walk up.
|
||||
loopClassCheck = loopClassCheck.getSuperclass();
|
||||
}
|
||||
|
||||
// couldn't find it.
|
||||
return null;
|
||||
}
|
||||
|
||||
// from: https://github.com/square/retrofit/blob/108fe23964b986107aed352ba467cd2007d15208/retrofit/src/main/java/retrofit2/Utils.java
|
||||
public static Class<?> getRawType(Type type) {
|
||||
Objects.requireNonNull(type, "type == null");
|
||||
|
||||
if (type instanceof Class<?>) {
|
||||
// Type is a normal class.
|
||||
return (Class<?>) type;
|
||||
}
|
||||
if (type instanceof ParameterizedType) {
|
||||
ParameterizedType parameterizedType = (ParameterizedType) type;
|
||||
|
||||
// I'm not exactly sure why getRawType() returns Type instead of Class. Neal isn't either but
|
||||
// suspects some pathological case related to nested classes exists.
|
||||
Type rawType = parameterizedType.getRawType();
|
||||
if (!(rawType instanceof Class)) throw new IllegalArgumentException();
|
||||
return (Class<?>) rawType;
|
||||
}
|
||||
if (type instanceof GenericArrayType) {
|
||||
Type componentType = ((GenericArrayType) type).getGenericComponentType();
|
||||
return Array.newInstance(getRawType(componentType), 0).getClass();
|
||||
}
|
||||
if (type instanceof TypeVariable) {
|
||||
// We could use the variable's bounds, but that won't work if there are multiple. Having a raw
|
||||
// type that's more general than necessary is okay.
|
||||
return Object.class;
|
||||
}
|
||||
if (type instanceof WildcardType) {
|
||||
return getRawType(((WildcardType) type).getUpperBounds()[0]);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
"Expected a Class, ParameterizedType, or "
|
||||
+ "GenericArrayType, but <"
|
||||
+ type
|
||||
+ "> is of type "
|
||||
+ type.getClass().getName());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check to see if clazz or interface directly has one of the interfaces defined by requiredClass
|
||||
* <p/>
|
||||
* If the class DOES NOT directly have the interface it will fail.
|
||||
*/
|
||||
public static
|
||||
boolean hasInterface(Class<?> requiredClass, Class<?> clazz) {
|
||||
if (requiredClass == clazz) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Class<?>[] interfaces = clazz.getInterfaces();
|
||||
for (Class<?> iface : interfaces) {
|
||||
if (iface == requiredClass) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// now walk up to see if we can find it.
|
||||
for (Class<?> iface : interfaces) {
|
||||
boolean b = hasInterface(requiredClass, iface);
|
||||
if (b) {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
// nothing, so now we check the PARENT of this class
|
||||
Class<?> superClass = clazz.getSuperclass();
|
||||
|
||||
// case of multiple inheritance, we are trying to get the first available generic info
|
||||
// don't check for Object.class (this is where superclass is null)
|
||||
while (superClass != null && superClass != Object.class) {
|
||||
// check to see if we have what we are looking for on our CURRENT class
|
||||
if (hasInterface(requiredClass, superClass)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// NO MATCH, so walk up.
|
||||
superClass = superClass.getSuperclass();
|
||||
}
|
||||
|
||||
// if we don't find it.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the clazz is a subclass of a parent class.
|
||||
*/
|
||||
@SuppressWarnings("SimplifiableIfStatement")
|
||||
public static
|
||||
boolean hasParentClass(Class<?> parentClazz, Class<?> clazz) {
|
||||
Class<?> superClass = clazz.getSuperclass();
|
||||
if (parentClazz == superClass) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (superClass != null && superClass != Object.class) {
|
||||
return hasParentClass(parentClazz, superClass);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private
|
||||
ClassHelper() {
|
||||
}
|
||||
}
|
|
@ -1,187 +0,0 @@
|
|||
/*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.classes;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||
|
||||
import dorkbox.collections.IdentityMap;
|
||||
|
||||
/**
|
||||
* @author dorkbox
|
||||
* Date: 4/1/15
|
||||
*/
|
||||
public final
|
||||
class ClassHierarchy {
|
||||
|
||||
private volatile IdentityMap<Class<?>, Class<?>> arrayCache;
|
||||
private volatile IdentityMap<Class<?>, Class<?>[]> superClassesCache;
|
||||
|
||||
// Recommended for best performance while adhering to the "single writer principle". Must be static-final
|
||||
private static final AtomicReferenceFieldUpdater<ClassHierarchy, IdentityMap> arrayREF =
|
||||
AtomicReferenceFieldUpdater.newUpdater(ClassHierarchy.class,
|
||||
IdentityMap.class,
|
||||
"arrayCache");
|
||||
|
||||
private static final AtomicReferenceFieldUpdater<ClassHierarchy, IdentityMap> superClassesREF =
|
||||
AtomicReferenceFieldUpdater.newUpdater(ClassHierarchy.class,
|
||||
IdentityMap.class,
|
||||
"superClassesCache");
|
||||
|
||||
/**
|
||||
* These data structures are never reset because the class hierarchy doesn't change at runtime. This class uses the "single writer
|
||||
* principle" for storing data, EVEN THOUGH it's not accessed by a single writer. This DOES NOT MATTER because duplicates DO NOT matter
|
||||
*/
|
||||
public
|
||||
ClassHierarchy(float loadFactor) {
|
||||
this.arrayCache = new IdentityMap<Class<?>, Class<?>>(32, loadFactor);
|
||||
this.superClassesCache = new IdentityMap<Class<?>, Class<?>[]>(32, loadFactor);
|
||||
}
|
||||
|
||||
/**
|
||||
* will return the class + parent classes as an array.
|
||||
* if parameter clazz is of type array, then the super classes are of array type as well
|
||||
* <p>
|
||||
* race conditions will result in DUPLICATE answers, which we don't care if happens
|
||||
* never returns null
|
||||
* never reset (class hierarchy never changes during runtime)
|
||||
*/
|
||||
public
|
||||
Class<?>[] getClassAndSuperClasses(final Class<?> clazz) {
|
||||
// access a snapshot of the subscriptions (single-writer-principle)
|
||||
final IdentityMap<Class<?>, Class<?>[]> cache = cast(superClassesREF.get(this));
|
||||
|
||||
Class<?>[] classes = cache.get(clazz);
|
||||
|
||||
// duplicates DO NOT MATTER
|
||||
if (classes == null) {
|
||||
// publish all super types of class
|
||||
final Iterator<Class<?>> superTypesIterator = getSuperTypes(clazz);
|
||||
final ArrayList<Class<?>> newList = new ArrayList<Class<?>>(16);
|
||||
|
||||
Class<?> c;
|
||||
final boolean isArray = clazz.isArray();
|
||||
|
||||
// have to add the original class to the front of the list
|
||||
newList.add(clazz);
|
||||
|
||||
if (isArray) {
|
||||
// super-types for an array ALSO must be an array.
|
||||
while (superTypesIterator.hasNext()) {
|
||||
c = superTypesIterator.next();
|
||||
c = getArrayClass(c);
|
||||
|
||||
if (c != clazz) {
|
||||
newList.add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
while (superTypesIterator.hasNext()) {
|
||||
c = superTypesIterator.next();
|
||||
|
||||
if (c != clazz) {
|
||||
newList.add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classes = new Class<?>[newList.size()];
|
||||
newList.toArray(classes);
|
||||
cache.put(clazz, classes);
|
||||
|
||||
// save this snapshot back to the original (single writer principle)
|
||||
superClassesREF.lazySet(this, cache);
|
||||
}
|
||||
|
||||
return classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* race conditions will result in DUPLICATE answers, which we don't care if happens
|
||||
* never returns null
|
||||
* never resets (class hierarchy never changes during runtime)
|
||||
*
|
||||
* https://bugs.openjdk.java.net/browse/JDK-6525802 (fixed this in 2007, so Array.newInstance is just as fast (via intrinsics) new [])
|
||||
* Cache is in place to keep GC down.
|
||||
*/
|
||||
public
|
||||
Class<?> getArrayClass(final Class<?> c) {
|
||||
// access a snapshot of the subscriptions (single-writer-principle)
|
||||
final IdentityMap<Class<?>, Class<?>> cache = cast(arrayREF.get(this));
|
||||
|
||||
Class<?> clazz = cache.get(c);
|
||||
|
||||
if (clazz == null) {
|
||||
// messy, but the ONLY way to do it. Array super types are also arrays
|
||||
final Object[] newInstance = (Object[]) Array.newInstance(c, 0);
|
||||
clazz = newInstance.getClass();
|
||||
cache.put(c, clazz);
|
||||
|
||||
// save this snapshot back to the original (single writer principle)
|
||||
arrayREF.lazySet(this, cache);
|
||||
}
|
||||
|
||||
return clazz;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all directly and indirectly related super types (classes and interfaces) of a given class.
|
||||
*
|
||||
* @param from The root class to start with
|
||||
* @return An array of classes, each representing a super type of the root class
|
||||
*/
|
||||
public static
|
||||
Iterator<Class<?>> getSuperTypes(Class<?> from) {
|
||||
// This must be a 'set' because there can be duplicates, depending on the object hierarchy
|
||||
final IdentityMap<Class<?>, Boolean> superclasses = new IdentityMap<Class<?>, Boolean>();
|
||||
collectInterfaces(from, superclasses);
|
||||
|
||||
while (!from.equals(Object.class) && !from.isInterface()) {
|
||||
superclasses.put(from.getSuperclass(), Boolean.TRUE);
|
||||
from = from.getSuperclass();
|
||||
collectInterfaces(from, superclasses);
|
||||
}
|
||||
|
||||
return superclasses.keys();
|
||||
}
|
||||
|
||||
private static
|
||||
void collectInterfaces(Class<?> from, IdentityMap<Class<?>, Boolean> accumulator) {
|
||||
for (Class<?> intface : from.getInterfaces()) {
|
||||
accumulator.put(intface, Boolean.TRUE);
|
||||
collectInterfaces(intface, accumulator);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clears the caches, should only be called on shutdown
|
||||
*/
|
||||
public
|
||||
void shutdown() {
|
||||
this.arrayCache.clear();
|
||||
this.superClassesCache.clear();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static
|
||||
<T> T cast(Object obj) {
|
||||
return (T) obj;
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.classes;
|
||||
|
||||
public abstract class ClassResolver {
|
||||
/**
|
||||
* A helper class to get the call context. It subclasses SecurityManager to make getClassContext() accessible. An instance of
|
||||
* CallerResolver only needs to be created, not installed as an actual security manager.
|
||||
*/
|
||||
private static final class CallerResolver extends SecurityManager {
|
||||
@Override
|
||||
protected Class<?>[] getClassContext() {
|
||||
return super.getClassContext();
|
||||
}
|
||||
}
|
||||
|
||||
private static final int CALL_CONTEXT_OFFSET = 3; // may need to change if this class is redesigned
|
||||
private static final CallerResolver CALLER_RESOLVER;
|
||||
|
||||
static {
|
||||
try {
|
||||
// This can fail if the current SecurityManager does not allow
|
||||
// RuntimePermission ("createSecurityManager"):
|
||||
CALLER_RESOLVER = new CallerResolver();
|
||||
} catch (SecurityException se) {
|
||||
throw new RuntimeException("ClassLoaderResolver: could not create CallerResolver: " + se);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indexes into the current method call context with a given offset.
|
||||
*/
|
||||
public static Class<?> getCallerClass(final int callerOffset) {
|
||||
return CALLER_RESOLVER.getClassContext()[CALL_CONTEXT_OFFSET + callerOffset];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,188 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012 Benjamin Diedrichsen
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.classes;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import dorkbox.collections.IdentityMap;
|
||||
|
||||
/**
|
||||
* @author bennidi
|
||||
* Date: 2/16/12
|
||||
* Time: 12:14 PM
|
||||
* @author dorkbox
|
||||
* Date: 2/2/15
|
||||
*/
|
||||
public final
|
||||
class ReflectionUtils {
|
||||
|
||||
private static final Method[] EMPTY_METHODS = new Method[0];
|
||||
|
||||
private
|
||||
ReflectionUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get methods annotated with the specified annotation.
|
||||
*
|
||||
* @param target the class that you are looking for the methods on
|
||||
* @param annotationClass the annotations that define the method you are looking for
|
||||
* @param <A> the annotation type
|
||||
*
|
||||
* @return the array of methods that match the target + annotation
|
||||
*/
|
||||
public static
|
||||
<A extends Annotation> Method[] getMethods(Class<?> target, Class<A> annotationClass) {
|
||||
ArrayList<Method> methods = new ArrayList<Method>();
|
||||
|
||||
getMethods(target, annotationClass, methods);
|
||||
return methods.toArray(EMPTY_METHODS);
|
||||
}
|
||||
|
||||
private static
|
||||
<A extends Annotation> void getMethods(Class<?> target, Class<A> annotationClass, ArrayList<Method> methods) {
|
||||
try {
|
||||
for (Method method : target.getDeclaredMethods()) {
|
||||
if (getAnnotation(method, annotationClass) != null) {
|
||||
methods.add(method);
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// recursively go until root
|
||||
if (!target.equals(Object.class)) {
|
||||
getMethods(target.getSuperclass(), annotationClass, methods);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses the class hierarchy upwards, starting at the given subclass, looking
|
||||
* for an override of the given methods -> finds the bottom most override of the given
|
||||
* method if any exists
|
||||
*/
|
||||
public static
|
||||
Method getOverridingMethod(final Method overridingMethod, final Class<?> subclass) {
|
||||
Class<?> current = subclass;
|
||||
while (!current.equals(overridingMethod.getDeclaringClass())) {
|
||||
try {
|
||||
return current.getDeclaredMethod(overridingMethod.getName(), overridingMethod.getParameterTypes());
|
||||
} catch (NoSuchMethodException e) {
|
||||
current = current.getSuperclass();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static
|
||||
boolean containsOverridingMethod(final Method[] allMethods, final Method methodToCheck) {
|
||||
final int length = allMethods.length;
|
||||
Method method;
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
method = allMethods[i];
|
||||
|
||||
if (isOverriddenBy(methodToCheck, method)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for an Annotation of the given type on the class. Supports meta annotations.
|
||||
*
|
||||
* @param from AnnotatedElement (class, method...)
|
||||
* @param annotationType Annotation class to look for.
|
||||
* @param <A> Class of annotation type
|
||||
* @return Annotation instance or null
|
||||
*/
|
||||
private static
|
||||
<A extends Annotation> A getAnnotation(AnnotatedElement from, Class<A> annotationType, IdentityMap<AnnotatedElement, Boolean> visited) {
|
||||
if (visited.containsKey(from)) {
|
||||
return null;
|
||||
}
|
||||
visited.put(from, Boolean.TRUE);
|
||||
A ann = from.getAnnotation(annotationType);
|
||||
if (ann != null) {
|
||||
return ann;
|
||||
}
|
||||
for (Annotation metaAnn : from.getAnnotations()) {
|
||||
ann = getAnnotation(metaAnn.annotationType(), annotationType, visited);
|
||||
if (ann != null) {
|
||||
return ann;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static
|
||||
<A extends Annotation> A getAnnotation(AnnotatedElement from, Class<A> annotationType) {
|
||||
return getAnnotation(from, annotationType, new IdentityMap<AnnotatedElement, Boolean>());
|
||||
}
|
||||
|
||||
|
||||
private static
|
||||
boolean isOverriddenBy(final Method superclassMethod, final Method subclassMethod) {
|
||||
// if the declaring classes are the same or the subclass method is not defined in the subclass
|
||||
// hierarchy of the given superclass method or the method names are not the same then
|
||||
// subclassMethod does not override superclassMethod
|
||||
if (superclassMethod.getDeclaringClass().equals(subclassMethod.getDeclaringClass()) ||
|
||||
!superclassMethod.getDeclaringClass().isAssignableFrom(subclassMethod.getDeclaringClass()) ||
|
||||
!superclassMethod.getName().equals(subclassMethod.getName())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Class<?>[] superClassMethodParameters = superclassMethod.getParameterTypes();
|
||||
final Class<?>[] subClassMethodParameters = subclassMethod.getParameterTypes();
|
||||
|
||||
// method must specify the same number of parameters
|
||||
//the parameters must occur in the exact same order
|
||||
for (int i = 0; i < subClassMethodParameters.length; i++) {
|
||||
if (!superClassMethodParameters[i].equals(subClassMethodParameters[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
/*
|
||||
* Copyright 2021 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.util.classes;
|
|
@ -1,801 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2006 Damien Miller <djm@mindrot.org>
|
||||
*
|
||||
* GWT modified version.
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
package dorkbox.util.crypto;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* BCrypt implements OpenBSD-style Blowfish password hashing using
|
||||
* the scheme described in "A Future-Adaptable Password Scheme" by
|
||||
* Niels Provos and David Mazieres.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* Usage is really simple. To hash a password for the first time,
|
||||
* call the hashpw method with a random salt, like this:
|
||||
* <p>
|
||||
* <code>
|
||||
* String pw_hash = BCrypt.hashpw(plain_password, BCrypt.gensalt()); <br />
|
||||
* </code>
|
||||
* <p>
|
||||
* To check whether a plaintext password matches one that has been
|
||||
* hashed previously, use the checkpw method:
|
||||
* <p>
|
||||
* <code>
|
||||
* if (BCrypt.checkpw(candidate_password, stored_hash))<br />
|
||||
* System.out.println("It matches");<br />
|
||||
* else<br />
|
||||
* System.out.println("It does not match");<br />
|
||||
* </code>
|
||||
* <p>
|
||||
* The gensalt() method takes an optional parameter (log_rounds)
|
||||
* that determines the computational complexity of the hashing:
|
||||
* <p>
|
||||
* <code>
|
||||
* String strong_salt = BCrypt.gensalt(10)<br />
|
||||
* String stronger_salt = BCrypt.gensalt(12)<br />
|
||||
* </code>
|
||||
* <p>
|
||||
* The amount of work increases exponentially (2**log_rounds), so
|
||||
* each increment is twice as much work. The default log_rounds is
|
||||
* 10, and the valid range is 4 to 31.
|
||||
*
|
||||
* @author Damien Miller
|
||||
* @version 0.2
|
||||
*/
|
||||
public class BCrypt {
|
||||
// BCrypt parameters
|
||||
private static final int GENSALT_DEFAULT_LOG2_ROUNDS = 10;
|
||||
private static final int BCRYPT_SALT_LEN = 16;
|
||||
|
||||
// Blowfish parameters
|
||||
private static final int BLOWFISH_NUM_ROUNDS = 16;
|
||||
|
||||
// Initial contents of key schedule
|
||||
private static final int P_orig[] = {
|
||||
0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
|
||||
0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
|
||||
0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
|
||||
0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
|
||||
0x9216d5d9, 0x8979fb1b
|
||||
};
|
||||
private static final int S_orig[] = {
|
||||
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"
|
||||
static private final int bf_crypt_ciphertext[] = {
|
||||
0x4f727068, 0x65616e42, 0x65686f6c,
|
||||
0x64657253, 0x63727944, 0x6f756274
|
||||
};
|
||||
|
||||
// Table for Base64 encoding
|
||||
static private final char base64_code[] = {
|
||||
'.', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
|
||||
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
|
||||
'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
|
||||
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
|
||||
'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5',
|
||||
'6', '7', '8', '9'
|
||||
};
|
||||
|
||||
// Table for Base64 decoding
|
||||
static private final byte index_64[] = {
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, 0, 1, 54, 55,
|
||||
56, 57, 58, 59, 60, 61, 62, 63, -1, -1,
|
||||
-1, -1, -1, -1, -1, 2, 3, 4, 5, 6,
|
||||
7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
|
||||
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
|
||||
-1, -1, -1, -1, -1, -1, 28, 29, 30,
|
||||
31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
|
||||
51, 52, 53, -1, -1, -1, -1, -1
|
||||
};
|
||||
|
||||
// Expanded Blowfish key
|
||||
private int P[];
|
||||
private int S[];
|
||||
|
||||
/**
|
||||
* Encode a byte array using bcrypt's slightly-modified base64
|
||||
* encoding scheme. Note that this is *not* compatible with
|
||||
* the standard MIME-base64 encoding.
|
||||
*
|
||||
* @param d the byte array to encode
|
||||
* @param len the number of bytes to encode
|
||||
* @return base64-encoded string
|
||||
* @exception IllegalArgumentException if the length is invalid
|
||||
*/
|
||||
private static String encode_base64(byte d[], int len)
|
||||
throws IllegalArgumentException {
|
||||
int off = 0;
|
||||
StringBuilder rs = new StringBuilder();
|
||||
int c1, c2;
|
||||
|
||||
if (len <= 0 || len > d.length) {
|
||||
throw new IllegalArgumentException ("Invalid len");
|
||||
}
|
||||
|
||||
while (off < len) {
|
||||
c1 = d[off++] & 0xff;
|
||||
rs.append(base64_code[c1 >> 2 & 0x3f]);
|
||||
c1 = (c1 & 0x03) << 4;
|
||||
if (off >= len) {
|
||||
rs.append(base64_code[c1 & 0x3f]);
|
||||
break;
|
||||
}
|
||||
c2 = d[off++] & 0xff;
|
||||
c1 |= c2 >> 4 & 0x0f;
|
||||
rs.append(base64_code[c1 & 0x3f]);
|
||||
c1 = (c2 & 0x0f) << 2;
|
||||
if (off >= len) {
|
||||
rs.append(base64_code[c1 & 0x3f]);
|
||||
break;
|
||||
}
|
||||
c2 = d[off++] & 0xff;
|
||||
c1 |= c2 >> 6 & 0x03;
|
||||
rs.append(base64_code[c1 & 0x3f]);
|
||||
rs.append(base64_code[c2 & 0x3f]);
|
||||
}
|
||||
return rs.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up the 3 bits base64-encoded by the specified character, range-checking againt conversion table
|
||||
*
|
||||
* @param x the base64-encoded value
|
||||
* @return the decoded value of x
|
||||
*/
|
||||
private static byte char64(char x) {
|
||||
if (x < 0 || x > index_64.length) {
|
||||
return -1;
|
||||
}
|
||||
return index_64[x];
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a string encoded using bcrypt's base64 scheme to a byte array. Note that this is *not* compatible with
|
||||
* the standard MIME-base64 encoding.
|
||||
*
|
||||
* @param s the string to decode
|
||||
* @param maxolen the maximum number of bytes to decode
|
||||
* @return an array containing the decoded bytes
|
||||
* @throws IllegalArgumentException if maxolen is invalid
|
||||
*/
|
||||
private static byte[] decode_base64(String s, int maxolen)
|
||||
throws IllegalArgumentException {
|
||||
StringBuilder rs = new StringBuilder();
|
||||
int off = 0, slen = s.length(), olen = 0;
|
||||
byte ret[];
|
||||
byte c1, c2, c3, c4, o;
|
||||
|
||||
if (maxolen <= 0) {
|
||||
throw new IllegalArgumentException ("Invalid maxolen");
|
||||
}
|
||||
|
||||
while (off < slen - 1 && olen < maxolen) {
|
||||
c1 = char64(s.charAt(off++));
|
||||
c2 = char64(s.charAt(off++));
|
||||
if (c1 == -1 || c2 == -1) {
|
||||
break;
|
||||
}
|
||||
o = (byte)(c1 << 2);
|
||||
o |= (c2 & 0x30) >> 4;
|
||||
rs.append((char)o);
|
||||
if (++olen >= maxolen || off >= slen) {
|
||||
break;
|
||||
}
|
||||
c3 = char64(s.charAt(off++));
|
||||
if (c3 == -1) {
|
||||
break;
|
||||
}
|
||||
o = (byte)((c2 & 0x0f) << 4);
|
||||
o |= (c3 & 0x3c) >> 2;
|
||||
rs.append((char)o);
|
||||
if (++olen >= maxolen || off >= slen) {
|
||||
break;
|
||||
}
|
||||
c4 = char64(s.charAt(off++));
|
||||
o = (byte)((c3 & 0x03) << 6);
|
||||
o |= c4;
|
||||
rs.append((char)o);
|
||||
++olen;
|
||||
}
|
||||
|
||||
ret = new byte[olen];
|
||||
for (off = 0; off < olen; off++) {
|
||||
ret[off] = (byte)rs.charAt(off);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Blowfish encipher a single 64-bit block encoded as two 32-bit halves
|
||||
*
|
||||
* @param lr an array containing the two 32-bit half blocks
|
||||
* @param off the position in the array of the blocks
|
||||
*/
|
||||
private
|
||||
void encipher(int lr[], int off) {
|
||||
int i, n, l = lr[off], r = lr[off + 1];
|
||||
|
||||
l ^= P[0];
|
||||
for (i = 0; i <= BLOWFISH_NUM_ROUNDS - 2;) {
|
||||
// Feistel substitution on left word
|
||||
n = S[l >> 24 & 0xff];
|
||||
n += S[0x100 | l >> 16 & 0xff];
|
||||
n ^= S[0x200 | l >> 8 & 0xff];
|
||||
n += S[0x300 | l & 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];
|
||||
l ^= n ^ P[++i];
|
||||
}
|
||||
lr[off] = r ^ P[BLOWFISH_NUM_ROUNDS + 1];
|
||||
lr[off + 1] = l;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 i;
|
||||
int word = 0;
|
||||
int off = offp[0];
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
word = word << 8 | data[off] & 0xff;
|
||||
off = (off + 1) % data.length;
|
||||
}
|
||||
|
||||
offp[0] = off;
|
||||
return word;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the Blowfish key schedule
|
||||
*/
|
||||
private void init_key() {
|
||||
P = Arrays.copyOf(P_orig, P_orig.length);
|
||||
S = Arrays.copyOf(S_orig, S_orig.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Key the Blowfish cipher
|
||||
* @param key an array containing the key
|
||||
*/
|
||||
private void key(byte key[]) {
|
||||
int i;
|
||||
int koffp[] = { 0 };
|
||||
int lr[] = { 0, 0 };
|
||||
int 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(lr, 0);
|
||||
P[i] = lr[0];
|
||||
P[i + 1] = lr[1];
|
||||
}
|
||||
|
||||
for (i = 0; i < slen; i += 2) {
|
||||
encipher(lr, 0);
|
||||
S[i] = lr[0];
|
||||
S[i + 1] = lr[1];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 data salt information
|
||||
* @param key password information
|
||||
*/
|
||||
private void ekskey(byte data[], byte key[]) {
|
||||
int i;
|
||||
int koffp[] = { 0 }, doffp[] = { 0 };
|
||||
int lr[] = { 0, 0 };
|
||||
int 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(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(lr, 0);
|
||||
S[i] = lr[0];
|
||||
S[i + 1] = lr[1];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the central password hashing step in the bcrypt scheme
|
||||
*
|
||||
* @param password the password to hash
|
||||
* @param salt the binary salt to hash with the password
|
||||
* @param log_rounds the binary logarithm of the number of rounds of hashing to apply
|
||||
* @return an array containing the binary hashed password
|
||||
*/
|
||||
private byte[] crypt_raw(byte password[], byte salt[], int log_rounds, int cdata[]) {
|
||||
int rounds, i, j;
|
||||
int clen = cdata.length;
|
||||
byte ret[];
|
||||
|
||||
if (log_rounds < 4 || log_rounds > 30) {
|
||||
throw new IllegalArgumentException ("Bad number of rounds");
|
||||
}
|
||||
rounds = 1 << log_rounds;
|
||||
if (salt.length != BCRYPT_SALT_LEN) {
|
||||
throw new IllegalArgumentException ("Bad salt length");
|
||||
}
|
||||
|
||||
init_key();
|
||||
ekskey(salt, password);
|
||||
for (i = 0; i != rounds; i++) {
|
||||
key(password);
|
||||
key(salt);
|
||||
}
|
||||
|
||||
for (i = 0; i < 64; i++) {
|
||||
for (j = 0; j < clen >> 1; j++) {
|
||||
encipher(cdata, j << 1);
|
||||
}
|
||||
}
|
||||
|
||||
ret = new byte[clen * 4];
|
||||
for (i = 0, j = 0; i < clen; i++) {
|
||||
ret[j++] = (byte)(cdata[i] >> 24 & 0xff);
|
||||
ret[j++] = (byte)(cdata[i] >> 16 & 0xff);
|
||||
ret[j++] = (byte)(cdata[i] >> 8 & 0xff);
|
||||
ret[j++] = (byte)(cdata[i] & 0xff);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash a password using the OpenBSD bcrypt scheme
|
||||
*
|
||||
* @param password the password to hash
|
||||
* @return the hashed password
|
||||
*/
|
||||
public static String hashpw(String password) {
|
||||
return hashpw(password, BCrypt.gensalt());
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash a password using the OpenBSD bcrypt scheme
|
||||
*
|
||||
* @param password the password to hash
|
||||
* @param salt the salt to hash with (perhaps generated using BCrypt.gensalt)
|
||||
* @return the hashed password
|
||||
*/
|
||||
public static String hashpw(String password, String salt) {
|
||||
BCrypt B;
|
||||
String real_salt;
|
||||
byte passwordb[], saltb[], hashed[];
|
||||
char minor = (char)0;
|
||||
int rounds, off = 0;
|
||||
StringBuilder rs = new StringBuilder();
|
||||
|
||||
if (salt.charAt(0) != '$' || salt.charAt(1) != '2') {
|
||||
throw new IllegalArgumentException ("Invalid salt version");
|
||||
}
|
||||
if (salt.charAt(2) == '$') {
|
||||
off = 3;
|
||||
} else {
|
||||
minor = salt.charAt(2);
|
||||
if (minor != 'a' || salt.charAt(3) != '$') {
|
||||
throw new IllegalArgumentException ("Invalid salt revision");
|
||||
}
|
||||
off = 4;
|
||||
}
|
||||
|
||||
// Extract number of rounds
|
||||
if (salt.charAt(off + 2) > '$') {
|
||||
throw new IllegalArgumentException ("Missing salt rounds");
|
||||
}
|
||||
rounds = Integer.parseInt(salt.substring(off, off + 2));
|
||||
|
||||
real_salt = salt.substring(off + 3, off + 25);
|
||||
try {
|
||||
passwordb = (password + (minor >= 'a' ? "\000" : "")).getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
throw new AssertionError("UTF-8 is not supported");
|
||||
}
|
||||
|
||||
saltb = decode_base64(real_salt, BCRYPT_SALT_LEN);
|
||||
|
||||
B = new BCrypt();
|
||||
hashed = B.crypt_raw(passwordb, saltb, rounds, Arrays.copyOf(bf_crypt_ciphertext, bf_crypt_ciphertext.length));
|
||||
|
||||
rs.append("$2");
|
||||
if (minor >= 'a') {
|
||||
rs.append(minor);
|
||||
}
|
||||
rs.append("$");
|
||||
if (rounds < 10) {
|
||||
rs.append("0");
|
||||
}
|
||||
if (rounds > 30) {
|
||||
throw new IllegalArgumentException("rounds exceeds maximum (30)");
|
||||
}
|
||||
|
||||
rs.append(rounds);
|
||||
rs.append("$");
|
||||
rs.append(encode_base64(saltb, saltb.length));
|
||||
rs.append(encode_base64(hashed, bf_crypt_ciphertext.length * 4 - 1));
|
||||
return rs.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a salt for use with the BCrypt.hashpw() method
|
||||
* @param log_rounds the log2 of the number of rounds of
|
||||
* hashing to apply - the work factor therefore increases as
|
||||
* 2**log_rounds.
|
||||
* @param random an instance of SecureRandom to use
|
||||
* @return an encoded salt value
|
||||
*/
|
||||
public static String gensalt(int log_rounds, SecureRandom random) {
|
||||
StringBuilder rs = new StringBuilder();
|
||||
byte rnd[] = new byte[BCRYPT_SALT_LEN];
|
||||
|
||||
random.nextBytes(rnd);
|
||||
|
||||
rs.append("$2a$");
|
||||
if (log_rounds < 10) {
|
||||
rs.append("0");
|
||||
}
|
||||
rs.append(String.valueOf(log_rounds));
|
||||
rs.append("$");
|
||||
rs.append(encode_base64(rnd, rnd.length));
|
||||
return rs.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a salt for use with the BCrypt.hashpw() method
|
||||
* @param log_rounds the log2 of the number of rounds of
|
||||
* hashing to apply - the work factor therefore increases as
|
||||
* 2**log_rounds.
|
||||
* @return an encoded salt value
|
||||
*/
|
||||
public static String gensalt(int log_rounds) {
|
||||
return gensalt(log_rounds, new SecureRandom());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a salt for use with the BCrypt.hashpw() method,
|
||||
* selecting a reasonable default for the number of hashing
|
||||
* rounds to apply
|
||||
* @return an encoded salt value
|
||||
*/
|
||||
public static String gensalt() {
|
||||
return gensalt(GENSALT_DEFAULT_LOG2_ROUNDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that a plaintext password matches a previously hashed
|
||||
* one
|
||||
* @param plaintext the plaintext password to verify
|
||||
* @param hashed the previously-hashed password
|
||||
* @return true if the passwords match, false otherwise
|
||||
*/
|
||||
public static boolean checkpw(String plaintext, String hashed) {
|
||||
byte hashed_bytes[];
|
||||
byte try_bytes[];
|
||||
try {
|
||||
String try_pw = hashpw(plaintext, hashed);
|
||||
hashed_bytes = hashed.getBytes("UTF-8");
|
||||
try_bytes = try_pw.getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
return false;
|
||||
}
|
||||
if (hashed_bytes.length != try_bytes.length)
|
||||
return false;
|
||||
byte ret = 0;
|
||||
for (int i = 0; i < try_bytes.length; i++)
|
||||
ret |= hashed_bytes[i] ^ try_bytes[i];
|
||||
|
||||
return ret == 0;
|
||||
}
|
||||
}
|
|
@ -1,485 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Provider;
|
||||
import java.security.Security;
|
||||
import java.util.Arrays;
|
||||
import java.util.Enumeration;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
import org.bouncycastle.crypto.Digest;
|
||||
import org.bouncycastle.crypto.PBEParametersGenerator;
|
||||
import org.bouncycastle.crypto.digests.MD5Digest;
|
||||
import org.bouncycastle.crypto.digests.SHA1Digest;
|
||||
import org.bouncycastle.crypto.digests.SHA256Digest;
|
||||
import org.bouncycastle.crypto.digests.SHA512Digest;
|
||||
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.lwjgl.util.xxhash.XXH32State;
|
||||
import org.lwjgl.util.xxhash.XXHash;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* http://en.wikipedia.org/wiki/NSA_Suite_B http://www.nsa.gov/ia/programs/suiteb_cryptography/
|
||||
* <p/>
|
||||
* NSA Suite B
|
||||
* <p/>
|
||||
* TOP-SECRET LEVEL AES256/GCM ECC with 384-bit prime curve (FIPS PUB 186-3), and SHA-384
|
||||
* <p/>
|
||||
* SECRET LEVEL AES 128 ECDH and ECDSA using the 256-bit prime (FIPS PUB 186-3), and SHA-256. RSA with 2048 can be used for DH key
|
||||
* negotiation
|
||||
* <p/>
|
||||
* WARNING! Note that this call is INCOMPATIBLE with GWT, so we have EXCLUDED IT from gwt, and created a CryptoGwt class in the web-client
|
||||
* project which only has the necessary crypto utility methods that are 1) Necessary 2) Compatible with GWT
|
||||
* <p/>
|
||||
* <p/>
|
||||
* To determine if we have hardware accelerated AES java -XX:+PrintFlagsFinal -version | grep UseAES
|
||||
*
|
||||
* Per NIST SP800-38D,
|
||||
* The total number of invocations of the authenticated encryption function shall not exceed 232, including all IV lengths and all instances of the authenticated encryption function with the given key.
|
||||
*
|
||||
*/
|
||||
public final
|
||||
class Crypto {
|
||||
|
||||
private
|
||||
Crypto() {
|
||||
}
|
||||
|
||||
// CUSTOM_HEADER USE
|
||||
// check to see if our extra data is OURS. if so, process it
|
||||
// cafeʞ, as UN signed bytes is: [254, 202, 202, 158], or as hex: FECA CA9E
|
||||
// cafeʞ, as signed bytes is: [-2, -54, -54, -98]
|
||||
private static final byte[] CUSTOM_HEADER = new byte[] {(byte) -2, (byte) -54, (byte) -54, (byte) -98};
|
||||
|
||||
public static
|
||||
void addProvider() {
|
||||
// make sure we only add it once (in case it's added elsewhere...)
|
||||
Provider provider = Security.getProvider(BouncyCastleProvider.PROVIDER_NAME);
|
||||
if (provider == null) {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if cryptography restrictions apply.
|
||||
* Restrictions apply if the value of {@link Cipher#getMaxAllowedKeyLength(String)} returns a value smaller than {@link Integer#MAX_VALUE} if there are any restrictions according to the JavaDoc of the method.
|
||||
* This method is used with the transform <code>"AES/CBC/PKCS5Padding"</code> as this is an often used algorithm that is <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#impl">an implementation requirement for Java SE</a>.
|
||||
*
|
||||
* @return <code>true</code> if restrictions apply, <code>false</code> otherwise
|
||||
*/
|
||||
public static boolean restrictedCryptography() {
|
||||
try {
|
||||
return Cipher.getMaxAllowedKeyLength("AES/CBC/PKCS5Padding") < Integer.MAX_VALUE;
|
||||
} catch (final NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException("The transform \"AES/CBC/PKCS5Padding\" is not available (the availability of this algorithm is mandatory for Java SE implementations)", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static
|
||||
byte[] hashFileMD5(File file) {
|
||||
MD5Digest digest = new MD5Digest();
|
||||
return hashFile(file, digest, null);
|
||||
}
|
||||
|
||||
public static
|
||||
byte[] hashFileSHA1(File file) {
|
||||
SHA1Digest digest = new SHA1Digest();
|
||||
return hashFile(file, digest, null);
|
||||
}
|
||||
|
||||
public static
|
||||
byte[] hashFileSHA256(File file) {
|
||||
SHA256Digest digest = new SHA256Digest();
|
||||
return hashFile(file, digest, null);
|
||||
}
|
||||
|
||||
public static
|
||||
byte[] hashFileSHA512(File file) {
|
||||
SHA512Digest digest = new SHA512Digest();
|
||||
return hashFile(file, digest, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the hash of the file or NULL if file is invalid
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*/
|
||||
public static
|
||||
byte[] hashFile(File file, Digest digest, Logger logger) {
|
||||
return hashFile(file, digest, 0L, file.length(), logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the hash of the file or NULL if file is invalid
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*/
|
||||
public static
|
||||
byte[] hashFile(File file, Digest digest, long startPosition, long endPosition, Logger logger) {
|
||||
if (file.isFile() && file.canRead()) {
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
inputStream = new FileInputStream(file);
|
||||
long skip = inputStream.skip(startPosition);
|
||||
if (skip != startPosition) {
|
||||
throw new RuntimeException("Unable to skip " + startPosition + " bytes. Only skippped " + skip + " instead");
|
||||
}
|
||||
|
||||
long size = file.length() - startPosition;
|
||||
long lengthFromEnd = size - endPosition;
|
||||
|
||||
if (lengthFromEnd > 0 && lengthFromEnd < size) {
|
||||
size -= lengthFromEnd;
|
||||
}
|
||||
|
||||
int bufferSize = 4096;
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
|
||||
int readBytes;
|
||||
digest.reset();
|
||||
|
||||
while (size > 0) {
|
||||
//noinspection NumericCastThatLosesPrecision
|
||||
int maxToRead = (int) Math.min(bufferSize, size);
|
||||
readBytes = inputStream.read(buffer, 0, maxToRead);
|
||||
size -= readBytes;
|
||||
|
||||
if (readBytes == 0) {
|
||||
//wtf. finally still gets called.
|
||||
return null;
|
||||
}
|
||||
|
||||
digest.update(buffer, 0, readBytes);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Error hashing file: {}", file.getAbsolutePath(), e);
|
||||
} else {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} finally {
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
byte[] digestBytes = new byte[digest.getDigestSize()];
|
||||
|
||||
digest.doFinal(digestBytes, 0);
|
||||
return digestBytes;
|
||||
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the xxhash of the file as or 0 if file is invalid
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*/
|
||||
public static
|
||||
int xxHashFile(File file, long lengthFromEnd, Logger logger) {
|
||||
if (file.isFile() && file.canRead()) {
|
||||
InputStream inputStream = null;
|
||||
|
||||
// used to initialize the hash value, use whatever value you want, but always the same
|
||||
int seed = 0x9747b28c; // must match number in C (in Auth::xxHash32())
|
||||
|
||||
|
||||
XXH32State state = XXHash.XXH32_createState();
|
||||
XXHash.XXH32_reset(state, seed);
|
||||
|
||||
try {
|
||||
inputStream = new FileInputStream(file);
|
||||
long size = file.length();
|
||||
|
||||
if (lengthFromEnd > 0 && lengthFromEnd < size) {
|
||||
size -= lengthFromEnd;
|
||||
}
|
||||
|
||||
int bufferSize = 4096;
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
ByteBuffer bbuffer = ByteBuffer.wrap(buffer);
|
||||
|
||||
|
||||
int readBytes;
|
||||
|
||||
while (size > 0) {
|
||||
//noinspection NumericCastThatLosesPrecision
|
||||
int maxToRead = (int) Math.min(bufferSize, size);
|
||||
readBytes = inputStream.read(buffer, 0, maxToRead);
|
||||
size -= readBytes;
|
||||
|
||||
if (readBytes == 0) {
|
||||
//wtf. finally still gets called.
|
||||
return 0;
|
||||
}
|
||||
|
||||
bbuffer.limit(readBytes);
|
||||
XXHash.XXH32_update(state, bbuffer);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Error hashing file: {}", file.getAbsolutePath(), e);
|
||||
} else {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} finally {
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return XXHash.XXH32_digest(state);
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
int toInt(final byte[] bytes) {
|
||||
int number = 0;
|
||||
|
||||
switch (bytes.length) {
|
||||
default:
|
||||
case 4:
|
||||
number |= (bytes[3] & 0xFF) << 24;
|
||||
case 3:
|
||||
number |= (bytes[2] & 0xFF) << 16;
|
||||
case 2:
|
||||
number |= (bytes[1] & 0xFF) << 8;
|
||||
case 1:
|
||||
number |= (bytes[0] & 0xFF) << 0;
|
||||
}
|
||||
|
||||
return number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifically, to return the hash of the ALL files/directories inside the jar, minus the action specified (LGPL) files.
|
||||
*/
|
||||
public static
|
||||
byte[] hashJarContentsExcludeAction(File jarDestFilename, Digest digest, int action) throws IOException {
|
||||
JarFile jarDestFile = new JarFile(jarDestFilename);
|
||||
|
||||
try {
|
||||
Enumeration<JarEntry> jarElements = jarDestFile.entries();
|
||||
|
||||
boolean okToHash;
|
||||
boolean hasAction;
|
||||
byte[] buffer = new byte[2048];
|
||||
int read;
|
||||
digest.reset();
|
||||
|
||||
while (jarElements.hasMoreElements()) {
|
||||
JarEntry jarEntry = jarElements.nextElement();
|
||||
String name = jarEntry.getName();
|
||||
okToHash = !jarEntry.isDirectory();
|
||||
|
||||
if (!okToHash) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// data with NO extra data will NOT BE HASHED
|
||||
// data that matches our action bitmask WILL NOT BE HASHED
|
||||
|
||||
okToHash = false;
|
||||
hasAction = false;
|
||||
|
||||
byte[] extraData = jarEntry.getExtra();
|
||||
if (extraData == null || extraData.length == 0) {
|
||||
okToHash = false;
|
||||
}
|
||||
else if (extraData.length >= 4) {
|
||||
for (int i = 0; i < CUSTOM_HEADER.length; i++) {
|
||||
if (extraData[i] != CUSTOM_HEADER[i]) {
|
||||
throw new RuntimeException("Unexpected extra data in zip assigned. Aborting");
|
||||
}
|
||||
}
|
||||
|
||||
// this means we matched our header
|
||||
if (extraData[4] > 0) {
|
||||
hasAction = true;
|
||||
|
||||
// we have an ACTION describing how it was compressed, etc
|
||||
int fileAction = toInt(new byte[] {extraData[5], extraData[6], extraData[7], extraData[8]});
|
||||
|
||||
if ((fileAction & action) != action) {
|
||||
okToHash = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
okToHash = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new RuntimeException("Unexpected extra data in zip assigned. Aborting");
|
||||
}
|
||||
|
||||
// skips hashing lgpl files. (technically, whatever our action bitmask is...)
|
||||
// we want to hash everything BY DEFAULT. we ALSO want to hash the NAME, LOAD ACTION TYPE, and the contents
|
||||
if (okToHash) {
|
||||
// System.err.println("HASHING: " + name);
|
||||
// hash the file name
|
||||
byte[] bytes = name.getBytes(StandardCharsets.US_ASCII);
|
||||
digest.update(bytes, 0, bytes.length);
|
||||
|
||||
if (hasAction) {
|
||||
// hash the action - since we don't want to permit anyone to change this after we sign the file
|
||||
digest.update(extraData, 5, 4);
|
||||
}
|
||||
|
||||
// hash the contents
|
||||
InputStream inputStream = jarDestFile.getInputStream(jarEntry);
|
||||
while ((read = inputStream.read(buffer)) > 0) {
|
||||
digest.update(buffer, 0, read);
|
||||
}
|
||||
inputStream.close();
|
||||
}
|
||||
//else {
|
||||
// System.err.println("Skipping: " + name);
|
||||
//}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unexpected extra data in zip assigned. Aborting");
|
||||
} finally {
|
||||
jarDestFile.close();
|
||||
}
|
||||
|
||||
byte[] digestBytes = new byte[digest.getDigestSize()];
|
||||
|
||||
digest.doFinal(digestBytes, 0);
|
||||
return digestBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash an input stream, based on the specified digest
|
||||
*/
|
||||
public static
|
||||
byte[] hashStream(Digest digest, InputStream inputStream) throws IOException {
|
||||
|
||||
byte[] buffer = new byte[2048];
|
||||
int read;
|
||||
digest.reset();
|
||||
|
||||
|
||||
while ((read = inputStream.read(buffer)) > 0) {
|
||||
digest.update(buffer, 0, read);
|
||||
}
|
||||
inputStream.close();
|
||||
|
||||
byte[] digestBytes = new byte[digest.getDigestSize()];
|
||||
|
||||
digest.doFinal(digestBytes, 0);
|
||||
return digestBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Secure way to generate an AES key based on a password. Will '*' out the passed-in password
|
||||
*
|
||||
* @param password
|
||||
* will be filled with '*'
|
||||
* @param salt
|
||||
* should be a RANDOM number, at least 256bits (32 bytes) in size.
|
||||
* @param iterationCount
|
||||
* should be a lot, like 10,000
|
||||
*
|
||||
* @return the secure key to use
|
||||
*/
|
||||
public static
|
||||
byte[] PBKDF2(char[] password, byte[] salt, int iterationCount) {
|
||||
// will also zero out the password.
|
||||
byte[] charToBytes = Crypto.charToBytesPassword_UTF16(password);
|
||||
|
||||
return PBKDF2(charToBytes, salt, iterationCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Secure way to generate an AES key based on a password.
|
||||
*
|
||||
* @param password
|
||||
* The password that you want to mix
|
||||
* @param salt
|
||||
* should be a RANDOM number, at least 256bits (32 bytes) in size.
|
||||
* @param iterationCount
|
||||
* should be a lot, like 10,000
|
||||
*
|
||||
* @return the secure key to use
|
||||
*/
|
||||
public static
|
||||
byte[] PBKDF2(byte[] password, byte[] salt, int iterationCount) {
|
||||
SHA256Digest digest = new SHA256Digest();
|
||||
PBEParametersGenerator pGen = new PKCS5S2ParametersGenerator(digest);
|
||||
pGen.init(password, salt, iterationCount);
|
||||
|
||||
KeyParameter key = (KeyParameter) pGen.generateDerivedMacParameters(digest.getDigestSize() * 8); // *8 for bit length.
|
||||
|
||||
// zero out the password.
|
||||
Arrays.fill(password, (byte) 0);
|
||||
|
||||
return key.getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* this saves the char array in UTF-16 format of bytes and BLANKS out the password char array.
|
||||
*/
|
||||
public static
|
||||
byte[] charToBytesPassword_UTF16(char[] password) {
|
||||
// note: this saves the char array in UTF-16 format of bytes.
|
||||
byte[] passwordBytes = new byte[password.length * 2];
|
||||
for (int i = 0; i < password.length; i++) {
|
||||
//noinspection NumericCastThatLosesPrecision
|
||||
passwordBytes[2 * i] = (byte) (((int) password[i] & 0xFF00) >> 8);
|
||||
//noinspection NumericCastThatLosesPrecision
|
||||
passwordBytes[2 * i + 1] = (byte) ((int) password[i] & 0x00FF);
|
||||
}
|
||||
|
||||
// asterisk out the password
|
||||
Arrays.fill(password, '*');
|
||||
|
||||
return passwordBytes;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,625 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto;
|
||||
|
||||
import org.bouncycastle.crypto.BufferedBlockCipher;
|
||||
import org.bouncycastle.crypto.CipherParameters;
|
||||
import org.bouncycastle.crypto.modes.GCMBlockCipher;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
import org.bouncycastle.crypto.params.ParametersWithIV;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* AES crypto functions
|
||||
*/
|
||||
@SuppressWarnings({"Duplicates"})
|
||||
public final
|
||||
class CryptoAES {
|
||||
private static final int ivSize = 16;
|
||||
|
||||
/**
|
||||
* AES encrypts data with a specified key.
|
||||
*
|
||||
* @param aesIV
|
||||
* must be a nonce (unique value) !!
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return empty byte[] if error
|
||||
*/
|
||||
public static
|
||||
byte[] encryptWithIV(GCMBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, byte[] data, Logger logger) {
|
||||
byte[] encryptAES = encrypt(aesEngine, aesKey, aesIV, data, logger);
|
||||
|
||||
int length = encryptAES.length;
|
||||
|
||||
byte[] out = new byte[length + ivSize];
|
||||
System.arraycopy(aesIV, 0, out, 0, ivSize);
|
||||
System.arraycopy(encryptAES, 0, out, ivSize, length);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO</b>
|
||||
* <p>
|
||||
* Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted
|
||||
* data.
|
||||
* <p>
|
||||
* AES encrypts data with a specified key.
|
||||
*
|
||||
* @param aesIV
|
||||
* must be a nonce (unique value) !!
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return empty byte[] if error
|
||||
*/
|
||||
@Deprecated
|
||||
public static
|
||||
byte[] encryptWithIV(BufferedBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, byte[] data, Logger logger) {
|
||||
|
||||
byte[] encryptAES = encrypt(aesEngine, aesKey, aesIV, data, logger);
|
||||
|
||||
int length = encryptAES.length;
|
||||
|
||||
byte[] out = new byte[length + ivSize];
|
||||
System.arraycopy(aesIV, 0, out, 0, ivSize);
|
||||
System.arraycopy(encryptAES, 0, out, ivSize, length);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* AES encrypts data with a specified key.
|
||||
*
|
||||
* @param aesIV
|
||||
* must be a nonce (unique value) !!
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
public static
|
||||
boolean encryptStreamWithIV(GCMBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, InputStream in, OutputStream out, Logger logger) {
|
||||
|
||||
try {
|
||||
out.write(aesIV);
|
||||
} catch (IOException e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform AES cipher.", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return encryptStream(aesEngine, aesKey, aesIV, in, out, logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO</b>
|
||||
* <p>
|
||||
* Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted
|
||||
* data.
|
||||
* <p>
|
||||
* AES encrypts data with a specified key.
|
||||
*
|
||||
* @param aesIV
|
||||
* must be a nonce (unique value) !!
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
@Deprecated
|
||||
public static
|
||||
boolean encryptStreamWithIV(BufferedBlockCipher aesEngine,
|
||||
byte[] aesKey,
|
||||
byte[] aesIV,
|
||||
InputStream in,
|
||||
OutputStream out,
|
||||
Logger logger) {
|
||||
|
||||
try {
|
||||
out.write(aesIV);
|
||||
} catch (IOException e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform AES cipher.", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return encryptStream(aesEngine, aesKey, aesIV, in, out, logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* AES encrypts data with a specified key.
|
||||
*
|
||||
* @param aesIV
|
||||
* must be a nonce (unique value) !!
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return empty byte[] if error
|
||||
*/
|
||||
public static
|
||||
byte[] encrypt(GCMBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, byte[] data, Logger logger) {
|
||||
int length = data.length;
|
||||
|
||||
CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
|
||||
return encrypt(aesEngine, aesIVAndKey, data, length, logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* AES encrypts data with a specified key.
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return length of encrypted data, -1 if there was an error.
|
||||
*/
|
||||
public static
|
||||
byte[] encrypt(GCMBlockCipher aesEngine, CipherParameters aesIVAndKey, byte[] data, int length, Logger logger) {
|
||||
|
||||
aesEngine.reset();
|
||||
aesEngine.init(true, aesIVAndKey);
|
||||
|
||||
int minSize = aesEngine.getOutputSize(length);
|
||||
byte[] outArray = new byte[minSize];
|
||||
|
||||
int actualLength = aesEngine.processBytes(data, 0, length, outArray, 0);
|
||||
|
||||
try {
|
||||
actualLength += aesEngine.doFinal(outArray, actualLength);
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform AES cipher.", e);
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
if (outArray.length == actualLength) {
|
||||
return outArray;
|
||||
}
|
||||
else {
|
||||
byte[] result = new byte[actualLength];
|
||||
System.arraycopy(outArray, 0, result, 0, result.length);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO</b>
|
||||
* <p>
|
||||
* Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted
|
||||
* data.
|
||||
* <p>
|
||||
* AES encrypts data with a specified key.
|
||||
*
|
||||
* @param aesIV
|
||||
* must be a nonce (unique value) !!
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return empty byte[] if error
|
||||
*/
|
||||
@Deprecated
|
||||
public static
|
||||
byte[] encrypt(BufferedBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, byte[] data, Logger logger) {
|
||||
int length = data.length;
|
||||
|
||||
CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
|
||||
aesEngine.reset();
|
||||
aesEngine.init(true, aesIVAndKey);
|
||||
|
||||
int minSize = aesEngine.getOutputSize(length);
|
||||
byte[] outBuf = new byte[minSize];
|
||||
|
||||
int actualLength = aesEngine.processBytes(data, 0, length, outBuf, 0);
|
||||
|
||||
try {
|
||||
actualLength += aesEngine.doFinal(outBuf, actualLength);
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform AES cipher.", e);
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
if (outBuf.length == actualLength) {
|
||||
return outBuf;
|
||||
}
|
||||
else {
|
||||
byte[] result = new byte[actualLength];
|
||||
System.arraycopy(outBuf, 0, result, 0, result.length);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO</b>
|
||||
* <p>
|
||||
* Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted
|
||||
* data.
|
||||
* <p>
|
||||
* AES encrypt from one stream to another.
|
||||
*
|
||||
* @param aesIV
|
||||
* must be a nonce (unique value) !!
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
@Deprecated
|
||||
public static
|
||||
boolean encryptStream(BufferedBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, InputStream in, OutputStream out, Logger logger) {
|
||||
byte[] buf = new byte[ivSize];
|
||||
byte[] outbuf = new byte[512];
|
||||
|
||||
CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
|
||||
aesEngine.reset();
|
||||
aesEngine.init(true, aesIVAndKey);
|
||||
|
||||
try {
|
||||
int bytesRead;
|
||||
int bytesProcessed;
|
||||
|
||||
while ((bytesRead = in.read(buf)) >= 0) {
|
||||
bytesProcessed = aesEngine.processBytes(buf, 0, bytesRead, outbuf, 0);
|
||||
out.write(outbuf, 0, bytesProcessed);
|
||||
}
|
||||
|
||||
bytesProcessed = aesEngine.doFinal(outbuf, 0);
|
||||
|
||||
out.write(outbuf, 0, bytesProcessed);
|
||||
out.flush();
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform AES cipher.", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* AES encrypt from one stream to another.
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
* @param aesIV
|
||||
* must be a nonce (unique value) !!
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
public static
|
||||
boolean encryptStream(GCMBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, InputStream in, OutputStream out, Logger logger) {
|
||||
|
||||
byte[] buf = new byte[ivSize];
|
||||
byte[] outbuf = new byte[512];
|
||||
|
||||
CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
|
||||
aesEngine.reset();
|
||||
aesEngine.init(true, aesIVAndKey);
|
||||
|
||||
try {
|
||||
int bytesRead;
|
||||
int bytesProcessed;
|
||||
|
||||
while ((bytesRead = in.read(buf)) >= 0) {
|
||||
bytesProcessed = aesEngine.processBytes(buf, 0, bytesRead, outbuf, 0);
|
||||
out.write(outbuf, 0, bytesProcessed);
|
||||
}
|
||||
|
||||
bytesProcessed = aesEngine.doFinal(outbuf, 0);
|
||||
|
||||
out.write(outbuf, 0, bytesProcessed);
|
||||
out.flush();
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform AES cipher.", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* AES decrypt (if the aes IV is included in the data). IV must be a nonce (unique value) !!
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return empty byte[] if error
|
||||
*/
|
||||
public static
|
||||
byte[] decryptWithIV(GCMBlockCipher aesEngine, byte[] aesKey, byte[] data, Logger logger) {
|
||||
byte[] aesIV = new byte[ivSize];
|
||||
System.arraycopy(data, 0, aesIV, 0, ivSize);
|
||||
|
||||
byte[] in = new byte[data.length - ivSize];
|
||||
System.arraycopy(data, ivSize, in, 0, in.length);
|
||||
|
||||
return decrypt(aesEngine, aesKey, aesIV, in, logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO</b>
|
||||
* <p>
|
||||
* Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted
|
||||
* data.
|
||||
* <p>
|
||||
* AES decrypt (if the aes IV is included in the data). IV must be a nonce (unique value)
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return empty byte[] if error
|
||||
*/
|
||||
@Deprecated
|
||||
public static
|
||||
byte[] decryptWithIV(BufferedBlockCipher aesEngine, byte[] aesKey, byte[] data, Logger logger) {
|
||||
byte[] aesIV = new byte[ivSize];
|
||||
System.arraycopy(data, 0, aesIV, 0, ivSize);
|
||||
|
||||
byte[] in = new byte[data.length - ivSize];
|
||||
System.arraycopy(data, ivSize, in, 0, in.length);
|
||||
|
||||
return decrypt(aesEngine, aesKey, aesIV, in, logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* AES decrypt (if the aes IV is included in the data. IV must be a nonce (unique value)
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
public static
|
||||
boolean decryptStreamWithIV(GCMBlockCipher aesEngine, byte[] aesKey, InputStream in, OutputStream out, Logger logger) {
|
||||
byte[] aesIV = new byte[ivSize];
|
||||
try {
|
||||
in.read(aesIV, 0, ivSize);
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform AES cipher.", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return decryptStream(aesEngine, aesKey, aesIV, in, out, logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO</b>
|
||||
* <p>
|
||||
* Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted
|
||||
* data.
|
||||
* <p>
|
||||
* AES decrypt (if the aes IV is included in the data). IV must be a nonce (unique value)
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
@Deprecated
|
||||
public static
|
||||
boolean decryptStreamWithIV(BufferedBlockCipher aesEngine, byte[] aesKey, InputStream in, OutputStream out, Logger logger) {
|
||||
byte[] aesIV = new byte[ivSize];
|
||||
try {
|
||||
in.read(aesIV, 0, ivSize);
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform AES cipher.", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return decryptStream(aesEngine, aesKey, aesIV, in, out, logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* AES decrypt (if we already know the aes IV -- and it's NOT included in the data)
|
||||
*
|
||||
* @param aesIV
|
||||
* must be a nonce (unique value) !!
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return empty byte[] if error
|
||||
*/
|
||||
public static
|
||||
byte[] decrypt(GCMBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, byte[] data, Logger logger) {
|
||||
int length = data.length;
|
||||
|
||||
CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
|
||||
aesEngine.reset();
|
||||
aesEngine.init(false, aesIVAndKey);
|
||||
|
||||
int minSize = aesEngine.getOutputSize(length);
|
||||
byte[] outBuf = new byte[minSize];
|
||||
|
||||
int actualLength = aesEngine.processBytes(data, 0, length, outBuf, 0);
|
||||
|
||||
try {
|
||||
actualLength += aesEngine.doFinal(outBuf, actualLength);
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.debug("Unable to perform AES cipher.", e);
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
if (outBuf.length == actualLength) {
|
||||
return outBuf;
|
||||
}
|
||||
else {
|
||||
byte[] result = new byte[actualLength];
|
||||
System.arraycopy(outBuf, 0, result, 0, result.length);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO</b>
|
||||
* <p>
|
||||
* Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted
|
||||
* data.
|
||||
* <p>
|
||||
* AES decrypt (if we already know the aes IV -- and it's NOT included in the data)
|
||||
*
|
||||
* @param aesIV
|
||||
* must be a nonce (unique value) !!
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return empty byte[] if error
|
||||
*/
|
||||
@Deprecated
|
||||
public static
|
||||
byte[] decrypt(BufferedBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, byte[] data, Logger logger) {
|
||||
|
||||
int length = data.length;
|
||||
|
||||
CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
|
||||
aesEngine.reset();
|
||||
aesEngine.init(false, aesIVAndKey);
|
||||
|
||||
int minSize = aesEngine.getOutputSize(length);
|
||||
byte[] outBuf = new byte[minSize];
|
||||
|
||||
int actualLength = aesEngine.processBytes(data, 0, length, outBuf, 0);
|
||||
|
||||
try {
|
||||
actualLength += aesEngine.doFinal(outBuf, actualLength);
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform AES cipher.", e);
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
if (outBuf.length == actualLength) {
|
||||
return outBuf;
|
||||
}
|
||||
else {
|
||||
byte[] result = new byte[actualLength];
|
||||
System.arraycopy(outBuf, 0, result, 0, result.length);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AES decrypt from one stream to another.
|
||||
*
|
||||
* @param aesIV
|
||||
* must be a nonce (unique value) !!
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
public static
|
||||
boolean decryptStream(GCMBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, InputStream in, OutputStream out, Logger logger) {
|
||||
byte[] buf = new byte[ivSize];
|
||||
byte[] outbuf = new byte[512];
|
||||
|
||||
CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
|
||||
aesEngine.reset();
|
||||
aesEngine.init(false, aesIVAndKey);
|
||||
|
||||
try {
|
||||
int bytesRead;
|
||||
int bytesProcessed;
|
||||
|
||||
while ((bytesRead = in.read(buf)) >= 0) {
|
||||
bytesProcessed = aesEngine.processBytes(buf, 0, bytesRead, outbuf, 0);
|
||||
out.write(outbuf, 0, bytesProcessed);
|
||||
}
|
||||
|
||||
bytesProcessed = aesEngine.doFinal(outbuf, 0);
|
||||
|
||||
out.write(outbuf, 0, bytesProcessed);
|
||||
out.flush();
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform AES cipher.", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO</b>
|
||||
* <p>
|
||||
* Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted
|
||||
* data.
|
||||
* <p>
|
||||
* AES decrypt from one stream to another.
|
||||
*
|
||||
* @param aesIV
|
||||
* must be a nonce (unique value) !!
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
@Deprecated
|
||||
public static
|
||||
boolean decryptStream(BufferedBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, InputStream in, OutputStream out, Logger logger) {
|
||||
byte[] buf = new byte[ivSize];
|
||||
byte[] outbuf = new byte[512];
|
||||
|
||||
CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
|
||||
aesEngine.reset();
|
||||
aesEngine.init(false, aesIVAndKey);
|
||||
|
||||
try {
|
||||
int bytesRead;
|
||||
int bytesProcessed;
|
||||
|
||||
while ((bytesRead = in.read(buf)) >= 0) {
|
||||
bytesProcessed = aesEngine.processBytes(buf, 0, bytesRead, outbuf, 0);
|
||||
out.write(outbuf, 0, bytesProcessed);
|
||||
}
|
||||
|
||||
bytesProcessed = aesEngine.doFinal(outbuf, 0);
|
||||
|
||||
out.write(outbuf, 0, bytesProcessed);
|
||||
out.flush();
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform AES cipher.", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private
|
||||
CryptoAES() {
|
||||
}
|
||||
}
|
|
@ -1,112 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto;
|
||||
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.bouncycastle.crypto.digests.SHA1Digest;
|
||||
import org.bouncycastle.crypto.generators.DSAKeyPairGenerator;
|
||||
import org.bouncycastle.crypto.generators.DSAParametersGenerator;
|
||||
import org.bouncycastle.crypto.params.DSAKeyGenerationParameters;
|
||||
import org.bouncycastle.crypto.params.DSAParameters;
|
||||
import org.bouncycastle.crypto.params.DSAPrivateKeyParameters;
|
||||
import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
|
||||
import org.bouncycastle.crypto.params.ParametersWithRandom;
|
||||
import org.bouncycastle.crypto.signers.DSASigner;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* this is here just for keeping track of how this is done. This should correct and working, but should NOT be used, and instead use ECC
|
||||
* crypto.
|
||||
*/
|
||||
@Deprecated
|
||||
public final
|
||||
class CryptoDSA {
|
||||
/**
|
||||
* Generates the DSA key (using RSA and SHA1)
|
||||
* <p/>
|
||||
* Note: this is here just for keeping track of how this is done. This should NOT be used, and instead use ECC crypto.
|
||||
*/
|
||||
public static
|
||||
AsymmetricCipherKeyPair generateKeyPair(SecureRandom secureRandom, int keyLength) {
|
||||
DSAKeyPairGenerator keyGen = new DSAKeyPairGenerator();
|
||||
|
||||
DSAParametersGenerator dsaParametersGenerator = new DSAParametersGenerator();
|
||||
dsaParametersGenerator.init(keyLength, 20, secureRandom);
|
||||
DSAParameters generateParameters = dsaParametersGenerator.generateParameters();
|
||||
|
||||
DSAKeyGenerationParameters params = new DSAKeyGenerationParameters(secureRandom, generateParameters);
|
||||
keyGen.init(params);
|
||||
return keyGen.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* The message will have the SHA1 hash calculated and used for the signature.
|
||||
* <p/>
|
||||
* Note: this is here just for keeping track of how this is done. This should NOT be used, and instead use ECC crypto.
|
||||
* <p/>
|
||||
* The returned signature is the {r,s} signature array.
|
||||
*/
|
||||
public static
|
||||
BigInteger[] generateSignature(DSAPrivateKeyParameters privateKey, SecureRandom secureRandom, byte[] message) {
|
||||
ParametersWithRandom param = new ParametersWithRandom(privateKey, secureRandom);
|
||||
|
||||
DSASigner dsa = new DSASigner();
|
||||
|
||||
dsa.init(true, param);
|
||||
|
||||
|
||||
SHA1Digest sha1Digest = new SHA1Digest();
|
||||
byte[] checksum = new byte[sha1Digest.getDigestSize()];
|
||||
|
||||
sha1Digest.update(message, 0, message.length);
|
||||
sha1Digest.doFinal(checksum, 0);
|
||||
|
||||
return dsa.generateSignature(checksum);
|
||||
}
|
||||
|
||||
/**
|
||||
* The message will have the SHA1 hash calculated and used for the signature.
|
||||
* <p/>
|
||||
* Note: this is here just for keeping track of how this is done. This should NOT be used, and instead use ECC crypto.
|
||||
*
|
||||
* @param signature
|
||||
* is the {r,s} signature array.
|
||||
*
|
||||
* @return true if the signature is valid
|
||||
*/
|
||||
public static
|
||||
boolean verifySignature(DSAPublicKeyParameters publicKey, byte[] message, BigInteger[] signature) {
|
||||
SHA1Digest sha1Digest = new SHA1Digest();
|
||||
byte[] checksum = new byte[sha1Digest.getDigestSize()];
|
||||
|
||||
sha1Digest.update(message, 0, message.length);
|
||||
sha1Digest.doFinal(checksum, 0);
|
||||
|
||||
|
||||
DSASigner dsa = new DSASigner();
|
||||
|
||||
dsa.init(false, publicKey);
|
||||
|
||||
return dsa.verifySignature(checksum, signature[0], signature[1]);
|
||||
}
|
||||
|
||||
|
||||
private
|
||||
CryptoDSA() {
|
||||
}
|
||||
}
|
|
@ -1,409 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.bouncycastle.crypto.CipherParameters;
|
||||
import org.bouncycastle.crypto.Digest;
|
||||
import org.bouncycastle.crypto.agreement.ECDHCBasicAgreement;
|
||||
import org.bouncycastle.crypto.digests.SHA384Digest;
|
||||
import org.bouncycastle.crypto.digests.SHA512Digest;
|
||||
import org.bouncycastle.crypto.engines.IESEngine;
|
||||
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
|
||||
import org.bouncycastle.crypto.generators.KDF2BytesGenerator;
|
||||
import org.bouncycastle.crypto.macs.HMac;
|
||||
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
|
||||
import org.bouncycastle.crypto.params.ECDomainParameters;
|
||||
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
|
||||
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
|
||||
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
||||
import org.bouncycastle.crypto.params.IESParameters;
|
||||
import org.bouncycastle.crypto.params.IESWithCipherParameters;
|
||||
import org.bouncycastle.crypto.params.ParametersWithRandom;
|
||||
import org.bouncycastle.crypto.signers.ECDSASigner;
|
||||
import org.bouncycastle.jcajce.provider.util.DigestFactory;
|
||||
import org.bouncycastle.jce.ECNamedCurveTable;
|
||||
import org.bouncycastle.jce.spec.ECParameterSpec;
|
||||
import org.bouncycastle.math.ec.ECFieldElement;
|
||||
import org.bouncycastle.math.ec.ECPoint;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* ECC crypto functions
|
||||
*/
|
||||
public final
|
||||
class CryptoECC {
|
||||
public static final String p521_curve = "secp521r1";
|
||||
public static final String curve25519 = "curve25519";
|
||||
public static final String default_curve = curve25519;
|
||||
|
||||
public static final int macSize = 512;
|
||||
// on NIST vs 25519 vs Brainpool, see:
|
||||
// - http://ogryb.blogspot.de/2014/11/why-i-dont-trust-nist-p-256.html
|
||||
// - http://credelius.com/credelius/?p=97
|
||||
// - http://safecurves.cr.yp.to/
|
||||
// we should be using 25519, because NIST and brainpool are "unsafe". Brainpool is "more random" than 25519, but is still not considered safe.
|
||||
|
||||
// more info about ECC from:
|
||||
// http://www.johannes-bauer.com/compsci/ecc/?menuid=4
|
||||
// http://stackoverflow.com/questions/7419183/problems-implementing-ecdh-on-android-using-bouncycastle
|
||||
// http://tools.ietf.org/html/draft-jivsov-openpgp-ecc-06#page-4
|
||||
// http://www.nsa.gov/ia/programs/suiteb_cryptography/
|
||||
// https://github.com/nelenkov/ecdh-kx/blob/master/src/org/nick/ecdhkx/Crypto.java
|
||||
// http://nelenkov.blogspot.com/2011/12/using-ecdh-on-android.html
|
||||
// http://www.secg.org/collateral/sec1_final.pdf
|
||||
|
||||
/**
|
||||
* Uses SHA512
|
||||
*/
|
||||
public static
|
||||
IESEngine createEngine() {
|
||||
return new IESEngine(new ECDHCBasicAgreement(), new KDF2BytesGenerator(new SHA384Digest()), new HMac(new SHA512Digest()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses SHA512
|
||||
*/
|
||||
public static
|
||||
IESEngine createEngine(PaddedBufferedBlockCipher aesEngine) {
|
||||
return new IESEngine(new ECDHCBasicAgreement(),
|
||||
new KDF2BytesGenerator(new SHA384Digest()),
|
||||
new HMac(new SHA512Digest()),
|
||||
aesEngine);
|
||||
}
|
||||
|
||||
/**
|
||||
* These parameters are shared between the two parties. These are a NONCE (use ONCE number!!)
|
||||
*/
|
||||
public static
|
||||
IESParameters generateSharedParameters(SecureRandom secureRandom) {
|
||||
|
||||
int macSize = CryptoECC.macSize; // must be the MAC size
|
||||
|
||||
// MUST be random EACH TIME encrypt/sign happens!
|
||||
byte[] derivation = new byte[macSize / 8];
|
||||
byte[] encoding = new byte[macSize / 8];
|
||||
|
||||
secureRandom.nextBytes(derivation);
|
||||
secureRandom.nextBytes(encoding);
|
||||
|
||||
return new IESParameters(derivation, encoding, macSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* AES-256 ONLY!
|
||||
*/
|
||||
public static
|
||||
IESWithCipherParameters generateSharedParametersWithCipher(SecureRandom secureRandom) {
|
||||
int macSize = CryptoECC.macSize; // must be the MAC size
|
||||
|
||||
byte[] derivation = new byte[macSize / 8]; // MUST be random EACH TIME encrypt/sign happens!
|
||||
byte[] encoding = new byte[macSize / 8];
|
||||
|
||||
secureRandom.nextBytes(derivation);
|
||||
secureRandom.nextBytes(encoding);
|
||||
|
||||
return new IESWithCipherParameters(derivation, encoding, macSize, 256);
|
||||
}
|
||||
|
||||
public static
|
||||
AsymmetricCipherKeyPair generateKeyPair(String eccCurveName, SecureRandom secureRandom) {
|
||||
ECParameterSpec eccSpec = ECNamedCurveTable.getParameterSpec(eccCurveName);
|
||||
|
||||
return generateKeyPair(eccSpec, secureRandom);
|
||||
}
|
||||
|
||||
public static
|
||||
AsymmetricCipherKeyPair generateKeyPair(ECParameterSpec eccSpec, SecureRandom secureRandom) {
|
||||
ECKeyGenerationParameters ecParams = new ECKeyGenerationParameters(new ECDomainParameters(eccSpec.getCurve(),
|
||||
eccSpec.getG(),
|
||||
eccSpec.getN()), secureRandom);
|
||||
|
||||
ECKeyPairGenerator ecKeyGen = new ECKeyPairGenerator();
|
||||
ecKeyGen.init(ecParams);
|
||||
|
||||
return ecKeyGen.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* ECC encrypts data with a specified key.
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return empty byte[] if error
|
||||
*/
|
||||
public static
|
||||
byte[] encrypt(IESEngine eccEngine,
|
||||
CipherParameters private1,
|
||||
CipherParameters public2,
|
||||
IESParameters cipherParams,
|
||||
byte[] message,
|
||||
Logger logger) {
|
||||
|
||||
eccEngine.init(true, private1, public2, cipherParams);
|
||||
|
||||
//noinspection Duplicates
|
||||
try {
|
||||
return eccEngine.processBlock(message, 0, message.length);
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform ECC cipher.", e);
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ECC decrypt data with a specified key.
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return empty byte[] if error
|
||||
*/
|
||||
public static
|
||||
byte[] decrypt(IESEngine eccEngine,
|
||||
CipherParameters private2,
|
||||
CipherParameters public1,
|
||||
IESParameters cipherParams,
|
||||
byte[] encrypted,
|
||||
Logger logger) {
|
||||
|
||||
eccEngine.init(false, private2, public1, cipherParams);
|
||||
|
||||
//noinspection Duplicates
|
||||
try {
|
||||
return eccEngine.processBlock(encrypted, 0, encrypted.length);
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform ECC cipher.", e);
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
public static
|
||||
boolean compare(ECPrivateKeyParameters privateA, ECPrivateKeyParameters privateB) {
|
||||
ECDomainParameters parametersA = privateA.getParameters();
|
||||
ECDomainParameters parametersB = privateB.getParameters();
|
||||
|
||||
// is it the same curve?
|
||||
boolean equals = parametersA.getCurve()
|
||||
.equals(parametersB.getCurve());
|
||||
if (!equals) {
|
||||
return false;
|
||||
}
|
||||
|
||||
equals = parametersA.getG()
|
||||
.equals(parametersB.getG());
|
||||
if (!equals) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
equals = parametersA.getH()
|
||||
.equals(parametersB.getH());
|
||||
if (!equals) {
|
||||
return false;
|
||||
}
|
||||
|
||||
equals = parametersA.getN()
|
||||
.equals(parametersB.getN());
|
||||
if (!equals) {
|
||||
return false;
|
||||
}
|
||||
|
||||
equals = privateA.getD()
|
||||
.equals(privateB.getD());
|
||||
|
||||
return equals;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if publicA and publicB are NOT NULL, and are both equal to eachother
|
||||
*/
|
||||
@SuppressWarnings({"RedundantIfStatement", "SpellCheckingInspection"})
|
||||
public static
|
||||
boolean compare(ECPublicKeyParameters publicA, ECPublicKeyParameters publicB) {
|
||||
if (publicA == null || publicB == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
ECDomainParameters parametersA = publicA.getParameters();
|
||||
ECDomainParameters parametersB = publicB.getParameters();
|
||||
|
||||
// is it the same curve?
|
||||
boolean equals = parametersA.getCurve()
|
||||
.equals(parametersB.getCurve());
|
||||
if (!equals) {
|
||||
return false;
|
||||
}
|
||||
|
||||
equals = parametersA.getG()
|
||||
.equals(parametersB.getG());
|
||||
if (!equals) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
equals = parametersA.getH()
|
||||
.equals(parametersB.getH());
|
||||
if (!equals) {
|
||||
return false;
|
||||
}
|
||||
|
||||
equals = parametersA.getN()
|
||||
.equals(parametersB.getN());
|
||||
if (!equals) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
ECPoint normalizeA = publicA.getQ()
|
||||
.normalize();
|
||||
ECPoint normalizeB = publicB.getQ()
|
||||
.normalize();
|
||||
|
||||
|
||||
ECFieldElement xCoordA = normalizeA.getXCoord();
|
||||
ECFieldElement xCoordB = normalizeB.getXCoord();
|
||||
|
||||
equals = xCoordA.equals(xCoordB);
|
||||
if (!equals) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ECFieldElement yCoordA = normalizeA.getYCoord();
|
||||
ECFieldElement yCoordB = normalizeB.getYCoord();
|
||||
|
||||
equals = yCoordA.equals(yCoordB);
|
||||
if (!equals) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("RedundantIfStatement")
|
||||
public static
|
||||
boolean compare(IESParameters cipherAParams, IESParameters cipherBParams) {
|
||||
if (!Arrays.equals(cipherAParams.getDerivationV(), cipherBParams.getDerivationV())) {
|
||||
return false;
|
||||
}
|
||||
if (!Arrays.equals(cipherAParams.getEncodingV(), cipherBParams.getEncodingV())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cipherAParams.getMacKeySize() != cipherBParams.getMacKeySize()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static
|
||||
boolean compare(IESWithCipherParameters cipherAParams, IESWithCipherParameters cipherBParams) {
|
||||
if (cipherAParams.getCipherKeySize() != cipherBParams.getCipherKeySize()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// only need to cast one side.
|
||||
return compare((IESParameters) cipherAParams, cipherBParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* The message will have the (digestName) hash calculated and used for the signature.
|
||||
* <p/>
|
||||
* The returned signature is the {r,s} signature array.
|
||||
*/
|
||||
public static
|
||||
BigInteger[] generateSignature(String digestName, ECPrivateKeyParameters privateKey, SecureRandom secureRandom, byte[] bytes) {
|
||||
|
||||
Digest digest = DigestFactory.getDigest(digestName);
|
||||
|
||||
byte[] checksum = new byte[digest.getDigestSize()];
|
||||
|
||||
digest.update(bytes, 0, bytes.length);
|
||||
digest.doFinal(checksum, 0);
|
||||
|
||||
return generateSignatureForHash(privateKey, secureRandom, checksum);
|
||||
}
|
||||
|
||||
/**
|
||||
* The message will use the bytes AS THE HASHED VALUE to calculate the signature.
|
||||
* <p/>
|
||||
* The returned signature is the {r,s} signature array.
|
||||
*/
|
||||
public static
|
||||
BigInteger[] generateSignatureForHash(ECPrivateKeyParameters privateKey, SecureRandom secureRandom, byte[] hashBytes) {
|
||||
|
||||
ParametersWithRandom param = new ParametersWithRandom(privateKey, secureRandom);
|
||||
|
||||
ECDSASigner ecdsa = new ECDSASigner();
|
||||
ecdsa.init(true, param);
|
||||
|
||||
return ecdsa.generateSignature(hashBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* The message will have the (digestName) hash calculated and used for the signature.
|
||||
*
|
||||
* @param signature
|
||||
* is the {r,s} signature array.
|
||||
*
|
||||
* @return true if the signature is valid
|
||||
*/
|
||||
public static
|
||||
boolean verifySignature(String digestName, ECPublicKeyParameters publicKey, byte[] message, BigInteger[] signature) {
|
||||
|
||||
Digest digest = DigestFactory.getDigest(digestName);
|
||||
|
||||
byte[] checksum = new byte[digest.getDigestSize()];
|
||||
|
||||
digest.update(message, 0, message.length);
|
||||
digest.doFinal(checksum, 0);
|
||||
|
||||
|
||||
return verifySignatureHash(publicKey, checksum, signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* The provided hash will be used in the signature verification.
|
||||
*
|
||||
* @param signature
|
||||
* is the {r,s} signature array.
|
||||
*
|
||||
* @return true if the signature is valid
|
||||
*/
|
||||
public static
|
||||
boolean verifySignatureHash(ECPublicKeyParameters publicKey, byte[] hash, BigInteger[] signature) {
|
||||
|
||||
ECDSASigner ecdsa = new ECDSASigner();
|
||||
ecdsa.init(false, publicKey);
|
||||
|
||||
|
||||
return ecdsa.verifySignature(hash, signature[0], signature[1]);
|
||||
}
|
||||
|
||||
private
|
||||
CryptoECC() {
|
||||
}
|
||||
}
|
|
@ -1,834 +0,0 @@
|
|||
/*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
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.nio.charset.StandardCharsets;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.bouncycastle.bcpg.ArmoredOutputStream;
|
||||
import org.bouncycastle.bcpg.BCPGOutputStream;
|
||||
import org.bouncycastle.bcpg.CompressionAlgorithmTags;
|
||||
import org.bouncycastle.openpgp.PGPCompressedData;
|
||||
import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
|
||||
import org.bouncycastle.openpgp.PGPEncryptedData;
|
||||
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPLiteralData;
|
||||
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
|
||||
import org.bouncycastle.openpgp.PGPObjectFactory;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.openpgp.PGPSignatureGenerator;
|
||||
import org.bouncycastle.openpgp.PGPSignatureList;
|
||||
import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
|
||||
import org.bouncycastle.openpgp.PGPUtil;
|
||||
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator;
|
||||
|
||||
import dorkbox.util.IO;
|
||||
|
||||
/**
|
||||
* PGP crypto related methods
|
||||
*/
|
||||
public final
|
||||
class CryptoPGP {
|
||||
private static final BcPGPDigestCalculatorProvider digestCalculatorProvider = new BcPGPDigestCalculatorProvider();
|
||||
private static final BcKeyFingerprintCalculator fingerprintCalculator = new BcKeyFingerprintCalculator();
|
||||
|
||||
|
||||
// https://github.com/weiliatgithub/bouncycastle-gpg-exampleC
|
||||
// https://gist.github.com/turingbirds/3df43f1920a98010667a
|
||||
// http://sloanseaman.com/wordpress/2012/05/13/revisited-pgp-encryptiondecryption-in-java/
|
||||
// http://bouncycastle-pgp-cookbook.blogspot.de/
|
||||
|
||||
/**
|
||||
* Sign a message using our private PGP key file, this matches gpg -ab "hello.txt"
|
||||
*
|
||||
* @param privateKeyInputStream
|
||||
* this is an armored key file, not a binary stream
|
||||
* @param userId
|
||||
* this is the userID to get out of the private key
|
||||
* @param password
|
||||
* this is the password to unlock the private key
|
||||
* @param messageAsUtf8Bytes
|
||||
* this is the message, in bytes, to sign
|
||||
*/
|
||||
public static
|
||||
byte[] signGpgCompatible(InputStream privateKeyInputStream, String userId, char[] password, byte[] messageAsUtf8Bytes)
|
||||
throws PGPException {
|
||||
|
||||
// the signature type (in gpg terms), is "sigclass". gpg is BINARY_DOC (0x00)
|
||||
return sign(privateKeyInputStream,
|
||||
userId,
|
||||
password,
|
||||
new ByteArrayInputStream(messageAsUtf8Bytes),
|
||||
PGPSignature.BINARY_DOCUMENT,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a message using our private PGP key file, this matches gpg -ab "hello.txt"
|
||||
*
|
||||
* @param privateKeyInputStream
|
||||
* this is an armored key file, not a binary stream
|
||||
* @param userId
|
||||
* this is the userID to get out of the private key
|
||||
* @param password
|
||||
* this is the password to unlock the private key
|
||||
* @param message
|
||||
* this is the message to sign
|
||||
*/
|
||||
public static
|
||||
byte[] signGpgCompatible(InputStream privateKeyInputStream, String userId, char[] password, InputStream message)
|
||||
throws PGPException {
|
||||
|
||||
// the signature type (in gpg terms), is "sigclass". gpg is BINARY_DOC (0x00)
|
||||
return sign(privateKeyInputStream,
|
||||
userId,
|
||||
password,
|
||||
message,
|
||||
PGPSignature.BINARY_DOCUMENT,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a message using our private PGP key file, this matches gpg -ab "hello.txt". This will save the signature of the passed-in
|
||||
* file to file name + .asc
|
||||
*
|
||||
* @param privateKeyInputStream
|
||||
* this is an armored key file, not a binary stream
|
||||
* @param userId
|
||||
* this is the userID to get out of the private key
|
||||
* @param password
|
||||
* this is the password to unlock the private key
|
||||
* @param file
|
||||
* this is the file to sign
|
||||
*/
|
||||
public static
|
||||
void signGpgCompatible(InputStream privateKeyInputStream, String userId, char[] password, File file)
|
||||
throws PGPException {
|
||||
|
||||
// the signature type (in gpg terms), is "sigclass". gpg is BINARY_DOC (0x00)
|
||||
final byte[] sign = sign(privateKeyInputStream,
|
||||
userId,
|
||||
password,
|
||||
file,
|
||||
PGPSignature.BINARY_DOCUMENT,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false);
|
||||
|
||||
FileOutputStream fileOutputStream1 = null;
|
||||
try {
|
||||
fileOutputStream1 = new FileOutputStream(new File(file.getAbsolutePath() + ".asc"));
|
||||
fileOutputStream1.write(sign);
|
||||
fileOutputStream1.flush();
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new PGPException("Unable to save signature to file " + file.getAbsolutePath() + ".asc", e);
|
||||
} catch (IOException e) {
|
||||
throw new PGPException("Unable to save signature to file " + file.getAbsolutePath() + ".asc", e);
|
||||
} finally {
|
||||
IO.close(fileOutputStream1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a message using our private PGP key file, with a variety of options
|
||||
*/
|
||||
@SuppressWarnings("Duplicates")
|
||||
public static
|
||||
byte[] sign(InputStream privateKeyInputStream,
|
||||
String userId,
|
||||
char[] password,
|
||||
InputStream message,
|
||||
int signatureType,
|
||||
boolean compressSignature,
|
||||
boolean asciiArmoredOutput,
|
||||
boolean includeDataInSignature,
|
||||
boolean generateUserIdSubPacket,
|
||||
boolean generateOnePassVersion) throws PGPException {
|
||||
|
||||
List<PGPSecretKey> secretKeys = getSecretKeys(privateKeyInputStream, userId);
|
||||
PGPSignatureGenerator signature = createSignature(secretKeys, password, signatureType, generateUserIdSubPacket);
|
||||
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
OutputStream outputStream = byteArrayOutputStream;
|
||||
if (asciiArmoredOutput) {
|
||||
outputStream = new ArmoredOutputStream(byteArrayOutputStream);
|
||||
}
|
||||
|
||||
PGPCompressedDataGenerator compressedDataGenerator = null;
|
||||
BCPGOutputStream bcOutputStream;
|
||||
|
||||
if (compressSignature) {
|
||||
compressedDataGenerator = new PGPCompressedDataGenerator(PGPCompressedData.ZLIB);
|
||||
try {
|
||||
bcOutputStream = new BCPGOutputStream(compressedDataGenerator.open(outputStream));
|
||||
} catch (IOException e) {
|
||||
throw new PGPException("Unable to open compression stream in the signature", e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
bcOutputStream = new BCPGOutputStream(outputStream);
|
||||
}
|
||||
|
||||
if (generateOnePassVersion) {
|
||||
try {
|
||||
signature.generateOnePassVersion(false)
|
||||
.encode(bcOutputStream);
|
||||
} catch (IOException e) {
|
||||
throw new PGPException("Unable to generate OnePass signature header", e);
|
||||
}
|
||||
}
|
||||
|
||||
PGPLiteralDataGenerator literalDataGenerator = null;
|
||||
OutputStream literalDataOutput = null;
|
||||
|
||||
if (includeDataInSignature) {
|
||||
literalDataGenerator = new PGPLiteralDataGenerator();
|
||||
try {
|
||||
literalDataOutput = literalDataGenerator.open(bcOutputStream,
|
||||
PGPLiteralData.BINARY,
|
||||
"_CONSOLE",
|
||||
message.available(),
|
||||
new Date());
|
||||
} catch (IOException e1) {
|
||||
throw new PGPException("Unable to generate Literal Data signature header", e1);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
byte[] buffer = new byte[4096];
|
||||
int read;
|
||||
|
||||
// update bytes in the streams
|
||||
if (literalDataOutput != null) {
|
||||
while ((read = message.read(buffer)) > 0) {
|
||||
literalDataOutput.write(buffer, 0, read);
|
||||
signature.update(buffer, 0, read);
|
||||
}
|
||||
literalDataOutput.flush();
|
||||
} else {
|
||||
|
||||
while ((read = message.read(buffer)) > 0) {
|
||||
signature.update(buffer, 0, read);
|
||||
}
|
||||
}
|
||||
|
||||
// close generators and update signature
|
||||
if (literalDataGenerator != null) {
|
||||
literalDataGenerator.close();
|
||||
}
|
||||
|
||||
signature.generate()
|
||||
.encode(bcOutputStream);
|
||||
|
||||
|
||||
if (compressedDataGenerator != null) {
|
||||
compressedDataGenerator.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
IO.close(bcOutputStream);
|
||||
IO.close(outputStream);
|
||||
IO.close(literalDataOutput);
|
||||
}
|
||||
|
||||
return byteArrayOutputStream.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a message using our private PGP key file, with a variety of options
|
||||
*/
|
||||
@SuppressWarnings("Duplicates")
|
||||
public static
|
||||
byte[] sign(InputStream privateKeyInputStream,
|
||||
String userId,
|
||||
char[] password,
|
||||
File fileMessage,
|
||||
int signatureType,
|
||||
boolean compressSignature,
|
||||
boolean asciiArmoredOutput,
|
||||
boolean includeDataInSignature,
|
||||
boolean generateUserIdSubPacket,
|
||||
boolean generateOnePassVersion) throws PGPException {
|
||||
|
||||
List<PGPSecretKey> secretKeys = getSecretKeys(privateKeyInputStream, userId);
|
||||
PGPSignatureGenerator signature = createSignature(secretKeys, password, signatureType, generateUserIdSubPacket);
|
||||
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
OutputStream outputStream = byteArrayOutputStream;
|
||||
if (asciiArmoredOutput) {
|
||||
outputStream = new ArmoredOutputStream(byteArrayOutputStream);
|
||||
}
|
||||
|
||||
PGPCompressedDataGenerator compressedDataGenerator = null;
|
||||
BCPGOutputStream bcOutputStream;
|
||||
|
||||
if (compressSignature) {
|
||||
compressedDataGenerator = new PGPCompressedDataGenerator(PGPCompressedData.ZLIB);
|
||||
try {
|
||||
bcOutputStream = new BCPGOutputStream(compressedDataGenerator.open(outputStream));
|
||||
} catch (IOException e) {
|
||||
throw new PGPException("Unable to open compression stream in the signature", e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
bcOutputStream = new BCPGOutputStream(outputStream);
|
||||
}
|
||||
|
||||
if (generateOnePassVersion) {
|
||||
try {
|
||||
signature.generateOnePassVersion(false)
|
||||
.encode(bcOutputStream);
|
||||
} catch (IOException e) {
|
||||
throw new PGPException("Unable to generate OnePass signature header", e);
|
||||
}
|
||||
}
|
||||
|
||||
PGPLiteralDataGenerator literalDataGenerator = null;
|
||||
OutputStream literalDataOutput = null;
|
||||
|
||||
if (includeDataInSignature) {
|
||||
literalDataGenerator = new PGPLiteralDataGenerator();
|
||||
try {
|
||||
literalDataOutput = literalDataGenerator.open(bcOutputStream,
|
||||
PGPLiteralData.BINARY,
|
||||
fileMessage);
|
||||
} catch (IOException e1) {
|
||||
throw new PGPException("Unable to generate Literal Data signature header", e1);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
final FileInputStream fileInputStream = new FileInputStream(fileMessage);
|
||||
|
||||
byte[] buffer = new byte[4096];
|
||||
int read;
|
||||
|
||||
// update bytes in the streams
|
||||
if (literalDataOutput != null) {
|
||||
while ((read = fileInputStream.read(buffer)) > 0) {
|
||||
literalDataOutput.write(buffer, 0, read);
|
||||
signature.update(buffer, 0, read);
|
||||
}
|
||||
literalDataOutput.flush();
|
||||
} else {
|
||||
|
||||
while ((read = fileInputStream.read(buffer)) > 0) {
|
||||
signature.update(buffer, 0, read);
|
||||
}
|
||||
}
|
||||
|
||||
// close generators and update signature
|
||||
if (literalDataGenerator != null) {
|
||||
literalDataGenerator.close();
|
||||
}
|
||||
|
||||
signature.generate()
|
||||
.encode(bcOutputStream);
|
||||
|
||||
|
||||
if (compressedDataGenerator != null) {
|
||||
compressedDataGenerator.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
IO.close(bcOutputStream);
|
||||
IO.close(outputStream);
|
||||
IO.close(literalDataOutput);
|
||||
}
|
||||
|
||||
return byteArrayOutputStream.toByteArray();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find private gpg key in InputStream, also closes the input stream
|
||||
*
|
||||
* @param inputStream
|
||||
* the inputStream that contains the private (secret) key
|
||||
* @param userId
|
||||
* the user id
|
||||
*
|
||||
* @return the PGP secret key
|
||||
*/
|
||||
public static
|
||||
List<PGPSecretKey> getSecretKeys(InputStream inputStream, String userId) throws PGPException {
|
||||
// iterate over every private key in the key ring
|
||||
PGPSecretKeyRingCollection secretKeyRings;
|
||||
try {
|
||||
secretKeyRings = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(inputStream), fingerprintCalculator);
|
||||
} catch (IOException e) {
|
||||
throw new PGPException("No private key found in stream!", e);
|
||||
} finally {
|
||||
IO.close(inputStream);
|
||||
}
|
||||
|
||||
// look for the key ring that is used to authenticate our reporting facilities
|
||||
Iterator<PGPSecretKeyRing> secretKeys = secretKeyRings.getKeyRings(userId);
|
||||
List<PGPSecretKey> pgpSecretKeys = new ArrayList<PGPSecretKey>();
|
||||
|
||||
// iterate over every private key in the ring
|
||||
while (secretKeys.hasNext()) {
|
||||
PGPSecretKeyRing secretKeyRing = secretKeys.next();
|
||||
PGPSecretKey tmpKey = secretKeyRing.getSecretKey();
|
||||
|
||||
if (tmpKey != null) {
|
||||
pgpSecretKeys.add(tmpKey);
|
||||
}
|
||||
}
|
||||
|
||||
if (!pgpSecretKeys.isEmpty()) {
|
||||
return pgpSecretKeys;
|
||||
}
|
||||
|
||||
throw new PGPException("No private key found in stream!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the signature that will be used to PGP sign data
|
||||
*
|
||||
* @param secretKeys
|
||||
* these are the secret keys
|
||||
* @param password
|
||||
* this is the password to unlock the secret key
|
||||
*
|
||||
* @return the signature used to sign data
|
||||
*
|
||||
* @throws PGPException
|
||||
*/
|
||||
private static
|
||||
PGPSignatureGenerator createSignature(List<PGPSecretKey> secretKeys,
|
||||
char[] password,
|
||||
int signatureType,
|
||||
boolean generateUserIdSubPacket) throws PGPException {
|
||||
|
||||
PGPSecretKey secretKey = null;
|
||||
for (int i = 0; i < secretKeys.size(); i++) {
|
||||
secretKey = secretKeys.get(i);
|
||||
|
||||
// we ONLY want the signing master key
|
||||
if (!secretKey.isSigningKey() || !secretKey.isMasterKey()) {
|
||||
secretKey = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (secretKey == null) {
|
||||
throw new PGPException("Secret key is not the signing master key");
|
||||
}
|
||||
|
||||
// System.err.println("Signing key = " + tmpKey.isSigningKey() +", Master key = " + tmpKey.isMasterKey() + ", UserId = " +
|
||||
// userId );
|
||||
|
||||
if (password == null) {
|
||||
password = new char[0];
|
||||
}
|
||||
|
||||
PBESecretKeyDecryptor build = new BcPBESecretKeyDecryptorBuilder(digestCalculatorProvider).build(password);
|
||||
|
||||
SecureRandom random = new SecureRandom();
|
||||
BcPGPContentSignerBuilder bcPGPContentSignerBuilder = new BcPGPContentSignerBuilder(secretKey.getPublicKey()
|
||||
.getAlgorithm(),
|
||||
PGPUtil.SHA1).setSecureRandom(random);
|
||||
|
||||
PGPSignatureGenerator signature = new PGPSignatureGenerator(bcPGPContentSignerBuilder);
|
||||
signature.init(signatureType, secretKey.extractPrivateKey(build));
|
||||
|
||||
Iterator userIds = secretKey.getPublicKey()
|
||||
.getUserIDs();
|
||||
|
||||
// use the first userId that matches
|
||||
if (userIds.hasNext()) {
|
||||
if (generateUserIdSubPacket) {
|
||||
PGPSignatureSubpacketGenerator subpacketGenerator = new PGPSignatureSubpacketGenerator();
|
||||
subpacketGenerator.addSignerUserID(false, (String) userIds.next());
|
||||
signature.setHashedSubpackets(subpacketGenerator.generate());
|
||||
}
|
||||
else {
|
||||
signature.setHashedSubpackets(null);
|
||||
}
|
||||
|
||||
return signature;
|
||||
}
|
||||
else {
|
||||
throw new PGPException("Did not find specified userId");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Decode a PGP public key block and return the keyring it represents.
|
||||
*/
|
||||
public static
|
||||
PGPPublicKeyRing getKeyring(InputStream keyBlockStream) throws IOException {
|
||||
|
||||
BcKeyFingerprintCalculator keyfp = new BcKeyFingerprintCalculator();
|
||||
|
||||
// PGPUtil.getDecoderStream() will detect ASCII-armor automatically and decode it,
|
||||
// the PGPObject factory then knows how to read all the data in the encoded stream
|
||||
PGPObjectFactory factory = new PGPObjectFactory(PGPUtil.getDecoderStream(keyBlockStream), keyfp);
|
||||
|
||||
// these files should really just have one object in them, and that object should be a PGPPublicKeyRing.
|
||||
Object o = factory.nextObject();
|
||||
if (o instanceof PGPPublicKeyRing) {
|
||||
return (PGPPublicKeyRing) o;
|
||||
}
|
||||
throw new IllegalArgumentException("Input stream does not contain a PGP Public Key");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first encryption key from the given keyring.
|
||||
*/
|
||||
public static
|
||||
PGPPublicKey getEncryptionKey(PGPPublicKeyRing keyRing) {
|
||||
if (keyRing == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// iterate over the keys on the ring, look for one which is suitable for encryption.
|
||||
Iterator keys = keyRing.getPublicKeys();
|
||||
PGPPublicKey key;
|
||||
while (keys.hasNext()) {
|
||||
key = (PGPPublicKey) keys.next();
|
||||
if (key.isEncryptionKey()) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first decryption key from the given keyring.
|
||||
*/
|
||||
public
|
||||
PGPSecretKey getDecryptionKey(PGPSecretKeyRing keyRing) {
|
||||
if (keyRing == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// iterate over the keys on the ring, look for one which is suitable for encryption.
|
||||
Iterator keys = keyRing.getSecretKeys();
|
||||
PGPSecretKey key;
|
||||
while (keys.hasNext()) {
|
||||
key = (PGPSecretKey) keys.next();
|
||||
if (key.isMasterKey()) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encrypt plaintext message using public key from publickeyFile.
|
||||
*
|
||||
* @param message
|
||||
* the message
|
||||
*
|
||||
* @return the string
|
||||
*/
|
||||
private
|
||||
String encrypt(InputStream publicKeyInputStream, String message) throws PGPException, IOException, NoSuchProviderException {
|
||||
// find the PGP key in the file
|
||||
PGPPublicKey publicKey = findPublicGPGKey(publicKeyInputStream);
|
||||
|
||||
if (publicKey == null) {
|
||||
System.err.println("Did not find public GPG key");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// Encode the string into bytes using utf-8
|
||||
byte[] utf8Bytes = message.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
ByteArrayOutputStream compressedOutput = new ByteArrayOutputStream();
|
||||
|
||||
// compress bytes with zip
|
||||
PGPLiteralDataGenerator literalDataGenerator = new PGPLiteralDataGenerator();
|
||||
|
||||
// the reason why we compress here is GPG not being able to decrypt our message input but if we do not compress.
|
||||
// I guess pkzip compression also encodes only to GPG-friendly characters.
|
||||
PGPCompressedDataGenerator compressedDataGenerator = new PGPCompressedDataGenerator(CompressionAlgorithmTags.ZIP);
|
||||
try {
|
||||
OutputStream literalDataOutput = literalDataGenerator.open(compressedOutput,
|
||||
PGPLiteralData.BINARY,
|
||||
"_CONSOLE",
|
||||
utf8Bytes.length,
|
||||
new Date());
|
||||
// update bytes in the stream
|
||||
literalDataOutput.write(utf8Bytes);
|
||||
} catch (IOException e) {
|
||||
// catch but close the streams in finally
|
||||
throw e;
|
||||
} finally {
|
||||
compressedDataGenerator.close();
|
||||
IO.close(compressedOutput);
|
||||
}
|
||||
|
||||
SecureRandom random = new SecureRandom();
|
||||
|
||||
// now we have zip-compressed bytes
|
||||
byte[] compressedBytes = compressedOutput.toByteArray();
|
||||
|
||||
BcPGPDataEncryptorBuilder bcPGPDataEncryptorBuilder = new BcPGPDataEncryptorBuilder(PGPEncryptedData.CAST5)
|
||||
.setWithIntegrityPacket(true)
|
||||
.setSecureRandom(random);
|
||||
|
||||
PGPEncryptedDataGenerator encryptedDataGenerator = new PGPEncryptedDataGenerator(bcPGPDataEncryptorBuilder);
|
||||
|
||||
// use public key to encrypt data
|
||||
|
||||
BcPublicKeyKeyEncryptionMethodGenerator encKeyGen = new BcPublicKeyKeyEncryptionMethodGenerator(publicKey)
|
||||
.setSecureRandom(random);
|
||||
|
||||
encryptedDataGenerator.addMethod(encKeyGen);
|
||||
|
||||
// literalDataOutput --> compressedOutput --> ArmoredOutputStream --> ByteArrayOutputStream
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
ArmoredOutputStream armoredOut = new ArmoredOutputStream(byteArrayOutputStream);
|
||||
OutputStream encryptedOutput = null;
|
||||
try {
|
||||
encryptedOutput = encryptedDataGenerator.open(armoredOut, compressedBytes.length);
|
||||
encryptedOutput.write(compressedBytes);
|
||||
} catch (IOException e) {
|
||||
throw e;
|
||||
} catch (PGPException e) {
|
||||
throw e;
|
||||
} finally {
|
||||
IO.close(encryptedOutput);
|
||||
IO.close(armoredOut);
|
||||
}
|
||||
String encrypted = new String(byteArrayOutputStream.toByteArray());
|
||||
|
||||
System.err.println("Message: " + message);
|
||||
System.err.println("Encrypted: " + encrypted);
|
||||
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find public gpg key in InputStream.
|
||||
*
|
||||
* @param inputStream
|
||||
* the input stream
|
||||
*
|
||||
* @return the PGP public key
|
||||
*/
|
||||
private static
|
||||
PGPPublicKey findPublicGPGKey(InputStream inputStream) throws IOException, PGPException {
|
||||
|
||||
// get all key rings in the input stream
|
||||
PGPPublicKeyRingCollection publicKeyRingCollection = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(inputStream), fingerprintCalculator);
|
||||
|
||||
System.err.println("key ring size: " + publicKeyRingCollection.size());
|
||||
|
||||
Iterator<PGPPublicKeyRing> keyRingIter = publicKeyRingCollection.getKeyRings();
|
||||
|
||||
// iterate over keyrings
|
||||
while (keyRingIter.hasNext()) {
|
||||
PGPPublicKeyRing keyRing = keyRingIter.next();
|
||||
Iterator<PGPPublicKey> keyIter = keyRing.getPublicKeys();
|
||||
// iterate over public keys in the key ring
|
||||
while (keyIter.hasNext()) {
|
||||
PGPPublicKey tmpKey = keyIter.next();
|
||||
|
||||
if (tmpKey == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
Iterator<String> userIDs = tmpKey.getUserIDs();
|
||||
ArrayList<String> strings = new ArrayList<String>();
|
||||
while (userIDs.hasNext()) {
|
||||
String next = userIDs.next();
|
||||
strings.add(next);
|
||||
}
|
||||
|
||||
System.err.println(
|
||||
"Encryption key = " + tmpKey.isEncryptionKey() + ", Master key = " + tmpKey.isMasterKey() + ", UserId = " +
|
||||
strings);
|
||||
|
||||
// we need a master encryption key
|
||||
if (tmpKey.isEncryptionKey() && tmpKey.isMasterKey()) {
|
||||
return tmpKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new PGPException("No public key found!");
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static
|
||||
void verify(final InputStream publicKeyInputStream, final byte[] signature) throws Exception {
|
||||
PGPPublicKey publicKey = findPublicGPGKey(publicKeyInputStream);
|
||||
|
||||
String text = new String(signature);
|
||||
|
||||
Pattern regex = Pattern.compile(
|
||||
"-----BEGIN PGP SIGNED MESSAGE-----\\r?\\n.*?\\r?\\n\\r?\\n(.*)\\r?\\n(-----BEGIN PGP SIGNATURE-----\\r?\\n.*-----END PGP SIGNATURE-----)",
|
||||
Pattern.CANON_EQ | Pattern.DOTALL);
|
||||
Matcher regexMatcher = regex.matcher(text);
|
||||
if (regexMatcher.find()) {
|
||||
String dataText = regexMatcher.group(1);
|
||||
String signText = regexMatcher.group(2);
|
||||
|
||||
ByteArrayInputStream dataIn = new ByteArrayInputStream(dataText.getBytes("UTF8"));
|
||||
ByteArrayInputStream signIn = new ByteArrayInputStream(signText.getBytes("UTF8"));
|
||||
|
||||
|
||||
InputStream signIn2 = PGPUtil.getDecoderStream(signIn);
|
||||
|
||||
PGPObjectFactory pgpFact = new PGPObjectFactory(signIn2, new BcKeyFingerprintCalculator());
|
||||
PGPSignatureList p3 = null;
|
||||
|
||||
Object o;
|
||||
|
||||
try {
|
||||
o = pgpFact.nextObject();
|
||||
if (o == null) {
|
||||
throw new Exception();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new Exception("Invalid input data");
|
||||
}
|
||||
|
||||
if (o instanceof PGPCompressedData) {
|
||||
PGPCompressedData c1 = (PGPCompressedData) o;
|
||||
|
||||
pgpFact = new PGPObjectFactory(c1.getDataStream(), new BcKeyFingerprintCalculator());
|
||||
|
||||
p3 = (PGPSignatureList) pgpFact.nextObject();
|
||||
}
|
||||
else {
|
||||
p3 = (PGPSignatureList) o;
|
||||
}
|
||||
|
||||
|
||||
// PGPSignature sig = p3.get(0);
|
||||
// PGPPublicKey key = KeyRing.getPublicKeyByID(sig.getKeyID());
|
||||
//
|
||||
// if (key == null)
|
||||
// throw new Exception("Cannot find key 0x" + Integer.toHexString((int) sig.getKeyID()).toUpperCase() + " in the pubring");
|
||||
//
|
||||
// sig.initVerify(key, "BC");
|
||||
//
|
||||
// while ((ch = dataIn.read()) >= 0) {
|
||||
// sig.update((byte) ch); //TODO migliorabile con byte[]
|
||||
// }
|
||||
//
|
||||
// if (sig.verify())
|
||||
// return new PrintablePGPPublicKey(key).toString();
|
||||
// else
|
||||
// return null;
|
||||
|
||||
// return verifyFile(dataIn, signIn);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private
|
||||
CryptoPGP() {
|
||||
}
|
||||
|
||||
public static
|
||||
void main(String[] args) throws Exception {
|
||||
InputStream privateKeyInputStream = new FileInputStream(new File("/home/user/dorkbox/sonatype_private.key"));
|
||||
|
||||
byte[] textBytes = "hello".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
byte[] bytes = CryptoPGP.signGpgCompatible(privateKeyInputStream, "Dorkbox <sonatype@dorkbox.com>", new char[0], textBytes);
|
||||
|
||||
// String s = new String(hello);
|
||||
// String s1 = s.replaceAll("\n", "\r\n");
|
||||
// byte[] bytes = s1.getBytes(OS.UTF_8);
|
||||
|
||||
//
|
||||
// String signed = new String(bytes);
|
||||
//
|
||||
// System.err.println("Message: " + new String(messageAsUtf8Bytes));
|
||||
// System.err.println("Signature: " + signed);
|
||||
//
|
||||
// return bytes;
|
||||
|
||||
// String s2 = new String(bytes);
|
||||
|
||||
|
||||
// InputStream publicKeyInputStream = new FileInputStream(new File("/home/user/dorkbox/sonatype_public.key"));
|
||||
// cryptoPGP.verify(publicKeyInputStream, hello);
|
||||
|
||||
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(new File("/home/user/dorkbox/hello2.txt"));
|
||||
fileOutputStream.write(textBytes);
|
||||
fileOutputStream.flush();
|
||||
IO.close(fileOutputStream);
|
||||
|
||||
|
||||
FileOutputStream fileOutputStream1 = new FileOutputStream(new File("/home/user/dorkbox/hello2.txt.asc"));
|
||||
fileOutputStream1.write(bytes);
|
||||
fileOutputStream1.flush();
|
||||
IO.close(fileOutputStream1);
|
||||
}
|
||||
}
|
|
@ -1,309 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto;
|
||||
|
||||
import org.bouncycastle.crypto.AsymmetricBlockCipher;
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.bouncycastle.crypto.Digest;
|
||||
import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
|
||||
import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
|
||||
import org.bouncycastle.crypto.params.RSAKeyParameters;
|
||||
import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
|
||||
import org.bouncycastle.crypto.signers.PSSSigner;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* This is here just for keeping track of how this is done. This should NOT be used, and instead use ECC crypto.
|
||||
*/
|
||||
@Deprecated
|
||||
public final
|
||||
class CryptoRSA {
|
||||
public static
|
||||
AsymmetricCipherKeyPair generateKeyPair(SecureRandom secureRandom, int keyLength) {
|
||||
RSAKeyPairGenerator keyGen = new RSAKeyPairGenerator();
|
||||
RSAKeyGenerationParameters params = new RSAKeyGenerationParameters(new BigInteger("65537"), // public exponent
|
||||
secureRandom, //pnrg
|
||||
keyLength, // key length
|
||||
8); //the number of iterations of the Miller-Rabin primality test.
|
||||
keyGen.init(params);
|
||||
return keyGen.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* RSA encrypt using public key A, and sign data with private key B.
|
||||
* <p/>
|
||||
* byte[0][] = encrypted data byte[1][] = signature
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return empty byte[][] if error
|
||||
*/
|
||||
public static
|
||||
byte[][] encryptAndSign(AsymmetricBlockCipher rsaEngine,
|
||||
Digest digest,
|
||||
RSAKeyParameters rsaPublicKeyA,
|
||||
RSAPrivateCrtKeyParameters rsaPrivateKeyB,
|
||||
byte[] bytes,
|
||||
Logger logger) {
|
||||
if (bytes.length == 0) {
|
||||
return new byte[0][0];
|
||||
}
|
||||
|
||||
byte[] encryptBytes = encrypt(rsaEngine, rsaPublicKeyA, bytes, logger);
|
||||
|
||||
if (encryptBytes.length == 0) {
|
||||
return new byte[0][0];
|
||||
}
|
||||
|
||||
// now sign it.
|
||||
PSSSigner signer = new PSSSigner(rsaEngine, digest, digest.getDigestSize());
|
||||
|
||||
byte[] signatureRSA = CryptoRSA.sign(signer, rsaPrivateKeyB, encryptBytes, logger);
|
||||
|
||||
if (signatureRSA.length == 0) {
|
||||
return new byte[0][0];
|
||||
}
|
||||
|
||||
byte[][] total = new byte[2][];
|
||||
total[0] = encryptBytes;
|
||||
total[1] = signatureRSA;
|
||||
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* RSA verify data with public key B, and decrypt using private key A.
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return empty byte[] if error
|
||||
*/
|
||||
public static
|
||||
byte[] decryptAndVerify(AsymmetricBlockCipher rsaEngine,
|
||||
Digest digest,
|
||||
RSAKeyParameters rsaPublicKeyA,
|
||||
RSAPrivateCrtKeyParameters rsaPrivateKeyB,
|
||||
byte[] encryptedData,
|
||||
byte[] signature,
|
||||
Logger logger) {
|
||||
if (encryptedData.length == 0 || signature.length == 0) {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
// verify encrypted data.
|
||||
PSSSigner signer = new PSSSigner(rsaEngine, digest, digest.getDigestSize());
|
||||
|
||||
boolean verify = verify(signer, rsaPublicKeyA, signature, encryptedData);
|
||||
if (!verify) {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
return decrypt(rsaEngine, rsaPrivateKeyB, encryptedData, logger);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* RSA encrypts data with a specified key.
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return empty byte[] if error
|
||||
*/
|
||||
public static
|
||||
byte[] encrypt(AsymmetricBlockCipher rsaEngine, RSAKeyParameters rsaPublicKey, byte[] bytes, Logger logger) {
|
||||
rsaEngine.init(true, rsaPublicKey);
|
||||
|
||||
try {
|
||||
int inputBlockSize = rsaEngine.getInputBlockSize();
|
||||
if (inputBlockSize < bytes.length) {
|
||||
int outSize = rsaEngine.getOutputBlockSize();
|
||||
//noinspection NumericCastThatLosesPrecision
|
||||
int realsize = (int) Math.round(bytes.length / (outSize * 1.0D) + 0.5);
|
||||
ByteBuffer buffer = ByteBuffer.allocateDirect(outSize * realsize);
|
||||
|
||||
int position = 0;
|
||||
|
||||
while (position < bytes.length) {
|
||||
int size = Math.min(inputBlockSize, bytes.length - position);
|
||||
|
||||
byte[] block = rsaEngine.processBlock(bytes, position, size);
|
||||
buffer.put(block, 0, block.length);
|
||||
|
||||
position += size;
|
||||
}
|
||||
|
||||
|
||||
return buffer.array();
|
||||
|
||||
}
|
||||
else {
|
||||
return rsaEngine.processBlock(bytes, 0, bytes.length);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform RSA cipher.", e);
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* RSA decrypt data with a specified key.
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return empty byte[] if error
|
||||
*/
|
||||
public static
|
||||
byte[] decrypt(AsymmetricBlockCipher rsaEngine, RSAPrivateCrtKeyParameters rsaPrivateKey, byte[] bytes, Logger logger) {
|
||||
rsaEngine.init(false, rsaPrivateKey);
|
||||
|
||||
try {
|
||||
int inputBlockSize = rsaEngine.getInputBlockSize();
|
||||
if (inputBlockSize < bytes.length) {
|
||||
int outSize = rsaEngine.getOutputBlockSize();
|
||||
//noinspection NumericCastThatLosesPrecision
|
||||
int realsize = (int) Math.round(bytes.length / (outSize * 1.0D) + 0.5);
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream(outSize * realsize);
|
||||
|
||||
int position = 0;
|
||||
|
||||
while (position < bytes.length) {
|
||||
int size = Math.min(inputBlockSize, bytes.length - position);
|
||||
|
||||
byte[] block = rsaEngine.processBlock(bytes, position, size);
|
||||
buffer.write(block, 0, block.length);
|
||||
|
||||
position += size;
|
||||
}
|
||||
|
||||
|
||||
return buffer.toByteArray();
|
||||
}
|
||||
else {
|
||||
return rsaEngine.processBlock(bytes, 0, bytes.length);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform RSA cipher.", e);
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* RSA sign data with a specified key.
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return empty byte[] if error
|
||||
*/
|
||||
public static
|
||||
byte[] sign(PSSSigner signer, RSAPrivateCrtKeyParameters rsaPrivateKey, byte[] mesg, Logger logger) {
|
||||
signer.init(true, rsaPrivateKey);
|
||||
signer.update(mesg, 0, mesg.length);
|
||||
|
||||
try {
|
||||
return signer.generateSignature();
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform RSA cipher.", e);
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* RSA verify data with a specified key.
|
||||
*/
|
||||
public static
|
||||
boolean verify(PSSSigner signer, RSAKeyParameters rsaPublicKey, byte[] sig, byte[] mesg) {
|
||||
signer.init(false, rsaPublicKey);
|
||||
signer.update(mesg, 0, mesg.length);
|
||||
|
||||
return signer.verifySignature(sig);
|
||||
}
|
||||
|
||||
@SuppressWarnings("RedundantIfStatement")
|
||||
public static
|
||||
boolean compare(RSAKeyParameters publicA, RSAKeyParameters publicB) {
|
||||
if (!publicA.getExponent()
|
||||
.equals(publicB.getExponent())) {
|
||||
return false;
|
||||
}
|
||||
if (!publicA.getModulus()
|
||||
.equals(publicB.getModulus())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("RedundantIfStatement")
|
||||
public static
|
||||
boolean compare(RSAPrivateCrtKeyParameters private1, RSAPrivateCrtKeyParameters private2) {
|
||||
if (!private1.getModulus()
|
||||
.equals(private2.getModulus())) {
|
||||
return false;
|
||||
}
|
||||
if (!private1.getExponent()
|
||||
.equals(private2.getExponent())) {
|
||||
return false;
|
||||
}
|
||||
if (!private1.getDP()
|
||||
.equals(private2.getDP())) {
|
||||
return false;
|
||||
}
|
||||
if (!private1.getDQ()
|
||||
.equals(private2.getDQ())) {
|
||||
return false;
|
||||
}
|
||||
if (!private1.getP()
|
||||
.equals(private2.getP())) {
|
||||
return false;
|
||||
}
|
||||
if (!private1.getPublicExponent()
|
||||
.equals(private2.getPublicExponent())) {
|
||||
return false;
|
||||
}
|
||||
if (!private1.getQ()
|
||||
.equals(private2.getQ())) {
|
||||
return false;
|
||||
}
|
||||
if (!private1.getQInv()
|
||||
.equals(private2.getQInv())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private
|
||||
CryptoRSA() {
|
||||
}
|
||||
}
|
|
@ -1,236 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* An implementation of the <a href="http://www.tarsnap.com/scrypt/scrypt.pdf"/>scrypt</a> key derivation function.
|
||||
*/
|
||||
public final
|
||||
class CryptoSCrypt {
|
||||
/**
|
||||
* Hash the supplied plaintext password and generate output using default parameters
|
||||
* <p/>
|
||||
* The password chars are no longer valid after this call
|
||||
*
|
||||
* @param password
|
||||
* Password.
|
||||
*/
|
||||
public static
|
||||
String encrypt(char[] password) {
|
||||
return encrypt(password, 16384, 32, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash the supplied plaintext password and generate output using default parameters
|
||||
* <p/>
|
||||
* The password chars are no longer valid after this call
|
||||
*
|
||||
* @param password
|
||||
* Password.
|
||||
* @param salt
|
||||
* Salt parameter
|
||||
*/
|
||||
public static
|
||||
String encrypt(char[] password, byte[] salt) {
|
||||
return encrypt(password, salt, 16384, 128, 1, 64);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash the supplied plaintext password and generate output.
|
||||
* <p/>
|
||||
* The password chars are no longer valid after this call
|
||||
*
|
||||
* @param password
|
||||
* Password.
|
||||
* @param N
|
||||
* CPU cost parameter.
|
||||
* @param r
|
||||
* Memory cost parameter.
|
||||
* @param p
|
||||
* Parallelization parameter.
|
||||
*
|
||||
* @return The hashed password.
|
||||
*/
|
||||
public static
|
||||
String encrypt(char[] password, int N, int r, int p) {
|
||||
SecureRandom secureRandom = new SecureRandom();
|
||||
byte[] salt = new byte[32];
|
||||
secureRandom.nextBytes(salt);
|
||||
|
||||
return encrypt(password, salt, N, r, p, 64);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash the supplied plaintext password and generate output.
|
||||
* <p/>
|
||||
* The password chars are no longer valid after this call
|
||||
*
|
||||
* @param password
|
||||
* Password.
|
||||
* @param salt
|
||||
* Salt parameter
|
||||
* @param N
|
||||
* CPU cost parameter.
|
||||
* @param r
|
||||
* Memory cost parameter.
|
||||
* @param p
|
||||
* Parallelization parameter.
|
||||
* @param dkLen
|
||||
* Intended length of the derived key.
|
||||
*
|
||||
* @return The hashed password.
|
||||
*/
|
||||
public static
|
||||
String encrypt(char[] password, byte[] salt, int N, int r, int p, int dkLen) {
|
||||
// Note: this saves the char array in UTF-16 format of bytes.
|
||||
// can't use password after this as it's been changed to '*'
|
||||
byte[] passwordBytes = Crypto.charToBytesPassword_UTF16(password);
|
||||
|
||||
byte[] derived = encrypt(passwordBytes, salt, N, r, p, dkLen);
|
||||
|
||||
String params = Integer.toString(log2(N) << 16 | r << 8 | p, 16);
|
||||
|
||||
@SuppressWarnings("StringBufferReplaceableByString")
|
||||
StringBuilder sb = new StringBuilder((salt.length + derived.length) * 2);
|
||||
sb.append("$s0$")
|
||||
.append(params)
|
||||
.append('$');
|
||||
sb.append(Base64.getEncoder().encodeToString(salt))
|
||||
.append('$');
|
||||
sb.append(Base64.getEncoder().encodeToString(derived));
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the supplied plaintext password to a hashed password.
|
||||
*
|
||||
* @param password
|
||||
* Plaintext password.
|
||||
* @param hashed
|
||||
* scrypt hashed password.
|
||||
*
|
||||
* @return true if password matches hashed value.
|
||||
*/
|
||||
public static
|
||||
boolean verify(char[] password, String hashed) {
|
||||
// Note: this saves the char array in UTF-16 format of bytes.
|
||||
// can't use password after this as it's been changed to '*'
|
||||
byte[] passwordBytes = Crypto.charToBytesPassword_UTF16(password);
|
||||
|
||||
String[] parts = hashed.split("\\$");
|
||||
|
||||
if (parts.length != 5 || !parts[1].equals("s0")) {
|
||||
throw new IllegalArgumentException("Invalid hashed value");
|
||||
}
|
||||
|
||||
int params = Integer.parseInt(parts[2], 16);
|
||||
byte[] salt = Base64.getDecoder().decode(parts[3]);
|
||||
byte[] derived0 = Base64.getDecoder().decode(parts[4]);
|
||||
|
||||
//noinspection NumericCastThatLosesPrecision
|
||||
int N = (int) Math.pow(2, params >> 16 & 0xFF);
|
||||
int r = params >> 8 & 0xFF;
|
||||
int p = params & 0xFF;
|
||||
|
||||
int length = derived0.length;
|
||||
if (length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
byte[] derived1 = encrypt(passwordBytes, salt, N, r, p, length);
|
||||
|
||||
if (length != derived1.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int result = 0;
|
||||
for (int i = 0; i < length; i++) {
|
||||
result |= derived0[i] ^ derived1[i];
|
||||
}
|
||||
|
||||
return result == 0;
|
||||
}
|
||||
|
||||
private static
|
||||
int log2(int n) {
|
||||
int log = 0;
|
||||
if ((n & 0xFFFF0000) != 0) {
|
||||
n >>>= 16;
|
||||
log = 16;
|
||||
}
|
||||
if (n >= 256) {
|
||||
n >>>= 8;
|
||||
log += 8;
|
||||
}
|
||||
if (n >= 16) {
|
||||
n >>>= 4;
|
||||
log += 4;
|
||||
}
|
||||
if (n >= 4) {
|
||||
n >>>= 2;
|
||||
log += 2;
|
||||
}
|
||||
return log + (n >>> 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pure Java implementation of the <a href="http://www.tarsnap.com/scrypt/scrypt.pdf"/>scrypt KDF</a>.
|
||||
*
|
||||
* @param password
|
||||
* Password.
|
||||
* @param salt
|
||||
* 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[] encrypt(byte[] password, byte[] salt, int N, int r, int p, int dkLen) {
|
||||
if (N == 0 || (N & N - 1) != 0) {
|
||||
throw new IllegalArgumentException("N must be > 0 and a power of 2");
|
||||
}
|
||||
|
||||
if (N > Integer.MAX_VALUE / 128 / r) {
|
||||
throw new IllegalArgumentException("Parameter N is too large");
|
||||
}
|
||||
if (r > Integer.MAX_VALUE / 128 / p) {
|
||||
throw new IllegalArgumentException("Parameter r is too large");
|
||||
}
|
||||
|
||||
try {
|
||||
return org.bouncycastle.crypto.generators.SCrypt.generate(password, salt, N, r, p, dkLen);
|
||||
} finally {
|
||||
// now zero out the bytes in password.
|
||||
Arrays.fill(password, (byte) 0);
|
||||
}
|
||||
}
|
||||
|
||||
private
|
||||
CryptoSCrypt() {
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,336 +0,0 @@
|
|||
package dorkbox.util.crypto
|
||||
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.nio.file.Files
|
||||
import java.security.GeneralSecurityException
|
||||
import java.security.MessageDigest
|
||||
import java.security.SecureRandom
|
||||
import java.util.*
|
||||
import javax.crypto.BadPaddingException
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.IllegalBlockSizeException
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
object OpenSSLDecryptor {
|
||||
private val INDEX_KEY = 0
|
||||
private val INDEX_IV = 1
|
||||
private val ITERATIONS = 1
|
||||
|
||||
private val ARG_INDEX_FILENAME = 0
|
||||
private val ARG_INDEX_PASSWORD = 1
|
||||
|
||||
private val SALT_OFFSET = 8
|
||||
private val SALT_SIZE = 8
|
||||
private val CIPHERTEXT_OFFSET = SALT_OFFSET + SALT_SIZE
|
||||
|
||||
private val KEY_SIZE_BITS = 256
|
||||
|
||||
/**
|
||||
* Thanks go to Ola Bini for releasing this source on his blog.
|
||||
* The source was obtained from [here](http://olabini.com/blog/tag/evp_bytestokey/) .
|
||||
*/
|
||||
fun EVP_BytesToKey(key_len: Int, iv_len: Int, md: MessageDigest, salt: ByteArray?, data: ByteArray?, count: Int): Array<ByteArray> {
|
||||
val key = ByteArray(key_len)
|
||||
var key_ix = 0
|
||||
|
||||
val iv = ByteArray(iv_len)
|
||||
var iv_ix = 0
|
||||
|
||||
val both = arrayOf(key, iv)
|
||||
if (data == null) {
|
||||
return both
|
||||
}
|
||||
|
||||
var md_buf: ByteArray? = null
|
||||
var nkey = key_len
|
||||
var niv = iv_len
|
||||
var i: Int
|
||||
|
||||
var addmd = 0
|
||||
|
||||
|
||||
while (true) {
|
||||
md.reset()
|
||||
if (addmd++ > 0) {
|
||||
md.update(md_buf!!)
|
||||
}
|
||||
|
||||
md.update(data)
|
||||
if (null != salt) {
|
||||
md.update(salt, 0, 8)
|
||||
}
|
||||
|
||||
md_buf = md.digest()
|
||||
i = 1
|
||||
while (i < count) {
|
||||
md.reset()
|
||||
md.update(md_buf!!)
|
||||
md_buf = md.digest()
|
||||
i++
|
||||
}
|
||||
|
||||
i = 0
|
||||
if (nkey > 0) {
|
||||
while (true) {
|
||||
if (nkey == 0) {
|
||||
break
|
||||
}
|
||||
if (i == md_buf!!.size) {
|
||||
break
|
||||
}
|
||||
key[key_ix++] = md_buf[i]
|
||||
nkey--
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
if (niv > 0 && i != md_buf!!.size) {
|
||||
while (true) {
|
||||
if (niv == 0) {
|
||||
break
|
||||
}
|
||||
if (i == md_buf.size) {
|
||||
break
|
||||
}
|
||||
iv[iv_ix++] = md_buf[i]
|
||||
niv--
|
||||
i++
|
||||
}
|
||||
}
|
||||
if (nkey == 0 && niv == 0) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
i = 0
|
||||
while (i < md_buf!!.size) {
|
||||
md_buf[i] = 0
|
||||
i++
|
||||
}
|
||||
|
||||
return both
|
||||
}
|
||||
|
||||
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
// /usr/bin/openssl enc -d -aes-256-cbc -md sha256 -in update_file.encrypted -out update_file.bin -pass pass:xyfjWNl6yPIZfRYLu64L2sleiF8vD5xgHsJ3sa3Ya6sY01
|
||||
|
||||
// NON-PBKDF2, WITH BASE64
|
||||
// openssl enc -aes-256-cbc -a -salt -md sha256 -in password.txt -out password.txt.enc -pass pass:xyfjWNl6yPIZfRYLu64L2sleiF8vD5xgHsJ3sa3Ya6sY01
|
||||
// openssl enc -aes-256-cbc -a -d -md sha256 -in password.txt.enc -out password.txt.new -pass pass:xyfjWNl6yPIZfRYLu64L2sleiF8vD5xgHsJ3sa3Ya6sY01 && cat password.txt.new
|
||||
|
||||
// NON-PBKDF2, WITHOUT BASE64
|
||||
// openssl enc -aes-256-cbc -salt -md sha256 -in password.txt -out password.txt.enc -pass pass:xyfjWNl6yPIZfRYLu64L2sleiF8vD5xgHsJ3sa3Ya6sY01
|
||||
// openssl enc -aes-256-cbc -d -md sha256 -in password.txt.enc -out password.txt.new -pass pass:xyfjWNl6yPIZfRYLu64L2sleiF8vD5xgHsJ3sa3Ya6sY01 && cat password.txt.new
|
||||
|
||||
|
||||
|
||||
// PBKDF2 (NOT WORKING)
|
||||
// openssl aes-256-cbc -a -salt -pbkdf2 -iter 1 -md sha256 -in password.txt -out password.txt.enc -pass pass:xyfjWNl6yPIZfRYLu64L2sleiF8vD5xgHsJ3sa3Ya6sY01
|
||||
// openssl aes-256-cbc -d -a -md sha256 -pbkdf2 -iter 1 -in password.txt.enc -out password.txt.new -pass pass:xyfjWNl6yPIZfRYLu64L2sleiF8vD5xgHsJ3sa3Ya6sY01 && cat password.txt.new
|
||||
|
||||
val decrypt = false
|
||||
try {
|
||||
val password = "xyfjWNl6yPIZfRYLu64L2sleiF8vD5xgHsJ3sa3Ya6sY01"
|
||||
val passwordBytes = password.toByteArray(Charsets.US_ASCII)
|
||||
|
||||
|
||||
|
||||
// openssl enc -aes-256-cbc -d -md sha256 -in install_2019.1.bin.enc -out install_2019.1.bin.new -pass pass:xyfjWNl6yPIZfRYLu64L2sleiF8vD5xgHsJ3sa3Ya6sY01
|
||||
// val plaintextFileName = "build/install_2019.1.bin"
|
||||
// val encryptedFileName = "build/install_2019.1.bin.enc"
|
||||
// val fileOutputStream = FileOutputStream(File(encryptedFileName))
|
||||
// OpenSSLPBEOutputStream(fileOutputStream, password).use { outputStream ->
|
||||
// Files.copy(Path.of(plaintextFileName), outputStream)
|
||||
// }
|
||||
//
|
||||
// if (true) {
|
||||
// return
|
||||
// }
|
||||
|
||||
|
||||
val plaintextFileName = "password.txt"
|
||||
val encryptedFileName = "password.txt.enc"
|
||||
if (decrypt) {
|
||||
// --- read base 64 encoded file ---
|
||||
|
||||
val encryptedFile = File(encryptedFileName).absoluteFile
|
||||
|
||||
// this is WITH BASE64 (with openssl -a)
|
||||
// val lines = Files.readAllLines(encryptedFile.toPath(), Charsets.US_ASCII)
|
||||
// val sb = StringBuilder()
|
||||
// for (line in lines) {
|
||||
// sb.append(line.trim())
|
||||
// }
|
||||
// val dataBase64 = sb.toString()
|
||||
// val headerSaltAndCipherText = Base64.getDecoder().decode(dataBase64) // when base64 encoded
|
||||
|
||||
// this is NOT base64
|
||||
val headerSaltAndCipherText = encryptedFile.readBytes()
|
||||
|
||||
// --- extract salt & encrypted ---
|
||||
|
||||
// header is "Salted__", ASCII encoded, if salt is being used (the default)
|
||||
val salt = Arrays.copyOfRange(headerSaltAndCipherText, SALT_OFFSET, SALT_OFFSET + SALT_SIZE)
|
||||
val encrypted = Arrays.copyOfRange(headerSaltAndCipherText, CIPHERTEXT_OFFSET, headerSaltAndCipherText.size)
|
||||
|
||||
// --- specify cipher and digest for EVP_BytesToKey method ---
|
||||
|
||||
val aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
||||
val md = MessageDigest.getInstance("SHA-256")
|
||||
|
||||
// --- create key and IV ---
|
||||
|
||||
// the IV is useless, OpenSSL might as well have use zero's
|
||||
// val keyAndIV = EVP_BytesToKey(KEY_SIZE_BITS / java.lang.Byte.SIZE, aesCBC.blockSize, md, salt, passString.toByteArray(ASCII), ITERATIONS)
|
||||
// val key = SecretKeySpec(keyAndIV[INDEX_KEY], "AES")
|
||||
// val iv = IvParameterSpec(keyAndIV[INDEX_IV])
|
||||
/////////////////////
|
||||
// val openssl = OpenSSLPBEParametersGenerator()
|
||||
// openssl.init(passString.toByteArray(Charsets.US_ASCII), salt)
|
||||
// val keyAndIV = openssl.generateDerivedParameters(256)
|
||||
//
|
||||
// val keyBytes = Arrays.copyOfRange(keyAndIV, 0, 32)
|
||||
// val ivBytes = Arrays.copyOfRange(keyAndIV, 32, 48)
|
||||
//
|
||||
// val key = SecretKeySpec(keyBytes, "AES")
|
||||
// val iv = IvParameterSpec(ivBytes)
|
||||
//////////////////////
|
||||
md.update(passwordBytes)
|
||||
md.update(salt)
|
||||
|
||||
var hash = md.digest()
|
||||
var keyAndIV = hash.clone()
|
||||
|
||||
// 1 round
|
||||
md.update(hash)
|
||||
md.update(passwordBytes)
|
||||
md.update(salt)
|
||||
|
||||
hash = md.digest()
|
||||
keyAndIV = concat(keyAndIV, hash)
|
||||
|
||||
val keyBytes = Arrays.copyOfRange(keyAndIV, 0, 32)
|
||||
val ivBytes = Arrays.copyOfRange(keyAndIV, 32, 48)
|
||||
|
||||
val key = SecretKeySpec(keyBytes, "AES")
|
||||
val iv = IvParameterSpec(ivBytes)
|
||||
///////////////////
|
||||
// val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
|
||||
// val spec = PBEKeySpec(passString.toCharArray(), salt, ITERATIONS, KEY_SIZE_BITS)
|
||||
// val tmp = factory.generateSecret(spec)
|
||||
// val key = SecretKeySpec(tmp.encoded, "AES")
|
||||
//
|
||||
//
|
||||
// val ivBytes = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
|
||||
// val iv = IvParameterSpec(ivBytes)
|
||||
/////////////////////////
|
||||
|
||||
// --- initialize cipher instance and decrypt ---
|
||||
aesCBC.init(Cipher.DECRYPT_MODE, key, iv)
|
||||
val decrypted = aesCBC.doFinal(encrypted)
|
||||
val answer = String(decrypted, Charsets.UTF_8)
|
||||
println(answer)
|
||||
}
|
||||
else {
|
||||
// read plaintext file
|
||||
val plainTextFile = File(plaintextFileName).absoluteFile
|
||||
val data= plainTextFile.readBytes()
|
||||
|
||||
// --- create salt ---
|
||||
val salt = ByteArray(8)
|
||||
val secureRandom = SecureRandom()
|
||||
secureRandom.nextBytes(salt)
|
||||
|
||||
// --- specify cipher and digest for EVP_BytesToKey method ---
|
||||
|
||||
val aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
||||
val md = MessageDigest.getInstance("SHA-256")
|
||||
|
||||
|
||||
//////////////////////////////////////////
|
||||
// the IV is useless, OpenSSL might as well have use zero's
|
||||
// val keyAndIV = EVP_BytesToKey(KEY_SIZE_BITS / java.lang.Byte.SIZE, aesCBC.blockSize, md, salt,
|
||||
// passString.toByteArray(Charsets.US_ASCII), ITERATIONS)
|
||||
// val key = SecretKeySpec(keyAndIV[INDEX_KEY], "AES")
|
||||
// val iv = IvParameterSpec(keyAndIV[INDEX_IV])
|
||||
//////////////////////////////////////////
|
||||
md.update(passwordBytes)
|
||||
md.update(salt)
|
||||
|
||||
var hash = md.digest()
|
||||
var keyAndIV = hash.clone()
|
||||
|
||||
// 1 round
|
||||
md.update(hash)
|
||||
md.update(passwordBytes)
|
||||
md.update(salt)
|
||||
|
||||
hash = md.digest()
|
||||
keyAndIV = concat(keyAndIV, hash)
|
||||
|
||||
val keyBytes = Arrays.copyOfRange(keyAndIV, 0, 32)
|
||||
val ivBytes = Arrays.copyOfRange(keyAndIV, 32, 48)
|
||||
|
||||
val key = SecretKeySpec(keyBytes, "AES")
|
||||
val iv = IvParameterSpec(ivBytes)
|
||||
//////////////////////////////////////////
|
||||
// val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
|
||||
// val spec = PBEKeySpec(passString.toCharArray(), salt, ITERATIONS, KEY_SIZE_BITS)
|
||||
// val tmp = factory.generateSecret(spec)
|
||||
// val key = SecretKeySpec(tmp.encoded, "AES")
|
||||
//
|
||||
//
|
||||
// val ivBytes = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
|
||||
// val iv = IvParameterSpec(ivBytes)
|
||||
/////////////////////////
|
||||
|
||||
|
||||
// --- initialize cipher instance and encrypt ---
|
||||
|
||||
aesCBC.init(Cipher.ENCRYPT_MODE, key, iv)
|
||||
val encrypted = aesCBC.doFinal(data)
|
||||
|
||||
// val cipher = OpenSSLPBECommon.initializeCipher(password, salt, Cipher.ENCRYPT_MODE)
|
||||
// val encrypted = cipher.doFinal(data)
|
||||
|
||||
// "Salted__" + salt + encrypted
|
||||
val a1 = "Salted__".toByteArray(Charsets.US_ASCII)
|
||||
|
||||
val finalEncrypted = concat(concat(a1, salt), encrypted)
|
||||
|
||||
val encryptedFile = File(encryptedFileName).absoluteFile
|
||||
Files.deleteIfExists(encryptedFile.toPath())
|
||||
|
||||
// WITH BASE64 By default the encoded file has a line break every 64 characters
|
||||
// encryptedFile.writeBytes(Base64.getMimeEncoder(64, "\n".toByteArray(Charsets.US_ASCII)).encode(finalEncrypted))
|
||||
|
||||
// No base64
|
||||
encryptedFile.writeBytes(finalEncrypted)
|
||||
}
|
||||
}
|
||||
catch (e: BadPaddingException) {
|
||||
// AKA "something went wrong"
|
||||
throw IllegalStateException("Bad password, algorithm, mode or padding;" + " no salt, wrong number of iterations or corrupted ciphertext.")
|
||||
}
|
||||
catch (e: IllegalBlockSizeException) {
|
||||
throw IllegalStateException("Bad algorithm, mode or corrupted (resized) ciphertext.")
|
||||
}
|
||||
catch (e: GeneralSecurityException) {
|
||||
throw IllegalStateException(e)
|
||||
}
|
||||
catch (e: IOException) {
|
||||
throw IllegalStateException(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun concat(a: ByteArray, b: ByteArray): ByteArray {
|
||||
val c = ByteArray(a.size + b.size)
|
||||
System.arraycopy(a, 0, c, 0, a.size)
|
||||
System.arraycopy(b, 0, c, a.size, b.size)
|
||||
return c
|
||||
}
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
package dorkbox.util.crypto
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.InvalidKeyException
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.security.spec.InvalidKeySpecException
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.NoSuchPaddingException
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
|
||||
// https://stackoverflow.com/questions/11783062/how-to-decrypt-file-in-java-encrypted-with-openssl-command-using-aes/11786924#11786924
|
||||
internal object OpenSSLPBECommon {
|
||||
const val SALT_SIZE_BYTES = 8
|
||||
const val OPENSSL_HEADER_STRING = "Salted__"
|
||||
val OPENSSL_HEADER_STRING_BYTES = OPENSSL_HEADER_STRING.toByteArray(Charsets.US_ASCII)
|
||||
|
||||
private val hashDigest = MessageDigest.getInstance("SHA-256")
|
||||
|
||||
fun toByteArray(chars: CharArray): ByteArray {
|
||||
val bytes = ByteArray(chars.size)
|
||||
|
||||
for (i in bytes.indices) {
|
||||
bytes[i] = chars[i].code.toByte()
|
||||
}
|
||||
|
||||
return bytes
|
||||
}
|
||||
|
||||
@Throws(NoSuchAlgorithmException::class,
|
||||
InvalidKeySpecException::class,
|
||||
InvalidKeyException::class,
|
||||
NoSuchPaddingException::class,
|
||||
InvalidAlgorithmParameterException::class
|
||||
)
|
||||
fun initializeCipher(password: String, salt: ByteArray, cipherMode: Int): Cipher {
|
||||
val passwordBytes = password.toByteArray(Charsets.US_ASCII)
|
||||
|
||||
hashDigest.update(passwordBytes)
|
||||
hashDigest.update(salt)
|
||||
|
||||
var hash = hashDigest.digest()
|
||||
var keyAndIV = hash.clone()
|
||||
|
||||
// 1 round
|
||||
hashDigest.update(hash)
|
||||
hashDigest.update(passwordBytes)
|
||||
hashDigest.update(salt)
|
||||
|
||||
hash = hashDigest.digest()
|
||||
keyAndIV = concat(keyAndIV, hash)
|
||||
|
||||
val keyBytes = keyAndIV.copyOfRange(0, 32)
|
||||
val ivBytes = keyAndIV.copyOfRange(32, 48)
|
||||
|
||||
val key = SecretKeySpec(keyBytes, "AES")
|
||||
val iv = IvParameterSpec(ivBytes)
|
||||
|
||||
|
||||
val cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING")
|
||||
cipher.init(cipherMode, key, iv);
|
||||
|
||||
return cipher
|
||||
}
|
||||
|
||||
private
|
||||
fun concat(a: ByteArray, b: ByteArray): ByteArray {
|
||||
val c = ByteArray(a.size + b.size)
|
||||
System.arraycopy(a, 0, c, 0, a.size)
|
||||
System.arraycopy(b, 0, c, a.size, b.size)
|
||||
return c
|
||||
}
|
||||
}
|
|
@ -1,104 +0,0 @@
|
|||
package dorkbox.util.crypto
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.InvalidKeyException
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.security.spec.InvalidKeySpecException
|
||||
|
||||
import javax.crypto.BadPaddingException
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.IllegalBlockSizeException
|
||||
import javax.crypto.NoSuchPaddingException
|
||||
import kotlin.experimental.and
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
// https://stackoverflow.com/questions/11783062/how-to-decrypt-file-in-java-encrypted-with-openssl-command-using-aes/11786924#11786924
|
||||
class OpenSSLPBEInputStream @Throws(IOException::class)
|
||||
constructor(private val inStream: InputStream, password: String) : InputStream() {
|
||||
companion object {
|
||||
private const val READ_BLOCK_SIZE = 64 * 1024
|
||||
}
|
||||
|
||||
private val cipher: Cipher
|
||||
private val bufferCipher = ByteArray(READ_BLOCK_SIZE)
|
||||
|
||||
private var bufferClear: ByteArray? = null
|
||||
|
||||
private var index = Integer.MAX_VALUE
|
||||
private var maxIndex = 0
|
||||
|
||||
init {
|
||||
try {
|
||||
val salt = readSalt()
|
||||
cipher = OpenSSLPBECommon.initializeCipher(password, salt, Cipher.DECRYPT_MODE)
|
||||
}
|
||||
catch (e: InvalidKeySpecException) {
|
||||
throw IOException(e)
|
||||
}
|
||||
catch (e: NoSuchPaddingException) {
|
||||
throw IOException(e)
|
||||
}
|
||||
catch (e: NoSuchAlgorithmException) {
|
||||
throw IOException(e)
|
||||
}
|
||||
catch (e: InvalidKeyException) {
|
||||
throw IOException(e)
|
||||
}
|
||||
catch (e: InvalidAlgorithmParameterException) {
|
||||
throw IOException(e)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun readSalt(): ByteArray {
|
||||
|
||||
val headerBytes = ByteArray(OpenSSLPBECommon.OPENSSL_HEADER_STRING.length)
|
||||
inStream.read(headerBytes)
|
||||
val headerString = String(headerBytes, Charsets.US_ASCII)
|
||||
|
||||
if (OpenSSLPBECommon.OPENSSL_HEADER_STRING != headerString) {
|
||||
throw IOException("unexpected magic bytes $headerString")
|
||||
}
|
||||
|
||||
val salt = ByteArray(OpenSSLPBECommon.SALT_SIZE_BYTES)
|
||||
inStream.read(salt)
|
||||
|
||||
return salt
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun read(): Int {
|
||||
if (index > maxIndex) {
|
||||
index = 0
|
||||
val read = inStream.read(bufferCipher)
|
||||
if (read != -1) {
|
||||
bufferClear = cipher.update(bufferCipher, 0, read)
|
||||
}
|
||||
if (read == -1 || bufferClear == null || bufferClear!!.isEmpty()) {
|
||||
try {
|
||||
bufferClear = cipher.doFinal()
|
||||
}
|
||||
catch (e: IllegalBlockSizeException) {
|
||||
bufferClear = null
|
||||
}
|
||||
catch (e: BadPaddingException) {
|
||||
bufferClear = null
|
||||
}
|
||||
|
||||
}
|
||||
if (bufferClear == null || bufferClear!!.isEmpty()) {
|
||||
return -1
|
||||
}
|
||||
maxIndex = bufferClear!!.size - 1
|
||||
}
|
||||
|
||||
return (bufferClear!![index++] and 0xFF.toByte()).toInt()
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun available(): Int {
|
||||
return inStream.available()
|
||||
}
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
package dorkbox.util.crypto
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.security.SecureRandom
|
||||
import java.security.spec.InvalidKeySpecException
|
||||
import javax.crypto.BadPaddingException
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.IllegalBlockSizeException
|
||||
import javax.crypto.NoSuchPaddingException
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
// https://stackoverflow.com/questions/11783062/how-to-decrypt-file-in-java-encrypted-with-openssl-command-using-aes/11786924#11786924
|
||||
class OpenSSLPBEOutputStream @Throws(IOException::class)
|
||||
constructor(private val outStream: OutputStream, password: String) : OutputStream() {
|
||||
companion object {
|
||||
private const val BUFFER_SIZE = 5 * 1024 * 1024
|
||||
}
|
||||
|
||||
private val cipher: Cipher
|
||||
private val buffer = ByteArray(BUFFER_SIZE)
|
||||
private var bufferIndex: Int = 0
|
||||
|
||||
init {
|
||||
try {
|
||||
// Create and use a random SALT for each instance of this output stream.
|
||||
val salt = ByteArray(OpenSSLPBECommon.SALT_SIZE_BYTES)
|
||||
val secureRandom = SecureRandom()
|
||||
secureRandom.nextBytes(salt)
|
||||
cipher = OpenSSLPBECommon.initializeCipher(password, salt, Cipher.ENCRYPT_MODE)
|
||||
|
||||
// Write header
|
||||
outStream.write(OpenSSLPBECommon.OPENSSL_HEADER_STRING_BYTES)
|
||||
outStream.write(salt)
|
||||
}
|
||||
catch (e: InvalidKeySpecException) {
|
||||
throw IOException(e)
|
||||
}
|
||||
catch (e: NoSuchPaddingException) {
|
||||
throw IOException(e)
|
||||
}
|
||||
catch (e: NoSuchAlgorithmException) {
|
||||
throw IOException(e)
|
||||
}
|
||||
catch (e: InvalidAlgorithmParameterException) {
|
||||
throw IOException(e)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun write(b: Int) {
|
||||
buffer[bufferIndex] = b.toByte()
|
||||
bufferIndex++
|
||||
|
||||
// only update the digest and write out the buffer if it's enough (this is a slow operation)
|
||||
if (bufferIndex == BUFFER_SIZE) {
|
||||
val result = cipher.update(buffer, 0, bufferIndex)
|
||||
outStream.write(result)
|
||||
bufferIndex = 0
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun flush() {
|
||||
if (bufferIndex > 0) {
|
||||
val result: ByteArray
|
||||
try {
|
||||
result = cipher.doFinal(buffer, 0, bufferIndex)
|
||||
outStream.write(result)
|
||||
}
|
||||
catch (e: IllegalBlockSizeException) {
|
||||
throw IOException(e)
|
||||
}
|
||||
catch (e: BadPaddingException) {
|
||||
throw IOException(e)
|
||||
}
|
||||
|
||||
bufferIndex = 0
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun close() {
|
||||
flush()
|
||||
outStream.close()
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
/*
|
||||
* Copyright 2021 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.util.crypto;
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
* Copyright 2013 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto.signers;
|
||||
|
||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
|
||||
import org.bouncycastle.crypto.Digest;
|
||||
import org.bouncycastle.crypto.Signer;
|
||||
import org.bouncycastle.crypto.signers.DSADigestSigner;
|
||||
import org.bouncycastle.crypto.signers.ECDSASigner;
|
||||
import org.bouncycastle.jcajce.provider.util.DigestFactory;
|
||||
import org.bouncycastle.operator.OperatorCreationException;
|
||||
import org.bouncycastle.operator.bc.BcContentSignerBuilder;
|
||||
|
||||
public
|
||||
class BcECDSAContentSignerBuilder extends BcContentSignerBuilder {
|
||||
|
||||
public
|
||||
BcECDSAContentSignerBuilder(AlgorithmIdentifier sigAlgId, AlgorithmIdentifier digAlgId) {
|
||||
super(sigAlgId, digAlgId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected
|
||||
Signer createSigner(AlgorithmIdentifier sigAlgId, AlgorithmIdentifier digAlgId) throws OperatorCreationException {
|
||||
Digest digest = DigestFactory.getDigest(digAlgId.getAlgorithm()
|
||||
.getId()); // SHA1, SHA512, etc
|
||||
|
||||
return new DSADigestSigner(new ECDSASigner(), digest);
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
* Copyright 2013 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto.signers;
|
||||
|
||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
|
||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
||||
import org.bouncycastle.crypto.Digest;
|
||||
import org.bouncycastle.crypto.Signer;
|
||||
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
|
||||
import org.bouncycastle.crypto.signers.DSADigestSigner;
|
||||
import org.bouncycastle.crypto.signers.ECDSASigner;
|
||||
import org.bouncycastle.crypto.util.PublicKeyFactory;
|
||||
import org.bouncycastle.jcajce.provider.util.DigestFactory;
|
||||
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
|
||||
import org.bouncycastle.operator.DigestAlgorithmIdentifierFinder;
|
||||
import org.bouncycastle.operator.OperatorCreationException;
|
||||
import org.bouncycastle.operator.bc.BcContentVerifierProviderBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public
|
||||
class BcECDSAContentVerifierProviderBuilder extends BcContentVerifierProviderBuilder {
|
||||
|
||||
public
|
||||
BcECDSAContentVerifierProviderBuilder(DigestAlgorithmIdentifierFinder digestAlgorithmFinder) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected
|
||||
AsymmetricKeyParameter extractKeyParameters(SubjectPublicKeyInfo publicKeyInfo) throws IOException {
|
||||
return PublicKeyFactory.createKey(publicKeyInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected
|
||||
Signer createSigner(AlgorithmIdentifier sigAlgId) throws OperatorCreationException {
|
||||
AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
|
||||
|
||||
Digest digest = DigestFactory.getDigest(digAlgId.getAlgorithm()
|
||||
.getId()); // 1.2.23.4.5.6, etc
|
||||
return new DSADigestSigner(new ECDSASigner(), digest);
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
/*
|
||||
* Copyright 2021 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.util.crypto.signers;
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -15,10 +15,16 @@
|
|||
*/
|
||||
package dorkbox.util.entropy
|
||||
|
||||
import dorkbox.util.Sys
|
||||
import dorkbox.util.exceptions.InitializationException
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
object Entropy {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = Sys.version
|
||||
|
||||
private var provider: EntropyProvider? = null
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,165 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.properties;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
import dorkbox.util.FileUtil;
|
||||
|
||||
public
|
||||
class PropertiesProvider {
|
||||
|
||||
private final Properties properties = new SortedProperties();
|
||||
private final File propertiesFile;
|
||||
private String comments = "Settings and configuration file. Strings must be escape formatted!";
|
||||
|
||||
public
|
||||
PropertiesProvider(String propertiesFile) {
|
||||
this(new File(propertiesFile));
|
||||
}
|
||||
|
||||
public
|
||||
PropertiesProvider(File propertiesFile) {
|
||||
if (propertiesFile == null) {
|
||||
throw new NullPointerException("propertiesFile");
|
||||
}
|
||||
|
||||
propertiesFile = FileUtil.INSTANCE.normalize(propertiesFile);
|
||||
// make sure the parent dir exists...
|
||||
File parentFile = propertiesFile.getParentFile();
|
||||
if (parentFile != null && !parentFile.exists()) {
|
||||
if (!parentFile.mkdirs()) {
|
||||
throw new RuntimeException("Unable to create directories for: " + propertiesFile);
|
||||
}
|
||||
}
|
||||
|
||||
this.propertiesFile = propertiesFile;
|
||||
|
||||
_load();
|
||||
}
|
||||
|
||||
public
|
||||
void setComments(String comments) {
|
||||
this.comments = comments;
|
||||
}
|
||||
|
||||
private
|
||||
void _load() {
|
||||
if (!this.propertiesFile.canRead() || !this.propertiesFile.exists()) {
|
||||
// in this case, our properties file doesn't exist yet... create one!
|
||||
_save();
|
||||
}
|
||||
|
||||
try {
|
||||
FileInputStream fis = new FileInputStream(this.propertiesFile);
|
||||
this.properties.load(fis);
|
||||
fis.close();
|
||||
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
// oops!
|
||||
System.err.println("Properties cannot load!");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private
|
||||
void _save() {
|
||||
try {
|
||||
FileOutputStream fos = new FileOutputStream(this.propertiesFile);
|
||||
this.properties.store(fos, this.comments);
|
||||
fos.flush();
|
||||
fos.close();
|
||||
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
System.err.println("Properties cannot save!");
|
||||
} catch (IOException e) {
|
||||
// oops!
|
||||
System.err.println("Properties cannot save!");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public final synchronized
|
||||
void remove(final String key) {
|
||||
this.properties.remove(key);
|
||||
_save();
|
||||
}
|
||||
|
||||
@SuppressWarnings("AutoBoxing")
|
||||
public final synchronized
|
||||
void save(final String key, Object value) {
|
||||
if (key == null || value == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (value instanceof Color) {
|
||||
value = ((Color) value).getRGB();
|
||||
}
|
||||
|
||||
this.properties.setProperty(key, value.toString());
|
||||
|
||||
_save();
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "AutoUnboxing"})
|
||||
public synchronized
|
||||
<T> T get(String key, Class<T> clazz) {
|
||||
if (key == null || clazz == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String property = this.properties.getProperty(key);
|
||||
if (property == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// special cases
|
||||
try {
|
||||
if (clazz.equals(Integer.class)) {
|
||||
return (T) Integer.valueOf(Integer.parseInt(property));
|
||||
}
|
||||
if (clazz.equals(Long.class)) {
|
||||
return (T) Long.valueOf(Long.parseLong(property));
|
||||
}
|
||||
if (clazz.equals(Color.class)) {
|
||||
return (T) new Color(Integer.parseInt(property), true);
|
||||
}
|
||||
|
||||
else {
|
||||
return (T) property;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Properties Loader for property: " + key + System.getProperty("line.separator") + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
String toString() {
|
||||
return "PropertiesProvider [" + this.propertiesFile + "]";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.properties
|
||||
|
||||
import java.awt.Color
|
||||
import java.io.*
|
||||
import java.util.*
|
||||
|
||||
class PropertiesProvider(propertiesFile: File) {
|
||||
private val properties: Properties = SortedProperties()
|
||||
private val propertiesFile: File
|
||||
private var comments = "Settings and configuration file. Strings must be escape formatted!"
|
||||
|
||||
constructor(propertiesFile: String) : this(File(propertiesFile))
|
||||
|
||||
init {
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val propertiesFile = propertiesFile.normalize()
|
||||
|
||||
// make sure the parent dir exists...
|
||||
val parentFile = propertiesFile.parentFile
|
||||
if (parentFile != null && !parentFile.exists()) {
|
||||
if (!parentFile.mkdirs()) {
|
||||
throw RuntimeException("Unable to create directories for: $propertiesFile")
|
||||
}
|
||||
}
|
||||
|
||||
this.propertiesFile = propertiesFile
|
||||
_load()
|
||||
}
|
||||
|
||||
fun setComments(comments: String) {
|
||||
this.comments = comments
|
||||
}
|
||||
|
||||
private fun _load() {
|
||||
if (!propertiesFile.canRead() || !propertiesFile.exists()) {
|
||||
// in this case, our properties file doesn't exist yet... create one!
|
||||
_save()
|
||||
}
|
||||
|
||||
try {
|
||||
val fis = FileInputStream(propertiesFile)
|
||||
properties.load(fis)
|
||||
fis.close()
|
||||
} catch (e: FileNotFoundException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IOException) {
|
||||
// oops!
|
||||
System.err.println("Properties cannot load!")
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
private fun _save() {
|
||||
try {
|
||||
val fos = FileOutputStream(propertiesFile)
|
||||
properties.store(fos, comments)
|
||||
fos.flush()
|
||||
fos.close()
|
||||
} catch (e: FileNotFoundException) {
|
||||
e.printStackTrace()
|
||||
System.err.println("Properties cannot save!")
|
||||
} catch (e: IOException) {
|
||||
// oops!
|
||||
System.err.println("Properties cannot save!")
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun remove(key: String) {
|
||||
properties.remove(key)
|
||||
_save()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun save(key: String?, value: Any?) {
|
||||
@Suppress("NAME_SHADOWING")
|
||||
var value = value
|
||||
if (key == null || value == null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (value is Color) {
|
||||
value = value.rgb
|
||||
}
|
||||
|
||||
properties.setProperty(key, value.toString())
|
||||
_save()
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@Synchronized
|
||||
operator fun <T> get(key: String?, clazz: Class<T>?): T? {
|
||||
if (key == null || clazz == null) {
|
||||
return null
|
||||
}
|
||||
val property = properties.getProperty(key) ?: return null
|
||||
|
||||
// special cases
|
||||
return try {
|
||||
if (clazz == Int::class.java) {
|
||||
return Integer.valueOf(property.toInt()) as T
|
||||
}
|
||||
if (clazz == Long::class.java) {
|
||||
return java.lang.Long.valueOf(property.toLong()) as T
|
||||
}
|
||||
if (clazz == Color::class.java) {
|
||||
Color(property.toInt(), true) as T
|
||||
} else {
|
||||
property as T
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException("Properties Loader for property: " + key + System.getProperty("line.separator") + e.message)
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "PropertiesProvider [" + propertiesFile + "]"
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.properties;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public
|
||||
class SortedProperties extends Properties {
|
||||
|
||||
private static final long serialVersionUID = 3988064683926999433L;
|
||||
|
||||
private final Comparator<Object> compare = new Comparator<Object>() {
|
||||
@Override
|
||||
public
|
||||
int compare(Object o1, Object o2) {
|
||||
return o1.toString()
|
||||
.compareTo(o2.toString());
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public synchronized
|
||||
Enumeration<Object> keys() {
|
||||
Enumeration<Object> keysEnum = super.keys();
|
||||
|
||||
Vector<Object> vector = new Vector<Object>(size());
|
||||
for (; keysEnum.hasMoreElements(); ) {
|
||||
vector.add(keysEnum.nextElement());
|
||||
}
|
||||
|
||||
Collections.sort(vector, this.compare);
|
||||
|
||||
return vector.elements();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.properties
|
||||
|
||||
import dorkbox.util.Sys
|
||||
import java.util.*
|
||||
|
||||
class SortedProperties : Properties() {
|
||||
private val compare = Comparator<Any> { o1, o2 ->
|
||||
o1.toString().compareTo(o2.toString())
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun keys(): Enumeration<Any> {
|
||||
val keysEnum = super.keys()
|
||||
val vector: Vector<Any> = Vector<Any>(this.size)
|
||||
|
||||
while (keysEnum.hasMoreElements()) {
|
||||
vector.add(keysEnum.nextElement())
|
||||
}
|
||||
|
||||
Collections.sort(vector, compare)
|
||||
return vector.elements()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val serialVersionUID = 3988064683926999433L
|
||||
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = Sys.version
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2018 Venkat Peri
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.util.sync
|
||||
|
||||
import dorkbox.util.Sys
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.TimeoutCancellationException
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import java.util.concurrent.*
|
||||
|
||||
/**
|
||||
* @param initialCount this is the initial count specified when the latch was created
|
||||
*/
|
||||
open class AbstractLatch(val initialCount: Int, val trigger: Trigger) : Deferred<Unit> by trigger {
|
||||
companion object {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = Sys.version
|
||||
}
|
||||
|
||||
/**
|
||||
* The current latch count affected by the count*() methods.
|
||||
*
|
||||
* Can be manually changed
|
||||
*/
|
||||
var count: Int
|
||||
get() { return trigger.get() }
|
||||
set(value) { trigger.set(value) }
|
||||
|
||||
/**
|
||||
* Waits the specified amount of time for the latch to reach 0
|
||||
*
|
||||
* @return true if the latch reached 0 before the timeout
|
||||
*/
|
||||
suspend fun await(timeMillis: Long): Boolean {
|
||||
return await(timeMillis, TimeUnit.MILLISECONDS)
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits the specified amount of time for the latch to reach 0
|
||||
*
|
||||
* @return true if the latch reached 0 before the timeout
|
||||
*/
|
||||
suspend fun await(time: Long, timeUnit: TimeUnit): Boolean {
|
||||
return try {
|
||||
withTimeout(timeUnit.toMillis(time)) {
|
||||
await()
|
||||
true
|
||||
}
|
||||
} catch (exception: TimeoutCancellationException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2018 Venkat Peri
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.util.sync
|
||||
|
||||
import kotlinx.coroutines.Job
|
||||
|
||||
/**
|
||||
* A synchronization aid that allows one or more coroutines to wait
|
||||
* without blocking until a set of operations being performed in other
|
||||
* coroutines complete.
|
||||
*
|
||||
* A [CountDownLatch] is initialized with a given count. The
|
||||
* [await] methods block until the current count reaches zero due to
|
||||
* invocations of the [countDown] method, after which all waiting coroutines
|
||||
* are released and any subsequent invocations of await return immediately.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* val count = 9L
|
||||
* val latch = CountDownLatch(count)
|
||||
* val counter = AtomicLong(0)
|
||||
*
|
||||
* runBlocking {
|
||||
* (0 until count).forEach {
|
||||
* async {
|
||||
* delay(ThreadLocalRandom.current().nextInt(100, 500))
|
||||
* counter.incrementAndGet()
|
||||
* latch.countDown()
|
||||
* }
|
||||
* }
|
||||
* latch.await()
|
||||
* assertEquals(count, counter.get())
|
||||
* println(counter.get()) //=> 9
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Once the latch is released, you must re-create this object to re-create the latch
|
||||
*
|
||||
* @constructor Constructs a [CountDownLatch] initialized with the given count.
|
||||
* @param count the number of times [countDown] must be invoked before
|
||||
* [await] will not block.
|
||||
*/
|
||||
class CountDownLatch(count: Int, parent: Job? = null) : AbstractLatch(count, Trigger(count, true, parent)) {
|
||||
|
||||
init {
|
||||
require(count >= 0) { "Count $count cannot be negative" }
|
||||
}
|
||||
|
||||
fun countDown() {
|
||||
trigger.decrement()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun withCountDown(count: Int, parent: Job? = null, block: suspend CountDownLatch.() -> Unit) {
|
||||
val latch = CountDownLatch(count, parent)
|
||||
block(latch)
|
||||
latch.await()
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2018 Venkat Peri
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.util.sync
|
||||
|
||||
import kotlinx.coroutines.Job
|
||||
|
||||
/**
|
||||
* Like a [CountDownLatch] but the count can be increased via [countUp]. At zero, the latch will release.
|
||||
*
|
||||
* Once the latch is released, you must re-create this object to re-create the latch
|
||||
*/
|
||||
class CountingLatch(count: Int = 0, parent: Job? = null) : AbstractLatch(count, Trigger(count, true, parent)) {
|
||||
|
||||
fun countDown() {
|
||||
trigger.decrement()
|
||||
}
|
||||
|
||||
fun countUp() {
|
||||
trigger.increment()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2018 Venkat Peri
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.util.sync
|
||||
|
||||
import kotlinx.coroutines.Job
|
||||
|
||||
/**
|
||||
* Like a [CountDownLatch] but the count can be increased via [countUp]. At a NON-ZERO number, the latch will release.
|
||||
*
|
||||
* Once the latch is released, you must re-create this object to re-create the latch
|
||||
*/
|
||||
class CountingLatchInverse(count: Int = 0, parent: Job? = null) : AbstractLatch(count, Trigger(count, false, parent)) {
|
||||
fun countDown() {
|
||||
trigger.decrement()
|
||||
}
|
||||
|
||||
fun countUp() {
|
||||
trigger.increment()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2018 Venkat Peri
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.util.sync
|
||||
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.Job
|
||||
import java.util.concurrent.atomic.*
|
||||
|
||||
class Trigger(
|
||||
initial: Int, private val releaseLatchOnZero: Boolean = true,
|
||||
parent: Job? = null
|
||||
) : CompletableDeferred<Unit> by CompletableDeferred(parent) {
|
||||
|
||||
private val value = AtomicInteger(initial)
|
||||
|
||||
init {
|
||||
validate {
|
||||
if (releaseLatchOnZero && initial == 0) {
|
||||
0
|
||||
}
|
||||
else if (!releaseLatchOnZero && initial != 0) {
|
||||
1
|
||||
} else {
|
||||
initial
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun increment(): Int = validate { value.incrementAndGet() }
|
||||
|
||||
fun decrement(): Int = validate { value.decrementAndGet() }
|
||||
|
||||
fun set(value: Int): Int = validate { this.value.getAndSet(value) }
|
||||
|
||||
fun get(): Int = value.get()
|
||||
|
||||
private fun validate(block: () -> Int): Int {
|
||||
val v = block()
|
||||
if (!isCompleted && (
|
||||
(releaseLatchOnZero && v == 0) ||
|
||||
(!releaseLatchOnZero && v != 0)
|
||||
)) {
|
||||
complete(Unit)
|
||||
}
|
||||
return v
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2021 dorkbox, llc
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.util.classes;
|
||||
package dorkbox.util.sync;
|
||||
|
||||
/**
|
||||
* Required for intellij to not complain regarding `module-info` for a multi-release jar.
|
|
@ -2,24 +2,18 @@ module dorkbox.utilities {
|
|||
exports dorkbox.exit;
|
||||
exports dorkbox.urlHandler;
|
||||
exports dorkbox.util;
|
||||
exports dorkbox.util.classes;
|
||||
exports dorkbox.util.crypto;
|
||||
exports dorkbox.util.crypto.signers;
|
||||
exports dorkbox.util.entropy;
|
||||
exports dorkbox.util.exceptions;
|
||||
exports dorkbox.util.gwt;
|
||||
exports dorkbox.util.properties;
|
||||
exports dorkbox.util.sync;
|
||||
exports dorkbox.util.userManagement;
|
||||
|
||||
requires transitive dorkbox.executor;
|
||||
requires transitive dorkbox.updates;
|
||||
requires transitive dorkbox.os;
|
||||
|
||||
requires transitive kotlin.stdlib;
|
||||
requires kotlinx.coroutines.core.jvm;
|
||||
|
||||
requires static com.sun.jna;
|
||||
requires static com.sun.jna.platform;
|
||||
requires transitive kotlinx.coroutines.core;
|
||||
|
||||
requires static org.slf4j;
|
||||
}
|
||||
|
|
|
@ -1,277 +0,0 @@
|
|||
/*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Random;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class MersenneTwisterFastTest {
|
||||
|
||||
@Test
|
||||
public void mersenneTwisterTest() throws IOException {
|
||||
int j;
|
||||
|
||||
MersenneTwisterFast r;
|
||||
|
||||
// CORRECTNESS TEST
|
||||
// COMPARE WITH
|
||||
// http://www.math.keio.ac.jp/matumoto/CODES/MT2002/mt19937ar.out
|
||||
|
||||
r = new MersenneTwisterFast(new int[] {0x123,0x234,0x345,0x456});
|
||||
// System.out.println("Output of MersenneTwisterFast with new (2002/1/26) seeding mechanism");
|
||||
for (j = 0; j < 1000; j++) {
|
||||
// first, convert the int from signed to "unsigned"
|
||||
long l = r.nextInt();
|
||||
if (l < 0) {
|
||||
l += 4294967296L; // max int value
|
||||
}
|
||||
String s = String.valueOf(l);
|
||||
while (s.length() < 10) {
|
||||
s = " " + s; // buffer
|
||||
}
|
||||
System.out.print(s + " ");
|
||||
if (j % 5 == 4) {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
|
||||
// SPEED TEST
|
||||
|
||||
final long SEED = 4357;
|
||||
|
||||
int xx;
|
||||
long ms;
|
||||
System.out.println("\nTime to test grabbing 100000000 ints");
|
||||
|
||||
Random rr = new Random(SEED);
|
||||
xx = 0;
|
||||
ms = System.currentTimeMillis();
|
||||
for (j = 0; j < 100000000; j++) {
|
||||
xx += rr.nextInt();
|
||||
}
|
||||
System.out.println("java.util.Random: " + (System.currentTimeMillis() - ms) + " Ignore this: " + xx);
|
||||
|
||||
r = new MersenneTwisterFast(SEED);
|
||||
ms = System.currentTimeMillis();
|
||||
xx = 0;
|
||||
for (j = 0; j < 100000000; j++) {
|
||||
xx += r.nextInt();
|
||||
}
|
||||
System.out.println("Mersenne Twister Fast: " + (System.currentTimeMillis() - ms) + " Ignore this: "
|
||||
+ xx);
|
||||
|
||||
// TEST TO COMPARE TYPE CONVERSION BETWEEN
|
||||
// MersenneTwisterFast.java AND MersenneTwister.java
|
||||
|
||||
boolean test = false;
|
||||
System.out.println("\nGrab the first 1000 booleans");
|
||||
ms = System.currentTimeMillis();
|
||||
r = new MersenneTwisterFast(SEED);
|
||||
for (j = 0; j < 1000; j++) {
|
||||
// System.out.print(r.nextBoolean() + " ");
|
||||
test = r.nextBoolean();
|
||||
if (j % 8 == 7) {
|
||||
// System.out.println();
|
||||
test = false;
|
||||
}
|
||||
}
|
||||
if (!(j % 8 == 7)) {
|
||||
// System.out.println();
|
||||
test = true;
|
||||
}
|
||||
|
||||
System.out.println("Mersenne Twister Fast: " + (System.currentTimeMillis() - ms) + " Ignore this: "
|
||||
+ xx + "" + test);
|
||||
|
||||
System.out.println("\nGrab 1000 booleans of increasing probability using nextBoolean(double)");
|
||||
r = new MersenneTwisterFast(SEED);
|
||||
ms = System.currentTimeMillis();
|
||||
for (j = 0; j < 1000; j++) {
|
||||
// System.out.print(r.nextBoolean(j / 999.0) + " ");
|
||||
test = r.nextBoolean(j / 999.0);
|
||||
if (j % 8 == 7) {
|
||||
// System.out.println();
|
||||
test = false;
|
||||
}
|
||||
}
|
||||
if (!(j % 8 == 7)) {
|
||||
// System.out.println();
|
||||
test = true;
|
||||
}
|
||||
|
||||
System.out.println("Mersenne Twister Fast: " + (System.currentTimeMillis() - ms) + " Ignore this: "
|
||||
+ xx + "" + test);
|
||||
|
||||
System.out.println("\nGrab 1000 booleans of increasing probability using nextBoolean(float)");
|
||||
r = new MersenneTwisterFast(SEED);
|
||||
ms = System.currentTimeMillis();
|
||||
for (j = 0; j < 1000; j++) {
|
||||
// System.out.print(r.nextBoolean(j / 999.0f) + " ");
|
||||
test = r.nextBoolean(j / 999.0f);
|
||||
if (j % 8 == 7) {
|
||||
test = false;
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
if (!(j % 8 == 7)) {
|
||||
// System.out.println();
|
||||
test = true;
|
||||
}
|
||||
|
||||
System.out.println("Mersenne Twister Fast: " + (System.currentTimeMillis() - ms) + " Ignore this: "
|
||||
+ xx + "" + test);
|
||||
|
||||
|
||||
byte[] bytes = new byte[1000];
|
||||
System.out.println("\nGrab the first 1000 bytes using nextBytes");
|
||||
r = new MersenneTwisterFast(SEED);
|
||||
r.nextBytes(bytes);
|
||||
for (j = 0; j < 1000; j++) {
|
||||
System.out.print(bytes[j] + " ");
|
||||
if (j % 16 == 15) {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
if (!(j % 16 == 15)) {
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
byte b;
|
||||
System.out.println("\nGrab the first 1000 bytes -- must be same as nextBytes");
|
||||
r = new MersenneTwisterFast(SEED);
|
||||
for (j = 0; j < 1000; j++) {
|
||||
System.out.print((b = r.nextByte()) + " ");
|
||||
if (b != bytes[j]) {
|
||||
System.out.print("BAD ");
|
||||
}
|
||||
if (j % 16 == 15) {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
if (!(j % 16 == 15)) {
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
System.out.println("\nGrab the first 1000 shorts");
|
||||
r = new MersenneTwisterFast(SEED);
|
||||
for (j = 0; j < 1000; j++) {
|
||||
System.out.print(r.nextShort() + " ");
|
||||
if (j % 8 == 7) {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
if (!(j % 8 == 7)) {
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
System.out.println("\nGrab the first 1000 ints");
|
||||
r = new MersenneTwisterFast(SEED);
|
||||
for (j = 0; j < 1000; j++) {
|
||||
System.out.print(r.nextInt() + " ");
|
||||
if (j % 4 == 3) {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
if (!(j % 4 == 3)) {
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
System.out.println("\nGrab the first 1000 ints of different sizes");
|
||||
r = new MersenneTwisterFast(SEED);
|
||||
int max = 1;
|
||||
for (j = 0; j < 1000; j++) {
|
||||
System.out.print(r.nextInt(max) + " ");
|
||||
max *= 2;
|
||||
if (max <= 0) {
|
||||
max = 1;
|
||||
}
|
||||
if (j % 4 == 3) {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
if (!(j % 4 == 3)) {
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
System.out.println("\nGrab the first 1000 longs");
|
||||
r = new MersenneTwisterFast(SEED);
|
||||
for (j = 0; j < 1000; j++) {
|
||||
System.out.print(r.nextLong() + " ");
|
||||
if (j % 3 == 2) {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
if (!(j % 3 == 2)) {
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
System.out.println("\nGrab the first 1000 longs of different sizes");
|
||||
r = new MersenneTwisterFast(SEED);
|
||||
long max2 = 1;
|
||||
for (j = 0; j < 1000; j++) {
|
||||
System.out.print(r.nextLong(max2) + " ");
|
||||
max2 *= 2;
|
||||
if (max2 <= 0) {
|
||||
max2 = 1;
|
||||
}
|
||||
if (j % 4 == 3) {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
if (!(j % 4 == 3)) {
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
System.out.println("\nGrab the first 1000 floats");
|
||||
r = new MersenneTwisterFast(SEED);
|
||||
for (j = 0; j < 1000; j++) {
|
||||
System.out.print(r.nextFloat() + " ");
|
||||
if (j % 4 == 3) {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
if (!(j % 4 == 3)) {
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
System.out.println("\nGrab the first 1000 doubles");
|
||||
r = new MersenneTwisterFast(SEED);
|
||||
for (j = 0; j < 1000; j++) {
|
||||
System.out.print(r.nextDouble() + " ");
|
||||
if (j % 3 == 2) {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
if (!(j % 3 == 2)) {
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
System.out.println("\nGrab the first 1000 gaussian doubles");
|
||||
r = new MersenneTwisterFast(SEED);
|
||||
for (j = 0; j < 1000; j++) {
|
||||
System.out.print(r.nextGaussian() + " ");
|
||||
if (j % 3 == 2) {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
if (!(j % 3 == 2)) {
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,292 @@
|
|||
/*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util
|
||||
|
||||
import org.junit.Test
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
class MersenneTwisterFastTest {
|
||||
@Test
|
||||
@Throws(IOException::class)
|
||||
fun mersenneTwisterTest() {
|
||||
var j: Int
|
||||
var r: MersenneTwisterFast
|
||||
|
||||
// CORRECTNESS TEST
|
||||
// COMPARE WITH
|
||||
// http://www.math.keio.ac.jp/matumoto/CODES/MT2002/mt19937ar.out
|
||||
r = MersenneTwisterFast(intArrayOf(0x123, 0x234, 0x345, 0x456))
|
||||
// System.out.println("Output of MersenneTwisterFast with new (2002/1/26) seeding mechanism");
|
||||
j = 0
|
||||
while (j < 1000) {
|
||||
|
||||
// first, convert the int from signed to "unsigned"
|
||||
var l = r.nextInt().toLong()
|
||||
if (l < 0) {
|
||||
l += 4294967296L // max int value
|
||||
}
|
||||
var s = l.toString()
|
||||
while (s.length < 10) {
|
||||
s = " $s" // buffer
|
||||
}
|
||||
print("$s ")
|
||||
if (j % 5 == 4) {
|
||||
println()
|
||||
}
|
||||
j++
|
||||
}
|
||||
|
||||
// SPEED TEST
|
||||
val SEED: Long = 4357
|
||||
var xx: Int
|
||||
var ms: Long
|
||||
println("\nTime to test grabbing 100000000 ints")
|
||||
val rr = Random(SEED)
|
||||
xx = 0
|
||||
ms = System.currentTimeMillis()
|
||||
j = 0
|
||||
while (j < 100000000) {
|
||||
xx += rr.nextInt()
|
||||
j++
|
||||
}
|
||||
println("java.util.Random: " + (System.currentTimeMillis() - ms) + " Ignore this: " + xx)
|
||||
r = MersenneTwisterFast(SEED)
|
||||
ms = System.currentTimeMillis()
|
||||
xx = 0
|
||||
j = 0
|
||||
while (j < 100000000) {
|
||||
xx += r.nextInt()
|
||||
j++
|
||||
}
|
||||
println(
|
||||
"Mersenne Twister Fast: " + (System.currentTimeMillis() - ms) + " Ignore this: " + xx
|
||||
)
|
||||
|
||||
// TEST TO COMPARE TYPE CONVERSION BETWEEN
|
||||
// MersenneTwisterFast.java AND MersenneTwister.java
|
||||
var test = false
|
||||
println("\nGrab the first 1000 booleans")
|
||||
ms = System.currentTimeMillis()
|
||||
r = MersenneTwisterFast(SEED)
|
||||
j = 0
|
||||
while (j < 1000) {
|
||||
|
||||
// System.out.print(r.nextBoolean() + " ");
|
||||
test = r.nextBoolean()
|
||||
if (j % 8 == 7) {
|
||||
// System.out.println();
|
||||
test = false
|
||||
}
|
||||
j++
|
||||
}
|
||||
if (j % 8 != 7) {
|
||||
// System.out.println();
|
||||
test = true
|
||||
}
|
||||
println(
|
||||
"Mersenne Twister Fast: " + (System.currentTimeMillis() - ms) + " Ignore this: " + xx + "" + test
|
||||
)
|
||||
println("\nGrab 1000 booleans of increasing probability using nextBoolean(double)")
|
||||
r = MersenneTwisterFast(SEED)
|
||||
ms = System.currentTimeMillis()
|
||||
j = 0
|
||||
while (j < 1000) {
|
||||
|
||||
// System.out.print(r.nextBoolean(j / 999.0) + " ");
|
||||
test = r.nextBoolean(j / 999.0)
|
||||
if (j % 8 == 7) {
|
||||
// System.out.println();
|
||||
test = false
|
||||
}
|
||||
j++
|
||||
}
|
||||
if (j % 8 != 7) {
|
||||
// System.out.println();
|
||||
test = true
|
||||
}
|
||||
println(
|
||||
"Mersenne Twister Fast: " + (System.currentTimeMillis() - ms) + " Ignore this: " + xx + "" + test
|
||||
)
|
||||
println("\nGrab 1000 booleans of increasing probability using nextBoolean(float)")
|
||||
r = MersenneTwisterFast(SEED)
|
||||
ms = System.currentTimeMillis()
|
||||
j = 0
|
||||
while (j < 1000) {
|
||||
|
||||
// System.out.print(r.nextBoolean(j / 999.0f) + " ");
|
||||
test = r.nextBoolean(j / 999.0f)
|
||||
if (j % 8 == 7) {
|
||||
test = false
|
||||
println()
|
||||
}
|
||||
j++
|
||||
}
|
||||
if (j % 8 != 7) {
|
||||
// System.out.println();
|
||||
test = true
|
||||
}
|
||||
println(
|
||||
"Mersenne Twister Fast: " + (System.currentTimeMillis() - ms) + " Ignore this: " + xx + "" + test
|
||||
)
|
||||
val bytes = ByteArray(1000)
|
||||
println("\nGrab the first 1000 bytes using nextBytes")
|
||||
r = MersenneTwisterFast(SEED)
|
||||
r.nextBytes(bytes)
|
||||
j = 0
|
||||
while (j < 1000) {
|
||||
print(bytes[j].toString() + " ")
|
||||
if (j % 16 == 15) {
|
||||
println()
|
||||
}
|
||||
j++
|
||||
}
|
||||
if (j % 16 != 15) {
|
||||
println()
|
||||
}
|
||||
var b: Byte
|
||||
println("\nGrab the first 1000 bytes -- must be same as nextBytes")
|
||||
r = MersenneTwisterFast(SEED)
|
||||
j = 0
|
||||
while (j < 1000) {
|
||||
print(r.nextByte().also { b = it }.toString() + " ")
|
||||
if (b != bytes[j]) {
|
||||
print("BAD ")
|
||||
}
|
||||
if (j % 16 == 15) {
|
||||
println()
|
||||
}
|
||||
j++
|
||||
}
|
||||
if (j % 16 != 15) {
|
||||
println()
|
||||
}
|
||||
println("\nGrab the first 1000 shorts")
|
||||
r = MersenneTwisterFast(SEED)
|
||||
j = 0
|
||||
while (j < 1000) {
|
||||
print(r.nextShort().toString() + " ")
|
||||
if (j % 8 == 7) {
|
||||
println()
|
||||
}
|
||||
j++
|
||||
}
|
||||
if (j % 8 != 7) {
|
||||
println()
|
||||
}
|
||||
println("\nGrab the first 1000 ints")
|
||||
r = MersenneTwisterFast(SEED)
|
||||
j = 0
|
||||
while (j < 1000) {
|
||||
print(r.nextInt().toString() + " ")
|
||||
if (j % 4 == 3) {
|
||||
println()
|
||||
}
|
||||
j++
|
||||
}
|
||||
if (j % 4 != 3) {
|
||||
println()
|
||||
}
|
||||
println("\nGrab the first 1000 ints of different sizes")
|
||||
r = MersenneTwisterFast(SEED)
|
||||
var max = 1
|
||||
j = 0
|
||||
while (j < 1000) {
|
||||
print(r.nextInt(max).toString() + " ")
|
||||
max *= 2
|
||||
if (max <= 0) {
|
||||
max = 1
|
||||
}
|
||||
if (j % 4 == 3) {
|
||||
println()
|
||||
}
|
||||
j++
|
||||
}
|
||||
if (j % 4 != 3) {
|
||||
println()
|
||||
}
|
||||
println("\nGrab the first 1000 longs")
|
||||
r = MersenneTwisterFast(SEED)
|
||||
j = 0
|
||||
while (j < 1000) {
|
||||
print(r.nextLong().toString() + " ")
|
||||
if (j % 3 == 2) {
|
||||
println()
|
||||
}
|
||||
j++
|
||||
}
|
||||
if (j % 3 != 2) {
|
||||
println()
|
||||
}
|
||||
println("\nGrab the first 1000 longs of different sizes")
|
||||
r = MersenneTwisterFast(SEED)
|
||||
var max2: Long = 1
|
||||
j = 0
|
||||
while (j < 1000) {
|
||||
print(r.nextLong(max2).toString() + " ")
|
||||
max2 *= 2
|
||||
if (max2 <= 0) {
|
||||
max2 = 1
|
||||
}
|
||||
if (j % 4 == 3) {
|
||||
println()
|
||||
}
|
||||
j++
|
||||
}
|
||||
if (j % 4 != 3) {
|
||||
println()
|
||||
}
|
||||
println("\nGrab the first 1000 floats")
|
||||
r = MersenneTwisterFast(SEED)
|
||||
j = 0
|
||||
while (j < 1000) {
|
||||
print(r.nextFloat().toString() + " ")
|
||||
if (j % 4 == 3) {
|
||||
println()
|
||||
}
|
||||
j++
|
||||
}
|
||||
if (j % 4 != 3) {
|
||||
println()
|
||||
}
|
||||
println("\nGrab the first 1000 doubles")
|
||||
r = MersenneTwisterFast(SEED)
|
||||
j = 0
|
||||
while (j < 1000) {
|
||||
print(r.nextDouble().toString() + " ")
|
||||
if (j % 3 == 2) {
|
||||
println()
|
||||
}
|
||||
j++
|
||||
}
|
||||
if (j % 3 != 2) {
|
||||
println()
|
||||
}
|
||||
println("\nGrab the first 1000 gaussian doubles")
|
||||
r = MersenneTwisterFast(SEED)
|
||||
j = 0
|
||||
while (j < 1000) {
|
||||
print(r.nextGaussian().toString() + " ")
|
||||
if (j % 3 == 2) {
|
||||
println()
|
||||
}
|
||||
j++
|
||||
}
|
||||
if (j % 3 != 2) {
|
||||
println()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util
|
||||
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import java.util.concurrent.*
|
||||
|
||||
class TimeTest {
|
||||
@Test
|
||||
fun time() {
|
||||
TimeUnit.DAYS.toNanos(3).also {
|
||||
Assert.assertEquals("3 days", Sys.getTimePrettyFull(it))
|
||||
}
|
||||
TimeUnit.DAYS.toNanos(30).also {
|
||||
Assert.assertEquals("30 days", Sys.getTimePrettyFull(it))
|
||||
}
|
||||
TimeUnit.DAYS.toNanos(300).also {
|
||||
Assert.assertEquals("300 days", Sys.getTimePrettyFull(it))
|
||||
}
|
||||
TimeUnit.DAYS.toNanos(3000).also {
|
||||
Assert.assertEquals("3000 days", Sys.getTimePrettyFull(it))
|
||||
}
|
||||
|
||||
|
||||
TimeUnit.HOURS.toNanos(3).also {
|
||||
Assert.assertEquals("3 hours", Sys.getTimePrettyFull(it))
|
||||
}
|
||||
TimeUnit.MINUTES.toNanos(3).also {
|
||||
Assert.assertEquals("3 minutes", Sys.getTimePrettyFull(it))
|
||||
}
|
||||
TimeUnit.SECONDS.toNanos(3).also {
|
||||
Assert.assertEquals("3 seconds", Sys.getTimePrettyFull(it))
|
||||
}
|
||||
TimeUnit.MILLISECONDS.toNanos(3).also {
|
||||
Assert.assertEquals("3 milli-seconds", Sys.getTimePrettyFull(it))
|
||||
}
|
||||
TimeUnit.MICROSECONDS.toNanos(3).also {
|
||||
Assert.assertEquals("3 micro-seconds", Sys.getTimePrettyFull(it))
|
||||
}
|
||||
TimeUnit.NANOSECONDS.toNanos(3).also {
|
||||
Assert.assertEquals("3 nano-seconds", Sys.getTimePrettyFull(it))
|
||||
}
|
||||
|
||||
|
||||
TimeUnit.NANOSECONDS.toNanos(1).also {
|
||||
Assert.assertEquals("1 nano-second", Sys.getTimePrettyFull(it))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,343 +0,0 @@
|
|||
/*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bouncycastle.crypto.engines.AESEngine;
|
||||
import org.bouncycastle.crypto.engines.AESFastEngine;
|
||||
import org.bouncycastle.crypto.modes.CBCBlockCipher;
|
||||
import org.bouncycastle.crypto.modes.GCMBlockCipher;
|
||||
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
|
||||
import org.junit.Test;
|
||||
|
||||
public class AesTest {
|
||||
|
||||
org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(this.getClass());
|
||||
private static String entropySeed = "asdjhasdkljalksdfhlaks4356268909087s0dfgkjh255124515hasdg87";
|
||||
|
||||
@Test
|
||||
public void AesGcm() throws IOException {
|
||||
byte[] bytes = "hello, my name is inigo montoya".getBytes();
|
||||
|
||||
SecureRandom rand = new SecureRandom(entropySeed.getBytes());
|
||||
|
||||
GCMBlockCipher aesEngine = new GCMBlockCipher(new AESEngine());
|
||||
|
||||
byte[] key = new byte[32];
|
||||
byte[] iv = new byte[16];
|
||||
|
||||
// note: the IV needs to be VERY unique!
|
||||
rand.nextBytes(key); // 256bit key (32 bytes)
|
||||
rand.nextBytes(iv); // 128bit block size (16 bytes)
|
||||
|
||||
|
||||
byte[] encryptAES = CryptoAES.encrypt(aesEngine, key, iv, bytes, logger);
|
||||
byte[] decryptAES = CryptoAES.decrypt(aesEngine, key, iv, encryptAES, logger);
|
||||
|
||||
if (Arrays.equals(bytes, encryptAES)) {
|
||||
fail("bytes should not be equal");
|
||||
}
|
||||
|
||||
if (!Arrays.equals(bytes, decryptAES)) {
|
||||
fail("bytes not equal");
|
||||
}
|
||||
}
|
||||
|
||||
// Note: this is still tested, but DO NOT USE BLOCK MODE as it does NOT provide authentication. GCM does.
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void AesBlock() throws IOException {
|
||||
byte[] bytes = "hello, my name is inigo montoya".getBytes();
|
||||
|
||||
SecureRandom rand = new SecureRandom(entropySeed.getBytes());
|
||||
|
||||
PaddedBufferedBlockCipher aesEngine = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine()));
|
||||
|
||||
byte[] key = new byte[32];
|
||||
byte[] iv = new byte[16];
|
||||
|
||||
// note: the IV needs to be VERY unique!
|
||||
rand.nextBytes(key); // 256bit key
|
||||
rand.nextBytes(iv); // 16bit block size
|
||||
|
||||
|
||||
byte[] encryptAES = CryptoAES.encrypt(aesEngine, key, iv, bytes, logger);
|
||||
byte[] decryptAES = CryptoAES.decrypt(aesEngine, key, iv, encryptAES, logger);
|
||||
|
||||
if (Arrays.equals(bytes, encryptAES)) {
|
||||
fail("bytes should not be equal");
|
||||
}
|
||||
|
||||
if (!Arrays.equals(bytes, decryptAES)) {
|
||||
fail("bytes not equal");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void AesGcmStream() throws IOException {
|
||||
byte[] originalBytes = "hello, my name is inigo montoya".getBytes();
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(originalBytes);
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
SecureRandom rand = new SecureRandom(entropySeed.getBytes());
|
||||
|
||||
GCMBlockCipher aesEngine = new GCMBlockCipher(new AESEngine());
|
||||
|
||||
byte[] key = new byte[32];
|
||||
byte[] iv = new byte[16];
|
||||
|
||||
// note: the IV needs to be VERY unique!
|
||||
rand.nextBytes(key); // 256bit key
|
||||
rand.nextBytes(iv); // 128bit block size
|
||||
|
||||
|
||||
boolean success = CryptoAES.encryptStream(aesEngine, key, iv, inputStream, outputStream, logger);
|
||||
|
||||
if (!success) {
|
||||
fail("crypto was not successful");
|
||||
}
|
||||
|
||||
byte[] encryptBytes = outputStream.toByteArray();
|
||||
|
||||
inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
|
||||
success = CryptoAES.decryptStream(aesEngine, key, iv, inputStream, outputStream, logger);
|
||||
|
||||
if (!success) {
|
||||
fail("crypto was not successful");
|
||||
}
|
||||
|
||||
byte[] decryptBytes = outputStream.toByteArray();
|
||||
|
||||
if (Arrays.equals(originalBytes, encryptBytes)) {
|
||||
fail("bytes should not be equal");
|
||||
}
|
||||
|
||||
if (!Arrays.equals(originalBytes, decryptBytes)) {
|
||||
fail("bytes not equal");
|
||||
}
|
||||
}
|
||||
|
||||
// Note: this is still tested, but DO NOT USE BLOCK MODE as it does NOT provide authentication. GCM does.
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void AesBlockStream() throws IOException {
|
||||
byte[] originalBytes = "hello, my name is inigo montoya".getBytes();
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(originalBytes);
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
SecureRandom rand = new SecureRandom(entropySeed.getBytes());
|
||||
|
||||
PaddedBufferedBlockCipher aesEngine = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine()));
|
||||
|
||||
byte[] key = new byte[32];
|
||||
byte[] iv = new byte[16];
|
||||
|
||||
// note: the IV needs to be VERY unique!
|
||||
rand.nextBytes(key); // 256bit key
|
||||
rand.nextBytes(iv); // 128bit block size
|
||||
|
||||
|
||||
boolean success = CryptoAES.encryptStream(aesEngine, key, iv, inputStream, outputStream, logger);
|
||||
|
||||
if (!success) {
|
||||
fail("crypto was not successful");
|
||||
}
|
||||
|
||||
byte[] encryptBytes = outputStream.toByteArray();
|
||||
|
||||
inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
|
||||
success = CryptoAES.decryptStream(aesEngine, key, iv, inputStream, outputStream, logger);
|
||||
|
||||
|
||||
if (!success) {
|
||||
fail("crypto was not successful");
|
||||
}
|
||||
|
||||
byte[] decryptBytes = outputStream.toByteArray();
|
||||
|
||||
if (Arrays.equals(originalBytes, encryptBytes)) {
|
||||
fail("bytes should not be equal");
|
||||
}
|
||||
|
||||
if (!Arrays.equals(originalBytes, decryptBytes)) {
|
||||
fail("bytes not equal");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void AesWithIVGcm() throws IOException {
|
||||
byte[] bytes = "hello, my name is inigo montoya".getBytes();
|
||||
|
||||
SecureRandom rand = new SecureRandom(entropySeed.getBytes());
|
||||
|
||||
GCMBlockCipher aesEngine = new GCMBlockCipher(new AESEngine());
|
||||
|
||||
byte[] key = new byte[32]; // 256bit key
|
||||
byte[] iv = new byte[aesEngine.getUnderlyingCipher().getBlockSize()];
|
||||
|
||||
// note: the IV needs to be VERY unique!
|
||||
rand.nextBytes(key);
|
||||
rand.nextBytes(iv);
|
||||
|
||||
|
||||
byte[] encryptAES = CryptoAES.encryptWithIV(aesEngine, key, iv, bytes, logger);
|
||||
byte[] decryptAES = CryptoAES.decryptWithIV(aesEngine, key, encryptAES, logger);
|
||||
|
||||
if (Arrays.equals(bytes, encryptAES)) {
|
||||
fail("bytes should not be equal");
|
||||
}
|
||||
|
||||
if (!Arrays.equals(bytes, decryptAES)) {
|
||||
fail("bytes not equal");
|
||||
}
|
||||
}
|
||||
|
||||
// Note: this is still tested, but DO NOT USE BLOCK MODE as it does NOT provide authentication. GCM does.
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void AesWithIVBlock() throws IOException {
|
||||
byte[] bytes = "hello, my name is inigo montoya".getBytes();
|
||||
|
||||
SecureRandom rand = new SecureRandom(entropySeed.getBytes());
|
||||
|
||||
PaddedBufferedBlockCipher aesEngine = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine()));
|
||||
|
||||
byte[] key = new byte[32]; // 256bit key
|
||||
byte[] iv = new byte[aesEngine.getUnderlyingCipher().getBlockSize()];
|
||||
|
||||
// note: the IV needs to be VERY unique!
|
||||
rand.nextBytes(key);
|
||||
rand.nextBytes(iv);
|
||||
|
||||
|
||||
byte[] encryptAES = CryptoAES.encryptWithIV(aesEngine, key, iv, bytes, logger);
|
||||
byte[] decryptAES = CryptoAES.decryptWithIV(aesEngine, key, encryptAES, logger);
|
||||
|
||||
if (Arrays.equals(bytes, encryptAES)) {
|
||||
fail("bytes should not be equal");
|
||||
}
|
||||
|
||||
if (!Arrays.equals(bytes, decryptAES)) {
|
||||
fail("bytes not equal");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void AesWithIVGcmStream() throws IOException {
|
||||
byte[] originalBytes = "hello, my name is inigo montoya".getBytes();
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(originalBytes);
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
SecureRandom rand = new SecureRandom(entropySeed.getBytes());
|
||||
|
||||
GCMBlockCipher aesEngine = new GCMBlockCipher(new AESEngine());
|
||||
|
||||
byte[] key = new byte[32];
|
||||
byte[] iv = new byte[16];
|
||||
|
||||
// note: the IV needs to be VERY unique!
|
||||
rand.nextBytes(key); // 256bit key
|
||||
rand.nextBytes(iv); // 128bit block size
|
||||
|
||||
|
||||
boolean success = CryptoAES.encryptStreamWithIV(aesEngine, key, iv, inputStream, outputStream, logger);
|
||||
|
||||
if (!success) {
|
||||
fail("crypto was not successful");
|
||||
}
|
||||
|
||||
byte[] encryptBytes = outputStream.toByteArray();
|
||||
|
||||
inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
|
||||
success = CryptoAES.decryptStreamWithIV(aesEngine, key, inputStream, outputStream, logger);
|
||||
|
||||
if (!success) {
|
||||
fail("crypto was not successful");
|
||||
}
|
||||
|
||||
byte[] decryptBytes = outputStream.toByteArray();
|
||||
|
||||
if (Arrays.equals(originalBytes, encryptBytes)) {
|
||||
fail("bytes should not be equal");
|
||||
}
|
||||
|
||||
if (!Arrays.equals(originalBytes, decryptBytes)) {
|
||||
fail("bytes not equal");
|
||||
}
|
||||
}
|
||||
|
||||
// Note: this is still tested, but DO NOT USE BLOCK MODE as it does NOT provide authentication. GCM does.
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void AesWithIVBlockStream() throws IOException {
|
||||
byte[] originalBytes = "hello, my name is inigo montoya".getBytes();
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(originalBytes);
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
SecureRandom rand = new SecureRandom(entropySeed.getBytes());
|
||||
|
||||
PaddedBufferedBlockCipher aesEngine = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine()));
|
||||
|
||||
byte[] key = new byte[32];
|
||||
byte[] iv = new byte[16];
|
||||
|
||||
// note: the IV needs to be VERY unique!
|
||||
rand.nextBytes(key); // 256bit key
|
||||
rand.nextBytes(iv); // 128bit block size
|
||||
|
||||
|
||||
boolean success = CryptoAES.encryptStreamWithIV(aesEngine, key, iv, inputStream, outputStream, logger);
|
||||
|
||||
if (!success) {
|
||||
fail("crypto was not successful");
|
||||
}
|
||||
|
||||
byte[] encryptBytes = outputStream.toByteArray();
|
||||
|
||||
inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
|
||||
success = CryptoAES.decryptStreamWithIV(aesEngine, key, inputStream, outputStream, logger);
|
||||
|
||||
|
||||
if (!success) {
|
||||
fail("crypto was not successful");
|
||||
}
|
||||
|
||||
byte[] decryptBytes = outputStream.toByteArray();
|
||||
|
||||
if (Arrays.equals(originalBytes, encryptBytes)) {
|
||||
fail("bytes should not be equal");
|
||||
}
|
||||
|
||||
if (!Arrays.equals(originalBytes, decryptBytes)) {
|
||||
fail("bytes not equal");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,148 +0,0 @@
|
|||
/*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
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.pkcs.PrivateKeyInfo;
|
||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
|
||||
import org.bouncycastle.asn1.x509.DSAParameter;
|
||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
||||
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.bouncycastle.crypto.params.DSAParameters;
|
||||
import org.bouncycastle.crypto.params.DSAPrivateKeyParameters;
|
||||
import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
|
||||
import org.bouncycastle.crypto.util.PrivateKeyFactory;
|
||||
import org.bouncycastle.crypto.util.PublicKeyFactory;
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class DsaTest {
|
||||
private static String entropySeed = "asdjhaffasttjjhgpx600gn,-356268909087s0dfgkjh255124515hasdg87";
|
||||
|
||||
// Note: this is here just for keeping track of how this is done. This should NOT be used, and instead ECC crypto used instead.
|
||||
@Test
|
||||
public void Dsa() {
|
||||
byte[] bytes = "hello, my name is inigo montoya".getBytes();
|
||||
|
||||
AsymmetricCipherKeyPair generateKeyPair = CryptoDSA.generateKeyPair(new SecureRandom(entropySeed.getBytes()), 1024);
|
||||
DSAPrivateKeyParameters privateKey = (DSAPrivateKeyParameters) generateKeyPair.getPrivate();
|
||||
DSAPublicKeyParameters publicKey = (DSAPublicKeyParameters) generateKeyPair.getPublic();
|
||||
|
||||
|
||||
BigInteger[] signature = CryptoDSA.generateSignature(privateKey, new SecureRandom(entropySeed.getBytes()), bytes);
|
||||
|
||||
boolean verify1 = CryptoDSA.verifySignature(publicKey, bytes, signature);
|
||||
|
||||
if (!verify1) {
|
||||
fail("failed signature verification");
|
||||
}
|
||||
|
||||
|
||||
byte[] bytes2 = "hello, my name is inigo montoya FAILED VERSION".getBytes();
|
||||
|
||||
if (Arrays.equals(bytes, bytes2)) {
|
||||
fail("failed to create different byte arrays for testing bad messages");
|
||||
}
|
||||
|
||||
|
||||
|
||||
boolean verify2 = CryptoDSA.verifySignature(publicKey, bytes2, signature);
|
||||
|
||||
if (verify2) {
|
||||
fail("failed signature verification with bad message");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void DsaJceSerializaion() throws IOException {
|
||||
|
||||
AsymmetricCipherKeyPair generateKeyPair = CryptoDSA.generateKeyPair(new SecureRandom(entropySeed.getBytes()), 1024);
|
||||
DSAPrivateKeyParameters privateKey = (DSAPrivateKeyParameters) generateKeyPair.getPrivate();
|
||||
DSAPublicKeyParameters publicKey = (DSAPublicKeyParameters) generateKeyPair.getPublic();
|
||||
|
||||
|
||||
// public key as bytes.
|
||||
DSAParameters parameters = publicKey.getParameters();
|
||||
byte[] bs = new SubjectPublicKeyInfo(
|
||||
new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa,
|
||||
new DSAParameter(parameters.getP(), parameters.getQ(), parameters.getG()).toASN1Primitive()),
|
||||
new ASN1Integer(publicKey.getY())).getEncoded();
|
||||
|
||||
|
||||
|
||||
parameters = privateKey.getParameters();
|
||||
byte[] bs2 = new PrivateKeyInfo(
|
||||
new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa,
|
||||
new DSAParameter(parameters.getP(), parameters.getQ(), parameters.getG()).toASN1Primitive()),
|
||||
new ASN1Integer(privateKey.getX())).getEncoded();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
DSAPrivateKeyParameters privateKey2 = (DSAPrivateKeyParameters) PrivateKeyFactory.createKey(bs2);
|
||||
DSAPublicKeyParameters publicKey2 = (DSAPublicKeyParameters) PublicKeyFactory.createKey(bs);
|
||||
|
||||
|
||||
|
||||
// test via signing
|
||||
byte[] bytes = "hello, my name is inigo montoya".getBytes();
|
||||
|
||||
|
||||
BigInteger[] signature = CryptoDSA.generateSignature(privateKey, new SecureRandom(entropySeed.getBytes()), bytes);
|
||||
|
||||
boolean verify1 = CryptoDSA.verifySignature(publicKey, bytes, signature);
|
||||
|
||||
if (!verify1) {
|
||||
fail("failed signature verification");
|
||||
}
|
||||
|
||||
|
||||
boolean verify2 = CryptoDSA.verifySignature(publicKey2, bytes, signature);
|
||||
|
||||
if (!verify2) {
|
||||
fail("failed signature verification");
|
||||
}
|
||||
|
||||
|
||||
|
||||
// now reverse who signs what.
|
||||
BigInteger[] signatureB = CryptoDSA.generateSignature(privateKey2, new SecureRandom(entropySeed.getBytes()), bytes);
|
||||
|
||||
boolean verifyB1 = CryptoDSA.verifySignature(publicKey, bytes, signatureB);
|
||||
|
||||
if (!verifyB1) {
|
||||
fail("failed signature verification");
|
||||
}
|
||||
|
||||
|
||||
boolean verifyB2 = CryptoDSA.verifySignature(publicKey2, bytes, signatureB);
|
||||
|
||||
if (!verifyB2) {
|
||||
fail("failed signature verification");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,332 +0,0 @@
|
|||
/*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.bouncycastle.crypto.BasicAgreement;
|
||||
import org.bouncycastle.crypto.CipherParameters;
|
||||
import org.bouncycastle.crypto.agreement.ECDHCBasicAgreement;
|
||||
import org.bouncycastle.crypto.engines.AESEngine;
|
||||
import org.bouncycastle.crypto.engines.IESEngine;
|
||||
import org.bouncycastle.crypto.modes.CBCBlockCipher;
|
||||
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
|
||||
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
|
||||
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
||||
import org.bouncycastle.crypto.params.IESParameters;
|
||||
import org.bouncycastle.crypto.params.IESWithCipherParameters;
|
||||
import org.bouncycastle.crypto.params.ParametersWithRandom;
|
||||
import org.bouncycastle.crypto.signers.ECDSASigner;
|
||||
import org.bouncycastle.crypto.util.PrivateKeyFactory;
|
||||
import org.bouncycastle.crypto.util.PublicKeyFactory;
|
||||
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
|
||||
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.jce.spec.ECParameterSpec;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.esotericsoftware.kryo.Kryo;
|
||||
import com.esotericsoftware.kryo.io.Input;
|
||||
import com.esotericsoftware.kryo.io.Output;
|
||||
|
||||
import dorkbox.serializers.bouncycastle.EccPrivateKeySerializer;
|
||||
import dorkbox.serializers.bouncycastle.EccPublicKeySerializer;
|
||||
import dorkbox.serializers.bouncycastle.IesParametersSerializer;
|
||||
import dorkbox.serializers.bouncycastle.IesWithCipherParametersSerializer;
|
||||
|
||||
public class EccTest {
|
||||
|
||||
org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(this.getClass());
|
||||
private static String entropySeed = "asdjhaffasttjasdasdgfgaerym0698768.,./8909087s0dfgkjgb49bmngrSGDSG#";
|
||||
|
||||
@Test
|
||||
public void EccStreamMode() throws IOException {
|
||||
SecureRandom secureRandom = new SecureRandom();
|
||||
|
||||
AsymmetricCipherKeyPair key1 = CryptoECC.generateKeyPair(CryptoECC.default_curve, secureRandom);
|
||||
AsymmetricCipherKeyPair key2 = CryptoECC.generateKeyPair(CryptoECC.default_curve, secureRandom);
|
||||
|
||||
IESParameters cipherParams = CryptoECC.generateSharedParameters(secureRandom);
|
||||
|
||||
IESEngine encrypt = CryptoECC.createEngine();
|
||||
IESEngine decrypt = CryptoECC.createEngine();
|
||||
|
||||
|
||||
// note: we want an ecc key that is AT LEAST 512 bits! (which is equal to AES 256)
|
||||
// using 521 bits from curve.
|
||||
CipherParameters private1 = key1.getPrivate();
|
||||
CipherParameters public1 = key1.getPublic();
|
||||
|
||||
CipherParameters private2 = key2.getPrivate();
|
||||
CipherParameters public2 = key2.getPublic();
|
||||
|
||||
byte[] message = Hex.decode(
|
||||
"123456784358754934597967249867359283792374987692348750276509765091834790abcdef123456784358754934597967249867359283792374987692348750276509765091834790abcdef123456784358754934597967249867359283792374987692348750276509765091834790abcdef");
|
||||
|
||||
|
||||
// test stream mode
|
||||
byte[] encrypted = CryptoECC.encrypt(encrypt, private1, public2, cipherParams, message, logger);
|
||||
byte[] plaintext = CryptoECC.decrypt(decrypt, private2, public1, cipherParams, encrypted, logger);
|
||||
|
||||
if (Arrays.equals(encrypted, message)) {
|
||||
fail("stream cipher test failed");
|
||||
}
|
||||
|
||||
if (!Arrays.equals(plaintext, message)) {
|
||||
fail("stream cipher test failed");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void EccAesMode() throws IOException {
|
||||
// test AES encrypt mode
|
||||
SecureRandom secureRandom = new SecureRandom();
|
||||
|
||||
AsymmetricCipherKeyPair key1 = CryptoECC.generateKeyPair(CryptoECC.default_curve, secureRandom);
|
||||
AsymmetricCipherKeyPair key2 = CryptoECC.generateKeyPair(CryptoECC.default_curve, secureRandom);
|
||||
|
||||
|
||||
PaddedBufferedBlockCipher aesEngine1 = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
|
||||
PaddedBufferedBlockCipher aesEngine2 = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
|
||||
|
||||
IESWithCipherParameters cipherParams = CryptoECC.generateSharedParametersWithCipher(secureRandom);
|
||||
|
||||
|
||||
IESEngine encrypt = CryptoECC.createEngine(aesEngine1);
|
||||
IESEngine decrypt = CryptoECC.createEngine(aesEngine2);
|
||||
|
||||
|
||||
// note: we want an ecc key that is AT LEAST 512 bits! (which is equal to AES 256)
|
||||
// using 521 bits from curve.
|
||||
CipherParameters private1 = key1.getPrivate();
|
||||
CipherParameters public1 = key1.getPublic();
|
||||
|
||||
CipherParameters private2 = key2.getPrivate();
|
||||
CipherParameters public2 = key2.getPublic();
|
||||
|
||||
byte[] message = Hex.decode("123456784358754934597967249867359283792374987692348750276509765091834790abcdef123456784358754934597967249867359283792374987692348750276509765091834790abcdef123456784358754934597967249867359283792374987692348750276509765091834790abcdef");
|
||||
|
||||
// test stream mode
|
||||
byte[] encrypted = CryptoECC.encrypt(encrypt, private1, public2, cipherParams, message, logger);
|
||||
byte[] plaintext = CryptoECC.decrypt(decrypt, private2, public1, cipherParams, encrypted, logger);
|
||||
|
||||
if (Arrays.equals(encrypted, message)) {
|
||||
fail("stream cipher test failed");
|
||||
}
|
||||
|
||||
if (!Arrays.equals(plaintext, message)) {
|
||||
fail("stream cipher test failed");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void Ecdh() throws IOException {
|
||||
// test DH key exchange
|
||||
SecureRandom secureRandom = new SecureRandom();
|
||||
|
||||
AsymmetricCipherKeyPair key1 = CryptoECC.generateKeyPair(CryptoECC.default_curve, secureRandom);
|
||||
AsymmetricCipherKeyPair key2 = CryptoECC.generateKeyPair(CryptoECC.default_curve, secureRandom);
|
||||
|
||||
BasicAgreement e1 = new ECDHCBasicAgreement();
|
||||
BasicAgreement e2 = new ECDHCBasicAgreement();
|
||||
|
||||
e1.init(key1.getPrivate());
|
||||
e2.init(key2.getPrivate());
|
||||
|
||||
BigInteger k1 = e1.calculateAgreement(key2.getPublic());
|
||||
BigInteger k2 = e2.calculateAgreement(key1.getPublic());
|
||||
|
||||
if (!k1.equals(k2)) {
|
||||
fail("ECDHC cipher test failed");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void EccDsa() throws IOException {
|
||||
SecureRandom secureRandom = new SecureRandom();
|
||||
|
||||
AsymmetricCipherKeyPair key1 = CryptoECC.generateKeyPair(CryptoECC.default_curve, secureRandom);
|
||||
|
||||
ParametersWithRandom param = new ParametersWithRandom(key1.getPrivate(), new SecureRandom());
|
||||
|
||||
ECDSASigner ecdsa = new ECDSASigner();
|
||||
|
||||
ecdsa.init(true, param);
|
||||
|
||||
byte[] message = new BigInteger("345234598734987394672039478602934578").toByteArray();
|
||||
BigInteger[] sig = ecdsa.generateSignature(message);
|
||||
|
||||
|
||||
ecdsa.init(false, key1.getPublic());
|
||||
|
||||
if (!ecdsa.verifySignature(message, sig[0], sig[1])) {
|
||||
fail("ECDSA signature fails");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void EccSerialization() {
|
||||
SecureRandom secureRandom = new SecureRandom();
|
||||
|
||||
AsymmetricCipherKeyPair key1 = CryptoECC.generateKeyPair(CryptoECC.default_curve, secureRandom);
|
||||
|
||||
IESParameters cipherAParams = CryptoECC.generateSharedParameters(secureRandom);
|
||||
IESWithCipherParameters cipherBParams = CryptoECC.generateSharedParametersWithCipher(secureRandom);
|
||||
|
||||
|
||||
// note: we want an ecc key that is AT LEAST 512 bits! (which is equal to AES 256)
|
||||
// using 521 bits from curve.
|
||||
ECPrivateKeyParameters private1 = (ECPrivateKeyParameters) key1.getPrivate();
|
||||
ECPublicKeyParameters public1 = (ECPublicKeyParameters) key1.getPublic();
|
||||
|
||||
|
||||
Kryo kryo = new Kryo();
|
||||
kryo.register(IESParameters.class, new IesParametersSerializer());
|
||||
kryo.register(IESWithCipherParameters.class, new IesWithCipherParametersSerializer());
|
||||
kryo.register(ECPublicKeyParameters.class, new EccPublicKeySerializer());
|
||||
kryo.register(ECPrivateKeyParameters.class, new EccPrivateKeySerializer());
|
||||
|
||||
|
||||
|
||||
// Test output to stream, large buffer.
|
||||
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
|
||||
Output output = new Output(outStream, 4096);
|
||||
kryo.writeClassAndObject(output, cipherAParams);
|
||||
output.flush();
|
||||
|
||||
// Test input from stream, large buffer.
|
||||
Input input = new Input(new ByteArrayInputStream(outStream.toByteArray()), 4096);
|
||||
IESParameters cipherAParams2 = (IESParameters) kryo.readClassAndObject(input);
|
||||
|
||||
|
||||
if (!CryptoECC.compare(cipherAParams, cipherAParams2)) {
|
||||
fail("cipher parameters not equal");
|
||||
}
|
||||
|
||||
// Test output to stream, large buffer.
|
||||
outStream = new ByteArrayOutputStream();
|
||||
output = new Output(outStream, 4096);
|
||||
kryo.writeClassAndObject(output, cipherBParams);
|
||||
output.flush();
|
||||
|
||||
// Test input from stream, large buffer.
|
||||
input = new Input(new ByteArrayInputStream(outStream.toByteArray()), 4096);
|
||||
IESWithCipherParameters cipherBParams2 = (IESWithCipherParameters) kryo.readClassAndObject(input);
|
||||
|
||||
if (!CryptoECC.compare(cipherBParams, cipherBParams2)) {
|
||||
fail("cipher parameters not equal");
|
||||
}
|
||||
|
||||
|
||||
// Test output to stream, large buffer.
|
||||
outStream = new ByteArrayOutputStream();
|
||||
output = new Output(outStream, 4096);
|
||||
kryo.writeClassAndObject(output, private1);
|
||||
output.flush();
|
||||
|
||||
// Test input from stream, large buffer.
|
||||
input = new Input(new ByteArrayInputStream(outStream.toByteArray()), 4096);
|
||||
ECPrivateKeyParameters private2 = (ECPrivateKeyParameters) kryo.readClassAndObject(input);
|
||||
|
||||
if (!CryptoECC.compare(private1, private2)) {
|
||||
fail("private keys not equal");
|
||||
}
|
||||
|
||||
|
||||
// Test output to stream, large buffer.
|
||||
outStream = new ByteArrayOutputStream();
|
||||
output = new Output(outStream, 4096);
|
||||
kryo.writeClassAndObject(output, public1);
|
||||
output.flush();
|
||||
|
||||
// Test input from stream, large buffer.
|
||||
input = new Input(new ByteArrayInputStream(outStream.toByteArray()), 4096);
|
||||
ECPublicKeyParameters public2 = (ECPublicKeyParameters) kryo.readClassAndObject(input);
|
||||
|
||||
if (!CryptoECC.compare(public1, public2)) {
|
||||
fail("public keys not equal");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void EccJceSerialization() throws IOException {
|
||||
AsymmetricCipherKeyPair generateKeyPair = CryptoECC.generateKeyPair(CryptoECC.default_curve, new SecureRandom());
|
||||
ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters) generateKeyPair.getPrivate();
|
||||
ECPublicKeyParameters publicKey = (ECPublicKeyParameters) generateKeyPair.getPublic();
|
||||
|
||||
|
||||
BCECPublicKey bcecPublicKey = new BCECPublicKey("EC", publicKey, (ECParameterSpec) null, BouncyCastleProvider.CONFIGURATION);
|
||||
byte[] publicBytes = bcecPublicKey.getEncoded();
|
||||
|
||||
|
||||
|
||||
// relies on the BC public key.
|
||||
BCECPrivateKey bcecPrivateKey = new BCECPrivateKey("EC", privateKey, bcecPublicKey, (ECParameterSpec) null, BouncyCastleProvider.CONFIGURATION);
|
||||
byte[] privateBytes = bcecPrivateKey.getEncoded();
|
||||
|
||||
|
||||
|
||||
ECPublicKeyParameters publicKey2 = (ECPublicKeyParameters) PublicKeyFactory.createKey(publicBytes);
|
||||
ECPrivateKeyParameters privateKey2 = (ECPrivateKeyParameters) PrivateKeyFactory.createKey(privateBytes);
|
||||
|
||||
|
||||
|
||||
// test via signing
|
||||
byte[] bytes = "hello, my name is inigo montoya".getBytes();
|
||||
|
||||
|
||||
BigInteger[] signature = CryptoECC.generateSignature("SHA384", privateKey, new SecureRandom(entropySeed.getBytes()), bytes);
|
||||
|
||||
boolean verify1 = CryptoECC.verifySignature("SHA384", publicKey, bytes, signature);
|
||||
|
||||
if (!verify1) {
|
||||
fail("failed signature verification");
|
||||
}
|
||||
|
||||
boolean verify2 = CryptoECC.verifySignature("SHA384", publicKey2, bytes, signature);
|
||||
|
||||
if (!verify2) {
|
||||
fail("failed signature verification");
|
||||
}
|
||||
|
||||
|
||||
|
||||
// now reverse who signs what.
|
||||
BigInteger[] signatureB = CryptoECC.generateSignature("SHA384", privateKey2, new SecureRandom(entropySeed.getBytes()), bytes);
|
||||
|
||||
boolean verifyB1 = CryptoECC.verifySignature("SHA384", publicKey, bytes, signatureB);
|
||||
|
||||
if (!verifyB1) {
|
||||
fail("failed signature verification");
|
||||
}
|
||||
|
||||
boolean verifyB2 = CryptoECC.verifySignature("SHA384", publicKey2, bytes, signatureB);
|
||||
|
||||
if (!verifyB2) {
|
||||
fail("failed signature verification");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,171 +0,0 @@
|
|||
/*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Random;
|
||||
|
||||
import org.bouncycastle.crypto.CipherParameters;
|
||||
import org.bouncycastle.crypto.engines.AESEngine;
|
||||
import org.bouncycastle.crypto.modes.GCMBlockCipher;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
import org.bouncycastle.crypto.params.ParametersWithIV;
|
||||
|
||||
|
||||
// See: https://stackoverflow.com/questions/25992131/slow-aes-gcm-encryption-and-decryption-with-java-8u20
|
||||
// java8 performance is 3 MB/s. BC is ~43 MB/s
|
||||
public
|
||||
class PerformanceTest {
|
||||
private static org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(PerformanceTest.class);
|
||||
private static String entropySeed = "asdjhasdkljalksdfhlaks4356268909087s0dfgkjh255124515hasdg87";
|
||||
|
||||
public static
|
||||
void main(String[] args) throws Exception {
|
||||
final int max = 5;
|
||||
for (int i = 0; i < max; i++) {
|
||||
System.out.println("Warming up " + (i+1) + " of " + max);
|
||||
new PerformanceTest(true);
|
||||
}
|
||||
new PerformanceTest(false);
|
||||
}
|
||||
|
||||
PerformanceTest(boolean isWarmup) {
|
||||
final byte[] bytes = new byte[64 * 1024];
|
||||
byte[] encrypted = null;
|
||||
final byte[] aesKey = new byte[32];
|
||||
final byte[] aesIV = new byte[12];
|
||||
|
||||
final Random random = new SecureRandom(entropySeed.getBytes());
|
||||
random.nextBytes(bytes);
|
||||
random.nextBytes(aesKey);
|
||||
random.nextBytes(aesIV);
|
||||
|
||||
int length = bytes.length;
|
||||
|
||||
if (!isWarmup) {
|
||||
System.out.println("Benchmarking AES-256 GCM encryption");
|
||||
}
|
||||
|
||||
long javaEncryptInputBytes = 0;
|
||||
long javaEncryptStartTime = System.currentTimeMillis();
|
||||
|
||||
// convert to bouncycastle
|
||||
GCMBlockCipher aesEngine = new GCMBlockCipher(new AESEngine());
|
||||
CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
|
||||
|
||||
long encryptInitTime = 0L;
|
||||
long encryptUpdate1Time = 0L;
|
||||
long encryptDoFinalTime = 0L;
|
||||
|
||||
while (System.currentTimeMillis() - javaEncryptStartTime < 10000) {
|
||||
random.nextBytes(aesIV);
|
||||
|
||||
long n1 = System.nanoTime();
|
||||
|
||||
aesEngine.reset();
|
||||
aesEngine.init(true, aesIVAndKey);
|
||||
|
||||
if (encrypted == null) {
|
||||
int minSize = aesEngine.getOutputSize(length);
|
||||
encrypted = new byte[minSize];
|
||||
}
|
||||
|
||||
long n2 = System.nanoTime();
|
||||
int actualLength = aesEngine.processBytes(bytes, 0, length, encrypted, 0);
|
||||
|
||||
long n3 = System.nanoTime();
|
||||
try {
|
||||
actualLength += aesEngine.doFinal(encrypted, actualLength);
|
||||
} catch (Exception e) {
|
||||
logger.error("Unable to perform AES cipher.", e);
|
||||
}
|
||||
|
||||
if (encrypted.length != actualLength) {
|
||||
byte[] result = new byte[actualLength];
|
||||
System.arraycopy(encrypted, 0, result, 0, result.length);
|
||||
encrypted = result;
|
||||
}
|
||||
|
||||
long n4 = System.nanoTime();
|
||||
|
||||
javaEncryptInputBytes += actualLength;
|
||||
|
||||
encryptInitTime = n2 - n1;
|
||||
encryptUpdate1Time = n3 - n2;
|
||||
encryptDoFinalTime = n4 - n3;
|
||||
}
|
||||
|
||||
long javaEncryptEndTime = System.currentTimeMillis();
|
||||
|
||||
if (!isWarmup) {
|
||||
System.out.println("Time init (ns): " + encryptInitTime);
|
||||
System.out.println("Time update (ns): " + encryptUpdate1Time);
|
||||
System.out.println("Time do final (ns): " + encryptDoFinalTime);
|
||||
System.out.println("Java calculated at " +
|
||||
(javaEncryptInputBytes / 1024 / 1024 / ((javaEncryptEndTime - javaEncryptStartTime) / 1000)) + " MB/s");
|
||||
|
||||
System.out.println("Benchmarking AES-256 GCM decryption");
|
||||
}
|
||||
|
||||
|
||||
long javaDecryptInputBytes = 0;
|
||||
long javaDecryptStartTime = System.currentTimeMillis();
|
||||
|
||||
length = encrypted.length;
|
||||
|
||||
long decryptInitTime = 0L;
|
||||
long decryptUpdate1Time = 0L;
|
||||
long decryptDoFinalTime = 0L;
|
||||
|
||||
while (System.currentTimeMillis() - javaDecryptStartTime < 10000) {
|
||||
long n1 = System.nanoTime();
|
||||
|
||||
aesEngine.reset();
|
||||
aesEngine.init(false, aesIVAndKey);
|
||||
|
||||
long n2 = System.nanoTime();
|
||||
|
||||
int actualLength = aesEngine.processBytes(encrypted, 0, length, bytes, 0);
|
||||
|
||||
long n3 = System.nanoTime();
|
||||
|
||||
try {
|
||||
actualLength += aesEngine.doFinal(bytes, actualLength);
|
||||
} catch (Exception e) {
|
||||
logger.debug("Unable to perform AES cipher.", e);
|
||||
}
|
||||
|
||||
|
||||
long n4 = System.nanoTime();
|
||||
|
||||
javaDecryptInputBytes += actualLength;
|
||||
|
||||
decryptInitTime += n2 - n1;
|
||||
decryptUpdate1Time += n3 - n2;
|
||||
decryptDoFinalTime += n4 - n3;
|
||||
}
|
||||
long javaDecryptEndTime = System.currentTimeMillis();
|
||||
|
||||
if (!isWarmup) {
|
||||
System.out.println("Time init (ns): " + decryptInitTime);
|
||||
System.out.println("Time update 1 (ns): " + decryptUpdate1Time);
|
||||
System.out.println("Time do final (ns): " + decryptDoFinalTime);
|
||||
System.out.println("Total bytes processed: " + javaDecryptInputBytes);
|
||||
System.out.println("Java calculated at " +
|
||||
(javaDecryptInputBytes / 1024 / 1024 / ((javaDecryptEndTime - javaDecryptStartTime) / 1000)) + " MB/s");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,141 +0,0 @@
|
|||
/*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto;
|
||||
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.bouncycastle.crypto.digests.SHA1Digest;
|
||||
import org.bouncycastle.crypto.encodings.OAEPEncoding;
|
||||
import org.bouncycastle.crypto.engines.RSAEngine;
|
||||
import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
|
||||
import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
|
||||
import org.bouncycastle.crypto.params.RSAKeyParameters;
|
||||
import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
|
||||
import org.bouncycastle.crypto.signers.PSSSigner;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.esotericsoftware.kryo.Kryo;
|
||||
import com.esotericsoftware.kryo.io.Input;
|
||||
import com.esotericsoftware.kryo.io.Output;
|
||||
|
||||
import dorkbox.serializers.bouncycastle.RsaPrivateKeySerializer;
|
||||
import dorkbox.serializers.bouncycastle.RsaPublicKeySerializer;
|
||||
|
||||
public class RsaTest {
|
||||
org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(this.getClass());
|
||||
private static String entropySeed = "asdjhaffasttjjhgpx600gn,-356268909087s0dfgkjh255124515hasdg87";
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void Rsa() {
|
||||
byte[] bytes = "hello, my name is inigo montoya".getBytes();
|
||||
|
||||
AsymmetricCipherKeyPair key = CryptoRSA.generateKeyPair(new SecureRandom(entropySeed.getBytes()), 1024);
|
||||
|
||||
RSAKeyParameters public1 = (RSAKeyParameters) key.getPublic();
|
||||
RSAPrivateCrtKeyParameters private1 = (RSAPrivateCrtKeyParameters) key.getPrivate();
|
||||
|
||||
|
||||
RSAEngine engine = new RSAEngine();
|
||||
SHA1Digest digest = new SHA1Digest();
|
||||
OAEPEncoding rsaEngine = new OAEPEncoding(engine, digest);
|
||||
|
||||
// test encrypt/decrypt
|
||||
byte[] encryptRSA = CryptoRSA.encrypt(rsaEngine, public1, bytes, logger);
|
||||
byte[] decryptRSA = CryptoRSA.decrypt(rsaEngine, private1, encryptRSA, logger);
|
||||
|
||||
if (Arrays.equals(bytes, encryptRSA)) {
|
||||
fail("bytes should not be equal");
|
||||
}
|
||||
|
||||
if (!Arrays.equals(bytes, decryptRSA)) {
|
||||
fail("bytes not equal");
|
||||
}
|
||||
|
||||
// test signing/verification
|
||||
PSSSigner signer = new PSSSigner(engine, digest, digest.getDigestSize());
|
||||
|
||||
byte[] signatureRSA = CryptoRSA.sign(signer, private1, bytes, logger);
|
||||
boolean verify = CryptoRSA.verify(signer, public1, signatureRSA, bytes);
|
||||
|
||||
if (!verify) {
|
||||
fail("failed signature verification");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void RsaSerialization () throws IOException {
|
||||
RSAKeyPairGenerator keyGen = new RSAKeyPairGenerator();
|
||||
RSAKeyGenerationParameters params = new RSAKeyGenerationParameters(new BigInteger("65537"), // public exponent
|
||||
new SecureRandom(entropySeed.getBytes()), //pnrg
|
||||
1024, // key length
|
||||
8); //the number of iterations of the Miller-Rabin primality test.
|
||||
keyGen.init(params);
|
||||
|
||||
|
||||
AsymmetricCipherKeyPair key = keyGen.generateKeyPair();
|
||||
|
||||
RSAKeyParameters public1 = (RSAKeyParameters) key.getPublic();
|
||||
RSAPrivateCrtKeyParameters private1 = (RSAPrivateCrtKeyParameters) key.getPrivate();
|
||||
|
||||
|
||||
Kryo kryo = new Kryo();
|
||||
kryo.register(RSAKeyParameters.class, new RsaPublicKeySerializer());
|
||||
kryo.register(RSAPrivateCrtKeyParameters.class, new RsaPrivateKeySerializer());
|
||||
|
||||
// Test output to stream, large buffer.
|
||||
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
|
||||
Output output = new Output(outStream, 4096);
|
||||
kryo.writeClassAndObject(output, public1);
|
||||
output.flush();
|
||||
|
||||
// Test input from stream, large buffer.
|
||||
Input input = new Input(new ByteArrayInputStream(outStream.toByteArray()), 4096);
|
||||
RSAKeyParameters public2 = (RSAKeyParameters) kryo.readClassAndObject(input);
|
||||
|
||||
|
||||
if (!CryptoRSA.compare(public1, public2)) {
|
||||
fail("public keys not equal");
|
||||
}
|
||||
|
||||
|
||||
// Test output to stream, large buffer.
|
||||
outStream = new ByteArrayOutputStream();
|
||||
output = new Output(outStream, 4096);
|
||||
kryo.writeClassAndObject(output, private1);
|
||||
output.flush();
|
||||
|
||||
// Test input from stream, large buffer.
|
||||
input = new Input(new ByteArrayInputStream(outStream.toByteArray()), 4096);
|
||||
RSAPrivateCrtKeyParameters private2 = (RSAPrivateCrtKeyParameters) kryo.readClassAndObject(input);
|
||||
|
||||
|
||||
if (!CryptoRSA.compare(private1, private2)) {
|
||||
fail("private keys not equal");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
/*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import dorkbox.util.Sys;
|
||||
|
||||
|
||||
public class SCryptTest {
|
||||
|
||||
@Test
|
||||
public void SCrypt() throws IOException, GeneralSecurityException {
|
||||
|
||||
byte[] P, S;
|
||||
int N, r, p, dkLen;
|
||||
String DK;
|
||||
|
||||
// empty key & salt test missing because unsupported by JCE
|
||||
|
||||
P = "password".getBytes("UTF-8");
|
||||
S = "NaCl".getBytes("UTF-8");
|
||||
N = 1024;
|
||||
r = 8;
|
||||
p = 16;
|
||||
dkLen = 64;
|
||||
DK = "FDBABE1C9D3472007856E7190D01E9FE7C6AD7CBC8237830E77376634B3731622EAF30D92E22A3886FF109279D9830DAC727AFB94A83EE6D8360CBDFA2CC0640";
|
||||
|
||||
assertEquals(DK, Sys.bytesToHex(CryptoSCrypt.encrypt(P, S, N, r, p, dkLen)));
|
||||
|
||||
|
||||
P = "pleaseletmein".getBytes("UTF-8");
|
||||
S = "SodiumChloride".getBytes("UTF-8");
|
||||
N = 16384;
|
||||
r = 8;
|
||||
p = 1;
|
||||
dkLen = 64;
|
||||
DK = "7023BDCB3AFD7348461C06CD81FD38EBFDA8FBBA904F8E3EA9B543F6545DA1F2D5432955613F0FCF62D49705242A9AF9E61E85DC0D651E40DFCF017B45575887";
|
||||
|
||||
assertEquals(DK, Sys.bytesToHex(CryptoSCrypt.encrypt(P, S, N, r, p, dkLen)));
|
||||
|
||||
|
||||
P = "pleaseletmein".getBytes("UTF-8");
|
||||
S = "SodiumChloride".getBytes("UTF-8");
|
||||
N = 1048576;
|
||||
r = 8;
|
||||
p = 1;
|
||||
dkLen = 64;
|
||||
DK = "2101CB9B6A511AAEADDBBE09CF70F881EC568D574A2FFD4DABE5EE9820ADAA478E56FD8F4BA5D09FFA1C6D927C40F4C337304049E8A952FBCBF45C6FA77A41A4";
|
||||
|
||||
assertEquals(DK, Sys.bytesToHex(CryptoSCrypt.encrypt(P, S, N, r, p, dkLen)));
|
||||
}
|
||||
}
|
|
@ -1,188 +0,0 @@
|
|||
package dorkbox.util.crypto;
|
||||
|
||||
// Copyright (c) 2006 Damien Miller <djm@mindrot.org>
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* JUnit unit tests for BCrypt routines
|
||||
* @author Damien Miller
|
||||
* @version 0.2
|
||||
*/
|
||||
public class TestBCrypt extends TestCase {
|
||||
String test_vectors[][] = {
|
||||
{ "",
|
||||
"$2a$06$DCq7YPn5Rq63x1Lad4cll.",
|
||||
"$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s." },
|
||||
{ "",
|
||||
"$2a$08$HqWuK6/Ng6sg9gQzbLrgb.",
|
||||
"$2a$08$HqWuK6/Ng6sg9gQzbLrgb.Tl.ZHfXLhvt/SgVyWhQqgqcZ7ZuUtye" },
|
||||
{ "",
|
||||
"$2a$10$k1wbIrmNyFAPwPVPSVa/ze",
|
||||
"$2a$10$k1wbIrmNyFAPwPVPSVa/zecw2BCEnBwVS2GbrmgzxFUOqW9dk4TCW" },
|
||||
{ "",
|
||||
"$2a$12$k42ZFHFWqBp3vWli.nIn8u",
|
||||
"$2a$12$k42ZFHFWqBp3vWli.nIn8uYyIkbvYRvodzbfbK18SSsY.CsIQPlxO" },
|
||||
{ "a",
|
||||
"$2a$06$m0CrhHm10qJ3lXRY.5zDGO",
|
||||
"$2a$06$m0CrhHm10qJ3lXRY.5zDGO3rS2KdeeWLuGmsfGlMfOxih58VYVfxe" },
|
||||
{ "a",
|
||||
"$2a$08$cfcvVd2aQ8CMvoMpP2EBfe",
|
||||
"$2a$08$cfcvVd2aQ8CMvoMpP2EBfeodLEkkFJ9umNEfPD18.hUF62qqlC/V." },
|
||||
{ "a",
|
||||
"$2a$10$k87L/MF28Q673VKh8/cPi.",
|
||||
"$2a$10$k87L/MF28Q673VKh8/cPi.SUl7MU/rWuSiIDDFayrKk/1tBsSQu4u" },
|
||||
{ "a",
|
||||
"$2a$12$8NJH3LsPrANStV6XtBakCe",
|
||||
"$2a$12$8NJH3LsPrANStV6XtBakCez0cKHXVxmvxIlcz785vxAIZrihHZpeS" },
|
||||
{ "abc",
|
||||
"$2a$06$If6bvum7DFjUnE9p2uDeDu",
|
||||
"$2a$06$If6bvum7DFjUnE9p2uDeDu0YHzrHM6tf.iqN8.yx.jNN1ILEf7h0i" },
|
||||
{ "abc",
|
||||
"$2a$08$Ro0CUfOqk6cXEKf3dyaM7O",
|
||||
"$2a$08$Ro0CUfOqk6cXEKf3dyaM7OhSCvnwM9s4wIX9JeLapehKK5YdLxKcm" },
|
||||
{ "abc",
|
||||
"$2a$10$WvvTPHKwdBJ3uk0Z37EMR.",
|
||||
"$2a$10$WvvTPHKwdBJ3uk0Z37EMR.hLA2W6N9AEBhEgrAOljy2Ae5MtaSIUi" },
|
||||
{ "abc",
|
||||
"$2a$12$EXRkfkdmXn2gzds2SSitu.",
|
||||
"$2a$12$EXRkfkdmXn2gzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q" },
|
||||
{ "abcdefghijklmnopqrstuvwxyz",
|
||||
"$2a$06$.rCVZVOThsIa97pEDOxvGu",
|
||||
"$2a$06$.rCVZVOThsIa97pEDOxvGuRRgzG64bvtJ0938xuqzv18d3ZpQhstC" },
|
||||
{ "abcdefghijklmnopqrstuvwxyz",
|
||||
"$2a$08$aTsUwsyowQuzRrDqFflhge",
|
||||
"$2a$08$aTsUwsyowQuzRrDqFflhgekJ8d9/7Z3GV3UcgvzQW3J5zMyrTvlz." },
|
||||
{ "abcdefghijklmnopqrstuvwxyz",
|
||||
"$2a$10$fVH8e28OQRj9tqiDXs1e1u",
|
||||
"$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq" },
|
||||
{ "abcdefghijklmnopqrstuvwxyz",
|
||||
"$2a$12$D4G5f18o7aMMfwasBL7Gpu",
|
||||
"$2a$12$D4G5f18o7aMMfwasBL7GpuQWuP3pkrZrOAnqP.bmezbMng.QwJ/pG" },
|
||||
{ "~!@#$%^&*() ~!@#$%^&*()PNBFRD",
|
||||
"$2a$06$fPIsBO8qRqkjj273rfaOI.",
|
||||
"$2a$06$fPIsBO8qRqkjj273rfaOI.HtSV9jLDpTbZn782DC6/t7qT67P6FfO" },
|
||||
{ "~!@#$%^&*() ~!@#$%^&*()PNBFRD",
|
||||
"$2a$08$Eq2r4G/76Wv39MzSX262hu",
|
||||
"$2a$08$Eq2r4G/76Wv39MzSX262huzPz612MZiYHVUJe/OcOql2jo4.9UxTW" },
|
||||
{ "~!@#$%^&*() ~!@#$%^&*()PNBFRD",
|
||||
"$2a$10$LgfYWkbzEvQ4JakH7rOvHe",
|
||||
"$2a$10$LgfYWkbzEvQ4JakH7rOvHe0y8pHKF9OaFgwUZ2q7W2FFZmZzJYlfS" },
|
||||
{ "~!@#$%^&*() ~!@#$%^&*()PNBFRD",
|
||||
"$2a$12$WApznUOJfkEGSmYRfnkrPO",
|
||||
"$2a$12$WApznUOJfkEGSmYRfnkrPOr466oFDCaj4b6HY3EXGvfxm43seyhgC" },
|
||||
};
|
||||
|
||||
/**
|
||||
* Test method for 'BCrypt.hashpw(String, String)'
|
||||
*/
|
||||
public void testHashpw() {
|
||||
System.out.print("BCrypt.hashpw(): ");
|
||||
for (int i = 0; i < test_vectors.length; i++) {
|
||||
String plain = test_vectors[i][0];
|
||||
String salt = test_vectors[i][1];
|
||||
String expected = test_vectors[i][2];
|
||||
String hashed = BCrypt.hashpw(plain, salt);
|
||||
assertEquals(hashed, expected);
|
||||
System.out.print(".");
|
||||
}
|
||||
System.out.println("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for 'BCrypt.gensalt(int)'
|
||||
*/
|
||||
public void testGensaltInt() {
|
||||
System.out.print("BCrypt.gensalt(log_rounds):");
|
||||
for (int i = 4; i <= 12; i++) {
|
||||
System.out.print(" " + Integer.toString(i) + ":");
|
||||
for (int j = 0; j < test_vectors.length; j += 4) {
|
||||
String plain = test_vectors[j][0];
|
||||
String salt = BCrypt.gensalt(i);
|
||||
String hashed1 = BCrypt.hashpw(plain, salt);
|
||||
String hashed2 = BCrypt.hashpw(plain, hashed1);
|
||||
assertEquals(hashed1, hashed2);
|
||||
System.out.print(".");
|
||||
}
|
||||
}
|
||||
System.out.println("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for 'BCrypt.gensalt()'
|
||||
*/
|
||||
public void testGensalt() {
|
||||
System.out.print("BCrypt.gensalt(): ");
|
||||
for (int i = 0; i < test_vectors.length; i += 4) {
|
||||
String plain = test_vectors[i][0];
|
||||
String salt = BCrypt.gensalt();
|
||||
String hashed1 = BCrypt.hashpw(plain, salt);
|
||||
String hashed2 = BCrypt.hashpw(plain, hashed1);
|
||||
assertEquals(hashed1, hashed2);
|
||||
System.out.print(".");
|
||||
}
|
||||
System.out.println("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for 'BCrypt.checkpw(String, String)'
|
||||
* expecting success
|
||||
*/
|
||||
public void testCheckpw_success() {
|
||||
System.out.print("BCrypt.checkpw w/ good passwords: ");
|
||||
for (int i = 0; i < test_vectors.length; i++) {
|
||||
String plain = test_vectors[i][0];
|
||||
String expected = test_vectors[i][2];
|
||||
assertTrue(BCrypt.checkpw(plain, expected));
|
||||
System.out.print(".");
|
||||
}
|
||||
System.out.println("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for 'BCrypt.checkpw(String, String)'
|
||||
* expecting failure
|
||||
*/
|
||||
public void testCheckpw_failure() {
|
||||
System.out.print("BCrypt.checkpw w/ bad passwords: ");
|
||||
for (int i = 0; i < test_vectors.length; i++) {
|
||||
int broken_index = (i + 4) % test_vectors.length;
|
||||
String plain = test_vectors[i][0];
|
||||
String expected = test_vectors[broken_index][2];
|
||||
assertFalse(BCrypt.checkpw(plain, expected));
|
||||
System.out.print(".");
|
||||
}
|
||||
System.out.println("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for correct hashing of non-US-ASCII passwords
|
||||
*/
|
||||
public void testInternationalChars() {
|
||||
System.out.print("BCrypt.hashpw w/ international chars: ");
|
||||
String pw1 = "\u2605\u2605\u2605\u2605\u2605\u2605\u2605\u2605";
|
||||
String pw2 = "????????";
|
||||
|
||||
String h1 = BCrypt.hashpw(pw1, BCrypt.gensalt());
|
||||
assertFalse(BCrypt.checkpw(pw2, h1));
|
||||
System.out.print(".");
|
||||
|
||||
String h2 = BCrypt.hashpw(pw2, BCrypt.gensalt());
|
||||
assertFalse(BCrypt.checkpw(pw1, h2));
|
||||
System.out.print(".");
|
||||
System.out.println("");
|
||||
}
|
||||
|
||||
}
|
|
@ -1,170 +0,0 @@
|
|||
/*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
import org.bouncycastle.cert.X509CertificateHolder;
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.bouncycastle.crypto.params.DSAPrivateKeyParameters;
|
||||
import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
|
||||
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
|
||||
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
||||
import org.bouncycastle.crypto.params.RSAKeyParameters;
|
||||
import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
public class x509Test {
|
||||
|
||||
private static String entropySeed = "asdjhaffasdgfaasttjjhgpx600gn,-356268909087s0dfg4-42kjh255124515hasdg87";
|
||||
|
||||
@Test
|
||||
public void EcdsaCertificate() throws IOException {
|
||||
// create the certificate
|
||||
Calendar expiry = Calendar.getInstance();
|
||||
expiry.add(Calendar.DAY_OF_YEAR, 360);
|
||||
|
||||
Date startDate = new Date(); // time from which certificate is valid
|
||||
Date expiryDate = expiry.getTime(); // time after which certificate is not valid
|
||||
BigInteger serialNumber = BigInteger.valueOf(System.currentTimeMillis()); // serial number for certificate
|
||||
|
||||
|
||||
AsymmetricCipherKeyPair generateKeyPair = CryptoECC.generateKeyPair(CryptoECC.p521_curve, new SecureRandom()); // key name from Crypto class
|
||||
ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters) generateKeyPair.getPrivate();
|
||||
ECPublicKeyParameters publicKey = (ECPublicKeyParameters) generateKeyPair.getPublic();
|
||||
|
||||
|
||||
|
||||
X509CertificateHolder ECDSAx509Certificate = CryptoX509.ECDSA.createCertHolder("SHA384",
|
||||
startDate, expiryDate,
|
||||
new X500Name("CN=Test"), new X500Name("CN=Test"), serialNumber,
|
||||
privateKey, publicKey);
|
||||
// make sure it's a valid cert.
|
||||
if (ECDSAx509Certificate != null) {
|
||||
boolean valid = CryptoX509.ECDSA.validate(ECDSAx509Certificate);
|
||||
|
||||
if (!valid) {
|
||||
fail("Unable to verify a x509 certificate.");
|
||||
}
|
||||
} else {
|
||||
fail("Unable to create a x509 certificate.");
|
||||
}
|
||||
|
||||
// now sign something, then verify the signature.
|
||||
byte[] data = "My keyboard is awesome".getBytes();
|
||||
byte[] signatureBlock = CryptoX509.createSignature(data, ECDSAx509Certificate, privateKey);
|
||||
|
||||
boolean verifySignature = CryptoX509.ECDSA.verifySignature(signatureBlock, publicKey);
|
||||
|
||||
if (!verifySignature) {
|
||||
fail("Unable to verify a x509 certificate signature.");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void DsaCertificate() throws IOException {
|
||||
// create the certificate
|
||||
Calendar expiry = Calendar.getInstance();
|
||||
expiry.add(Calendar.DAY_OF_YEAR, 360);
|
||||
|
||||
Date startDate = new Date(); // time from which certificate is valid
|
||||
Date expiryDate = expiry.getTime(); // time after which certificate is not valid
|
||||
BigInteger serialNumber = BigInteger.valueOf(System.currentTimeMillis()); // serial number for certificate
|
||||
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
AsymmetricCipherKeyPair generateKeyPair = CryptoDSA.generateKeyPair(new SecureRandom(entropySeed.getBytes()), 1024);
|
||||
|
||||
|
||||
DSAPrivateKeyParameters privateKey = (DSAPrivateKeyParameters) generateKeyPair.getPrivate();
|
||||
DSAPublicKeyParameters publicKey = (DSAPublicKeyParameters) generateKeyPair.getPublic();
|
||||
|
||||
|
||||
|
||||
X509CertificateHolder DSAx509Certificate = CryptoX509.DSA.createCertHolder(startDate, expiryDate,
|
||||
new X500Name("CN=Test"), new X500Name("CN=Test"), serialNumber,
|
||||
privateKey, publicKey);
|
||||
// make sure it's a valid cert.
|
||||
if (DSAx509Certificate != null) {
|
||||
boolean valid = CryptoX509.DSA.validate(DSAx509Certificate);
|
||||
|
||||
if (!valid) {
|
||||
fail("Unable to verify a x509 certificate.");
|
||||
}
|
||||
} else {
|
||||
fail("Unable to create a x509 certificate.");
|
||||
}
|
||||
|
||||
// now sign something, then verify the signature.
|
||||
byte[] data = "My keyboard is awesome".getBytes();
|
||||
byte[] signatureBlock = CryptoX509.createSignature(data, DSAx509Certificate, privateKey);
|
||||
|
||||
boolean verifySignature = CryptoX509.DSA.verifySignature(signatureBlock, publicKey);
|
||||
|
||||
if (!verifySignature) {
|
||||
fail("Unable to verify a x509 certificate signature.");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void RsaCertificate() throws IOException {
|
||||
// create the certificate
|
||||
Calendar expiry = Calendar.getInstance();
|
||||
expiry.add(Calendar.DAY_OF_YEAR, 360);
|
||||
|
||||
Date startDate = new Date(); // time from which certificate is valid
|
||||
Date expiryDate = expiry.getTime(); // time after which certificate is not valid
|
||||
BigInteger serialNumber = BigInteger.valueOf(System.currentTimeMillis()); // serial number for certificate
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
AsymmetricCipherKeyPair generateKeyPair = CryptoRSA.generateKeyPair(new SecureRandom(entropySeed.getBytes()), 1024);
|
||||
RSAPrivateCrtKeyParameters privateKey = (RSAPrivateCrtKeyParameters) generateKeyPair.getPrivate();
|
||||
RSAKeyParameters publicKey = (RSAKeyParameters) generateKeyPair.getPublic();
|
||||
|
||||
|
||||
X509CertificateHolder RSAx509Certificate = CryptoX509.RSA.createCertHolder(startDate, expiryDate,
|
||||
new X500Name("CN=Test"), new X500Name("CN=Test"), serialNumber,
|
||||
privateKey, publicKey);
|
||||
// make sure it's a valid cert.
|
||||
if (RSAx509Certificate != null) {
|
||||
boolean valid = CryptoX509.RSA.validate(RSAx509Certificate);
|
||||
|
||||
if (!valid) {
|
||||
fail("Unable to verify a x509 certificate.");
|
||||
}
|
||||
} else {
|
||||
fail("Unable to create a x509 certificate.");
|
||||
}
|
||||
|
||||
// now sign something, then verify the signature.
|
||||
byte[] data = "My keyboard is awesome".getBytes();
|
||||
byte[] signatureBlock = CryptoX509.createSignature(data, RSAx509Certificate, privateKey);
|
||||
|
||||
boolean verifySignature = CryptoX509.RSA.verifySignature(signatureBlock, publicKey);
|
||||
|
||||
if (!verifySignature) {
|
||||
fail("Unable to verify a x509 certificate signature.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2018 Venkat Peri
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.util.sync
|
||||
|
||||
import kotlinx.coroutines.TimeoutCancellationException
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import java.util.concurrent.*
|
||||
import java.util.concurrent.atomic.*
|
||||
|
||||
class CountDownLatchTest {
|
||||
|
||||
private fun randomDelay() = ThreadLocalRandom.current().nextInt(300, 500).toLong()
|
||||
|
||||
@Test
|
||||
fun count() {
|
||||
val latch = CountDownLatch(10)
|
||||
Assert.assertEquals(10, latch.initialCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun awaitWithZero() {
|
||||
// await on a latch of 0 should not block
|
||||
val latch = CountDownLatch(0)
|
||||
runBlocking {
|
||||
if (!latch.await(100)) {
|
||||
Assert.fail("waited and it shouldn't have!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun await() {
|
||||
val count = 9
|
||||
val latch = CountDownLatch(count)
|
||||
val counter = AtomicInteger(0)
|
||||
|
||||
runBlocking {
|
||||
(0 until count).forEach { _ ->
|
||||
async {
|
||||
delay(randomDelay())
|
||||
|
||||
counter.incrementAndGet()
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
latch.await()
|
||||
Assert.assertEquals(count, counter.get())
|
||||
println(counter.get())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun builder() {
|
||||
val count = 9
|
||||
val counter = AtomicInteger(0)
|
||||
runBlocking {
|
||||
withCountDown(count) {
|
||||
(0 until count).forEach { _ ->
|
||||
async {
|
||||
delay(randomDelay())
|
||||
|
||||
counter.incrementAndGet()
|
||||
countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Assert.assertEquals(count, counter.get())
|
||||
}
|
||||
|
||||
@Test(expected = TimeoutCancellationException::class)
|
||||
fun await_timeout_expires() {
|
||||
val count = 100
|
||||
val latch = CountDownLatch(count)
|
||||
runBlocking {
|
||||
withTimeout(50) {
|
||||
(0 until count).forEach { _ ->
|
||||
async {
|
||||
delay(randomDelay())
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
latch.await()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun await_timeout_expires2() {
|
||||
val count = 100
|
||||
val latch = CountDownLatch(count)
|
||||
runBlocking {
|
||||
(0 until count).forEach { _ ->
|
||||
async {
|
||||
|
||||
delay(randomDelay())
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
val success = latch.await(50)
|
||||
|
||||
Assert.assertFalse(success)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun await_timeout_does_not_expire() {
|
||||
val count = 100
|
||||
val latch = CountDownLatch(count)
|
||||
var x = 0
|
||||
runBlocking {
|
||||
withTimeout(1000) {
|
||||
(0 until count).forEach { _ ->
|
||||
async {
|
||||
delay(randomDelay())
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
latch.await()
|
||||
x = 1
|
||||
}
|
||||
Assert.assertEquals(1, x)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun await_timeout_does_not_expire2() {
|
||||
val count = 100
|
||||
val latch = CountDownLatch(count)
|
||||
var x = 0
|
||||
runBlocking {
|
||||
withTimeout(1000) {
|
||||
(0 until count).forEach { _ ->
|
||||
async {
|
||||
delay(randomDelay())
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
Assert.assertTrue(latch.await(1000))
|
||||
x = 1
|
||||
}
|
||||
Assert.assertEquals(1, x)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun stress() {
|
||||
val count = 1000000
|
||||
val latch = CountDownLatch(count)
|
||||
runBlocking {
|
||||
(0 until count).forEach { _ ->
|
||||
async {
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
latch.await()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2018 Venkat Peri
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package dorkbox.util.sync
|
||||
|
||||
import kotlinx.coroutines.TimeoutCancellationException
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import java.util.concurrent.*
|
||||
import java.util.concurrent.atomic.*
|
||||
|
||||
class CountingLatchTest {
|
||||
|
||||
private fun randomDelay() = ThreadLocalRandom.current().nextInt(300, 500).toLong()
|
||||
|
||||
@Test
|
||||
fun awaitCountingWithZero() {
|
||||
// await on a latch of 0 should not block
|
||||
val latch = CountingLatch(0)
|
||||
runBlocking {
|
||||
if (!latch.await(100)) {
|
||||
Assert.fail("latch did not trigger and it shouldn't have!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun awaitCountingWithOne() {
|
||||
// await on a latch of 0 should not block
|
||||
val latch = CountingLatch(1)
|
||||
runBlocking {
|
||||
if (latch.await(100)) {
|
||||
Assert.fail("latch triggered and it shouldn't have!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun awaitCountingWithOneStartZeroUpDown() {
|
||||
// await on a latch of 0 should not block
|
||||
val latch = CountingLatch(1)
|
||||
runBlocking {
|
||||
latch.countUp()
|
||||
latch.countDown()
|
||||
latch.countDown()
|
||||
if (!latch.await(100)) {
|
||||
Assert.fail("waited and it shouldn't have!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun count() {
|
||||
val latch = CountingLatch(10)
|
||||
Assert.assertEquals(10, latch.initialCount)
|
||||
Assert.assertEquals(10, latch.count)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun await() {
|
||||
val count = 9
|
||||
val latch = CountingLatch(count)
|
||||
val counter = AtomicInteger(0)
|
||||
runBlocking {
|
||||
(0 until count).forEach { _ ->
|
||||
async {
|
||||
delay(randomDelay())
|
||||
|
||||
counter.incrementAndGet()
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
latch.await()
|
||||
Assert.assertEquals(count, counter.get())
|
||||
println(counter.get())
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = TimeoutCancellationException::class)
|
||||
fun await_timeout_expires() {
|
||||
val count = 100
|
||||
val latch = CountingLatch(count)
|
||||
runBlocking {
|
||||
withTimeout(50) {
|
||||
(0 until count).forEach { _ ->
|
||||
async {
|
||||
delay(randomDelay())
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
|
||||
latch.await()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun await_timeout_expires2() {
|
||||
val count = 100
|
||||
val latch = CountingLatch(count)
|
||||
runBlocking {
|
||||
(0 until count).forEach { _ ->
|
||||
async {
|
||||
delay(randomDelay())
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
|
||||
val success = latch.await(50)
|
||||
|
||||
Assert.assertFalse(success)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun await_timeout_does_not_expire() {
|
||||
val count = 100
|
||||
val latch = CountingLatch(count)
|
||||
var x = 0
|
||||
runBlocking {
|
||||
withTimeout(1000) {
|
||||
(0 until count).forEach { _ ->
|
||||
async {
|
||||
delay(randomDelay())
|
||||
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
latch.await()
|
||||
x = 1
|
||||
}
|
||||
|
||||
Assert.assertEquals(1, x)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun await_timeout_does_not_expire2() {
|
||||
val count = 100
|
||||
val latch = CountingLatch(count)
|
||||
var x = 0
|
||||
runBlocking {
|
||||
withTimeout(1000) {
|
||||
(0 until count).forEach { _ ->
|
||||
async {
|
||||
delay(randomDelay())
|
||||
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
|
||||
Assert.assertTrue(latch.await(1000))
|
||||
x = 1
|
||||
}
|
||||
|
||||
Assert.assertEquals(1, x)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun stress() {
|
||||
val count = 1000000
|
||||
val latch = CountingLatch(count)
|
||||
runBlocking {
|
||||
(0 until count).forEach { _ ->
|
||||
async {
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
latch.await()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when count is 0`() {
|
||||
val latch = CountingLatch(0)
|
||||
Assert.assertTrue(latch.isCompleted)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when count is 1`() {
|
||||
val latch = CountingLatch(1)
|
||||
Assert.assertFalse(latch.isCompleted)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when count is 1 to 0`() {
|
||||
val latch = CountingLatch(1)
|
||||
latch.countDown()
|
||||
Assert.assertTrue(latch.isCompleted)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when inverse count is 0`() {
|
||||
val latch = CountingLatchInverse(0)
|
||||
Assert.assertFalse(latch.isCompleted)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when inverse count is 1`() {
|
||||
val latch = CountingLatchInverse(0)
|
||||
latch.countDown()
|
||||
Assert.assertTrue(latch.isCompleted)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue