Compare commits

...

52 Commits

Author SHA1 Message Date
Robinson 23b9a4f7ff
version 1.48 2023-11-22 22:09:01 +01:00
Robinson bc5c28ef08
Fixed cacheName errors when null. 2023-11-22 12:28:40 +01:00
Robinson 81588e2a8e
Updated build deps 2023-10-09 12:27:09 +02:00
Robinson da9c34b538
updated build deps, kotlin 1.9.0 2023-10-02 16:15:04 +02:00
Robinson efcdbf9559
Version 1.47 2023-09-14 17:54:29 +02:00
Robinson e13205166a
updated unit tests 2023-09-14 17:54:10 +02:00
Robinson 0ce4bb2e2e
Rename .java to .kt 2023-09-14 17:54:10 +02:00
Robinson dc1bfb8371
Cleaned up getTimePretty() output 2023-09-14 17:54:00 +02:00
Robinson 3bf845f355
version 1.46 2023-08-21 01:43:56 +02:00
Robinson 0ff5f377d1
updated license 2023-08-21 01:43:36 +02:00
Robinson 5fa2e2aff6
updated gradle 2023-08-21 01:42:39 +02:00
Robinson db0be638b4
Moved byte array utilities to byteUtils project 2023-08-21 01:41:15 +02:00
Robinson ed5d2d2f74
version 1.45 2023-08-20 11:11:50 +02:00
Robinson ff21f4353e
updated deps 2023-08-20 11:08:30 +02:00
Robinson 0561f66e8e
Added cc0 license 2023-08-07 23:02:41 -06:00
Robinson 869b3b4167
Moved LZMA to byte utils 2023-08-06 17:39:37 -06:00
Robinson a0d02c83fc
code cleanup 2023-08-06 17:22:04 -06:00
Robinson 11c30aed96
Updated classutils 2023-08-05 13:24:48 -06:00
Robinson d3a767806b
Version 1.44 2023-08-05 10:16:25 -06:00
Robinson 27921e22ff
Moved classutils to their own project 2023-08-05 10:15:55 -06:00
Robinson be2dfae824
Updated version 2023-07-12 17:21:29 +02:00
Robinson 9c8f6c97dc
Added mutex.safeUnlock 2023-07-12 17:20:48 +02:00
Robinson f874a0da68
Updated version 2023-07-03 01:27:46 +02:00
Robinson 26a05099e9
updated license 2023-07-03 01:26:46 +02:00
Robinson f2782248db
Code cleanup 2023-07-03 01:26:38 +02:00
Robinson 298e760ee5
Code cleanup 2023-07-03 01:24:50 +02:00
Robinson 52495cbe48
Added version/updates 2023-07-03 01:24:44 +02:00
Robinson b12a86d9bc
Removed fileUtils (can use kotlin library now) 2023-07-03 01:21:02 +02:00
Robinson c4a6c98540
Updated to kotlin 2023-07-03 01:20:37 +02:00
Robinson 61fa96539f
Rename .java to .kt 2023-07-03 01:20:37 +02:00
Robinson 1b5827e33a
Moved all crypto utils to their own project 2023-07-03 00:57:44 +02:00
Robinson c1f1ead3fb
Code cleanup 2023-07-02 11:34:47 +02:00
Robinson 18f7b2a2b8
Moved functions to ByteUtils 2023-07-02 11:34:35 +02:00
Robinson 1db6417333
More classes migrated to kotlin 2023-06-29 19:29:40 +02:00
Robinson b3f94b28c9
Rename .java to .kt 2023-06-29 19:29:39 +02:00
Robinson ca30e648a8
Added another constructor when specifying just the name 2023-06-25 14:05:04 +02:00
Robinson b53df2ba32
updated dependencies 2023-06-23 21:12:34 +02:00
Robinson 3b5a0482e1
Updated license 2023-06-23 21:12:09 +02:00
Robinson 11b3fe0d1f
Moved webutil to networkutil 2023-06-23 21:04:31 +02:00
Robinson d74b89cf92
Updated countdown + counting latch. Added inverse latch. Updated docs 2023-06-23 21:03:52 +02:00
Robinson d19bd8bbff
fixed JPMS for newer version of kotlin coroutines 2023-06-20 13:33:11 +02:00
Robinson 7fefbb9a7d
NamedThreadFactory has a callback now when a new thread is created 2023-06-20 13:32:55 +02:00
Robinson 9808c02f57
CountingLatch will not block if the count is initialized to 0 2023-06-20 13:32:37 +02:00
Robinson 69a65ac109
updated version number 2023-06-07 11:59:29 +02:00
Robinson 7e2ee15f31
code cleanup 2023-03-10 15:52:38 -06:00
Robinson 175a3e06a6
Added await(xxx) to CDL, to mirror behavior of Java concurrent CDL 2023-03-10 14:39:59 -06:00
Robinson 63ae2fe26b
Updated license info 2023-03-10 09:16:26 -06:00
Robinson ff0dc51666
Updated deps 2023-03-10 09:16:15 -06:00
Robinson f9ddf39cdf
Added support for a Kotlin suspending CountDownLatch + CountingLatch
Updated license info
Removed unused/old/deprecated classes.
2023-03-10 09:15:13 -06:00
Robinson a044df0999
Removed async utility function. It's a delicate API, and should not be "made easy". Updated copyright. 2023-03-09 18:15:40 -06:00
Robinson 391de3b011
update version 2023-02-07 14:33:12 +01:00
Robinson bf925c10d3
updated build deps + dependencies 2023-02-07 14:32:19 +01:00
95 changed files with 5263 additions and 16078 deletions

439
LICENSE
View File

@ -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,19 +60,6 @@
Tatu Saloranta (tatu.saloranta@iki.fi)
Contributors. See source release-notes/CREDITS
- 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
@ -119,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

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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.1"
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.39"
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.1.0")
api("com.fasterxml.uuid:java-uuid-generator:4.2.0")
// // 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.6")
// 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.87.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.

View File

@ -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

12
gradlew vendored Normal file → Executable file
View File

@ -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

1
gradlew.bat vendored
View File

@ -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%

View File

@ -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"

View File

@ -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) {
}
}
}
}

View File

@ -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) {
}
}
}
}
}

View File

@ -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,12 +32,6 @@
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
@ -56,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)

View File

@ -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;
}
}
}
}
}

View File

@ -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;
}
}

View File

@ -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
}
}
}

View File

@ -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);
}
}

View File

@ -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)
}
}

View File

@ -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.
@ -18,20 +18,7 @@ package dorkbox.util
import dorkbox.os.OS
import org.slf4j.Logger
import org.slf4j.LoggerFactory
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 java.io.*
import java.nio.file.DirectoryIteratorException
import java.util.*
import java.util.zip.*
@ -57,7 +44,7 @@ object FileUtil {
/**
* Gets the version number.
*/
val version = "1.39"
val version = Sys.version
private val log: Logger = LoggerFactory.getLogger(FileUtil::class.java)
@ -569,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")
}
@ -662,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" }
@ -769,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
@ -785,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) {
@ -861,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()
}
@ -869,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")
@ -890,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
}
/**
@ -905,7 +890,7 @@ object FileUtil {
if (!file.mkdir()) {
throw IOException("Unable to create temp directory: $file")
}
return normalize(file)!!.absolutePath
return file.normalize().absolutePath
}
/**
@ -1045,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
}
@ -1068,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.*
@ -1081,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.*
@ -1094,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.
*/
@ -1104,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)
@ -1348,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
}
}
}

View File

@ -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;
}
}

View File

@ -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
}
}

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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();
}

View File

@ -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) }
}
}

View File

@ -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() + "] ";
}
}

View File

@ -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 + "] "
}
}
}

View File

@ -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

View File

@ -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 {
}

View File

@ -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
}
}

View File

@ -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() {
}
}

View File

@ -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
})
}
}

View File

@ -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)
}
}

View File

@ -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 Javas native character set: Unicode */
public static final String whitespace_charclass = "[" + whitespace_chars + "]";
const val WHITESPACE = "[" + whitespace_chars + "]"
/* A \S that actually works for Javas native character set: Unicode */
public static final String not_whitespace_charclass = "[^" + whitespace_chars + "]";
const val NOT_WHITESPACE = "[^" + whitespace_chars + "]"
}

View File

@ -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;
// }
}

View File

@ -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)
}
}

View File

@ -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;
}
}

View File

@ -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
}
}

View File

@ -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() {
}
}

312
src/dorkbox/util/Sys.kt Normal file
View File

@ -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()
}
}

View File

@ -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 {
}

View File

@ -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);
}
}

View File

@ -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")
}
}

View File

@ -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()
}
}

View File

@ -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
}
}

View File

@ -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() {
}
}

View File

@ -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;
}
}

View File

@ -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];
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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 />
* &nbsp;&nbsp;&nbsp;&nbsp;System.out.println("It matches");<br />
* else<br />
* &nbsp;&nbsp;&nbsp;&nbsp;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;
}
}

View File

@ -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;
}
}

View File

@ -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() {
}
}

View File

@ -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() {
}
}

View File

@ -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() {
}
}

View File

@ -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);
}
}

View File

@ -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() {
}
}

View File

@ -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

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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
/**

View File

@ -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 + "]";
}
}

View File

@ -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 + "]"
}
}

View File

@ -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();
}
}

View File

@ -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
}
}

View File

@ -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
}
}
}

View File

@ -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()
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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
}
}

View File

@ -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.

View File

@ -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;
}

View File

@ -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();
}
}
}

View File

@ -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()
}
}
}

View File

@ -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))
}
}
}

View File

@ -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");
}
}
}

View File

@ -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");
}
}
}

View File

@ -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");
}
}
}

View File

@ -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");
}
}
}

View File

@ -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");
}
}
}

View File

@ -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)));
}
}

View File

@ -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("");
}
}

View File

@ -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.");
}
}
}

View File

@ -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()
}
}
}

View File

@ -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)
}
}