From d218e8717ecc3d136f882756c0790c1602034a76 Mon Sep 17 00:00:00 2001 From: Niki Roo Date: Sun, 30 Jun 2024 19:06:21 +0200 Subject: [PATCH] use submodules --- .gitmodules | 4 + .../utils}/BufferedInputStreamTest.java | 2 +- .../utils}/BufferedOutputStreamTest.java | 2 +- .../test_code => tests/utils}/BundleTest.java | 2 +- .../utils}/CryptUtilsTest.java | 2 +- .../utils}/IOUtilsTest.java | 2 +- .../utils}/NextableInputStreamTest.java | 2 +- .../utils}/ProgressTest.java | 2 +- .../utils}/ReplaceInputStreamTest.java | 2 +- .../utils}/ReplaceOutputStreamTest.java | 2 +- .../utils}/SerialServerTest.java | 2 +- .../test_code => tests/utils}/SerialTest.java | 2 +- .../utils}/StringUtilsTest.java | 2 +- .../utils}/TempFilesTest.java | 2 +- .../test_code => tests/utils}/Test.java | 2 +- .../utils}/VersionTest.java | 2 +- .../utils}/bundle_test.properties | 0 src/be/nikiroo/utils | 1 + src/be/nikiroo/utils/Cache.java | 457 ---- src/be/nikiroo/utils/CacheMemory.java | 124 - src/be/nikiroo/utils/CookieUtils.java | 62 - src/be/nikiroo/utils/CryptUtils.java | 441 --- src/be/nikiroo/utils/Downloader.java | 480 ---- src/be/nikiroo/utils/HashUtils.java | 89 - src/be/nikiroo/utils/IOUtils.java | 521 ---- src/be/nikiroo/utils/Image.java | 287 -- src/be/nikiroo/utils/ImageUtils.java | 266 -- src/be/nikiroo/utils/LoginResult.java | 211 -- .../utils/MarkableFileInputStream.java | 22 - src/be/nikiroo/utils/NanoHTTPD.java | 2358 ----------------- src/be/nikiroo/utils/Progress.java | 535 ---- src/be/nikiroo/utils/Proxy.java | 150 -- src/be/nikiroo/utils/StringJustifier.java | 286 -- src/be/nikiroo/utils/StringUtils.java | 1165 -------- src/be/nikiroo/utils/TempFiles.java | 187 -- src/be/nikiroo/utils/TraceHandler.java | 156 -- src/be/nikiroo/utils/Version.java | 366 --- src/be/nikiroo/utils/VersionCheck.java | 172 -- .../utils/android/ImageUtilsAndroid.java | 99 - .../utils/android/test/TestAndroid.java | 7 - src/be/nikiroo/utils/main/bridge.java | 136 - src/be/nikiroo/utils/main/img2aa.java | 137 - src/be/nikiroo/utils/main/justify.java | 53 - src/be/nikiroo/utils/resources/Bundle.java | 1313 --------- .../nikiroo/utils/resources/BundleHelper.java | 589 ---- src/be/nikiroo/utils/resources/Bundles.java | 40 - .../resources/FixedResourceBundleControl.java | 60 - src/be/nikiroo/utils/resources/Meta.java | 132 - src/be/nikiroo/utils/resources/MetaInfo.java | 770 ------ .../nikiroo/utils/resources/TransBundle.java | 404 --- .../resources/TransBundle_ResourceList.java | 125 - .../nikiroo/utils/resources/package-info.java | 14 - .../utils/serial/CustomSerializer.java | 150 -- src/be/nikiroo/utils/serial/Exporter.java | 60 - src/be/nikiroo/utils/serial/Importer.java | 288 -- src/be/nikiroo/utils/serial/SerialUtils.java | 733 ----- .../utils/serial/server/ConnectAction.java | 474 ---- .../serial/server/ConnectActionClient.java | 166 -- .../server/ConnectActionClientObject.java | 175 -- .../server/ConnectActionClientString.java | 165 -- .../serial/server/ConnectActionServer.java | 171 -- .../server/ConnectActionServerObject.java | 72 - .../server/ConnectActionServerString.java | 52 - .../nikiroo/utils/serial/server/Server.java | 419 --- .../utils/serial/server/ServerBridge.java | 292 -- .../utils/serial/server/ServerObject.java | 180 -- .../utils/serial/server/ServerString.java | 183 -- src/be/nikiroo/utils/streams/Base64.java | 752 ------ .../utils/streams/Base64InputStream.java | 161 -- .../utils/streams/Base64OutputStream.java | 153 -- .../utils/streams/BufferedInputStream.java | 610 ----- .../utils/streams/BufferedOutputStream.java | 260 -- .../streams/MarkableFileInputStream.java | 66 - .../utils/streams/NextableInputStream.java | 279 -- .../streams/NextableInputStreamStep.java | 112 - .../utils/streams/ReplaceInputStream.java | 217 -- .../utils/streams/ReplaceOutputStream.java | 148 -- src/be/nikiroo/utils/streams/StreamUtils.java | 69 - src/be/nikiroo/utils/test/TestCase.java | 535 ---- src/be/nikiroo/utils/test/TestLauncher.java | 434 --- src/be/nikiroo/utils/ui/BreadCrumbsBar.java | 230 -- src/be/nikiroo/utils/ui/ConfigEditor.java | 165 -- src/be/nikiroo/utils/ui/ConfigItem.java | 574 ---- src/be/nikiroo/utils/ui/ConfigItemBase.java | 467 ---- .../nikiroo/utils/ui/ConfigItemBoolean.java | 67 - src/be/nikiroo/utils/ui/ConfigItemBrowse.java | 116 - src/be/nikiroo/utils/ui/ConfigItemColor.java | 169 -- .../nikiroo/utils/ui/ConfigItemCombobox.java | 68 - .../nikiroo/utils/ui/ConfigItemInteger.java | 53 - src/be/nikiroo/utils/ui/ConfigItemLocale.java | 62 - .../nikiroo/utils/ui/ConfigItemPassword.java | 109 - src/be/nikiroo/utils/ui/ConfigItemString.java | 53 - src/be/nikiroo/utils/ui/DataNode.java | 104 - src/be/nikiroo/utils/ui/DataTree.java | 68 - src/be/nikiroo/utils/ui/DelayWorker.java | 220 -- src/be/nikiroo/utils/ui/ImageTextAwt.java | 512 ---- src/be/nikiroo/utils/ui/ImageUtilsAwt.java | 334 --- src/be/nikiroo/utils/ui/Item.java | 479 ---- src/be/nikiroo/utils/ui/ListModel.java | 570 ---- src/be/nikiroo/utils/ui/ListSnapshot.java | 62 - src/be/nikiroo/utils/ui/ListenerItem.java | 53 - src/be/nikiroo/utils/ui/ListenerPanel.java | 73 - src/be/nikiroo/utils/ui/NavBar.java | 414 --- src/be/nikiroo/utils/ui/ProgressBar.java | 188 -- src/be/nikiroo/utils/ui/SearchBar.java | 148 -- src/be/nikiroo/utils/ui/TreeCellSpanner.java | 169 -- .../utils/ui/TreeModelTransformer.java | 1217 --------- src/be/nikiroo/utils/ui/TreeSnapshot.java | 127 - src/be/nikiroo/utils/ui/UIUtils.java | 371 --- src/be/nikiroo/utils/ui/WaitingDialog.java | 176 -- src/be/nikiroo/utils/ui/WrapLayout.java | 205 -- src/be/nikiroo/utils/ui/ZoomBox.java | 477 ---- src/be/nikiroo/utils/ui/clear-16x16.png | Bin 1232 -> 0 bytes .../utils/ui/compat/DefaultListModel6.java | 22 - src/be/nikiroo/utils/ui/compat/JList6.java | 84 - .../utils/ui/compat/ListCellRenderer6.java | 65 - .../nikiroo/utils/ui/compat/ListModel6.java | 19 - src/be/nikiroo/utils/ui/search-16x16.png | Bin 1274 -> 0 bytes .../utils/ui/test/ProgressBarManualTest.java | 82 - src/be/nikiroo/utils/ui/test/TestUI.java | 8 - 120 files changed, 20 insertions(+), 27985 deletions(-) create mode 100644 .gitmodules rename src/be/nikiroo/{utils/test_code => tests/utils}/BufferedInputStreamTest.java (99%) rename src/be/nikiroo/{utils/test_code => tests/utils}/BufferedOutputStreamTest.java (98%) rename src/be/nikiroo/{utils/test_code => tests/utils}/BundleTest.java (99%) rename src/be/nikiroo/{utils/test_code => tests/utils}/CryptUtilsTest.java (99%) rename src/be/nikiroo/{utils/test_code => tests/utils}/IOUtilsTest.java (93%) rename src/be/nikiroo/{utils/test_code => tests/utils}/NextableInputStreamTest.java (99%) rename src/be/nikiroo/{utils/test_code => tests/utils}/ProgressTest.java (99%) rename src/be/nikiroo/{utils/test_code => tests/utils}/ReplaceInputStreamTest.java (99%) rename src/be/nikiroo/{utils/test_code => tests/utils}/ReplaceOutputStreamTest.java (99%) rename src/be/nikiroo/{utils/test_code => tests/utils}/SerialServerTest.java (99%) rename src/be/nikiroo/{utils/test_code => tests/utils}/SerialTest.java (99%) rename src/be/nikiroo/{utils/test_code => tests/utils}/StringUtilsTest.java (99%) rename src/be/nikiroo/{utils/test_code => tests/utils}/TempFilesTest.java (98%) rename src/be/nikiroo/{utils/test_code => tests/utils}/Test.java (97%) rename src/be/nikiroo/{utils/test_code => tests/utils}/VersionTest.java (99%) rename src/be/nikiroo/{utils/test_code => tests/utils}/bundle_test.properties (100%) create mode 160000 src/be/nikiroo/utils delete mode 100644 src/be/nikiroo/utils/Cache.java delete mode 100644 src/be/nikiroo/utils/CacheMemory.java delete mode 100644 src/be/nikiroo/utils/CookieUtils.java delete mode 100644 src/be/nikiroo/utils/CryptUtils.java delete mode 100644 src/be/nikiroo/utils/Downloader.java delete mode 100644 src/be/nikiroo/utils/HashUtils.java delete mode 100644 src/be/nikiroo/utils/IOUtils.java delete mode 100644 src/be/nikiroo/utils/Image.java delete mode 100644 src/be/nikiroo/utils/ImageUtils.java delete mode 100644 src/be/nikiroo/utils/LoginResult.java delete mode 100644 src/be/nikiroo/utils/MarkableFileInputStream.java delete mode 100644 src/be/nikiroo/utils/NanoHTTPD.java delete mode 100644 src/be/nikiroo/utils/Progress.java delete mode 100644 src/be/nikiroo/utils/Proxy.java delete mode 100644 src/be/nikiroo/utils/StringJustifier.java delete mode 100644 src/be/nikiroo/utils/StringUtils.java delete mode 100644 src/be/nikiroo/utils/TempFiles.java delete mode 100644 src/be/nikiroo/utils/TraceHandler.java delete mode 100644 src/be/nikiroo/utils/Version.java delete mode 100644 src/be/nikiroo/utils/VersionCheck.java delete mode 100644 src/be/nikiroo/utils/android/ImageUtilsAndroid.java delete mode 100644 src/be/nikiroo/utils/android/test/TestAndroid.java delete mode 100644 src/be/nikiroo/utils/main/bridge.java delete mode 100644 src/be/nikiroo/utils/main/img2aa.java delete mode 100644 src/be/nikiroo/utils/main/justify.java delete mode 100644 src/be/nikiroo/utils/resources/Bundle.java delete mode 100644 src/be/nikiroo/utils/resources/BundleHelper.java delete mode 100644 src/be/nikiroo/utils/resources/Bundles.java delete mode 100644 src/be/nikiroo/utils/resources/FixedResourceBundleControl.java delete mode 100644 src/be/nikiroo/utils/resources/Meta.java delete mode 100644 src/be/nikiroo/utils/resources/MetaInfo.java delete mode 100644 src/be/nikiroo/utils/resources/TransBundle.java delete mode 100644 src/be/nikiroo/utils/resources/TransBundle_ResourceList.java delete mode 100644 src/be/nikiroo/utils/resources/package-info.java delete mode 100644 src/be/nikiroo/utils/serial/CustomSerializer.java delete mode 100644 src/be/nikiroo/utils/serial/Exporter.java delete mode 100644 src/be/nikiroo/utils/serial/Importer.java delete mode 100644 src/be/nikiroo/utils/serial/SerialUtils.java delete mode 100644 src/be/nikiroo/utils/serial/server/ConnectAction.java delete mode 100644 src/be/nikiroo/utils/serial/server/ConnectActionClient.java delete mode 100644 src/be/nikiroo/utils/serial/server/ConnectActionClientObject.java delete mode 100644 src/be/nikiroo/utils/serial/server/ConnectActionClientString.java delete mode 100644 src/be/nikiroo/utils/serial/server/ConnectActionServer.java delete mode 100644 src/be/nikiroo/utils/serial/server/ConnectActionServerObject.java delete mode 100644 src/be/nikiroo/utils/serial/server/ConnectActionServerString.java delete mode 100644 src/be/nikiroo/utils/serial/server/Server.java delete mode 100644 src/be/nikiroo/utils/serial/server/ServerBridge.java delete mode 100644 src/be/nikiroo/utils/serial/server/ServerObject.java delete mode 100644 src/be/nikiroo/utils/serial/server/ServerString.java delete mode 100644 src/be/nikiroo/utils/streams/Base64.java delete mode 100644 src/be/nikiroo/utils/streams/Base64InputStream.java delete mode 100644 src/be/nikiroo/utils/streams/Base64OutputStream.java delete mode 100644 src/be/nikiroo/utils/streams/BufferedInputStream.java delete mode 100644 src/be/nikiroo/utils/streams/BufferedOutputStream.java delete mode 100644 src/be/nikiroo/utils/streams/MarkableFileInputStream.java delete mode 100644 src/be/nikiroo/utils/streams/NextableInputStream.java delete mode 100644 src/be/nikiroo/utils/streams/NextableInputStreamStep.java delete mode 100644 src/be/nikiroo/utils/streams/ReplaceInputStream.java delete mode 100644 src/be/nikiroo/utils/streams/ReplaceOutputStream.java delete mode 100644 src/be/nikiroo/utils/streams/StreamUtils.java delete mode 100644 src/be/nikiroo/utils/test/TestCase.java delete mode 100644 src/be/nikiroo/utils/test/TestLauncher.java delete mode 100644 src/be/nikiroo/utils/ui/BreadCrumbsBar.java delete mode 100644 src/be/nikiroo/utils/ui/ConfigEditor.java delete mode 100644 src/be/nikiroo/utils/ui/ConfigItem.java delete mode 100644 src/be/nikiroo/utils/ui/ConfigItemBase.java delete mode 100644 src/be/nikiroo/utils/ui/ConfigItemBoolean.java delete mode 100644 src/be/nikiroo/utils/ui/ConfigItemBrowse.java delete mode 100644 src/be/nikiroo/utils/ui/ConfigItemColor.java delete mode 100644 src/be/nikiroo/utils/ui/ConfigItemCombobox.java delete mode 100644 src/be/nikiroo/utils/ui/ConfigItemInteger.java delete mode 100644 src/be/nikiroo/utils/ui/ConfigItemLocale.java delete mode 100644 src/be/nikiroo/utils/ui/ConfigItemPassword.java delete mode 100644 src/be/nikiroo/utils/ui/ConfigItemString.java delete mode 100644 src/be/nikiroo/utils/ui/DataNode.java delete mode 100644 src/be/nikiroo/utils/ui/DataTree.java delete mode 100644 src/be/nikiroo/utils/ui/DelayWorker.java delete mode 100644 src/be/nikiroo/utils/ui/ImageTextAwt.java delete mode 100644 src/be/nikiroo/utils/ui/ImageUtilsAwt.java delete mode 100644 src/be/nikiroo/utils/ui/Item.java delete mode 100644 src/be/nikiroo/utils/ui/ListModel.java delete mode 100644 src/be/nikiroo/utils/ui/ListSnapshot.java delete mode 100644 src/be/nikiroo/utils/ui/ListenerItem.java delete mode 100644 src/be/nikiroo/utils/ui/ListenerPanel.java delete mode 100644 src/be/nikiroo/utils/ui/NavBar.java delete mode 100644 src/be/nikiroo/utils/ui/ProgressBar.java delete mode 100644 src/be/nikiroo/utils/ui/SearchBar.java delete mode 100644 src/be/nikiroo/utils/ui/TreeCellSpanner.java delete mode 100644 src/be/nikiroo/utils/ui/TreeModelTransformer.java delete mode 100644 src/be/nikiroo/utils/ui/TreeSnapshot.java delete mode 100644 src/be/nikiroo/utils/ui/UIUtils.java delete mode 100644 src/be/nikiroo/utils/ui/WaitingDialog.java delete mode 100644 src/be/nikiroo/utils/ui/WrapLayout.java delete mode 100644 src/be/nikiroo/utils/ui/ZoomBox.java delete mode 100644 src/be/nikiroo/utils/ui/clear-16x16.png delete mode 100644 src/be/nikiroo/utils/ui/compat/DefaultListModel6.java delete mode 100644 src/be/nikiroo/utils/ui/compat/JList6.java delete mode 100644 src/be/nikiroo/utils/ui/compat/ListCellRenderer6.java delete mode 100644 src/be/nikiroo/utils/ui/compat/ListModel6.java delete mode 100644 src/be/nikiroo/utils/ui/search-16x16.png delete mode 100644 src/be/nikiroo/utils/ui/test/ProgressBarManualTest.java delete mode 100644 src/be/nikiroo/utils/ui/test/TestUI.java diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..0a81b17 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "src/be/nikiroo/utils"] + path = src/be/nikiroo/utils + url = . + branch = subtree diff --git a/src/be/nikiroo/utils/test_code/BufferedInputStreamTest.java b/src/be/nikiroo/tests/utils/BufferedInputStreamTest.java similarity index 99% rename from src/be/nikiroo/utils/test_code/BufferedInputStreamTest.java rename to src/be/nikiroo/tests/utils/BufferedInputStreamTest.java index c715585..ed753bf 100644 --- a/src/be/nikiroo/utils/test_code/BufferedInputStreamTest.java +++ b/src/be/nikiroo/tests/utils/BufferedInputStreamTest.java @@ -1,4 +1,4 @@ -package be.nikiroo.utils.test_code; +package be.nikiroo.tests.utils; import java.io.ByteArrayInputStream; import java.io.InputStream; diff --git a/src/be/nikiroo/utils/test_code/BufferedOutputStreamTest.java b/src/be/nikiroo/tests/utils/BufferedOutputStreamTest.java similarity index 98% rename from src/be/nikiroo/utils/test_code/BufferedOutputStreamTest.java rename to src/be/nikiroo/tests/utils/BufferedOutputStreamTest.java index 5646e61..928faad 100644 --- a/src/be/nikiroo/utils/test_code/BufferedOutputStreamTest.java +++ b/src/be/nikiroo/tests/utils/BufferedOutputStreamTest.java @@ -1,4 +1,4 @@ -package be.nikiroo.utils.test_code; +package be.nikiroo.tests.utils; import java.io.ByteArrayOutputStream; import java.util.ArrayList; diff --git a/src/be/nikiroo/utils/test_code/BundleTest.java b/src/be/nikiroo/tests/utils/BundleTest.java similarity index 99% rename from src/be/nikiroo/utils/test_code/BundleTest.java rename to src/be/nikiroo/tests/utils/BundleTest.java index 2e25eb0..f8e833f 100644 --- a/src/be/nikiroo/utils/test_code/BundleTest.java +++ b/src/be/nikiroo/tests/utils/BundleTest.java @@ -1,4 +1,4 @@ -package be.nikiroo.utils.test_code; +package be.nikiroo.tests.utils; import java.io.File; import java.util.ArrayList; diff --git a/src/be/nikiroo/utils/test_code/CryptUtilsTest.java b/src/be/nikiroo/tests/utils/CryptUtilsTest.java similarity index 99% rename from src/be/nikiroo/utils/test_code/CryptUtilsTest.java rename to src/be/nikiroo/tests/utils/CryptUtilsTest.java index 0c53461..826035e 100644 --- a/src/be/nikiroo/utils/test_code/CryptUtilsTest.java +++ b/src/be/nikiroo/tests/utils/CryptUtilsTest.java @@ -1,4 +1,4 @@ -package be.nikiroo.utils.test_code; +package be.nikiroo.tests.utils; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; diff --git a/src/be/nikiroo/utils/test_code/IOUtilsTest.java b/src/be/nikiroo/tests/utils/IOUtilsTest.java similarity index 93% rename from src/be/nikiroo/utils/test_code/IOUtilsTest.java rename to src/be/nikiroo/tests/utils/IOUtilsTest.java index 9f22896..71acf1c 100644 --- a/src/be/nikiroo/utils/test_code/IOUtilsTest.java +++ b/src/be/nikiroo/tests/utils/IOUtilsTest.java @@ -1,4 +1,4 @@ -package be.nikiroo.utils.test_code; +package be.nikiroo.tests.utils; import java.io.InputStream; diff --git a/src/be/nikiroo/utils/test_code/NextableInputStreamTest.java b/src/be/nikiroo/tests/utils/NextableInputStreamTest.java similarity index 99% rename from src/be/nikiroo/utils/test_code/NextableInputStreamTest.java rename to src/be/nikiroo/tests/utils/NextableInputStreamTest.java index 4e59823..3dc6fd1 100644 --- a/src/be/nikiroo/utils/test_code/NextableInputStreamTest.java +++ b/src/be/nikiroo/tests/utils/NextableInputStreamTest.java @@ -1,4 +1,4 @@ -package be.nikiroo.utils.test_code; +package be.nikiroo.tests.utils; import java.io.ByteArrayInputStream; diff --git a/src/be/nikiroo/utils/test_code/ProgressTest.java b/src/be/nikiroo/tests/utils/ProgressTest.java similarity index 99% rename from src/be/nikiroo/utils/test_code/ProgressTest.java rename to src/be/nikiroo/tests/utils/ProgressTest.java index 22e36cb..c829e70 100644 --- a/src/be/nikiroo/utils/test_code/ProgressTest.java +++ b/src/be/nikiroo/tests/utils/ProgressTest.java @@ -1,4 +1,4 @@ -package be.nikiroo.utils.test_code; +package be.nikiroo.tests.utils; import be.nikiroo.utils.Progress; import be.nikiroo.utils.test.TestCase; diff --git a/src/be/nikiroo/utils/test_code/ReplaceInputStreamTest.java b/src/be/nikiroo/tests/utils/ReplaceInputStreamTest.java similarity index 99% rename from src/be/nikiroo/utils/test_code/ReplaceInputStreamTest.java rename to src/be/nikiroo/tests/utils/ReplaceInputStreamTest.java index efab8c7..08213e1 100644 --- a/src/be/nikiroo/utils/test_code/ReplaceInputStreamTest.java +++ b/src/be/nikiroo/tests/utils/ReplaceInputStreamTest.java @@ -1,4 +1,4 @@ -package be.nikiroo.utils.test_code; +package be.nikiroo.tests.utils; import java.io.ByteArrayInputStream; import java.io.InputStream; diff --git a/src/be/nikiroo/utils/test_code/ReplaceOutputStreamTest.java b/src/be/nikiroo/tests/utils/ReplaceOutputStreamTest.java similarity index 99% rename from src/be/nikiroo/utils/test_code/ReplaceOutputStreamTest.java rename to src/be/nikiroo/tests/utils/ReplaceOutputStreamTest.java index 1db3397..4453320 100644 --- a/src/be/nikiroo/utils/test_code/ReplaceOutputStreamTest.java +++ b/src/be/nikiroo/tests/utils/ReplaceOutputStreamTest.java @@ -1,4 +1,4 @@ -package be.nikiroo.utils.test_code; +package be.nikiroo.tests.utils; import java.io.ByteArrayOutputStream; diff --git a/src/be/nikiroo/utils/test_code/SerialServerTest.java b/src/be/nikiroo/tests/utils/SerialServerTest.java similarity index 99% rename from src/be/nikiroo/utils/test_code/SerialServerTest.java rename to src/be/nikiroo/tests/utils/SerialServerTest.java index c10a158..61d01a4 100644 --- a/src/be/nikiroo/utils/test_code/SerialServerTest.java +++ b/src/be/nikiroo/tests/utils/SerialServerTest.java @@ -1,4 +1,4 @@ -package be.nikiroo.utils.test_code; +package be.nikiroo.tests.utils; import java.net.URL; diff --git a/src/be/nikiroo/utils/test_code/SerialTest.java b/src/be/nikiroo/tests/utils/SerialTest.java similarity index 99% rename from src/be/nikiroo/utils/test_code/SerialTest.java rename to src/be/nikiroo/tests/utils/SerialTest.java index bf08f5c..46cda26 100644 --- a/src/be/nikiroo/utils/test_code/SerialTest.java +++ b/src/be/nikiroo/tests/utils/SerialTest.java @@ -1,4 +1,4 @@ -package be.nikiroo.utils.test_code; +package be.nikiroo.tests.utils; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; diff --git a/src/be/nikiroo/utils/test_code/StringUtilsTest.java b/src/be/nikiroo/tests/utils/StringUtilsTest.java similarity index 99% rename from src/be/nikiroo/utils/test_code/StringUtilsTest.java rename to src/be/nikiroo/tests/utils/StringUtilsTest.java index a441195..a17731f 100644 --- a/src/be/nikiroo/utils/test_code/StringUtilsTest.java +++ b/src/be/nikiroo/tests/utils/StringUtilsTest.java @@ -1,4 +1,4 @@ -package be.nikiroo.utils.test_code; +package be.nikiroo.tests.utils; import java.util.Arrays; import java.util.Date; diff --git a/src/be/nikiroo/utils/test_code/TempFilesTest.java b/src/be/nikiroo/tests/utils/TempFilesTest.java similarity index 98% rename from src/be/nikiroo/utils/test_code/TempFilesTest.java rename to src/be/nikiroo/tests/utils/TempFilesTest.java index dad4cac..19ed207 100644 --- a/src/be/nikiroo/utils/test_code/TempFilesTest.java +++ b/src/be/nikiroo/tests/utils/TempFilesTest.java @@ -1,4 +1,4 @@ -package be.nikiroo.utils.test_code; +package be.nikiroo.tests.utils; import java.io.File; import java.io.IOException; diff --git a/src/be/nikiroo/utils/test_code/Test.java b/src/be/nikiroo/tests/utils/Test.java similarity index 97% rename from src/be/nikiroo/utils/test_code/Test.java rename to src/be/nikiroo/tests/utils/Test.java index 8d99cba..23f4946 100644 --- a/src/be/nikiroo/utils/test_code/Test.java +++ b/src/be/nikiroo/tests/utils/Test.java @@ -1,4 +1,4 @@ -package be.nikiroo.utils.test_code; +package be.nikiroo.tests.utils; import be.nikiroo.utils.Cache; import be.nikiroo.utils.CacheMemory; diff --git a/src/be/nikiroo/utils/test_code/VersionTest.java b/src/be/nikiroo/tests/utils/VersionTest.java similarity index 99% rename from src/be/nikiroo/utils/test_code/VersionTest.java rename to src/be/nikiroo/tests/utils/VersionTest.java index 2d84476..52d80e7 100644 --- a/src/be/nikiroo/utils/test_code/VersionTest.java +++ b/src/be/nikiroo/tests/utils/VersionTest.java @@ -1,4 +1,4 @@ -package be.nikiroo.utils.test_code; +package be.nikiroo.tests.utils; import be.nikiroo.utils.Version; import be.nikiroo.utils.test.TestCase; diff --git a/src/be/nikiroo/utils/test_code/bundle_test.properties b/src/be/nikiroo/tests/utils/bundle_test.properties similarity index 100% rename from src/be/nikiroo/utils/test_code/bundle_test.properties rename to src/be/nikiroo/tests/utils/bundle_test.properties diff --git a/src/be/nikiroo/utils b/src/be/nikiroo/utils new file mode 160000 index 0000000..34835dd --- /dev/null +++ b/src/be/nikiroo/utils @@ -0,0 +1 @@ +Subproject commit 34835dd204994ab0be1899848c4de2f9dde9f766 diff --git a/src/be/nikiroo/utils/Cache.java b/src/be/nikiroo/utils/Cache.java deleted file mode 100644 index 6233082..0000000 --- a/src/be/nikiroo/utils/Cache.java +++ /dev/null @@ -1,457 +0,0 @@ -package be.nikiroo.utils; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.Date; - -import be.nikiroo.utils.streams.MarkableFileInputStream; - -/** - * A generic cache system, with special support for {@link URL}s. - *

- * This cache also manages timeout information. - * - * @author niki - */ -public class Cache { - private File dir; - private long tooOldChanging; - private long tooOldStable; - private TraceHandler tracer = new TraceHandler(); - - /** - * Only for inheritance. - */ - protected Cache() { - } - - /** - * Create a new {@link Cache} object. - * - * @param dir - * the directory to use as cache - * @param hoursChanging - * the number of hours after which a cached file that is thought - * to change ~often is considered too old (or -1 for - * "never too old") - * @param hoursStable - * the number of hours after which a cached file that is thought - * to change rarely is considered too old (or -1 for - * "never too old") - * - * @throws IOException - * in case of I/O error - */ - public Cache(File dir, int hoursChanging, int hoursStable) - throws IOException { - this.dir = dir; - this.tooOldChanging = 1000L * 60 * 60 * hoursChanging; - this.tooOldStable = 1000L * 60 * 60 * hoursStable; - - if (dir != null && !dir.exists()) { - dir.mkdirs(); - } - - if (dir == null || !dir.exists()) { - throw new IOException("Cannot create the cache directory: " - + (dir == null ? "null" : dir.getAbsolutePath())); - } - } - - /** - * The traces handler for this {@link Cache}. - * - * @return the traces handler - */ - public TraceHandler getTraceHandler() { - return tracer; - } - - /** - * The traces handler for this {@link Cache}. - * - * @param tracer - * the new traces handler - */ - public void setTraceHandler(TraceHandler tracer) { - if (tracer == null) { - tracer = new TraceHandler(false, false, false); - } - - this.tracer = tracer; - } - - /** - * Check the resource to see if it is in the cache. - * - * @param uniqueID - * the resource to check - * @param allowTooOld - * allow files even if they are considered too old - * @param stable - * a stable file (that dones't change too often) -- parameter - * used to check if the file is too old to keep or not - * - * @return TRUE if it is - * - */ - public boolean check(String uniqueID, boolean allowTooOld, boolean stable) { - return check(getCached(uniqueID), allowTooOld, stable); - } - - /** - * Check the resource to see if it is in the cache. - * - * @param url - * the resource to check - * @param allowTooOld - * allow files even if they are considered too old - * @param stable - * a stable file (that dones't change too often) -- parameter - * used to check if the file is too old to keep or not - * - * @return TRUE if it is - * - */ - public boolean check(URL url, boolean allowTooOld, boolean stable) { - return check(getCached(url), allowTooOld, stable); - } - - /** - * Check the resource to see if it is in the cache. - * - * @param cached - * the resource to check - * @param allowTooOld - * allow files even if they are considered too old - * @param stable - * a stable file (that dones't change too often) -- parameter - * used to check if the file is too old to keep or not - * - * @return TRUE if it is - * - */ - private boolean check(File cached, boolean allowTooOld, boolean stable) { - if (cached.exists() && cached.isFile()) { - if (!allowTooOld && isOld(cached, stable)) { - if (!cached.delete()) { - tracer.error("Cannot delete temporary file: " - + cached.getAbsolutePath()); - } - } else { - return true; - } - } - - return false; - } - - /** - * Clean the cache (delete the cached items). - * - * @param onlyOld - * only clean the files that are considered too old for a stable - * resource - * - * @return the number of cleaned items - */ - public int clean(boolean onlyOld) { - long ms = System.currentTimeMillis(); - - tracer.trace("Cleaning cache from old files..."); - - int num = clean(onlyOld, dir, -1); - - tracer.trace(num + "cache items cleaned in " - + (System.currentTimeMillis() - ms) + " ms"); - - return num; - } - - /** - * Clean the cache (delete the cached items) in the given cache directory. - * - * @param onlyOld - * only clean the files that are considered too old for stable - * resources - * @param cacheDir - * the cache directory to clean - * @param limit - * stop after limit files deleted, or -1 for unlimited - * - * @return the number of cleaned items - */ - private int clean(boolean onlyOld, File cacheDir, int limit) { - int num = 0; - File[] files = cacheDir.listFiles(); - if (files != null) { - for (File file : files) { - if (limit >= 0 && num >= limit) { - return num; - } - - if (file.isDirectory()) { - num += clean(onlyOld, file, limit); - file.delete(); // only if empty - } else { - if (!onlyOld || isOld(file, true)) { - if (file.delete()) { - num++; - } else { - tracer.error("Cannot delete temporary file: " - + file.getAbsolutePath()); - } - } - } - } - } - - return num; - } - - /** - * Open a resource from the cache if it exists. - * - * @param uniqueID - * the unique ID - * @param allowTooOld - * allow files even if they are considered too old - * @param stable - * a stable file (that dones't change too often) -- parameter - * used to check if the file is too old to keep or not - * - * @return the opened resource if found, NULL if not - */ - public InputStream load(String uniqueID, boolean allowTooOld, boolean stable) { - return load(getCached(uniqueID), allowTooOld, stable); - } - - /** - * Open a resource from the cache if it exists. - * - * @param url - * the resource to open - * @param allowTooOld - * allow files even if they are considered too old - * @param stable - * a stable file (that doesn't change too often) -- parameter - * used to check if the file is too old to keep or not in the - * cache - * - * @return the opened resource if found, NULL if not - */ - public InputStream load(URL url, boolean allowTooOld, boolean stable) { - return load(getCached(url), allowTooOld, stable); - } - - /** - * Open a resource from the cache if it exists. - * - * @param cached - * the resource to open - * @param allowTooOld - * allow files even if they are considered too old - * @param stable - * a stable file (that dones't change too often) -- parameter - * used to check if the file is too old to keep or not - * - * @return the opened resource if found, NULL if not - */ - private InputStream load(File cached, boolean allowTooOld, boolean stable) { - if (cached.exists() && cached.isFile() - && (allowTooOld || !isOld(cached, stable))) { - try { - return new MarkableFileInputStream(cached); - } catch (FileNotFoundException e) { - return null; - } - } - - return null; - } - - /** - * Save the given resource to the cache. - * - * @param in - * the input data - * @param uniqueID - * a unique ID used to locate the cached resource - * - * @return the number of bytes written - * - * @throws IOException - * in case of I/O error - */ - public long save(InputStream in, String uniqueID) throws IOException { - File cached = getCached(uniqueID); - cached.getParentFile().mkdirs(); - return save(in, cached); - } - - /** - * Save the given resource to the cache. - * - * @param in - * the input data - * @param url - * the {@link URL} used to locate the cached resource - * - * @return the number of bytes written - * - * @throws IOException - * in case of I/O error - */ - public long save(InputStream in, URL url) throws IOException { - File cached = getCached(url); - return save(in, cached); - } - - /** - * Save the given resource to the cache. - *

- * Will also clean the {@link Cache} from old files. - * - * @param in - * the input data - * @param cached - * the cached {@link File} to save to - * - * @return the number of bytes written - * - * @throws IOException - * in case of I/O error - */ - private long save(InputStream in, File cached) throws IOException { - // We want to force at least an immediate SAVE/LOAD to work for some - // workflows, even if we don't accept cached files (times set to "0" - // -- and not "-1" or a positive value) - clean(true, dir, 10); - cached.getParentFile().mkdirs(); // in case we deleted our own parent - long bytes = IOUtils.write(in, cached); - return bytes; - } - - /** - * Remove the given resource from the cache. - * - * @param uniqueID - * a unique ID used to locate the cached resource - * - * @return TRUE if it was removed - */ - public boolean remove(String uniqueID) { - File cached = getCached(uniqueID); - return cached.delete(); - } - - /** - * Remove the given resource from the cache. - * - * @param url - * the {@link URL} used to locate the cached resource - * - * @return TRUE if it was removed - */ - public boolean remove(URL url) { - File cached = getCached(url); - return cached.delete(); - } - - /** - * Check if the {@link File} is too old according to - * {@link Cache#tooOldChanging}. - * - * @param file - * the file to check - * @param stable - * TRUE to denote stable files, that are not supposed to change - * too often - * - * @return TRUE if it is - */ - private boolean isOld(File file, boolean stable) { - long max = tooOldChanging; - if (stable) { - max = tooOldStable; - } - - if (max < 0) { - return false; - } - - long time = new Date().getTime() - file.lastModified(); - if (time < 0) { - tracer.error("Timestamp in the future for file: " - + file.getAbsolutePath()); - } - - return time < 0 || time > max; - } - - /** - * Return the associated cache {@link File} from this {@link URL}. - * - * @param url - * the {@link URL} - * - * @return the cached {@link File} version of this {@link URL} - */ - private File getCached(URL url) { - File subdir; - - String name = url.getHost(); - if (name == null || name.isEmpty()) { - // File - File file = new File(url.getFile()); - if (file.getParent() == null) { - subdir = new File("+"); - } else { - subdir = new File(file.getParent().replace("..", "__")); - } - subdir = new File(dir, allowedChars(subdir.getPath())); - name = allowedChars(url.getFile()); - } else { - // URL - File subsubDir = new File(dir, allowedChars(url.getHost())); - subdir = new File(subsubDir, "_" + allowedChars(url.getPath())); - name = allowedChars("_" + url.getQuery()); - } - - File cacheFile = new File(subdir, name); - subdir.mkdirs(); - - return cacheFile; - } - - /** - * Get the basic cache resource file corresponding to this unique ID. - *

- * Note that you may need to add a sub-directory in some cases. - * - * @param uniqueID - * the id - * - * @return the cached version if present, NULL if not - */ - private File getCached(String uniqueID) { - File file = new File(dir, allowedChars(uniqueID)); - File subdir = new File(file.getParentFile(), "_"); - return new File(subdir, file.getName()); - } - - /** - * Replace not allowed chars (in a {@link File}) by "_". - * - * @param raw - * the raw {@link String} - * - * @return the sanitised {@link String} - */ - private String allowedChars(String raw) { - return raw.replace('/', '_').replace(':', '_').replace("\\", "_"); - } -} \ No newline at end of file diff --git a/src/be/nikiroo/utils/CacheMemory.java b/src/be/nikiroo/utils/CacheMemory.java deleted file mode 100644 index de4fae3..0000000 --- a/src/be/nikiroo/utils/CacheMemory.java +++ /dev/null @@ -1,124 +0,0 @@ -package be.nikiroo.utils; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.HashMap; -import java.util.Map; - -/** - * A memory only version of {@link Cache}. - * - * @author niki - */ -public class CacheMemory extends Cache { - private Map data; - - /** - * Create a new {@link CacheMemory}. - */ - public CacheMemory() { - data = new HashMap(); - } - - @Override - public boolean check(String uniqueID, boolean allowTooOld, boolean stable) { - return data.containsKey(getKey(uniqueID)); - } - - @Override - public boolean check(URL url, boolean allowTooOld, boolean stable) { - return data.containsKey(getKey(url)); - } - - @Override - public int clean(boolean onlyOld) { - int cleaned = 0; - if (!onlyOld) { - cleaned = data.size(); - data.clear(); - } - - return cleaned; - } - - @Override - public InputStream load(String uniqueID, boolean allowTooOld, boolean stable) { - if (check(uniqueID, allowTooOld, stable)) { - return load(getKey(uniqueID)); - } - - return null; - } - - @Override - public InputStream load(URL url, boolean allowTooOld, boolean stable) { - if (check(url, allowTooOld, stable)) { - return load(getKey(url)); - } - - return null; - } - - @Override - public boolean remove(String uniqueID) { - return data.remove(getKey(uniqueID)) != null; - } - - @Override - public boolean remove(URL url) { - return data.remove(getKey(url)) != null; - } - - @Override - public long save(InputStream in, String uniqueID) throws IOException { - byte[] bytes = IOUtils.toByteArray(in); - data.put(getKey(uniqueID), bytes); - return bytes.length; - } - - @Override - public long save(InputStream in, URL url) throws IOException { - byte[] bytes = IOUtils.toByteArray(in); - data.put(getKey(url), bytes); - return bytes.length; - } - - /** - * Return a key mapping to the given unique ID. - * - * @param uniqueID the unique ID - * - * @return the key - */ - private String getKey(String uniqueID) { - return "UID:" + uniqueID; - } - - /** - * Return a key mapping to the given urm. - * - * @param url the url - * - * @return the key - */ - private String getKey(URL url) { - return "URL:" + url.toString(); - } - - /** - * Load the given key. - * - * @param key the key to load - * @return the loaded data - */ - private InputStream load(String key) { - byte[] data = this.data.get(key); - if (data != null) { - return new ByteArrayInputStream(data); - } - - return null; - } -} diff --git a/src/be/nikiroo/utils/CookieUtils.java b/src/be/nikiroo/utils/CookieUtils.java deleted file mode 100644 index 8d307a2..0000000 --- a/src/be/nikiroo/utils/CookieUtils.java +++ /dev/null @@ -1,62 +0,0 @@ -package be.nikiroo.utils; - -import java.util.Date; - -/** - * Some utilities for cookie management. - * - * @author niki - */ -public class CookieUtils { - /** - * The number of seconds for the period (we accept the current or the - * previous period as valid for a cookie, via "offset"). - */ - static public int GRACE_PERIOD = 3600 * 1000; // between 1 and 2h - - /** - * Generate a new cookie value from the user (email) and an offset. - *

- * You should use an offset of "0" when creating the cookie, and an offset - * of "0" or "-1" if required when checking for the value (the idea is to - * allow a cookie to persist across two timespans; if not, the cookie will - * be expired the very second we switch to a new timespan). - * - * @param value - * the value to generate a cookie for -- you must be able to - * regenerate it in order to check it later - * @param offset - * the offset (should be 0 for creating, 0 then -1 if needed for - * checking) - * - * @return the new cookie - */ - static public String generateCookie(String value, int offset) { - long unixTime = (long) Math.floor(new Date().getTime() / GRACE_PERIOD) - + offset; - return HashUtils.sha512(value + Long.toString(unixTime)); - } - - /** - * Check the given cookie. - * - * @param value - * the value to generate a cookie for -- you must be able to - * regenerate it in order to check it later - * @param cookie - * the cookie to validate - * - * @return TRUE if it is correct - */ - static public boolean validateCookie(String value, String cookie) { - if (cookie != null) - cookie = cookie.trim(); - - String newCookie = generateCookie(value, 0); - if (!newCookie.equals(cookie)) { - newCookie = generateCookie(value, -1); - } - - return newCookie.equals(cookie); - } -} diff --git a/src/be/nikiroo/utils/CryptUtils.java b/src/be/nikiroo/utils/CryptUtils.java deleted file mode 100644 index 638f82f..0000000 --- a/src/be/nikiroo/utils/CryptUtils.java +++ /dev/null @@ -1,441 +0,0 @@ -package be.nikiroo.utils; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.security.InvalidKeyException; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.CipherInputStream; -import javax.crypto.CipherOutputStream; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; -import javax.net.ssl.SSLException; - -import be.nikiroo.utils.streams.Base64InputStream; -import be.nikiroo.utils.streams.Base64OutputStream; - -/** - * Small utility class to do AES encryption/decryption. - *

- * It is multi-thread compatible, but beware: - *

- *

- * Do not assume it is secure; it just here to offer a more-or-less protected - * exchange of data because anonymous and self-signed certificates backed SSL is - * against Google wishes, and I need Android support. - * - * @author niki - */ -public class CryptUtils { - static private final String AES_NAME = "AES/CFB128/NoPadding"; - - private Cipher ecipher; - private Cipher dcipher; - private byte[] bytes32; - - /** - * Small and lazy-easy way to initialize a 128 bits key with - * {@link CryptUtils}. - *

- * Some part of the key will be used to generate a 128 bits key and - * initialize the {@link CryptUtils}; even NULL will generate something. - *

- * This is most probably not secure. Do not use if you actually care - * about security. - * - * @param key - * the {@link String} to use as a base for the key, can be NULL - */ - public CryptUtils(String key) { - try { - init(key2key(key)); - } catch (InvalidKeyException e) { - // We made sure that the key is correct, so nothing here - e.printStackTrace(); - } - } - - /** - * Create a new instance of {@link CryptUtils} with the given 128 bits key. - *

- * The key must be exactly 128 bits long. - * - * @param bytes32 - * the 128 bits (32 bytes) of the key - * - * @throws InvalidKeyException - * if the key is not an array of 128 bits - */ - public CryptUtils(byte[] bytes32) throws InvalidKeyException { - init(bytes32); - } - - /** - * Wrap the given {@link InputStream} so it is transparently encrypted by - * the current {@link CryptUtils}. - * - * @param in - * the {@link InputStream} to wrap - * @return the auto-encode {@link InputStream} - */ - public InputStream encrypt(InputStream in) { - Cipher ecipher = newCipher(Cipher.ENCRYPT_MODE); - return new CipherInputStream(in, ecipher); - } - - /** - * Wrap the given {@link InputStream} so it is transparently encrypted by - * the current {@link CryptUtils} and encoded in base64. - * - * @param in - * the {@link InputStream} to wrap - * - * @return the auto-encode {@link InputStream} - * - * @throws IOException - * in case of I/O error - */ - public InputStream encrypt64(InputStream in) throws IOException { - return new Base64InputStream(encrypt(in), true); - } - - /** - * Wrap the given {@link OutputStream} so it is transparently encrypted by - * the current {@link CryptUtils}. - * - * @param out - * the {@link OutputStream} to wrap - * - * @return the auto-encode {@link OutputStream} - */ - public OutputStream encrypt(OutputStream out) { - Cipher ecipher = newCipher(Cipher.ENCRYPT_MODE); - return new CipherOutputStream(out, ecipher); - } - - /** - * Wrap the given {@link OutputStream} so it is transparently encrypted by - * the current {@link CryptUtils} and encoded in base64. - * - * @param out - * the {@link OutputStream} to wrap - * - * @return the auto-encode {@link OutputStream} - * - * @throws IOException - * in case of I/O error - */ - public OutputStream encrypt64(OutputStream out) throws IOException { - return encrypt(new Base64OutputStream(out, true)); - } - - /** - * Wrap the given {@link OutputStream} so it is transparently decoded by the - * current {@link CryptUtils}. - * - * @param in - * the {@link InputStream} to wrap - * - * @return the auto-decode {@link InputStream} - */ - public InputStream decrypt(InputStream in) { - Cipher dcipher = newCipher(Cipher.DECRYPT_MODE); - return new CipherInputStream(in, dcipher); - } - - /** - * Wrap the given {@link OutputStream} so it is transparently decoded by the - * current {@link CryptUtils} and decoded from base64. - * - * @param in - * the {@link InputStream} to wrap - * - * @return the auto-decode {@link InputStream} - * - * @throws IOException - * in case of I/O error - */ - public InputStream decrypt64(InputStream in) throws IOException { - return decrypt(new Base64InputStream(in, false)); - } - - /** - * Wrap the given {@link OutputStream} so it is transparently decoded by the - * current {@link CryptUtils}. - * - * @param out - * the {@link OutputStream} to wrap - * @return the auto-decode {@link OutputStream} - */ - public OutputStream decrypt(OutputStream out) { - Cipher dcipher = newCipher(Cipher.DECRYPT_MODE); - return new CipherOutputStream(out, dcipher); - } - - /** - * Wrap the given {@link OutputStream} so it is transparently decoded by the - * current {@link CryptUtils} and decoded from base64. - * - * @param out - * the {@link OutputStream} to wrap - * - * @return the auto-decode {@link OutputStream} - * - * @throws IOException - * in case of I/O error - */ - public OutputStream decrypt64(OutputStream out) throws IOException { - return new Base64OutputStream(decrypt(out), false); - } - - /** - * This method required an array of 128 bits. - * - * @param bytes32 - * the array, which must be of 128 bits (32 bytes) - * - * @throws InvalidKeyException - * if the key is not an array of 128 bits (32 bytes) - */ - private void init(byte[] bytes32) throws InvalidKeyException { - if (bytes32 == null || bytes32.length != 32) { - throw new InvalidKeyException( - "The size of the key must be of 128 bits (32 bytes), it is: " - + (bytes32 == null ? "null" : "" + bytes32.length) - + " bytes"); - } - - this.bytes32 = bytes32; - this.ecipher = newCipher(Cipher.ENCRYPT_MODE); - this.dcipher = newCipher(Cipher.DECRYPT_MODE); - } - - /** - * Create a new {@link Cipher}of the given mode (see - * {@link Cipher#ENCRYPT_MODE} and {@link Cipher#ENCRYPT_MODE}). - * - * @param mode - * the mode ({@link Cipher#ENCRYPT_MODE} or - * {@link Cipher#ENCRYPT_MODE}) - * - * @return the new {@link Cipher} - */ - private Cipher newCipher(int mode) { - try { - // bytes32 = 32 bytes, 32 > 16 - byte[] iv = new byte[16]; - for (int i = 0; i < iv.length; i++) { - iv[i] = bytes32[i]; - } - IvParameterSpec ivspec = new IvParameterSpec(iv); - Cipher cipher = Cipher.getInstance(AES_NAME); - cipher.init(mode, new SecretKeySpec(bytes32, "AES"), ivspec); - return cipher; - } catch (Exception e) { - e.printStackTrace(); - throw new RuntimeException( - "Cannot initialize encryption sub-system", e); - } - } - - /** - * Encrypt the data. - * - * @param data - * the data to encrypt - * - * @return the encrypted data - * - * @throws SSLException - * in case of I/O error (i.e., the data is not what you assumed - * it was) - */ - public byte[] encrypt(byte[] data) throws SSLException { - synchronized (ecipher) { - try { - return ecipher.doFinal(data); - } catch (IllegalBlockSizeException e) { - throw new SSLException(e); - } catch (BadPaddingException e) { - throw new SSLException(e); - } - } - } - - /** - * Encrypt the data. - * - * @param data - * the data to encrypt - * - * @return the encrypted data - * - * @throws SSLException - * in case of I/O error (i.e., the data is not what you assumed - * it was) - */ - public byte[] encrypt(String data) throws SSLException { - return encrypt(StringUtils.getBytes(data)); - } - - /** - * Encrypt the data, then encode it into Base64. - * - * @param data - * the data to encrypt - * @param zip - * TRUE to also compress the data in GZIP format; remember that - * compressed and not-compressed content are different; you need - * to know which is which when decoding - * - * @return the encrypted data, encoded in Base64 - * - * @throws SSLException - * in case of I/O error (i.e., the data is not what you assumed - * it was) - */ - public String encrypt64(String data) throws SSLException { - return encrypt64(StringUtils.getBytes(data)); - } - - /** - * Encrypt the data, then encode it into Base64. - * - * @param data - * the data to encrypt - * - * @return the encrypted data, encoded in Base64 - * - * @throws SSLException - * in case of I/O error (i.e., the data is not what you assumed - * it was) - */ - public String encrypt64(byte[] data) throws SSLException { - try { - return StringUtils.base64(encrypt(data)); - } catch (IOException e) { - // not exactly true, but we consider here that this error is a crypt - // error, not a normal I/O error - throw new SSLException(e); - } - } - - /** - * Decode the data which is assumed to be encrypted with the same utilities. - * - * @param data - * the encrypted data to decode - * - * @return the original, decoded data - * - * @throws SSLException - * in case of I/O error - */ - public byte[] decrypt(byte[] data) throws SSLException { - synchronized (dcipher) { - try { - return dcipher.doFinal(data); - } catch (IllegalBlockSizeException e) { - throw new SSLException(e); - } catch (BadPaddingException e) { - throw new SSLException(e); - } - } - } - - /** - * Decode the data which is assumed to be encrypted with the same utilities - * and to be a {@link String}. - * - * @param data - * the encrypted data to decode - * - * @return the original, decoded data,as a {@link String} - * - * @throws SSLException - * in case of I/O error - */ - public String decrypts(byte[] data) throws SSLException { - try { - return new String(decrypt(data), "UTF-8"); - } catch (UnsupportedEncodingException e) { - // UTF-8 is required in all conform JVMs - e.printStackTrace(); - return null; - } - } - - /** - * Decode the data which is assumed to be encrypted with the same utilities - * and is a Base64 encoded value. - * - * @param data - * the encrypted data to decode in Base64 format - * @param zip - * TRUE to also uncompress the data from a GZIP format - * automatically; if set to FALSE, zipped data can be returned - * - * @return the original, decoded data - * - * @throws SSLException - * in case of I/O error - */ - public byte[] decrypt64(String data) throws SSLException { - try { - return decrypt(StringUtils.unbase64(data)); - } catch (IOException e) { - // not exactly true, but we consider here that this error is a crypt - // error, not a normal I/O error - throw new SSLException(e); - } - } - - /** - * Decode the data which is assumed to be encrypted with the same utilities - * and is a Base64 encoded value, then convert it into a String (this method - * assumes the data was indeed a UTF-8 encoded {@link String}). - * - * @param data - * the encrypted data to decode in Base64 format - * @param zip - * TRUE to also uncompress the data from a GZIP format - * automatically; if set to FALSE, zipped data can be returned - * - * @return the original, decoded data - * - * @throws SSLException - * in case of I/O error - */ - public String decrypt64s(String data) throws SSLException { - try { - return new String(decrypt(StringUtils.unbase64(data)), "UTF-8"); - } catch (UnsupportedEncodingException e) { - // UTF-8 is required in all conform JVMs - e.printStackTrace(); - return null; - } catch (IOException e) { - // not exactly true, but we consider here that this error is a crypt - // error, not a normal I/O error - throw new SSLException(e); - } - } - - /** - * This is probably NOT secure! - * - * @param input - * some {@link String} input - * - * @return a 128 bits key computed from the given input - */ - static private byte[] key2key(String input) { - return StringUtils.getMd5Hash("" + input).getBytes(); - } -} diff --git a/src/be/nikiroo/utils/Downloader.java b/src/be/nikiroo/utils/Downloader.java deleted file mode 100644 index 4191d0a..0000000 --- a/src/be/nikiroo/utils/Downloader.java +++ /dev/null @@ -1,480 +0,0 @@ -package be.nikiroo.utils; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStreamWriter; -import java.net.CookieHandler; -import java.net.CookieManager; -import java.net.CookiePolicy; -import java.net.CookieStore; -import java.net.HttpCookie; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLConnection; -import java.net.URLEncoder; -import java.util.Map; -import java.util.zip.GZIPInputStream; - -/** - * This class will help you download content from Internet Sites ({@link URL} - * based). - *

- * It allows you to control some options often required on web sites that do not - * want to simply serve HTML, but actively makes your life difficult with stupid - * checks. - * - * @author niki - */ -public class Downloader { - private String UA; - private CookieManager cookies; - private TraceHandler tracer = new TraceHandler(); - private Cache cache; - private boolean offline; - - /** - * Create a new {@link Downloader}. - * - * @param UA - * the User-Agent to use to download the resources -- note that - * some websites require one, some actively blacklist real UAs - * like the one from wget, some whitelist a couple of browsers - * only (!) -- can be NULL - */ - public Downloader(String UA) { - this(UA, null); - } - - /** - * Create a new {@link Downloader}. - * - * @param UA - * the User-Agent to use to download the resources -- note that - * some websites require one, some actively blacklist real UAs - * like the one from wget, some whitelist a couple of browsers - * only (!) -- can be NULL - * @param cache - * the {@link Cache} to use for all access (can be NULL) - */ - public Downloader(String UA, Cache cache) { - this.UA = UA; - - cookies = new CookieManager(null, CookiePolicy.ACCEPT_ALL); - CookieHandler.setDefault(cookies); - - setCache(cache); - } - - /** - * This {@link Downloader} is forbidden to try and connect to the network. - *

- * If TRUE, it will only check the cache if any. - *

- * Default is FALSE. - * - * @return TRUE if offline - */ - public boolean isOffline() { - return offline; - } - - /** - * This {@link Downloader} is forbidden to try and connect to the network. - *

- * If TRUE, it will only check the cache if any. - *

- * Default is FALSE. - * - * @param offline TRUE for offline, FALSE for online - */ - public void setOffline(boolean offline) { - this.offline = offline; - } - - /** - * The traces handler for this {@link Cache}. - * - * @return the traces handler - */ - public TraceHandler getTraceHandler() { - return tracer; - } - - /** - * The traces handler for this {@link Cache}. - * - * @param tracer - * the new traces handler - */ - public void setTraceHandler(TraceHandler tracer) { - if (tracer == null) { - tracer = new TraceHandler(false, false, false); - } - - this.tracer = tracer; - } - - /** - * The {@link Cache} to use for all access (can be NULL). - * - * @return the cache - */ - public Cache getCache() { - return cache; - } - - /** - * The {@link Cache} to use for all access (can be NULL). - * - * @param cache - * the new cache - */ - public void setCache(Cache cache) { - this.cache = cache; - } - - /** - * Clear all the cookies currently in the jar. - *

- * As long as you don't, the cookies are kept. - */ - public void clearCookies() { - cookies.getCookieStore().removeAll(); - } - - /** - * Open the given {@link URL} and update the cookies. - * - * @param url - * the {@link URL} to open - * @return the {@link InputStream} of the opened page - * - * @throws IOException - * in case of I/O error - **/ - public InputStream open(URL url) throws IOException { - return open(url, false); - } - - /** - * Open the given {@link URL} and update the cookies. - * - * @param url - * the {@link URL} to open - * @param stable - * stable a stable file (that doesn't change too often) -- - * parameter used to check if the file is too old to keep or not - * in the cache (default is false) - * - * @return the {@link InputStream} of the opened page - * - * @throws IOException - * in case of I/O error - **/ - public InputStream open(URL url, boolean stable) throws IOException { - return open(url, url, url, null, null, null, null, stable); - } - - /** - * Open the given {@link URL} and update the cookies. - * - * @param url - * the {@link URL} to open - * @param currentReferer - * the current referer, for websites that needs this info - * @param cookiesValues - * the cookies - * @param postParams - * the POST parameters - * @param getParams - * the GET parameters (priority over POST) - * @param oauth - * OAuth authorization (aka, "bearer XXXXXXX") - * - * @return the {@link InputStream} of the opened page - * - * @throws IOException - * in case of I/O error (including offline mode + not in cache) - */ - public InputStream open(URL url, URL currentReferer, - Map cookiesValues, Map postParams, - Map getParams, String oauth) throws IOException { - return open(url, currentReferer, cookiesValues, postParams, getParams, - oauth, false); - } - - /** - * Open the given {@link URL} and update the cookies. - * - * @param url - * the {@link URL} to open - * @param currentReferer - * the current referer, for websites that needs this info - * @param cookiesValues - * the cookies - * @param postParams - * the POST parameters - * @param getParams - * the GET parameters (priority over POST) - * @param oauth - * OAuth authorization (aka, "bearer XXXXXXX") - * @param stable - * stable a stable file (that doesn't change too often) -- - * parameter used to check if the file is too old to keep or not - * in the cache (default is false) - * - * @return the {@link InputStream} of the opened page - * - * @throws IOException - * in case of I/O error (including offline mode + not in cache) - */ - public InputStream open(URL url, URL currentReferer, - Map cookiesValues, Map postParams, - Map getParams, String oauth, boolean stable) - throws IOException { - return open(url, url, currentReferer, cookiesValues, postParams, - getParams, oauth, stable); - } - - /** - * Open the given {@link URL} and update the cookies. - * - * @param url - * the {@link URL} to open - * @param originalUrl - * the original {@link URL} before any redirection occurs, which - * is also used for the cache ID if needed (so we can retrieve - * the content with this URL if needed) - * @param currentReferer - * the current referer, for websites that needs this info - * @param cookiesValues - * the cookies - * @param postParams - * the POST parameters - * @param getParams - * the GET parameters (priority over POST) - * @param oauth - * OAuth authorisation (aka, "bearer XXXXXXX") - * @param stable - * a stable file (that doesn't change too often) -- parameter - * used to check if the file is too old to keep or not in the - * cache - * - * @return the {@link InputStream} of the opened page - * - * @throws IOException - * in case of I/O error (including offline mode + not in cache) - */ - public InputStream open(URL url, final URL originalUrl, URL currentReferer, - Map cookiesValues, Map postParams, - Map getParams, String oauth, boolean stable) - throws IOException { - - tracer.trace("Request: " + url); - - if (cache != null) { - InputStream in = cache.load(originalUrl, false, stable); - if (in != null) { - tracer.trace("Use the cache: " + url); - tracer.trace("Original URL : " + originalUrl); - return in; - } - } - - String protocol = originalUrl == null ? null : originalUrl - .getProtocol(); - if (isOffline() && !"file".equalsIgnoreCase(protocol)) { - tracer.error("Downloader OFFLINE, cannot proceed to URL: " + url); - throw new IOException("Downloader is currently OFFLINE, cannot download: " + url); - } - - tracer.trace("Download: " + url); - - URLConnection conn = openConnectionWithCookies(url, currentReferer, - cookiesValues); - - // Priority: GET over POST - Map params = getParams; - if (getParams == null) { - params = postParams; - } - - StringBuilder requestData = null; - if ((params != null || oauth != null) - && conn instanceof HttpURLConnection) { - if (params != null) { - requestData = new StringBuilder(); - for (Map.Entry param : params.entrySet()) { - if (requestData.length() != 0) - requestData.append('&'); - requestData.append(URLEncoder.encode(param.getKey(), - "UTF-8")); - requestData.append('='); - requestData.append(URLEncoder.encode( - String.valueOf(param.getValue()), "UTF-8")); - } - - if (getParams == null && postParams != null) { - ((HttpURLConnection) conn).setRequestMethod("POST"); - } - - conn.setRequestProperty("Content-Type", - "application/x-www-form-urlencoded"); - conn.setRequestProperty("Content-Length", - Integer.toString(requestData.length())); - } - - if (oauth != null) { - conn.setRequestProperty("Authorization", oauth); - } - - if (requestData != null) { - conn.setDoOutput(true); - OutputStreamWriter writer = new OutputStreamWriter( - conn.getOutputStream()); - try { - writer.write(requestData.toString()); - writer.flush(); - } finally { - writer.close(); - } - } - } - - // Manual redirection, much better for POST data - if (conn instanceof HttpURLConnection) { - ((HttpURLConnection) conn).setInstanceFollowRedirects(false); - } - - conn.connect(); - - // Check if redirect - // BEWARE! POST data cannot be redirected (some webservers complain) for - // HTTP codes 302 and 303 - if (conn instanceof HttpURLConnection) { - int repCode = 0; - try { - // Can fail in some circumstances - repCode = ((HttpURLConnection) conn).getResponseCode(); - } catch (IOException e) { - } - - if (repCode / 100 == 3) { - String newUrl = conn.getHeaderField("Location"); - return open(new URL(newUrl), originalUrl, currentReferer, - cookiesValues, // - (repCode == 302 || repCode == 303) ? null : postParams, // - getParams, oauth, stable); - } - } - - try { - InputStream in = conn.getInputStream(); - if ("gzip".equals(conn.getContentEncoding())) { - in = new GZIPInputStream(in); - } - - if (in == null) { - throw new IOException("No InputStream!"); - } - - if (cache != null) { - String size = conn.getContentLength() < 0 ? "unknown size" - : StringUtils.formatNumber(conn.getContentLength()) - + "bytes"; - tracer.trace("Save to cache (" + size + "): " + originalUrl); - try { - try { - long bytes = cache.save(in, originalUrl); - tracer.trace("Saved to cache: " - + StringUtils.formatNumber(bytes) + "bytes"); - } finally { - in.close(); - } - in = cache.load(originalUrl, true, true); - } catch (IOException e) { - tracer.error(new IOException( - "Cannot save URL to cache, will ignore cache: " - + url, e)); - } - } - - if (in == null) { - throw new IOException( - "Cannot retrieve the file after storing it in the cache (??)"); - } - - return in; - } catch (IOException e) { - throw new IOException(String.format( - "Cannot find %s (current URL: %s)", originalUrl, url), e); - } - } - - /** - * Open a connection on the given {@link URL}, and manage the cookies that - * come with it. - * - * @param url - * the {@link URL} to open - * - * @return the connection - * - * @throws IOException - * in case of I/O error - */ - private URLConnection openConnectionWithCookies(URL url, - URL currentReferer, Map cookiesValues) - throws IOException { - URLConnection conn = url.openConnection(); - - String cookies = generateCookies(cookiesValues); - if (cookies != null && !cookies.isEmpty()) { - conn.setRequestProperty("Cookie", cookies); - } - - if (UA != null) { - conn.setRequestProperty("User-Agent", UA); - } - conn.setRequestProperty("Accept-Encoding", "gzip"); - conn.setRequestProperty("Accept", "*/*"); - conn.setRequestProperty("Charset", "utf-8"); - - if (currentReferer != null) { - conn.setRequestProperty("Referer", currentReferer.toString()); - conn.setRequestProperty("Host", currentReferer.getHost()); - } - - return conn; - } - - /** - * Generate the cookie {@link String} from the local {@link CookieStore} so - * it is ready to be passed. - * - * @return the cookie - */ - private String generateCookies(Map cookiesValues) { - StringBuilder builder = new StringBuilder(); - for (HttpCookie cookie : cookies.getCookieStore().getCookies()) { - if (builder.length() > 0) { - builder.append(';'); - } - - builder.append(cookie.toString()); - } - - if (cookiesValues != null) { - for (Map.Entry set : cookiesValues.entrySet()) { - if (builder.length() > 0) { - builder.append(';'); - } - builder.append(set.getKey()); - builder.append('='); - builder.append(set.getValue()); - } - } - - return builder.toString(); - } -} diff --git a/src/be/nikiroo/utils/HashUtils.java b/src/be/nikiroo/utils/HashUtils.java deleted file mode 100644 index df8d7c6..0000000 --- a/src/be/nikiroo/utils/HashUtils.java +++ /dev/null @@ -1,89 +0,0 @@ -package be.nikiroo.utils; - -import java.io.UnsupportedEncodingException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -/** - * Small class to easily hash some values in a few different ways. - *

- * Does not handle the salt itself, you have to add it yourself. - * - * @author niki - */ -public class HashUtils { - /** - * Hash the given value. - * - * @param value - * the value to hash - * - * @return the hash that can be used to confirm a value - * - * @throws RuntimeException - * if UTF-8 support is not available (!) or SHA-512 support is - * not available - * @throws NullPointerException - * if email or pass is NULL - */ - static public String sha512(String value) { - return hash("SHA-512", value); - } - - /** - * Hash the given value. - * - * @param value - * the value to hash - * - * @return the hash that can be used to confirm the a value - * - * @throws RuntimeException - * if UTF-8 support is not available (!) or MD5 support is not - * available - * @throws NullPointerException - * if email or pass is NULL - */ - static public String md5(String value) { - return hash("MD5", value); - } - - /** - * Hash the given value. - * - * @param algo - * the hash algorithm to use ("MD5" and "SHA-512" are supported) - * @param value - * the value to hash - * - * @return the hash that can be used to confirm a value - * - * @throws RuntimeException - * if UTF-8 support is not available (!) or the algorithm - * support is not available - * @throws NullPointerException - * if email or pass is NULL - */ - static private String hash(String algo, String value) { - try { - MessageDigest md = MessageDigest.getInstance(algo); - md.update(value.getBytes("UTF-8")); - byte byteData[] = md.digest(); - - StringBuffer hexString = new StringBuffer(); - for (int i = 0; i < byteData.length; i++) { - String hex = Integer.toHexString(0xff & byteData[i]); - if (hex.length() % 2 == 1) - hexString.append('0'); - hexString.append(hex); - } - - return hexString.toString(); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(algo + " hashing not available", e); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException( - "UTF-8 encoding is required in a compatible JVM", e); - } - } -} diff --git a/src/be/nikiroo/utils/IOUtils.java b/src/be/nikiroo/utils/IOUtils.java deleted file mode 100644 index 439964d..0000000 --- a/src/be/nikiroo/utils/IOUtils.java +++ /dev/null @@ -1,521 +0,0 @@ -package be.nikiroo.utils; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; -import java.util.zip.ZipOutputStream; - -import be.nikiroo.utils.streams.MarkableFileInputStream; - -/** - * This class offer some utilities based around Streams and Files. - * - * @author niki - */ -public class IOUtils { - /** - * Write the data to the given {@link File}. - * - * @param in - * the data source - * @param target - * the target {@link File} - * - * @return the number of bytes written - * - * @throws IOException - * in case of I/O error - */ - public static long write(InputStream in, File target) throws IOException { - OutputStream out = new FileOutputStream(target); - try { - return write(in, out); - } finally { - out.close(); - } - } - - /** - * Write the data to the given {@link OutputStream}. - * - * @param in - * the data source - * @param out - * the target {@link OutputStream} - * - * @return the number of bytes written - * - * @throws IOException - * in case of I/O error - */ - public static long write(InputStream in, OutputStream out) - throws IOException { - long written = 0; - byte buffer[] = new byte[4096]; - int len = in.read(buffer); - while (len > -1) { - out.write(buffer, 0, len); - written += len; - len = in.read(buffer); - } - - return written; - } - - /** - * Recursively Add a {@link File} (which can thus be a directory, too) to a - * {@link ZipOutputStream}. - * - * @param zip - * the stream - * @param base - * the path to prepend to the ZIP info before the actual - * {@link File} path - * @param target - * the source {@link File} (which can be a directory) - * @param targetIsRoot - * FALSE if we need to add a {@link ZipEntry} for base/target, - * TRUE to add it at the root of the ZIP - * - * @throws IOException - * in case of I/O error - */ - public static void zip(ZipOutputStream zip, String base, File target, - boolean targetIsRoot) throws IOException { - if (target.isDirectory()) { - if (!targetIsRoot) { - if (base == null || base.isEmpty()) { - base = target.getName(); - } else { - base += "/" + target.getName(); - } - zip.putNextEntry(new ZipEntry(base + "/")); - } - - File[] files = target.listFiles(); - if (files != null) { - for (File file : files) { - zip(zip, base, file, false); - } - } - } else { - if (base == null || base.isEmpty()) { - base = target.getName(); - } else { - base += "/" + target.getName(); - } - zip.putNextEntry(new ZipEntry(base)); - FileInputStream in = new FileInputStream(target); - try { - IOUtils.write(in, zip); - } finally { - in.close(); - } - } - } - - /** - * Zip the given source into dest. - * - * @param src - * the source {@link File} (which can be a directory) - * @param dest - * the destination .zip file - * @param srcIsRoot - * FALSE if we need to add a {@link ZipEntry} for src, TRUE to - * add it at the root of the ZIP - * - * @throws IOException - * in case of I/O error - */ - public static void zip(File src, File dest, boolean srcIsRoot) - throws IOException { - OutputStream out = new FileOutputStream(dest); - try { - ZipOutputStream zip = new ZipOutputStream(out); - try { - IOUtils.zip(zip, "", src, srcIsRoot); - } finally { - zip.close(); - } - } finally { - out.close(); - } - } - - /** - * Unzip the given ZIP file into the target directory. - * - * @param zipFile - * the ZIP file - * @param targetDirectory - * the target directory - * - * @return the number of extracted files (not directories) - * - * @throws IOException - * in case of I/O errors - */ - public static long unzip(File zipFile, File targetDirectory) - throws IOException { - long count = 0; - - if (targetDirectory.exists() && targetDirectory.isFile()) { - throw new IOException("Cannot unzip " + zipFile + " into " - + targetDirectory + ": it is not a directory"); - } - - targetDirectory.mkdir(); - if (!targetDirectory.exists()) { - throw new IOException("Cannot create target directory " - + targetDirectory); - } - - FileInputStream in = new FileInputStream(zipFile); - try { - ZipInputStream zipStream = new ZipInputStream(in); - try { - for (ZipEntry entry = zipStream.getNextEntry(); entry != null; entry = zipStream - .getNextEntry()) { - File file = new File(targetDirectory, entry.getName()); - if (entry.isDirectory()) { - file.mkdirs(); - } else { - IOUtils.write(zipStream, file); - count++; - } - } - } finally { - zipStream.close(); - } - } finally { - in.close(); - } - - return count; - } - - /** - * Write the {@link String} content to {@link File}. - * - * @param dir - * the directory where to write the {@link File} - * @param filename - * the {@link File} name - * @param content - * the content - * - * @throws IOException - * in case of I/O error - */ - public static void writeSmallFile(File dir, String filename, String content) - throws IOException { - if (!dir.exists()) { - dir.mkdirs(); - } - - writeSmallFile(new File(dir, filename), content); - } - - /** - * Write the {@link String} content to {@link File}. - * - * @param file - * the {@link File} to write - * @param content - * the content - * - * @throws IOException - * in case of I/O error - */ - public static void writeSmallFile(File file, String content) - throws IOException { - FileOutputStream out = new FileOutputStream(file); - try { - out.write(StringUtils.getBytes(content)); - } finally { - out.close(); - } - } - - /** - * Read the whole {@link File} content into a {@link String}. - * - * @param file - * the {@link File} - * - * @return the content - * - * @throws IOException - * in case of I/O error - */ - public static String readSmallFile(File file) throws IOException { - InputStream stream = new FileInputStream(file); - try { - return readSmallStream(stream); - } finally { - stream.close(); - } - } - - /** - * Read the whole {@link InputStream} content into a {@link String}. - * - * @param stream - * the {@link InputStream} - * - * @return the content - * - * @throws IOException - * in case of I/O error - */ - public static String readSmallStream(InputStream stream) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - try { - write(stream, out); - return out.toString("UTF-8"); - } finally { - out.close(); - } - } - - /** - * Recursively delete the given {@link File}, which may of course also be a - * directory. - *

- * Will either silently continue or throw an exception in case of error, - * depending upon the parameters. - * - * @param target - * the target to delete - * @param exception - * TRUE to throw an {@link IOException} in case of error, FALSE - * to silently continue - * - * @return TRUE if all files were deleted, FALSE if an error occurred - * - * @throws IOException - * if an error occurred and the parameters allow an exception to - * be thrown - */ - public static boolean deltree(File target, boolean exception) - throws IOException { - List list = deltree(target, null); - if (exception && !list.isEmpty()) { - StringBuilder slist = new StringBuilder(); - for (File file : list) { - slist.append("\n").append(file.getPath()); - } - - throw new IOException("Cannot delete all the files from: <" // - + target + ">:" + slist.toString()); - } - - return list.isEmpty(); - } - - /** - * Recursively delete the given {@link File}, which may of course also be a - * directory. - *

- * Will silently continue in case of error. - * - * @param target - * the target to delete - * - * @return TRUE if all files were deleted, FALSE if an error occurred - */ - public static boolean deltree(File target) { - return deltree(target, null).isEmpty(); - } - - /** - * Recursively delete the given {@link File}, which may of course also be a - * directory. - *

- * Will collect all {@link File} that cannot be deleted in the given - * accumulator. - * - * @param target - * the target to delete - * @param errorAcc - * the accumulator to use for errors, or NULL to create a new one - * - * @return the errors accumulator - */ - public static List deltree(File target, List errorAcc) { - if (errorAcc == null) { - errorAcc = new ArrayList(); - } - - File[] files = target.listFiles(); - if (files != null) { - for (File file : files) { - errorAcc = deltree(file, errorAcc); - } - } - - if (!target.delete()) { - errorAcc.add(target); - } - - return errorAcc; - } - - /** - * Open the resource next to the given {@link Class}. - * - * @param location - * the location where to look for the resource - * @param name - * the resource name (only the filename, no path) - * - * @return the opened resource if found, NULL if not - */ - public static InputStream openResource( - @SuppressWarnings("rawtypes") Class location, String name) { - String loc = location.getName().replace(".", "/") - .replaceAll("/[^/]*$", "/"); - return openResource(loc + name); - } - - /** - * Open the given /-separated resource (from the binary root). - * - * @param name - * the resource name (the full path, with "/" as separator) - * - * @return the opened resource if found, NULL if not - */ - public static InputStream openResource(String name) { - ClassLoader loader = IOUtils.class.getClassLoader(); - if (loader == null) { - loader = ClassLoader.getSystemClassLoader(); - } - - return loader.getResourceAsStream(name); - } - - /** - * Return the running directory/file, that is, the root binary directory for - * running java classes or the running JAR file for JAR files. - * - * @param clazz - * a Class from the running program (will only have an impact - * when not running from a JAR file) - * @param base - * return the base directory (the one where the binary root or - * the JAR file resides) - * - * @return the directory or file - */ - public static File getRunningDirectory( - @SuppressWarnings("rawtypes") Class clazz, boolean base) { - String uri = clazz.getProtectionDomain().getCodeSource().getLocation() - .toString(); - if (uri.startsWith("file:")) - uri = uri.substring("file:".length()); - File root = new File(uri); - - if (base) { - root = root.getParentFile(); - } - - return root; - } - - /** - * Return a resetable {@link InputStream} from this stream, and reset it. - * - * @param in - * the input stream - * @return the resetable stream, which may be the same - * - * @throws IOException - * in case of I/O error - */ - public static InputStream forceResetableStream(InputStream in) - throws IOException { - boolean resetable = in.markSupported(); - if (resetable) { - try { - in.reset(); - } catch (IOException e) { - resetable = false; - } - } - - if (resetable) { - return in; - } - - final File tmp = File.createTempFile(".tmp-stream.", ".tmp"); - try { - write(in, tmp); - in.close(); - - return new MarkableFileInputStream(tmp) { - @Override - public void close() throws IOException { - try { - super.close(); - } finally { - tmp.delete(); - } - } - }; - } catch (IOException e) { - tmp.delete(); - throw e; - } - } - - /** - * Convert the {@link InputStream} into a byte array. - * - * @param in - * the input stream - * - * @return the array - * - * @throws IOException - * in case of I/O error - */ - public static byte[] toByteArray(InputStream in) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - try { - write(in, out); - return out.toByteArray(); - } finally { - out.close(); - } - } - - /** - * Convert the {@link File} into a byte array. - * - * @param file - * the input {@link File} - * - * @return the array - * - * @throws IOException - * in case of I/O error - */ - public static byte[] toByteArray(File file) throws IOException { - FileInputStream fis = new FileInputStream(file); - try { - return toByteArray(fis); - } finally { - fis.close(); - } - } -} diff --git a/src/be/nikiroo/utils/Image.java b/src/be/nikiroo/utils/Image.java deleted file mode 100644 index 76d2b20..0000000 --- a/src/be/nikiroo/utils/Image.java +++ /dev/null @@ -1,287 +0,0 @@ -package be.nikiroo.utils; - -import java.io.ByteArrayInputStream; -import java.io.Closeable; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.OutputStream; -import java.io.Serializable; - -import be.nikiroo.utils.streams.Base64InputStream; -import be.nikiroo.utils.streams.MarkableFileInputStream; - -/** - * This class represents an image data. - * - * @author niki - */ -public class Image implements Closeable, Serializable { - static private final long serialVersionUID = 1L; - - static private File tempRoot; - static private TempFiles tmpRepository; - static private long count = 0; - static private Object lock = new Object(); - - private Object instanceLock = new Object(); - private File data; - private long size; - - /** - * Do not use -- for serialisation purposes only. - */ - @SuppressWarnings("unused") - private Image() { - } - - /** - * Create a new {@link Image} with the given data. - * - * @param data - * the data - */ - public Image(byte[] data) { - ByteArrayInputStream in = new ByteArrayInputStream(data); - try { - this.data = getTemporaryFile(); - size = IOUtils.write(in, this.data); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - try { - in.close(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } - - /** - * Create an image from Base64 encoded data. - * - *

- * Please use {@link Image#Image(InputStream)} when possible instead, with a - * {@link Base64InputStream}; it can be much more efficient. - * - * @param base64EncodedData - * the Base64 encoded data as a String - * - * @throws IOException - * in case of I/O error or badly formated Base64 - */ - public Image(String base64EncodedData) throws IOException { - this(new Base64InputStream(new ByteArrayInputStream( - StringUtils.getBytes(base64EncodedData)), false)); - } - - /** - * Create a new {@link Image} from a stream. - * - * @param in - * the stream - * - * @throws IOException - * in case of I/O error - */ - public Image(InputStream in) throws IOException { - data = getTemporaryFile(); - size = IOUtils.write(in, data); - } - - /** - * The size of the enclosed image in bytes. - * - * @return the size - */ - public long getSize() { - return size; - } - - /** - * Generate an {@link InputStream} that you can {@link InputStream#reset()} - * for this {@link Image}. - *

- * This {@link InputStream} will (always) be a new one, and you are - * responsible for it. - *

- * Note: take care that the {@link InputStream} must not live past - * the {@link Image} life time! - * - * @return the stream - * - * @throws IOException - * in case of I/O error - */ - public InputStream newInputStream() throws IOException { - synchronized (instanceLock) { - if (data == null) { - throw new IOException("Image was close()d"); - } - - return new MarkableFileInputStream(data); - } - } - - /** - * Read the actual image data, as a byte array. - * - * @deprecated if possible, prefer the {@link Image#newInputStream()} - * method, as it can be more efficient - * - * @return the image data - */ - @Deprecated - public byte[] getData() { - try { - InputStream in = newInputStream(); - try { - return IOUtils.toByteArray(in); - } finally { - in.close(); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - /** - * Convert the given {@link Image} object into a Base64 representation of - * the same {@link Image} object. - * - * @deprecated Please use {@link Image#newInputStream()} instead, it is more - * efficient - * - * @return the Base64 representation - */ - @Deprecated - public String toBase64() { - try { - Base64InputStream stream = new Base64InputStream(newInputStream(), - true); - try { - return IOUtils.readSmallStream(stream); - } finally { - stream.close(); - } - } catch (IOException e) { - return null; - } - } - - /** - * Closing the {@link Image} will delete the associated temporary file on - * disk. - *

- * Note that even if you don't, the program will still try to delete - * all the temporary files at JVM termination. - */ - @Override - public void close() throws IOException { - synchronized (instanceLock) { - if (size >= 0) { - size = -1; - data.delete(); - data = null; - - synchronized (lock) { - count--; - if (count <= 0) { - count = 0; - tmpRepository.close(); - tmpRepository = null; - } - } - } - } - } - - @Override - protected void finalize() throws Throwable { - try { - close(); - } finally { - super.finalize(); - } - } - - /** - * Return a newly created temporary file to work on. - * - * @return the file - * - * @throws IOException - * in case of I/O error - */ - private File getTemporaryFile() throws IOException { - synchronized (lock) { - if (tmpRepository == null) { - tmpRepository = new TempFiles(tempRoot, "images"); - count = 0; - } - - count++; - - return tmpRepository.createTempFile("image"); - } - } - - /** - * Write this {@link Image} for serialization purposes; that is, write the - * content of the backing temporary file. - * - * @param out - * the {@link OutputStream} to write to - * - * @throws IOException - * in case of I/O error - */ - private void writeObject(ObjectOutputStream out) throws IOException { - InputStream in = newInputStream(); - try { - IOUtils.write(in, out); - } finally { - in.close(); - } - } - - /** - * Read an {@link Image} written by - * {@link Image#writeObject(java.io.ObjectOutputStream)}; that is, create a - * new temporary file with the saved content. - * - * @param in - * the {@link InputStream} to read from - * @throws IOException - * in case of I/O error - * @throws ClassNotFoundException - * will not be thrown by this method - */ - @SuppressWarnings("unused") - private void readObject(ObjectInputStream in) throws IOException, - ClassNotFoundException { - data = getTemporaryFile(); - IOUtils.write(in, data); - } - - /** - * Change the temporary root directory used by the program. - *

- * Caution: the directory will be owned by the system, all its files - * now belong to us (and will most probably be deleted). - *

- * Note: it may take some time until the new temporary root is used, we - * first need to make sure the previous one is not used anymore (i.e., we - * must reach a point where no unclosed {@link Image} remains in memory) to - * switch the temporary root. - * - * @param root - * the new temporary root, which will be owned by the - * system - */ - public static void setTemporaryFilesRoot(File root) { - tempRoot = root; - } -} diff --git a/src/be/nikiroo/utils/ImageUtils.java b/src/be/nikiroo/utils/ImageUtils.java deleted file mode 100644 index 877c8fa..0000000 --- a/src/be/nikiroo/utils/ImageUtils.java +++ /dev/null @@ -1,266 +0,0 @@ -package be.nikiroo.utils; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; - -import be.nikiroo.utils.serial.SerialUtils; - -/** - * This class offer some utilities based around images. - * - * @author niki - */ -public abstract class ImageUtils { - private static ImageUtils instance = newObject(); - - /** - * Get a (unique) instance of an {@link ImageUtils} compatible with your - * system. - * - * @return an {@link ImageUtils} - */ - public static ImageUtils getInstance() { - return instance; - } - - /** - * Save the given resource as an image on disk using the given image format - * for content, or with "png" format if it fails. - * - * @param img - * the resource - * @param target - * the target file - * @param format - * the file format ("png", "jpeg", "bmp"...) - * - * @throws IOException - * in case of I/O error - */ - public abstract void saveAsImage(Image img, File target, String format) - throws IOException; - - /** - * Scale a dimension. - * - * - * @param imageWidth - * the actual image width - * @param imageHeight - * the actual image height - * @param areaWidth - * the base width of the target dimension for snap sizes - * @param areaHeight - * the base height of the target dimension for snap sizes - * @param zoom - * the zoom factor (ignored on snap mode) - * @param snapMode - * NULL for no snap mode, TRUE to snap to width and FALSE for - * snap to height) - * - * @return the scaled size, width is [0] and height is [1] (minimum is 1x1) - */ - protected static Integer[] scaleSize(int imageWidth, int imageHeight, - int areaWidth, int areaHeight, double zoom, Boolean snapMode) { - int width; - int height; - if (snapMode == null) { - width = (int) Math.round(imageWidth * zoom); - height = (int) Math.round(imageHeight * zoom); - } else if (snapMode) { - width = areaWidth; - height = (int) Math - .round((((double) areaWidth) / imageWidth) * imageHeight); - } else { - height = areaHeight; - width = (int) Math - .round((((double) areaHeight) / imageHeight) * imageWidth); - - } - - if (width < 1) - width = 1; - if (height < 1) - height = 1; - - return new Integer[] { width, height }; - } - - /** - * Return the EXIF transformation flag of this image if any. - * - *

- * Note: this code has been found on internet; thank you anonymous coder. - *

- * - * @param in - * the data {@link InputStream} - * - * @return the transformation flag if any - * - * @throws IOException - * in case of IO error - */ - protected static int getExifTransorm(InputStream in) throws IOException { - int[] exif_data = new int[100]; - int set_flag = 0; - int is_motorola = 0; - - /* Read File head, check for JPEG SOI + Exif APP1 */ - for (int i = 0; i < 4; i++) - exif_data[i] = in.read(); - - if (exif_data[0] != 0xFF || exif_data[1] != 0xD8 - || exif_data[2] != 0xFF || exif_data[3] != 0xE1) - return -2; - - /* Get the marker parameter length count */ - int length = (in.read() << 8 | in.read()); - - /* Length includes itself, so must be at least 2 */ - /* Following Exif data length must be at least 6 */ - if (length < 8) - return -1; - length -= 8; - /* Read Exif head, check for "Exif" */ - for (int i = 0; i < 6; i++) - exif_data[i] = in.read(); - - if (exif_data[0] != 0x45 || exif_data[1] != 0x78 - || exif_data[2] != 0x69 || exif_data[3] != 0x66 - || exif_data[4] != 0 || exif_data[5] != 0) - return -1; - - /* Read Exif body */ - length = length > exif_data.length ? exif_data.length : length; - for (int i = 0; i < length; i++) - exif_data[i] = in.read(); - - if (length < 12) - return -1; /* Length of an IFD entry */ - - /* Discover byte order */ - if (exif_data[0] == 0x49 && exif_data[1] == 0x49) - is_motorola = 0; - else if (exif_data[0] == 0x4D && exif_data[1] == 0x4D) - is_motorola = 1; - else - return -1; - - /* Check Tag Mark */ - if (is_motorola == 1) { - if (exif_data[2] != 0) - return -1; - if (exif_data[3] != 0x2A) - return -1; - } else { - if (exif_data[3] != 0) - return -1; - if (exif_data[2] != 0x2A) - return -1; - } - - /* Get first IFD offset (offset to IFD0) */ - int offset; - if (is_motorola == 1) { - if (exif_data[4] != 0) - return -1; - if (exif_data[5] != 0) - return -1; - offset = exif_data[6]; - offset <<= 8; - offset += exif_data[7]; - } else { - if (exif_data[7] != 0) - return -1; - if (exif_data[6] != 0) - return -1; - offset = exif_data[5]; - offset <<= 8; - offset += exif_data[4]; - } - if (offset > length - 2) - return -1; /* check end of data segment */ - - /* Get the number of directory entries contained in this IFD */ - int number_of_tags; - if (is_motorola == 1) { - number_of_tags = exif_data[offset]; - number_of_tags <<= 8; - number_of_tags += exif_data[offset + 1]; - } else { - number_of_tags = exif_data[offset + 1]; - number_of_tags <<= 8; - number_of_tags += exif_data[offset]; - } - if (number_of_tags == 0) - return -1; - offset += 2; - - /* Search for Orientation Tag in IFD0 */ - for (;;) { - if (offset > length - 12) - return -1; /* check end of data segment */ - /* Get Tag number */ - int tagnum; - if (is_motorola == 1) { - tagnum = exif_data[offset]; - tagnum <<= 8; - tagnum += exif_data[offset + 1]; - } else { - tagnum = exif_data[offset + 1]; - tagnum <<= 8; - tagnum += exif_data[offset]; - } - if (tagnum == 0x0112) - break; /* found Orientation Tag */ - if (--number_of_tags == 0) - return -1; - offset += 12; - } - - /* Get the Orientation value */ - if (is_motorola == 1) { - if (exif_data[offset + 8] != 0) - return -1; - set_flag = exif_data[offset + 9]; - } else { - if (exif_data[offset + 9] != 0) - return -1; - set_flag = exif_data[offset + 8]; - } - if (set_flag > 8) - return -1; - - return set_flag; - } - - /** - * Check that the class can operate (for instance, that all the required - * libraries or frameworks are present). - * - * @return TRUE if it works - */ - abstract protected boolean check(); - - /** - * Create a new {@link ImageUtils}. - * - * @return the {@link ImageUtils} - */ - private static ImageUtils newObject() { - for (String clazz : new String[] { "be.nikiroo.utils.ui.ImageUtilsAwt", - "be.nikiroo.utils.android.ImageUtilsAndroid" }) { - try { - ImageUtils obj = (ImageUtils) SerialUtils.createObject(clazz); - if (obj.check()) { - return obj; - } - } catch (Throwable e) { - } - } - - return null; - } -} diff --git a/src/be/nikiroo/utils/LoginResult.java b/src/be/nikiroo/utils/LoginResult.java deleted file mode 100644 index ddc148b..0000000 --- a/src/be/nikiroo/utils/LoginResult.java +++ /dev/null @@ -1,211 +0,0 @@ -package be.nikiroo.utils; - -import java.util.ArrayList; -import java.util.List; - -/** - * A simple login facility using cookies. - * - * @author niki - */ -public class LoginResult { - private boolean success; - private String cookie; - private boolean badLogin; - private boolean badCookie; - private String option; - - /** - * Generate a failed login. - * - * @param badLogin - * TRUE if the login failed because of a who/key/subkey error - * @param badCookie - * TRUE if the login failed because of a bad cookie - * - */ - public LoginResult(boolean badLogin, boolean badCookie) { - this.badLogin = badLogin; - this.badCookie = badCookie; - } - - /** - * Generate a successful login for the given user. - * - * @param who - * the user (can be NULL) - * @param key - * the password (can be NULL) - * @param subkey - * a sub-password (can be NULL) - * @param option - * an option assigned to this login (can be NULL) - */ - public LoginResult(String who, String key, String subkey, String option) { - this.option = option; - this.cookie = generateCookie(who, key, subkey, option); - this.success = true; - } - - /** - * Generate a login via this token and checks its validity. - *

- * Will fail with a NULL token, but - * {@link LoginResult#isBadCookie()} will still be false. - * - * @param cookie - * the token to check (if NULL, will simply fail but - * {@link LoginResult#isBadCookie()} will still be false) - * @param who - * the user (can be NULL) - * @param key - * the password (can be NULL) - */ - public LoginResult(String cookie, String who, String key) { - this(cookie, who, key, null, true); - } - - /** - * Generate a login via this token and checks its validity. - *

- * Will fail with a NULL token, but - * {@link LoginResult#isBadCookie()} will still be false. - * - * @param cookie - * the token to check (if NULL, will simply fail but - * {@link LoginResult#isBadCookie()} will still be false) - * @param who - * the user (can be NULL) - * @param key - * the password (can be NULL) - * @param subkeys - * the list of candidate subkey (can be NULL) - * @param allowNoSubkey - * allow the login if no subkey was present in the token - */ - public LoginResult(String cookie, String who, String key, - List subkeys, boolean allowNoSubkey) { - if (cookie != null) { - String hashes[] = cookie.split("~"); - if (hashes.length >= 2) { - String wookie = hashes[0]; - String rehashed = hashes[1]; - String opts = hashes.length > 2 ? hashes[2] : ""; - - if (CookieUtils.validateCookie(who + key, wookie)) { - if (subkeys == null) { - subkeys = new ArrayList(); - } - - if (allowNoSubkey) { - subkeys = new ArrayList(subkeys); - subkeys.add(""); - } - - for (String subkey : subkeys) { - if (CookieUtils.validateCookie(wookie + subkey + opts, - rehashed)) { - this.cookie = generateCookie(who, key, subkey, - opts); - this.option = opts; - this.success = true; - } - } - } - } - - this.badCookie = !success; - } - - // No token -> no bad token - } - - /** - * The login wa successful. - * - * @return TRUE if it is - */ - public boolean isSuccess() { - return success; - } - - /** - * The refreshed token if the login is successful (NULL if not). - * - * @return the token, or NULL - */ - public String getCookie() { - return cookie; - } - - /** - * An option that was used to generate this login (always NULL if the login - * was not successful). - *

- * It can come from a manually generated {@link LoginResult}, but also from - * a {@link LoginResult} generated with a token. - * - * @return the option - */ - public String getOption() { - return option; - } - - /** - * The login failed because of a who/key/subkey error. - * - * @return TRUE if it failed because of a who/key/subkey error - */ - public boolean isBadLogin() { - return badLogin; - } - - /** - * The login failed because the cookie was not accepted - * - * @return TRUE if it failed because the cookie was not accepted - */ - public boolean isBadCookie() { - return badCookie; - } - - @Override - public String toString() { - if (success) - return "Login succeeded"; - - if (badLogin && badCookie) - return "Login failed because of bad login and bad cookie"; - - if (badLogin) - return "Login failed because of bad login"; - - if (badCookie) - return "Login failed because of bad cookie"; - - return "Login failed without giving a reason"; - } - - /** - * Generate a cookie. - * - * @param who - * the user name (can be NULL) - * @param key - * the password (can be NULL) - * @param subkey - * a subkey (can be NULL) - * @param option - * an option linked to the login (can be NULL) - * - * @return a fresh cookie - */ - private String generateCookie(String who, String key, String subkey, - String option) { - String wookie = CookieUtils.generateCookie(who + key, 0); - return wookie + "~" - + CookieUtils.generateCookie( - wookie + (subkey == null ? "" : subkey) + option, 0) - + "~" + option; - } -} \ No newline at end of file diff --git a/src/be/nikiroo/utils/MarkableFileInputStream.java b/src/be/nikiroo/utils/MarkableFileInputStream.java deleted file mode 100644 index 3f28064..0000000 --- a/src/be/nikiroo/utils/MarkableFileInputStream.java +++ /dev/null @@ -1,22 +0,0 @@ -package be.nikiroo.utils; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; - -/** - * Class was moved to {@link be.nikiroo.utils.streams.MarkableFileInputStream}. - * - * @author niki - */ -@Deprecated -public class MarkableFileInputStream extends - be.nikiroo.utils.streams.MarkableFileInputStream { - public MarkableFileInputStream(File file) throws FileNotFoundException { - super(file); - } - - public MarkableFileInputStream(FileInputStream fis) { - super(fis); - } -} diff --git a/src/be/nikiroo/utils/NanoHTTPD.java b/src/be/nikiroo/utils/NanoHTTPD.java deleted file mode 100644 index 8d183c1..0000000 --- a/src/be/nikiroo/utils/NanoHTTPD.java +++ /dev/null @@ -1,2358 +0,0 @@ -package be.nikiroo.utils; - -/* - * #%L - * NanoHttpd-Core - * %% - * Copyright (C) 2012 - 2015 nanohttpd - * %% - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. 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. - * - * 3. Neither the name of the nanohttpd 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 HOLDER 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. - * #L% - */ - -import java.io.BufferedInputStream; -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.Closeable; -import java.io.DataOutput; -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.FilterOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.RandomAccessFile; -import java.io.UnsupportedEncodingException; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.SocketException; -import java.net.SocketTimeoutException; -import java.net.URL; -import java.net.URLDecoder; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.charset.Charset; -import java.nio.charset.CharsetEncoder; -import java.security.KeyStore; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collections; -import java.util.Date; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Properties; -import java.util.StringTokenizer; -import java.util.TimeZone; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.zip.GZIPOutputStream; - -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLServerSocket; -import javax.net.ssl.SSLServerSocketFactory; -import javax.net.ssl.TrustManagerFactory; - -import be.nikiroo.utils.NanoHTTPD.Response.IStatus; -import be.nikiroo.utils.NanoHTTPD.Response.Status; - -/** - * A simple, tiny, nicely embeddable HTTP server in Java - *

- *

- * NanoHTTPD - *

- * Copyright (c) 2012-2013 by Paul S. Hawke, 2001,2005-2013 by Jarno Elonen, - * 2010 by Konstantinos Togias - *

- *

- *

- * Features + limitations: - *

- *

- *

- * How to use: - *

- *

- * See the separate "LICENSE.md" file for the distribution license (Modified BSD - * licence) - */ -public abstract class NanoHTTPD { - - /** - * Pluggable strategy for asynchronously executing requests. - */ - public interface AsyncRunner { - - void closeAll(); - - void closed(ClientHandler clientHandler); - - void exec(ClientHandler code); - } - - /** - * The runnable that will be used for every new client connection. - */ - public class ClientHandler implements Runnable { - - private final InputStream inputStream; - - private final Socket acceptSocket; - - public ClientHandler(InputStream inputStream, Socket acceptSocket) { - this.inputStream = inputStream; - this.acceptSocket = acceptSocket; - } - - public void close() { - safeClose(this.inputStream); - safeClose(this.acceptSocket); - } - - @Override - public void run() { - OutputStream outputStream = null; - try { - outputStream = this.acceptSocket.getOutputStream(); - TempFileManager tempFileManager = NanoHTTPD.this.tempFileManagerFactory.create(); - HTTPSession session = new HTTPSession(tempFileManager, this.inputStream, outputStream, this.acceptSocket.getInetAddress()); - while (!this.acceptSocket.isClosed()) { - session.execute(); - } - } catch (Exception e) { - // When the socket is closed by the client, - // we throw our own SocketException - // to break the "keep alive" loop above. If - // the exception was anything other - // than the expected SocketException OR a - // SocketTimeoutException, print the - // stacktrace - if (!(e instanceof SocketException && "NanoHttpd Shutdown".equals(e.getMessage())) && !(e instanceof SocketTimeoutException)) { - NanoHTTPD.LOG.log(Level.SEVERE, "Communication with the client broken, or an bug in the handler code", e); - } - } finally { - safeClose(outputStream); - safeClose(this.inputStream); - safeClose(this.acceptSocket); - NanoHTTPD.this.asyncRunner.closed(this); - } - } - } - - public static class Cookie { - - public static String getHTTPTime(int days) { - Calendar calendar = Calendar.getInstance(); - SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); - dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); - calendar.add(Calendar.DAY_OF_MONTH, days); - return dateFormat.format(calendar.getTime()); - } - - private final String n, v, e; - - public Cookie(String name, String value) { - this(name, value, 30); - } - - public Cookie(String name, String value, int numDays) { - this.n = name; - this.v = value; - this.e = getHTTPTime(numDays); - } - - public Cookie(String name, String value, String expires) { - this.n = name; - this.v = value; - this.e = expires; - } - - public String getHTTPHeader() { - String fmt = "%s=%s; expires=%s"; - return String.format(fmt, this.n, this.v, this.e); - } - } - - /** - * Provides rudimentary support for cookies. Doesn't support 'path', - * 'secure' nor 'httpOnly'. Feel free to improve it and/or add unsupported - * features. - * - * @author LordFokas - */ - public class CookieHandler implements Iterable { - - private final HashMap cookies = new HashMap(); - - private final ArrayList queue = new ArrayList(); - - public CookieHandler(Map httpHeaders) { - String raw = httpHeaders.get("cookie"); - if (raw != null) { - String[] tokens = raw.split(";"); - for (String token : tokens) { - String[] data = token.trim().split("="); - if (data.length == 2) { - this.cookies.put(data[0], data[1]); - } - } - } - } - - /** - * Set a cookie with an expiration date from a month ago, effectively - * deleting it on the client side. - * - * @param name - * The cookie name. - */ - public void delete(String name) { - set(name, "-delete-", -30); - } - - @Override - public Iterator iterator() { - return this.cookies.keySet().iterator(); - } - - /** - * Read a cookie from the HTTP Headers. - * - * @param name - * The cookie's name. - * @return The cookie's value if it exists, null otherwise. - */ - public String read(String name) { - return this.cookies.get(name); - } - - public void set(Cookie cookie) { - this.queue.add(cookie); - } - - /** - * Sets a cookie. - * - * @param name - * The cookie's name. - * @param value - * The cookie's value. - * @param expires - * How many days until the cookie expires. - */ - public void set(String name, String value, int expires) { - this.queue.add(new Cookie(name, value, Cookie.getHTTPTime(expires))); - } - - /** - * Internally used by the webserver to add all queued cookies into the - * Response's HTTP Headers. - * - * @param response - * The Response object to which headers the queued cookies - * will be added. - */ - public void unloadQueue(Response response) { - for (Cookie cookie : this.queue) { - response.addHeader("Set-Cookie", cookie.getHTTPHeader()); - } - } - } - - /** - * Default threading strategy for NanoHTTPD. - *

- *

- * By default, the server spawns a new Thread for every incoming request. - * These are set to daemon status, and named according to the request - * number. The name is useful when profiling the application. - *

- */ - public static class DefaultAsyncRunner implements AsyncRunner { - - private long requestCount; - - private final List running = Collections.synchronizedList(new ArrayList()); - - /** - * @return a list with currently running clients. - */ - public List getRunning() { - return running; - } - - @Override - public void closeAll() { - // copy of the list for concurrency - for (ClientHandler clientHandler : new ArrayList(this.running)) { - clientHandler.close(); - } - } - - @Override - public void closed(ClientHandler clientHandler) { - this.running.remove(clientHandler); - } - - @Override - public void exec(ClientHandler clientHandler) { - ++this.requestCount; - Thread t = new Thread(clientHandler); - t.setDaemon(true); - t.setName("NanoHttpd Request Processor (#" + this.requestCount + ")"); - this.running.add(clientHandler); - t.start(); - } - } - - /** - * Default strategy for creating and cleaning up temporary files. - *

- *

- * By default, files are created by File.createTempFile() in - * the directory specified. - *

- */ - public static class DefaultTempFile implements TempFile { - - private final File file; - - private final OutputStream fstream; - - public DefaultTempFile(File tempdir) throws IOException { - this.file = File.createTempFile("NanoHTTPD-", "", tempdir); - this.fstream = new FileOutputStream(this.file); - } - - @Override - public void delete() throws Exception { - safeClose(this.fstream); - if (!this.file.delete()) { - throw new Exception("could not delete temporary file: " + this.file.getAbsolutePath()); - } - } - - @Override - public String getName() { - return this.file.getAbsolutePath(); - } - - @Override - public OutputStream open() throws Exception { - return this.fstream; - } - } - - /** - * Default strategy for creating and cleaning up temporary files. - *

- *

- * This class stores its files in the standard location (that is, wherever - * java.io.tmpdir points to). Files are added to an internal - * list, and deleted when no longer needed (that is, when - * clear() is invoked at the end of processing a request). - *

- */ - public static class DefaultTempFileManager implements TempFileManager { - - private final File tmpdir; - - private final List tempFiles; - - public DefaultTempFileManager() { - this.tmpdir = new File(System.getProperty("java.io.tmpdir")); - if (!tmpdir.exists()) { - tmpdir.mkdirs(); - } - this.tempFiles = new ArrayList(); - } - - @Override - public void clear() { - for (TempFile file : this.tempFiles) { - try { - file.delete(); - } catch (Exception ignored) { - NanoHTTPD.LOG.log(Level.WARNING, "could not delete file ", ignored); - } - } - this.tempFiles.clear(); - } - - @Override - public TempFile createTempFile(String filename_hint) throws Exception { - DefaultTempFile tempFile = new DefaultTempFile(this.tmpdir); - this.tempFiles.add(tempFile); - return tempFile; - } - } - - /** - * Default strategy for creating and cleaning up temporary files. - */ - private class DefaultTempFileManagerFactory implements TempFileManagerFactory { - - @Override - public TempFileManager create() { - return new DefaultTempFileManager(); - } - } - - /** - * Creates a normal ServerSocket for TCP connections - */ - public static class DefaultServerSocketFactory implements ServerSocketFactory { - - @Override - public ServerSocket create() throws IOException { - return new ServerSocket(); - } - - } - - /** - * Creates a new SSLServerSocket - */ - public static class SecureServerSocketFactory implements ServerSocketFactory { - - private SSLServerSocketFactory sslServerSocketFactory; - - private String[] sslProtocols; - - public SecureServerSocketFactory(SSLServerSocketFactory sslServerSocketFactory, String[] sslProtocols) { - this.sslServerSocketFactory = sslServerSocketFactory; - this.sslProtocols = sslProtocols; - } - - @Override - public ServerSocket create() throws IOException { - SSLServerSocket ss = null; - ss = (SSLServerSocket) this.sslServerSocketFactory.createServerSocket(); - if (this.sslProtocols != null) { - ss.setEnabledProtocols(this.sslProtocols); - } else { - ss.setEnabledProtocols(ss.getSupportedProtocols()); - } - ss.setUseClientMode(false); - ss.setWantClientAuth(false); - ss.setNeedClientAuth(false); - return ss; - } - - } - - private static final String CONTENT_DISPOSITION_REGEX = "([ |\t]*Content-Disposition[ |\t]*:)(.*)"; - - private static final Pattern CONTENT_DISPOSITION_PATTERN = Pattern.compile(CONTENT_DISPOSITION_REGEX, Pattern.CASE_INSENSITIVE); - - private static final String CONTENT_TYPE_REGEX = "([ |\t]*content-type[ |\t]*:)(.*)"; - - private static final Pattern CONTENT_TYPE_PATTERN = Pattern.compile(CONTENT_TYPE_REGEX, Pattern.CASE_INSENSITIVE); - - private static final String CONTENT_DISPOSITION_ATTRIBUTE_REGEX = "[ |\t]*([a-zA-Z]*)[ |\t]*=[ |\t]*['|\"]([^\"^']*)['|\"]"; - - private static final Pattern CONTENT_DISPOSITION_ATTRIBUTE_PATTERN = Pattern.compile(CONTENT_DISPOSITION_ATTRIBUTE_REGEX); - - protected static class ContentType { - - private static final String ASCII_ENCODING = "US-ASCII"; - - private static final String MULTIPART_FORM_DATA_HEADER = "multipart/form-data"; - - private static final String CONTENT_REGEX = "[ |\t]*([^/^ ^;^,]+/[^ ^;^,]+)"; - - private static final Pattern MIME_PATTERN = Pattern.compile(CONTENT_REGEX, Pattern.CASE_INSENSITIVE); - - private static final String CHARSET_REGEX = "[ |\t]*(charset)[ |\t]*=[ |\t]*['|\"]?([^\"^'^;^,]*)['|\"]?"; - - private static final Pattern CHARSET_PATTERN = Pattern.compile(CHARSET_REGEX, Pattern.CASE_INSENSITIVE); - - private static final String BOUNDARY_REGEX = "[ |\t]*(boundary)[ |\t]*=[ |\t]*['|\"]?([^\"^'^;^,]*)['|\"]?"; - - private static final Pattern BOUNDARY_PATTERN = Pattern.compile(BOUNDARY_REGEX, Pattern.CASE_INSENSITIVE); - - private final String contentTypeHeader; - - private final String contentType; - - private final String encoding; - - private final String boundary; - - public ContentType(String contentTypeHeader) { - this.contentTypeHeader = contentTypeHeader; - if (contentTypeHeader != null) { - contentType = getDetailFromContentHeader(contentTypeHeader, MIME_PATTERN, "", 1); - encoding = getDetailFromContentHeader(contentTypeHeader, CHARSET_PATTERN, null, 2); - } else { - contentType = ""; - encoding = "UTF-8"; - } - if (MULTIPART_FORM_DATA_HEADER.equalsIgnoreCase(contentType)) { - boundary = getDetailFromContentHeader(contentTypeHeader, BOUNDARY_PATTERN, null, 2); - } else { - boundary = null; - } - } - - private String getDetailFromContentHeader(String contentTypeHeader, Pattern pattern, String defaultValue, int group) { - Matcher matcher = pattern.matcher(contentTypeHeader); - return matcher.find() ? matcher.group(group) : defaultValue; - } - - public String getContentTypeHeader() { - return contentTypeHeader; - } - - public String getContentType() { - return contentType; - } - - public String getEncoding() { - return encoding == null ? ASCII_ENCODING : encoding; - } - - public String getBoundary() { - return boundary; - } - - public boolean isMultipart() { - return MULTIPART_FORM_DATA_HEADER.equalsIgnoreCase(contentType); - } - - public ContentType tryUTF8() { - if (encoding == null) { - return new ContentType(this.contentTypeHeader + "; charset=UTF-8"); - } - return this; - } - } - - protected class HTTPSession implements IHTTPSession { - - private static final int REQUEST_BUFFER_LEN = 512; - - private static final int MEMORY_STORE_LIMIT = 1024; - - public static final int BUFSIZE = 8192; - - public static final int MAX_HEADER_SIZE = 1024; - - private final TempFileManager tempFileManager; - - private final OutputStream outputStream; - - private final BufferedInputStream inputStream; - - private int splitbyte; - - private int rlen; - - private String uri; - - private Method method; - - private Map> parms; - - private Map headers; - - private CookieHandler cookies; - - private String queryParameterString; - - private String remoteIp; - - private String remoteHostname; - - private String protocolVersion; - - public HTTPSession(TempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream) { - this.tempFileManager = tempFileManager; - this.inputStream = new BufferedInputStream(inputStream, HTTPSession.BUFSIZE); - this.outputStream = outputStream; - } - - public HTTPSession(TempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream, InetAddress inetAddress) { - this.tempFileManager = tempFileManager; - this.inputStream = new BufferedInputStream(inputStream, HTTPSession.BUFSIZE); - this.outputStream = outputStream; - this.remoteIp = inetAddress.isLoopbackAddress() || inetAddress.isAnyLocalAddress() ? "127.0.0.1" : inetAddress.getHostAddress().toString(); - this.remoteHostname = inetAddress.isLoopbackAddress() || inetAddress.isAnyLocalAddress() ? "localhost" : inetAddress.getHostName().toString(); - this.headers = new HashMap(); - } - - /** - * Decodes the sent headers and loads the data into Key/value pairs - */ - private void decodeHeader(BufferedReader in, Map pre, Map> parms, Map headers) throws ResponseException { - try { - // Read the request line - String inLine = in.readLine(); - if (inLine == null) { - return; - } - - StringTokenizer st = new StringTokenizer(inLine); - if (!st.hasMoreTokens()) { - throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Syntax error. Usage: GET /example/file.html"); - } - - pre.put("method", st.nextToken()); - - if (!st.hasMoreTokens()) { - throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Missing URI. Usage: GET /example/file.html"); - } - - String uri = st.nextToken(); - - // Decode parameters from the URI - int qmi = uri.indexOf('?'); - if (qmi >= 0) { - decodeParms(uri.substring(qmi + 1), parms); - uri = decodePercent(uri.substring(0, qmi)); - } else { - uri = decodePercent(uri); - } - - // If there's another token, its protocol version, - // followed by HTTP headers. - // NOTE: this now forces header names lower case since they are - // case insensitive and vary by client. - if (st.hasMoreTokens()) { - protocolVersion = st.nextToken(); - } else { - protocolVersion = "HTTP/1.1"; - NanoHTTPD.LOG.log(Level.FINE, "no protocol version specified, strange. Assuming HTTP/1.1."); - } - String line = in.readLine(); - while (line != null && !line.trim().isEmpty()) { - int p = line.indexOf(':'); - if (p >= 0) { - headers.put(line.substring(0, p).trim().toLowerCase(Locale.US), line.substring(p + 1).trim()); - } - line = in.readLine(); - } - - pre.put("uri", uri); - } catch (IOException ioe) { - throw new ResponseException(Response.Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage(), ioe); - } - } - - /** - * Decodes the Multipart Body data and put it into Key/Value pairs. - */ - private void decodeMultipartFormData(ContentType contentType, ByteBuffer fbuf, Map> parms, Map files) throws ResponseException { - int pcount = 0; - try { - int[] boundaryIdxs = getBoundaryPositions(fbuf, contentType.getBoundary().getBytes()); - if (boundaryIdxs.length < 2) { - throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but contains less than two boundary strings."); - } - - byte[] partHeaderBuff = new byte[MAX_HEADER_SIZE]; - for (int boundaryIdx = 0; boundaryIdx < boundaryIdxs.length - 1; boundaryIdx++) { - fbuf.position(boundaryIdxs[boundaryIdx]); - int len = (fbuf.remaining() < MAX_HEADER_SIZE) ? fbuf.remaining() : MAX_HEADER_SIZE; - fbuf.get(partHeaderBuff, 0, len); - BufferedReader in = - new BufferedReader(new InputStreamReader(new ByteArrayInputStream(partHeaderBuff, 0, len), Charset.forName(contentType.getEncoding())), len); - - int headerLines = 0; - // First line is boundary string - String mpline = in.readLine(); - headerLines++; - if (mpline == null || !mpline.contains(contentType.getBoundary())) { - throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but chunk does not start with boundary."); - } - - String partName = null, fileName = null, partContentType = null; - // Parse the reset of the header lines - mpline = in.readLine(); - headerLines++; - while (mpline != null && mpline.trim().length() > 0) { - Matcher matcher = CONTENT_DISPOSITION_PATTERN.matcher(mpline); - if (matcher.matches()) { - String attributeString = matcher.group(2); - matcher = CONTENT_DISPOSITION_ATTRIBUTE_PATTERN.matcher(attributeString); - while (matcher.find()) { - String key = matcher.group(1); - if ("name".equalsIgnoreCase(key)) { - partName = matcher.group(2); - } else if ("filename".equalsIgnoreCase(key)) { - fileName = matcher.group(2); - // add these two line to support multiple - // files uploaded using the same field Id - if (!fileName.isEmpty()) { - if (pcount > 0) - partName = partName + String.valueOf(pcount++); - else - pcount++; - } - } - } - } - matcher = CONTENT_TYPE_PATTERN.matcher(mpline); - if (matcher.matches()) { - partContentType = matcher.group(2).trim(); - } - mpline = in.readLine(); - headerLines++; - } - int partHeaderLength = 0; - while (headerLines-- > 0) { - partHeaderLength = scipOverNewLine(partHeaderBuff, partHeaderLength); - } - // Read the part data - if (partHeaderLength >= len - 4) { - throw new ResponseException(Response.Status.INTERNAL_ERROR, "Multipart header size exceeds MAX_HEADER_SIZE."); - } - int partDataStart = boundaryIdxs[boundaryIdx] + partHeaderLength; - int partDataEnd = boundaryIdxs[boundaryIdx + 1] - 4; - - fbuf.position(partDataStart); - - List values = parms.get(partName); - if (values == null) { - values = new ArrayList(); - parms.put(partName, values); - } - - if (partContentType == null) { - // Read the part into a string - byte[] data_bytes = new byte[partDataEnd - partDataStart]; - fbuf.get(data_bytes); - - values.add(new String(data_bytes, contentType.getEncoding())); - } else { - // Read it into a file - String path = saveTmpFile(fbuf, partDataStart, partDataEnd - partDataStart, fileName); - if (!files.containsKey(partName)) { - files.put(partName, path); - } else { - int count = 2; - while (files.containsKey(partName + count)) { - count++; - } - files.put(partName + count, path); - } - values.add(fileName); - } - } - } catch (ResponseException re) { - throw re; - } catch (Exception e) { - throw new ResponseException(Response.Status.INTERNAL_ERROR, e.toString()); - } - } - - private int scipOverNewLine(byte[] partHeaderBuff, int index) { - while (partHeaderBuff[index] != '\n') { - index++; - } - return ++index; - } - - /** - * Decodes parameters in percent-encoded URI-format ( e.g. - * "name=Jack%20Daniels&pass=Single%20Malt" ) and adds them to given - * Map. - */ - private void decodeParms(String parms, Map> p) { - if (parms == null) { - this.queryParameterString = ""; - return; - } - - this.queryParameterString = parms; - StringTokenizer st = new StringTokenizer(parms, "&"); - while (st.hasMoreTokens()) { - String e = st.nextToken(); - int sep = e.indexOf('='); - String key = null; - String value = null; - - if (sep >= 0) { - key = decodePercent(e.substring(0, sep)).trim(); - value = decodePercent(e.substring(sep + 1)); - } else { - key = decodePercent(e).trim(); - value = ""; - } - - List values = p.get(key); - if (values == null) { - values = new ArrayList(); - p.put(key, values); - } - - values.add(value); - } - } - - @Override - public void execute() throws IOException { - Response r = null; - try { - // Read the first 8192 bytes. - // The full header should fit in here. - // Apache's default header limit is 8KB. - // Do NOT assume that a single read will get the entire header - // at once! - byte[] buf = new byte[HTTPSession.BUFSIZE]; - this.splitbyte = 0; - this.rlen = 0; - - int read = -1; - this.inputStream.mark(HTTPSession.BUFSIZE); - try { - read = this.inputStream.read(buf, 0, HTTPSession.BUFSIZE); - } catch (SSLException e) { - throw e; - } catch (IOException e) { - safeClose(this.inputStream); - safeClose(this.outputStream); - throw new SocketException("NanoHttpd Shutdown"); - } - if (read == -1) { - // socket was been closed - safeClose(this.inputStream); - safeClose(this.outputStream); - throw new SocketException("NanoHttpd Shutdown"); - } - while (read > 0) { - this.rlen += read; - this.splitbyte = findHeaderEnd(buf, this.rlen); - if (this.splitbyte > 0) { - break; - } - read = this.inputStream.read(buf, this.rlen, HTTPSession.BUFSIZE - this.rlen); - } - - if (this.splitbyte < this.rlen) { - this.inputStream.reset(); - this.inputStream.skip(this.splitbyte); - } - - this.parms = new HashMap>(); - if (null == this.headers) { - this.headers = new HashMap(); - } else { - this.headers.clear(); - } - - // Create a BufferedReader for parsing the header. - BufferedReader hin = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(buf, 0, this.rlen))); - - // Decode the header into parms and header java properties - Map pre = new HashMap(); - decodeHeader(hin, pre, this.parms, this.headers); - - if (null != this.remoteIp) { - this.headers.put("remote-addr", this.remoteIp); - this.headers.put("http-client-ip", this.remoteIp); - } - - this.method = Method.lookup(pre.get("method")); - if (this.method == null) { - throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Syntax error. HTTP verb " + pre.get("method") + " unhandled."); - } - - this.uri = pre.get("uri"); - - this.cookies = new CookieHandler(this.headers); - - String connection = this.headers.get("connection"); - boolean keepAlive = "HTTP/1.1".equals(protocolVersion) && (connection == null || !connection.matches("(?i).*close.*")); - - // Ok, now do the serve() - - // TODO: long body_size = getBodySize(); - // TODO: long pos_before_serve = this.inputStream.totalRead() - // (requires implementation for totalRead()) - r = serve(this); - // TODO: this.inputStream.skip(body_size - - // (this.inputStream.totalRead() - pos_before_serve)) - - if (r == null) { - throw new ResponseException(Response.Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: Serve() returned a null response."); - } else { - String acceptEncoding = this.headers.get("accept-encoding"); - this.cookies.unloadQueue(r); - r.setRequestMethod(this.method); - r.setGzipEncoding(useGzipWhenAccepted(r) && acceptEncoding != null && acceptEncoding.contains("gzip")); - r.setKeepAlive(keepAlive); - r.send(this.outputStream); - } - if (!keepAlive || r.isCloseConnection()) { - throw new SocketException("NanoHttpd Shutdown"); - } - } catch (SocketException e) { - // throw it out to close socket object (finalAccept) - throw e; - } catch (SocketTimeoutException ste) { - // treat socket timeouts the same way we treat socket exceptions - // i.e. close the stream & finalAccept object by throwing the - // exception up the call stack. - throw ste; - } catch (SSLException ssle) { - Response resp = newFixedLengthResponse(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "SSL PROTOCOL FAILURE: " + ssle.getMessage()); - resp.send(this.outputStream); - safeClose(this.outputStream); - } catch (IOException ioe) { - Response resp = newFixedLengthResponse(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage()); - resp.send(this.outputStream); - safeClose(this.outputStream); - } catch (ResponseException re) { - Response resp = newFixedLengthResponse(re.getStatus(), NanoHTTPD.MIME_PLAINTEXT, re.getMessage()); - resp.send(this.outputStream); - safeClose(this.outputStream); - } finally { - safeClose(r); - this.tempFileManager.clear(); - } - } - - /** - * Find byte index separating header from body. It must be the last byte - * of the first two sequential new lines. - */ - private int findHeaderEnd(final byte[] buf, int rlen) { - int splitbyte = 0; - while (splitbyte + 1 < rlen) { - - // RFC2616 - if (buf[splitbyte] == '\r' && buf[splitbyte + 1] == '\n' && splitbyte + 3 < rlen && buf[splitbyte + 2] == '\r' && buf[splitbyte + 3] == '\n') { - return splitbyte + 4; - } - - // tolerance - if (buf[splitbyte] == '\n' && buf[splitbyte + 1] == '\n') { - return splitbyte + 2; - } - splitbyte++; - } - return 0; - } - - /** - * Find the byte positions where multipart boundaries start. This reads - * a large block at a time and uses a temporary buffer to optimize - * (memory mapped) file access. - */ - private int[] getBoundaryPositions(ByteBuffer b, byte[] boundary) { - int[] res = new int[0]; - if (b.remaining() < boundary.length) { - return res; - } - - int search_window_pos = 0; - byte[] search_window = new byte[4 * 1024 + boundary.length]; - - int first_fill = (b.remaining() < search_window.length) ? b.remaining() : search_window.length; - b.get(search_window, 0, first_fill); - int new_bytes = first_fill - boundary.length; - - do { - // Search the search_window - for (int j = 0; j < new_bytes; j++) { - for (int i = 0; i < boundary.length; i++) { - if (search_window[j + i] != boundary[i]) - break; - if (i == boundary.length - 1) { - // Match found, add it to results - int[] new_res = new int[res.length + 1]; - System.arraycopy(res, 0, new_res, 0, res.length); - new_res[res.length] = search_window_pos + j; - res = new_res; - } - } - } - search_window_pos += new_bytes; - - // Copy the end of the buffer to the start - System.arraycopy(search_window, search_window.length - boundary.length, search_window, 0, boundary.length); - - // Refill search_window - new_bytes = search_window.length - boundary.length; - new_bytes = (b.remaining() < new_bytes) ? b.remaining() : new_bytes; - b.get(search_window, boundary.length, new_bytes); - } while (new_bytes > 0); - return res; - } - - @Override - public CookieHandler getCookies() { - return this.cookies; - } - - @Override - public final Map getHeaders() { - return this.headers; - } - - @Override - public final InputStream getInputStream() { - return this.inputStream; - } - - @Override - public final Method getMethod() { - return this.method; - } - - /** - * @deprecated use {@link #getParameters()} instead. - */ - @Override - @Deprecated - public final Map getParms() { - Map result = new HashMap(); - for (String key : this.parms.keySet()) { - result.put(key, this.parms.get(key).get(0)); - } - - return result; - } - - @Override - public final Map> getParameters() { - return this.parms; - } - - @Override - public String getQueryParameterString() { - return this.queryParameterString; - } - - private RandomAccessFile getTmpBucket() { - try { - TempFile tempFile = this.tempFileManager.createTempFile(null); - return new RandomAccessFile(tempFile.getName(), "rw"); - } catch (Exception e) { - throw new Error(e); // we won't recover, so throw an error - } - } - - @Override - public final String getUri() { - return this.uri; - } - - /** - * Deduce body length in bytes. Either from "content-length" header or - * read bytes. - */ - public long getBodySize() { - if (this.headers.containsKey("content-length")) { - return Long.parseLong(this.headers.get("content-length")); - } else if (this.splitbyte < this.rlen) { - return this.rlen - this.splitbyte; - } - return 0; - } - - @Override - public void parseBody(Map files) throws IOException, ResponseException { - RandomAccessFile randomAccessFile = null; - try { - long size = getBodySize(); - ByteArrayOutputStream baos = null; - DataOutput requestDataOutput = null; - - // Store the request in memory or a file, depending on size - if (size < MEMORY_STORE_LIMIT) { - baos = new ByteArrayOutputStream(); - requestDataOutput = new DataOutputStream(baos); - } else { - randomAccessFile = getTmpBucket(); - requestDataOutput = randomAccessFile; - } - - // Read all the body and write it to request_data_output - byte[] buf = new byte[REQUEST_BUFFER_LEN]; - while (this.rlen >= 0 && size > 0) { - this.rlen = this.inputStream.read(buf, 0, (int) Math.min(size, REQUEST_BUFFER_LEN)); - size -= this.rlen; - if (this.rlen > 0) { - requestDataOutput.write(buf, 0, this.rlen); - } - } - - ByteBuffer fbuf = null; - if (baos != null) { - fbuf = ByteBuffer.wrap(baos.toByteArray(), 0, baos.size()); - } else { - fbuf = randomAccessFile.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, randomAccessFile.length()); - randomAccessFile.seek(0); - } - - // If the method is POST, there may be parameters - // in data section, too, read it: - if (Method.POST.equals(this.method)) { - ContentType contentType = new ContentType(this.headers.get("content-type")); - if (contentType.isMultipart()) { - String boundary = contentType.getBoundary(); - if (boundary == null) { - throw new ResponseException(Response.Status.BAD_REQUEST, - "BAD REQUEST: Content type is multipart/form-data but boundary missing. Usage: GET /example/file.html"); - } - decodeMultipartFormData(contentType, fbuf, this.parms, files); - } else { - byte[] postBytes = new byte[fbuf.remaining()]; - fbuf.get(postBytes); - String postLine = new String(postBytes, contentType.getEncoding()).trim(); - // Handle application/x-www-form-urlencoded - if ("application/x-www-form-urlencoded".equalsIgnoreCase(contentType.getContentType())) { - decodeParms(postLine, this.parms); - } else if (postLine.length() != 0) { - // Special case for raw POST data => create a - // special files entry "postData" with raw content - // data - files.put("postData", postLine); - } - } - } else if (Method.PUT.equals(this.method)) { - files.put("content", saveTmpFile(fbuf, 0, fbuf.limit(), null)); - } - } finally { - safeClose(randomAccessFile); - } - } - - /** - * Retrieves the content of a sent file and saves it to a temporary - * file. The full path to the saved file is returned. - */ - private String saveTmpFile(ByteBuffer b, int offset, int len, String filename_hint) { - String path = ""; - if (len > 0) { - FileOutputStream fileOutputStream = null; - try { - TempFile tempFile = this.tempFileManager.createTempFile(filename_hint); - ByteBuffer src = b.duplicate(); - fileOutputStream = new FileOutputStream(tempFile.getName()); - FileChannel dest = fileOutputStream.getChannel(); - src.position(offset).limit(offset + len); - dest.write(src.slice()); - path = tempFile.getName(); - } catch (Exception e) { // Catch exception if any - throw new Error(e); // we won't recover, so throw an error - } finally { - safeClose(fileOutputStream); - } - } - return path; - } - - @Override - public String getRemoteIpAddress() { - return this.remoteIp; - } - - @Override - public String getRemoteHostName() { - return this.remoteHostname; - } - } - - /** - * Handles one session, i.e. parses the HTTP request and returns the - * response. - */ - public interface IHTTPSession { - - void execute() throws IOException; - - CookieHandler getCookies(); - - Map getHeaders(); - - InputStream getInputStream(); - - Method getMethod(); - - /** - * This method will only return the first value for a given parameter. - * You will want to use getParameters if you expect multiple values for - * a given key. - * - * @deprecated use {@link #getParameters()} instead. - */ - @Deprecated - Map getParms(); - - Map> getParameters(); - - String getQueryParameterString(); - - /** - * @return the path part of the URL. - */ - String getUri(); - - /** - * Adds the files in the request body to the files map. - * - * @param files - * map to modify - */ - void parseBody(Map files) throws IOException, ResponseException; - - /** - * Get the remote ip address of the requester. - * - * @return the IP address. - */ - String getRemoteIpAddress(); - - /** - * Get the remote hostname of the requester. - * - * @return the hostname. - */ - String getRemoteHostName(); - } - - /** - * HTTP Request methods, with the ability to decode a String - * back to its enum value. - */ - public enum Method { - GET, - PUT, - POST, - DELETE, - HEAD, - OPTIONS, - TRACE, - CONNECT, - PATCH, - PROPFIND, - PROPPATCH, - MKCOL, - MOVE, - COPY, - LOCK, - UNLOCK; - - static Method lookup(String method) { - if (method == null) - return null; - - try { - return valueOf(method); - } catch (IllegalArgumentException e) { - // TODO: Log it? - return null; - } - } - } - - /** - * HTTP response. Return one of these from serve(). - */ - public static class Response implements Closeable { - - public interface IStatus { - - String getDescription(); - - int getRequestStatus(); - } - - /** - * Some HTTP response status codes - */ - public enum Status implements IStatus { - SWITCH_PROTOCOL(101, "Switching Protocols"), - - OK(200, "OK"), - CREATED(201, "Created"), - ACCEPTED(202, "Accepted"), - NO_CONTENT(204, "No Content"), - PARTIAL_CONTENT(206, "Partial Content"), - MULTI_STATUS(207, "Multi-Status"), - - REDIRECT(301, "Moved Permanently"), - /** - * Many user agents mishandle 302 in ways that violate the RFC1945 - * spec (i.e., redirect a POST to a GET). 303 and 307 were added in - * RFC2616 to address this. You should prefer 303 and 307 unless the - * calling user agent does not support 303 and 307 functionality - */ - @Deprecated - FOUND(302, "Found"), - REDIRECT_SEE_OTHER(303, "See Other"), - NOT_MODIFIED(304, "Not Modified"), - TEMPORARY_REDIRECT(307, "Temporary Redirect"), - - BAD_REQUEST(400, "Bad Request"), - UNAUTHORIZED(401, "Unauthorized"), - FORBIDDEN(403, "Forbidden"), - NOT_FOUND(404, "Not Found"), - METHOD_NOT_ALLOWED(405, "Method Not Allowed"), - NOT_ACCEPTABLE(406, "Not Acceptable"), - REQUEST_TIMEOUT(408, "Request Timeout"), - CONFLICT(409, "Conflict"), - GONE(410, "Gone"), - LENGTH_REQUIRED(411, "Length Required"), - PRECONDITION_FAILED(412, "Precondition Failed"), - PAYLOAD_TOO_LARGE(413, "Payload Too Large"), - UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type"), - RANGE_NOT_SATISFIABLE(416, "Requested Range Not Satisfiable"), - EXPECTATION_FAILED(417, "Expectation Failed"), - TOO_MANY_REQUESTS(429, "Too Many Requests"), - - INTERNAL_ERROR(500, "Internal Server Error"), - NOT_IMPLEMENTED(501, "Not Implemented"), - SERVICE_UNAVAILABLE(503, "Service Unavailable"), - UNSUPPORTED_HTTP_VERSION(505, "HTTP Version Not Supported"); - - private final int requestStatus; - - private final String description; - - Status(int requestStatus, String description) { - this.requestStatus = requestStatus; - this.description = description; - } - - public static Status lookup(int requestStatus) { - for (Status status : Status.values()) { - if (status.getRequestStatus() == requestStatus) { - return status; - } - } - return null; - } - - @Override - public String getDescription() { - return "" + this.requestStatus + " " + this.description; - } - - @Override - public int getRequestStatus() { - return this.requestStatus; - } - - } - - /** - * Output stream that will automatically send every write to the wrapped - * OutputStream according to chunked transfer: - * http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1 - */ - private static class ChunkedOutputStream extends FilterOutputStream { - - public ChunkedOutputStream(OutputStream out) { - super(out); - } - - @Override - public void write(int b) throws IOException { - byte[] data = { - (byte) b - }; - write(data, 0, 1); - } - - @Override - public void write(byte[] b) throws IOException { - write(b, 0, b.length); - } - - @Override - public void write(byte[] b, int off, int len) throws IOException { - if (len == 0) - return; - out.write(String.format("%x\r\n", len).getBytes()); - out.write(b, off, len); - out.write("\r\n".getBytes()); - } - - public void finish() throws IOException { - out.write("0\r\n\r\n".getBytes()); - } - - } - - /** - * HTTP status code after processing, e.g. "200 OK", Status.OK - */ - private IStatus status; - - /** - * MIME type of content, e.g. "text/html" - */ - private String mimeType; - - /** - * Data of the response, may be null. - */ - private InputStream data; - - private long contentLength; - - /** - * Headers for the HTTP response. Use addHeader() to add lines. the - * lowercase map is automatically kept up to date. - */ - @SuppressWarnings("serial") - private final Map header = new HashMap() { - - public String put(String key, String value) { - lowerCaseHeader.put(key == null ? key : key.toLowerCase(), value); - return super.put(key, value); - }; - }; - - /** - * copy of the header map with all the keys lowercase for faster - * searching. - */ - private final Map lowerCaseHeader = new HashMap(); - - /** - * The request method that spawned this response. - */ - private Method requestMethod; - - /** - * Use chunkedTransfer - */ - private boolean chunkedTransfer; - - private boolean encodeAsGzip; - - private boolean keepAlive; - - /** - * Creates a fixed length response if totalBytes>=0, otherwise chunked. - */ - protected Response(IStatus status, String mimeType, InputStream data, long totalBytes) { - this.status = status; - this.mimeType = mimeType; - if (data == null) { - this.data = new ByteArrayInputStream(new byte[0]); - this.contentLength = 0L; - } else { - this.data = data; - this.contentLength = totalBytes; - } - this.chunkedTransfer = this.contentLength < 0; - keepAlive = true; - } - - @Override - public void close() throws IOException { - if (this.data != null) { - this.data.close(); - } - } - - /** - * Adds given line to the header. - */ - public void addHeader(String name, String value) { - this.header.put(name, value); - } - - /** - * Indicate to close the connection after the Response has been sent. - * - * @param close - * {@code true} to hint connection closing, {@code false} to - * let connection be closed by client. - */ - public void closeConnection(boolean close) { - if (close) - this.header.put("connection", "close"); - else - this.header.remove("connection"); - } - - /** - * @return {@code true} if connection is to be closed after this - * Response has been sent. - */ - public boolean isCloseConnection() { - return "close".equals(getHeader("connection")); - } - - public InputStream getData() { - return this.data; - } - - public String getHeader(String name) { - return this.lowerCaseHeader.get(name.toLowerCase()); - } - - public String getMimeType() { - return this.mimeType; - } - - public Method getRequestMethod() { - return this.requestMethod; - } - - public IStatus getStatus() { - return this.status; - } - - public void setGzipEncoding(boolean encodeAsGzip) { - this.encodeAsGzip = encodeAsGzip; - } - - public void setKeepAlive(boolean useKeepAlive) { - this.keepAlive = useKeepAlive; - } - - /** - * Sends given response to the socket. - */ - protected void send(OutputStream outputStream) { - SimpleDateFormat gmtFrmt = new SimpleDateFormat("E, d MMM yyyy HH:mm:ss 'GMT'", Locale.US); - gmtFrmt.setTimeZone(TimeZone.getTimeZone("GMT")); - - try { - if (this.status == null) { - throw new Error("sendResponse(): Status can't be null."); - } - PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(outputStream, new ContentType(this.mimeType).getEncoding())), false); - pw.append("HTTP/1.1 ").append(this.status.getDescription()).append(" \r\n"); - if (this.mimeType != null) { - printHeader(pw, "Content-Type", this.mimeType); - } - if (getHeader("date") == null) { - printHeader(pw, "Date", gmtFrmt.format(new Date())); - } - for (Entry entry : this.header.entrySet()) { - printHeader(pw, entry.getKey(), entry.getValue()); - } - if (getHeader("connection") == null) { - printHeader(pw, "Connection", (this.keepAlive ? "keep-alive" : "close")); - } - if (getHeader("content-length") != null) { - encodeAsGzip = false; - } - if (encodeAsGzip) { - printHeader(pw, "Content-Encoding", "gzip"); - setChunkedTransfer(true); - } - long pending = this.data != null ? this.contentLength : 0; - if (this.requestMethod != Method.HEAD && this.chunkedTransfer) { - printHeader(pw, "Transfer-Encoding", "chunked"); - } else if (!encodeAsGzip) { - pending = sendContentLengthHeaderIfNotAlreadyPresent(pw, pending); - } - pw.append("\r\n"); - pw.flush(); - sendBodyWithCorrectTransferAndEncoding(outputStream, pending); - outputStream.flush(); - safeClose(this.data); - } catch (IOException ioe) { - NanoHTTPD.LOG.log(Level.SEVERE, "Could not send response to the client", ioe); - } - } - - @SuppressWarnings("static-method") - protected void printHeader(PrintWriter pw, String key, String value) { - pw.append(key).append(": ").append(value).append("\r\n"); - } - - protected long sendContentLengthHeaderIfNotAlreadyPresent(PrintWriter pw, long defaultSize) { - String contentLengthString = getHeader("content-length"); - long size = defaultSize; - if (contentLengthString != null) { - try { - size = Long.parseLong(contentLengthString); - } catch (NumberFormatException ex) { - LOG.severe("content-length was no number " + contentLengthString); - } - } - pw.print("Content-Length: " + size + "\r\n"); - return size; - } - - private void sendBodyWithCorrectTransferAndEncoding(OutputStream outputStream, long pending) throws IOException { - if (this.requestMethod != Method.HEAD && this.chunkedTransfer) { - ChunkedOutputStream chunkedOutputStream = new ChunkedOutputStream(outputStream); - sendBodyWithCorrectEncoding(chunkedOutputStream, -1); - chunkedOutputStream.finish(); - } else { - sendBodyWithCorrectEncoding(outputStream, pending); - } - } - - private void sendBodyWithCorrectEncoding(OutputStream outputStream, long pending) throws IOException { - if (encodeAsGzip) { - GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream); - sendBody(gzipOutputStream, -1); - gzipOutputStream.finish(); - } else { - sendBody(outputStream, pending); - } - } - - /** - * Sends the body to the specified OutputStream. The pending parameter - * limits the maximum amounts of bytes sent unless it is -1, in which - * case everything is sent. - * - * @param outputStream - * the OutputStream to send data to - * @param pending - * -1 to send everything, otherwise sets a max limit to the - * number of bytes sent - * @throws IOException - * if something goes wrong while sending the data. - */ - private void sendBody(OutputStream outputStream, long pending) throws IOException { - long BUFFER_SIZE = 16 * 1024; - byte[] buff = new byte[(int) BUFFER_SIZE]; - boolean sendEverything = pending == -1; - while (pending > 0 || sendEverything) { - long bytesToRead = sendEverything ? BUFFER_SIZE : Math.min(pending, BUFFER_SIZE); - int read = this.data.read(buff, 0, (int) bytesToRead); - if (read <= 0) { - break; - } - outputStream.write(buff, 0, read); - if (!sendEverything) { - pending -= read; - } - } - } - - public void setChunkedTransfer(boolean chunkedTransfer) { - this.chunkedTransfer = chunkedTransfer; - } - - public void setData(InputStream data) { - this.data = data; - } - - public void setMimeType(String mimeType) { - this.mimeType = mimeType; - } - - public void setRequestMethod(Method requestMethod) { - this.requestMethod = requestMethod; - } - - public void setStatus(IStatus status) { - this.status = status; - } - } - - public static final class ResponseException extends Exception { - - private static final long serialVersionUID = 6569838532917408380L; - - private final Response.Status status; - - public ResponseException(Response.Status status, String message) { - super(message); - this.status = status; - } - - public ResponseException(Response.Status status, String message, Exception e) { - super(message, e); - this.status = status; - } - - public Response.Status getStatus() { - return this.status; - } - } - - /** - * The runnable that will be used for the main listening thread. - */ - public class ServerRunnable implements Runnable { - - private final int timeout; - - private IOException bindException; - - private boolean hasBinded = false; - - public ServerRunnable(int timeout) { - this.timeout = timeout; - } - - @Override - public void run() { - try { - myServerSocket.bind(hostname != null ? new InetSocketAddress(hostname, myPort) : new InetSocketAddress(myPort)); - hasBinded = true; - } catch (IOException e) { - this.bindException = e; - return; - } - do { - try { - final Socket finalAccept = NanoHTTPD.this.myServerSocket.accept(); - if (this.timeout > 0) { - finalAccept.setSoTimeout(this.timeout); - } - final InputStream inputStream = finalAccept.getInputStream(); - NanoHTTPD.this.asyncRunner.exec(createClientHandler(finalAccept, inputStream)); - } catch (IOException e) { - NanoHTTPD.LOG.log(Level.FINE, "Communication with the client broken", e); - } - } while (!NanoHTTPD.this.myServerSocket.isClosed()); - } - } - - /** - * A temp file. - *

- *

- * Temp files are responsible for managing the actual temporary storage and - * cleaning themselves up when no longer needed. - *

- */ - public interface TempFile { - - public void delete() throws Exception; - - public String getName(); - - public OutputStream open() throws Exception; - } - - /** - * Temp file manager. - *

- *

- * Temp file managers are created 1-to-1 with incoming requests, to create - * and cleanup temporary files created as a result of handling the request. - *

- */ - public interface TempFileManager { - - void clear(); - - public TempFile createTempFile(String filename_hint) throws Exception; - } - - /** - * Factory to create temp file managers. - */ - public interface TempFileManagerFactory { - - public TempFileManager create(); - } - - /** - * Factory to create ServerSocketFactories. - */ - public interface ServerSocketFactory { - - public ServerSocket create() throws IOException; - - } - - /** - * Maximum time to wait on Socket.getInputStream().read() (in milliseconds) - * This is required as the Keep-Alive HTTP connections would otherwise block - * the socket reading thread forever (or as long the browser is open). - */ - public static final int SOCKET_READ_TIMEOUT = 5000; - - /** - * Common MIME type for dynamic content: plain text - */ - public static final String MIME_PLAINTEXT = "text/plain"; - - /** - * Common MIME type for dynamic content: html - */ - public static final String MIME_HTML = "text/html"; - - /** - * Pseudo-Parameter to use to store the actual query string in the - * parameters map for later re-processing. - */ - private static final String QUERY_STRING_PARAMETER = "NanoHttpd.QUERY_STRING"; - - /** - * logger to log to. - */ - private static final Logger LOG = Logger.getLogger(NanoHTTPD.class.getName()); - - /** - * Hashtable mapping (String)FILENAME_EXTENSION -> (String)MIME_TYPE - */ - protected static Map MIME_TYPES; - - public static Map mimeTypes() { - if (MIME_TYPES == null) { - MIME_TYPES = new HashMap(); - loadMimeTypes(MIME_TYPES, "META-INF/nanohttpd/default-mimetypes.properties"); - loadMimeTypes(MIME_TYPES, "META-INF/nanohttpd/mimetypes.properties"); - if (MIME_TYPES.isEmpty()) { - LOG.log(Level.WARNING, "no mime types found in the classpath! please provide mimetypes.properties"); - } - } - return MIME_TYPES; - } - - @SuppressWarnings({ - "unchecked", - "rawtypes" - }) - private static void loadMimeTypes(Map result, String resourceName) { - try { - Enumeration resources = NanoHTTPD.class.getClassLoader().getResources(resourceName); - while (resources.hasMoreElements()) { - URL url = (URL) resources.nextElement(); - Properties properties = new Properties(); - InputStream stream = null; - try { - stream = url.openStream(); - properties.load(stream); - } catch (IOException e) { - LOG.log(Level.SEVERE, "could not load mimetypes from " + url, e); - } finally { - safeClose(stream); - } - result.putAll((Map) properties); - } - } catch (IOException e) { - LOG.log(Level.INFO, "no mime types available at " + resourceName); - } - }; - - /** - * Creates an SSLSocketFactory for HTTPS. Pass a loaded KeyStore and an - * array of loaded KeyManagers. These objects must properly - * loaded/initialized by the caller. - */ - public static SSLServerSocketFactory makeSSLSocketFactory(KeyStore loadedKeyStore, KeyManager[] keyManagers) throws IOException { - SSLServerSocketFactory res = null; - try { - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(loadedKeyStore); - SSLContext ctx = SSLContext.getInstance("TLS"); - ctx.init(keyManagers, trustManagerFactory.getTrustManagers(), null); - res = ctx.getServerSocketFactory(); - } catch (Exception e) { - throw new IOException(e.getMessage()); - } - return res; - } - - /** - * Creates an SSLSocketFactory for HTTPS. Pass a loaded KeyStore and a - * loaded KeyManagerFactory. These objects must properly loaded/initialized - * by the caller. - */ - public static SSLServerSocketFactory makeSSLSocketFactory(KeyStore loadedKeyStore, KeyManagerFactory loadedKeyFactory) throws IOException { - try { - return makeSSLSocketFactory(loadedKeyStore, loadedKeyFactory.getKeyManagers()); - } catch (Exception e) { - throw new IOException(e.getMessage()); - } - } - - /** - * Creates an SSLSocketFactory for HTTPS. Pass a KeyStore resource with your - * certificate and passphrase - */ - public static SSLServerSocketFactory makeSSLSocketFactory(String keyAndTrustStoreClasspathPath, char[] passphrase) throws IOException { - try { - KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); - InputStream keystoreStream = NanoHTTPD.class.getResourceAsStream(keyAndTrustStoreClasspathPath); - - if (keystoreStream == null) { - throw new IOException("Unable to load keystore from classpath: " + keyAndTrustStoreClasspathPath); - } - - keystore.load(keystoreStream, passphrase); - KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - keyManagerFactory.init(keystore, passphrase); - return makeSSLSocketFactory(keystore, keyManagerFactory); - } catch (Exception e) { - throw new IOException(e.getMessage()); - } - } - - /** - * Get MIME type from file name extension, if possible - * - * @param uri - * the string representing a file - * @return the connected mime/type - */ - public static String getMimeTypeForFile(String uri) { - int dot = uri.lastIndexOf('.'); - String mime = null; - if (dot >= 0) { - mime = mimeTypes().get(uri.substring(dot + 1).toLowerCase()); - } - return mime == null ? "application/octet-stream" : mime; - } - - private static final void safeClose(Object closeable) { - try { - if (closeable != null) { - if (closeable instanceof Closeable) { - ((Closeable) closeable).close(); - } else if (closeable instanceof Socket) { - ((Socket) closeable).close(); - } else if (closeable instanceof ServerSocket) { - ((ServerSocket) closeable).close(); - } else { - throw new IllegalArgumentException("Unknown object to close"); - } - } - } catch (IOException e) { - NanoHTTPD.LOG.log(Level.SEVERE, "Could not close", e); - } - } - - private final String hostname; - - private final int myPort; - - private volatile ServerSocket myServerSocket; - - private ServerSocketFactory serverSocketFactory = new DefaultServerSocketFactory(); - - private Thread myThread; - - /** - * Pluggable strategy for asynchronously executing requests. - */ - protected AsyncRunner asyncRunner; - - /** - * Pluggable strategy for creating and cleaning up temporary files. - */ - private TempFileManagerFactory tempFileManagerFactory; - - /** - * Constructs an HTTP server on given port. - */ - public NanoHTTPD(int port) { - this(null, port); - } - - // ------------------------------------------------------------------------------- - // // - // - // Threading Strategy. - // - // ------------------------------------------------------------------------------- - // // - - /** - * Constructs an HTTP server on given hostname and port. - */ - public NanoHTTPD(String hostname, int port) { - this.hostname = hostname; - this.myPort = port; - setTempFileManagerFactory(new DefaultTempFileManagerFactory()); - setAsyncRunner(new DefaultAsyncRunner()); - } - - /** - * Forcibly closes all connections that are open. - */ - public synchronized void closeAllConnections() { - stop(); - } - - /** - * create a instance of the client handler, subclasses can return a subclass - * of the ClientHandler. - * - * @param finalAccept - * the socket the cleint is connected to - * @param inputStream - * the input stream - * @return the client handler - */ - protected ClientHandler createClientHandler(final Socket finalAccept, final InputStream inputStream) { - return new ClientHandler(inputStream, finalAccept); - } - - /** - * Instantiate the server runnable, can be overwritten by subclasses to - * provide a subclass of the ServerRunnable. - * - * @param timeout - * the socet timeout to use. - * @return the server runnable. - */ - protected ServerRunnable createServerRunnable(final int timeout) { - return new ServerRunnable(timeout); - } - - /** - * Decode parameters from a URL, handing the case where a single parameter - * name might have been supplied several times, by return lists of values. - * In general these lists will contain a single element. - * - * @param parms - * original NanoHTTPD parameters values, as passed to the - * serve() method. - * @return a map of String (parameter name) to - * List<String> (a list of the values supplied). - */ - protected static Map> decodeParameters(Map parms) { - return decodeParameters(parms.get(NanoHTTPD.QUERY_STRING_PARAMETER)); - } - - // ------------------------------------------------------------------------------- - // // - - /** - * Decode parameters from a URL, handing the case where a single parameter - * name might have been supplied several times, by return lists of values. - * In general these lists will contain a single element. - * - * @param queryString - * a query string pulled from the URL. - * @return a map of String (parameter name) to - * List<String> (a list of the values supplied). - */ - protected static Map> decodeParameters(String queryString) { - Map> parms = new HashMap>(); - if (queryString != null) { - StringTokenizer st = new StringTokenizer(queryString, "&"); - while (st.hasMoreTokens()) { - String e = st.nextToken(); - int sep = e.indexOf('='); - String propertyName = sep >= 0 ? decodePercent(e.substring(0, sep)).trim() : decodePercent(e).trim(); - if (!parms.containsKey(propertyName)) { - parms.put(propertyName, new ArrayList()); - } - String propertyValue = sep >= 0 ? decodePercent(e.substring(sep + 1)) : null; - if (propertyValue != null) { - parms.get(propertyName).add(propertyValue); - } - } - } - return parms; - } - - /** - * Decode percent encoded String values. - * - * @param str - * the percent encoded String - * @return expanded form of the input, for example "foo%20bar" becomes - * "foo bar" - */ - protected static String decodePercent(String str) { - String decoded = null; - try { - decoded = URLDecoder.decode(str, "UTF8"); - } catch (UnsupportedEncodingException ignored) { - NanoHTTPD.LOG.log(Level.WARNING, "Encoding not supported, ignored", ignored); - } - return decoded; - } - - /** - * @return true if the gzip compression should be used if the client - * accespts it. Default this option is on for text content and off - * for everything. Override this for custom semantics. - */ - @SuppressWarnings("static-method") - protected boolean useGzipWhenAccepted(Response r) { - return r.getMimeType() != null && (r.getMimeType().toLowerCase().contains("text/") || r.getMimeType().toLowerCase().contains("/json")); - } - - public final int getListeningPort() { - return this.myServerSocket == null ? -1 : this.myServerSocket.getLocalPort(); - } - - public final boolean isAlive() { - return wasStarted() && !this.myServerSocket.isClosed() && this.myThread.isAlive(); - } - - public ServerSocketFactory getServerSocketFactory() { - return serverSocketFactory; - } - - public void setServerSocketFactory(ServerSocketFactory serverSocketFactory) { - this.serverSocketFactory = serverSocketFactory; - } - - public String getHostname() { - return hostname; - } - - public TempFileManagerFactory getTempFileManagerFactory() { - return tempFileManagerFactory; - } - - /** - * Call before start() to serve over HTTPS instead of HTTP - */ - public void makeSecure(SSLServerSocketFactory sslServerSocketFactory, String[] sslProtocols) { - this.serverSocketFactory = new SecureServerSocketFactory(sslServerSocketFactory, sslProtocols); - } - - /** - * Create a response with unknown length (using HTTP 1.1 chunking). - */ - public static Response newChunkedResponse(IStatus status, String mimeType, InputStream data) { - return new Response(status, mimeType, data, -1); - } - - /** - * Create a response with known length. - */ - public static Response newFixedLengthResponse(IStatus status, String mimeType, InputStream data, long totalBytes) { - return new Response(status, mimeType, data, totalBytes); - } - - /** - * Create a text response with known length. - */ - public static Response newFixedLengthResponse(IStatus status, String mimeType, String txt) { - ContentType contentType = new ContentType(mimeType); - if (txt == null) { - return newFixedLengthResponse(status, mimeType, new ByteArrayInputStream(new byte[0]), 0); - } else { - byte[] bytes; - try { - CharsetEncoder newEncoder = Charset.forName(contentType.getEncoding()).newEncoder(); - if (!newEncoder.canEncode(txt)) { - contentType = contentType.tryUTF8(); - } - bytes = txt.getBytes(contentType.getEncoding()); - } catch (UnsupportedEncodingException e) { - NanoHTTPD.LOG.log(Level.SEVERE, "encoding problem, responding nothing", e); - bytes = new byte[0]; - } - return newFixedLengthResponse(status, contentType.getContentTypeHeader(), new ByteArrayInputStream(bytes), bytes.length); - } - } - - /** - * Create a text response with known length. - */ - public static Response newFixedLengthResponse(String msg) { - return newFixedLengthResponse(Status.OK, NanoHTTPD.MIME_HTML, msg); - } - - /** - * Override this to customize the server. - *

- *

- * (By default, this returns a 404 "Not Found" plain text error response.) - * - * @param session - * The HTTP session - * @return HTTP response, see class Response for details - */ - public Response serve(IHTTPSession session) { - Map files = new HashMap(); - Method method = session.getMethod(); - if (Method.PUT.equals(method) || Method.POST.equals(method)) { - try { - session.parseBody(files); - } catch (IOException ioe) { - return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage()); - } catch (ResponseException re) { - return newFixedLengthResponse(re.getStatus(), NanoHTTPD.MIME_PLAINTEXT, re.getMessage()); - } - } - - Map parms = session.getParms(); - parms.put(NanoHTTPD.QUERY_STRING_PARAMETER, session.getQueryParameterString()); - return serve(session.getUri(), method, session.getHeaders(), parms, files); - } - - /** - * Override this to customize the server. - *

- *

- * (By default, this returns a 404 "Not Found" plain text error response.) - * - * @param uri - * Percent-decoded URI without parameters, for example - * "/index.cgi" - * @param method - * "GET", "POST" etc. - * @param parms - * Parsed, percent decoded parameters from URI and, in case of - * POST, data. - * @param headers - * Header entries, percent decoded - * @return HTTP response, see class Response for details - */ - @Deprecated - public Response serve(String uri, Method method, Map headers, Map parms, Map files) { - return newFixedLengthResponse(Response.Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT, "Not Found"); - } - - /** - * Pluggable strategy for asynchronously executing requests. - * - * @param asyncRunner - * new strategy for handling threads. - */ - public void setAsyncRunner(AsyncRunner asyncRunner) { - this.asyncRunner = asyncRunner; - } - - /** - * Pluggable strategy for creating and cleaning up temporary files. - * - * @param tempFileManagerFactory - * new strategy for handling temp files. - */ - public void setTempFileManagerFactory(TempFileManagerFactory tempFileManagerFactory) { - this.tempFileManagerFactory = tempFileManagerFactory; - } - - /** - * Start the server. - * - * @throws IOException - * if the socket is in use. - */ - public void start() throws IOException { - start(NanoHTTPD.SOCKET_READ_TIMEOUT); - } - - /** - * Starts the server (in setDaemon(true) mode). - */ - public void start(final int timeout) throws IOException { - start(timeout, true); - } - - /** - * Start the server. - * - * @param timeout - * timeout to use for socket connections. - * @param daemon - * start the thread daemon or not. - * @throws IOException - * if the socket is in use. - */ - public void start(final int timeout, boolean daemon) throws IOException { - this.myServerSocket = this.getServerSocketFactory().create(); - this.myServerSocket.setReuseAddress(true); - - ServerRunnable serverRunnable = createServerRunnable(timeout); - this.myThread = new Thread(serverRunnable); - this.myThread.setDaemon(daemon); - this.myThread.setName("NanoHttpd Main Listener"); - this.myThread.start(); - while (!serverRunnable.hasBinded && serverRunnable.bindException == null) { - try { - Thread.sleep(10L); - } catch (Throwable e) { - // on android this may not be allowed, that's why we - // catch throwable the wait should be very short because we are - // just waiting for the bind of the socket - } - } - if (serverRunnable.bindException != null) { - throw serverRunnable.bindException; - } - } - - /** - * Stop the server. - */ - public void stop() { - try { - safeClose(this.myServerSocket); - this.asyncRunner.closeAll(); - if (this.myThread != null) { - this.myThread.join(); - } - } catch (Exception e) { - NanoHTTPD.LOG.log(Level.SEVERE, "Could not stop all connections", e); - } - } - - public final boolean wasStarted() { - return this.myServerSocket != null && this.myThread != null; - } -} diff --git a/src/be/nikiroo/utils/Progress.java b/src/be/nikiroo/utils/Progress.java deleted file mode 100644 index bb143ef..0000000 --- a/src/be/nikiroo/utils/Progress.java +++ /dev/null @@ -1,535 +0,0 @@ -package be.nikiroo.utils; - -import java.util.ArrayList; -import java.util.EventListener; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -/** - * Progress reporting system, possibly nested. - *

- * A {@link Progress} can have a name, and that name will be reported through - * the event system (it will report the first non-null name in the stack from - * the {@link Progress} from which the event originated to the parent the event - * is listened on). - *

- * The {@link Progress} also has a table of keys/values shared amongst all the - * hierarchy (note that when adding a {@link Progress} to others, its values - * will be prioritized if some with the same keys were already present in the - * hierarchy). - * - * @author niki - */ -public class Progress { - /** - * This event listener is designed to report progress events from - * {@link Progress}. - * - * @author niki - */ - public interface ProgressListener extends EventListener { - /** - * A progression event. - * - * @param progress - * the {@link Progress} object that generated it, not - * necessarily the same as the one where the listener was - * attached (it could be a child {@link Progress} of this - * {@link Progress}). - * @param name - * the first non-null name of the {@link Progress} step that - * generated this event - */ - public void progress(Progress progress, String name); - } - - private Map map = new HashMap(); - private Progress parent = null; - private Object lock = new Object(); - private String name; - private Map children; - private List listeners; - private int min; - private int max; - private double relativeLocalProgress; - private double relativeProgress; // children included - - /** - * Create a new default unnamed {@link Progress}, from 0 to 100. - */ - public Progress() { - this(null); - } - - /** - * Create a new default {@link Progress}, from 0 to 100. - * - * @param name - * the name of this {@link Progress} step - */ - public Progress(String name) { - this(name, 0, 100); - } - - /** - * Create a new unnamed {@link Progress}, from min to max. - * - * @param min - * the minimum progress value (and starting value) -- must be - * non-negative - * @param max - * the maximum progress value - */ - public Progress(int min, int max) { - this(null, min, max); - } - - /** - * Create a new {@link Progress}, from min to max. - * - * @param name - * the name of this {@link Progress} step - * @param min - * the minimum progress value (and starting value) -- must be - * non-negative - * @param max - * the maximum progress value - */ - public Progress(String name, int min, int max) { - this.name = name; - this.children = new HashMap(); - this.listeners = new ArrayList(); - setMinMax(min, max); - setProgress(min); - } - - /** - * The name of this {@link Progress} step. - * - * @return the name, can be NULL - */ - public String getName() { - return name; - } - - /** - * The name of this {@link Progress} step. - * - * @param name - * the new name - */ - public void setName(String name) { - this.name = name; - changed(this, name); - } - - /** - * The minimum progress value. - * - * @return the min - */ - public int getMin() { - return min; - } - - /** - * The minimum progress value. - * - * @param min - * the min to set - * - * - * @throws RuntimeException - * if min < 0 or if min > max - */ - public void setMin(int min) { - if (min < 0) { - throw new RuntimeException("negative values not supported"); - } - - synchronized (lock) { - if (min > max) { - throw new RuntimeException( - "The minimum progress value must be <= the maximum progress value"); - } - - this.min = min; - } - } - - /** - * The maximum progress value. - * - * @return the max - */ - public int getMax() { - return max; - } - - /** - * The maximum progress value (must be >= the minimum progress value). - * - * @param max - * the max to set - * - * - * @throws RuntimeException - * if max < min - */ - public void setMax(int max) { - synchronized (lock) { - if (max < min) { - throw new Error( - "The maximum progress value must be >= the minimum progress value"); - } - - this.max = max; - } - } - - /** - * Set both the minimum and maximum progress values. - * - * @param min - * the min - * @param max - * the max - * - * @throws RuntimeException - * if min < 0 or if min > max - */ - public void setMinMax(int min, int max) { - if (min < 0) { - throw new RuntimeException("negative values not supported"); - } - - if (min > max) { - throw new RuntimeException( - "The minimum progress value must be <= the maximum progress value"); - } - - synchronized (lock) { - this.min = min; - this.max = max; - } - } - - /** - * Get the total progress value (including the optional children - * {@link Progress}) on a {@link Progress#getMin()} to - * {@link Progress#getMax()} scale. - * - * @return the progress the value - */ - public int getProgress() { - return (int) Math.round(relativeProgress * (max - min)); - } - - /** - * Set the local progress value (not including the optional children - * {@link Progress}), on a {@link Progress#getMin()} to - * {@link Progress#getMax()} scale. - * - * @param progress - * the progress to set - */ - public void setProgress(int progress) { - synchronized (lock) { - double childrenProgress = relativeProgress - relativeLocalProgress; - - relativeLocalProgress = ((double) progress) / (max - min); - - setRelativeProgress(this, name, - relativeLocalProgress + childrenProgress); - } - } - - /** - * Get the total progress value (including the optional children - * {@link Progress}) on a 0.0 to 1.0 scale. - * - * @return the progress - */ - public double getRelativeProgress() { - return relativeProgress; - } - - /** - * Set the total progress value (including the optional children - * {@link Progress}), on a 0 to 1 scale. - *

- * Will generate a changed event from this very {@link Progress}. - * - * @param relativeProgress - * the progress to set - */ - public void setRelativeProgress(double relativeProgress) { - setRelativeProgress(this, name, relativeProgress); - } - - /** - * Set the total progress value (including the optional children - * {@link Progress}), on a 0 to 1 scale. - * - * @param pg - * the {@link Progress} to report as the progression emitter (can - * be NULL, will then be considered the same as this) - * @param name - * the current name (if it is NULL, the first non-null name in - * the hierarchy will overwrite it) of the {@link Progress} who - * emitted this change - * @param relativeProgress - * the progress to set - */ - private void setRelativeProgress(Progress pg, String name, - double relativeProgress) { - synchronized (lock) { - relativeProgress = Math.max(0, relativeProgress); - relativeProgress = Math.min(1, relativeProgress); - this.relativeProgress = relativeProgress; - - changed(pg, name); - } - } - - /** - * Get the total progress value (including the optional children - * {@link Progress}) on a 0 to 1 scale. - * - * @return the progress the value - */ - private int getLocalProgress() { - return (int) Math.round(relativeLocalProgress * (max - min)); - } - - /** - * Add some value to the current progression of this {@link Progress}. - * - * @param step - * the amount to add - */ - public void add(int step) { - synchronized (lock) { - setProgress(getLocalProgress() + step); - } - } - - /** - * Check if the action corresponding to this {@link Progress} is done (i.e., - * if its progress value == its max value). - * - * @return TRUE if it is - */ - public boolean isDone() { - return getProgress() == max; - } - - /** - * Mark the {@link Progress} as done by setting its value to max. - */ - public void done() { - synchronized (lock) { - double childrenProgress = relativeProgress - relativeLocalProgress; - relativeLocalProgress = 1 - childrenProgress; - setRelativeProgress(this, name, 1d); - } - } - - /** - * Return the list of direct children of this {@link Progress}. - *

- * Can return an empty list, but never NULL. - * - * @return the children (Who will think of the children??) - */ - public List getChildren() { - synchronized (lock) { - return new ArrayList(children.keySet()); - } - } - - /** - * The weight of this children if it is actually a child of this. - * - * @param child - * the child to get the weight of - * - * @return NULL if this is not a child of this - */ - public Double getWeight(Progress child) { - synchronized (lock) { - return children.get(child); - } - } - - /** - * Notify the listeners that this {@link Progress} changed value. - * - * @param pg - * the emmiter, that is, the (sub-){link Progress} that just - * reported some change, not always the same as this - * @param name - * the current name (if it is NULL, the first non-null name in - * the hierarchy will overwrite it) of the {@link Progress} who - * emitted this change - */ - private void changed(Progress pg, String name) { - if (pg == null) { - pg = this; - } - - if (name == null) { - name = this.name; - } - - synchronized (lock) { - for (ProgressListener l : listeners) { - l.progress(pg, name); - } - } - } - - /** - * Add a {@link ProgressListener} that will trigger on progress changes. - *

- * Note: the {@link Progress} that will be reported will be the active - * progress, not necessarily the same as the current one (it could be a - * child {@link Progress} of this {@link Progress}). - * - * @param l - * the listener - */ - public void addProgressListener(ProgressListener l) { - synchronized (lock) { - this.listeners.add(l); - } - } - - /** - * Remove a {@link ProgressListener} that would trigger on progress changes. - * - * @param l - * the listener - * - * @return TRUE if it was found (and removed) - */ - public boolean removeProgressListener(ProgressListener l) { - synchronized (lock) { - return this.listeners.remove(l); - } - } - - /** - * Add a child {@link Progress} of the given weight. - * - * @param progress - * the child {@link Progress} to add - * @param weight - * the weight (on a {@link Progress#getMin()} to - * {@link Progress#getMax()} scale) of this child - * {@link Progress} in relation to its parent - * - * @throws RuntimeException - * if weight exceed {@link Progress#getMax()} or if progress - * already has a parent - */ - public void addProgress(Progress progress, double weight) { - if (weight < min || weight > max) { - throw new RuntimeException(String.format( - "Progress object %s cannot have a weight of %f, " - + "it is outside of its parent (%s) range (%d)", - progress.name, weight, name, max)); - } - - if (progress.parent != null) { - throw new RuntimeException(String.format( - "Progress object %s cannot be added to %s, " - + "as it already has a parent (%s)", - progress.name, name, progress.parent.name)); - } - - ProgressListener progressListener = new ProgressListener() { - @Override - public void progress(Progress pg, String name) { - synchronized (lock) { - double total = relativeLocalProgress; - for (Entry entry : children.entrySet()) { - total += (entry.getValue() / (max - min)) - * entry.getKey().getRelativeProgress(); - } - - setRelativeProgress(pg, name, total); - } - } - }; - - synchronized (lock) { - // Should not happen but just in case - if (this.map != progress.map) { - this.map.putAll(progress.map); - } - progress.map = this.map; - progress.parent = this; - this.children.put(progress, weight); - progress.addProgressListener(progressListener); - } - } - - /** - * Set the given value for the given key on this {@link Progress} and it's - * children. - * - * @param key - * the key - * @param value - * the value - */ - public void put(Object key, Object value) { - map.put(key, value); - } - - /** - * Return the value associated with this key as a {@link String} if any, - * NULL if not. - *

- * If the value is not NULL but not a {@link String}, it will be converted - * via {@link Object#toString()}. - * - * @param key - * the key to check - * - * @return the value or NULL - */ - public String getString(Object key) { - Object value = map.get(key); - if (value == null) { - return null; - } - - return value.toString(); - } - - /** - * Return the value associated with this key if any, NULL if not. - * - * @param key - * the key to check - * - * @return the value or NULL - */ - public Object get(Object key) { - return map.get(key); - } - - @Override - public String toString() { - return "[Progress]" // - + (name == null || name.isEmpty() ? "" : " " + name) // - + ": " + getProgress() + " / " + getMax() // - + (children.isEmpty() ? "" - : " (with " + children.size() + " children)") // - ; - } -} diff --git a/src/be/nikiroo/utils/Proxy.java b/src/be/nikiroo/utils/Proxy.java deleted file mode 100644 index 750b3ee..0000000 --- a/src/be/nikiroo/utils/Proxy.java +++ /dev/null @@ -1,150 +0,0 @@ -package be.nikiroo.utils; - -import java.net.Authenticator; -import java.net.PasswordAuthentication; - -/** - * Simple proxy helper to select a default internet proxy. - * - * @author niki - */ -public class Proxy { - /** - * Use the proxy described by this string: - *

    - *
  • ((user(:pass)@)proxy:port)
  • - *
  • System proxy is noted :
  • - *
- * Some examples: - *
    - *
  • → do not use any proxy
  • - *
  • : → use the system proxy
  • - *
  • user@prox.com → use the proxy "prox.com" with default port - * and user "user"
  • - *
  • prox.com:8080 → use the proxy "prox.com" on port 8080
  • - *
  • user:pass@prox.com:8080 → use "prox.com" on port 8080 - * authenticated as "user" with password "pass"
  • - *
  • user:pass@: → use the system proxy authenticated as user - * "user" with password "pass"
  • - *
- * - * @param proxy - * the proxy - */ - static public void use(String proxy) { - if (proxy != null && !proxy.isEmpty()) { - String user = null; - String password = null; - int port = 8080; - - if (proxy.contains("@")) { - int pos = proxy.indexOf("@"); - user = proxy.substring(0, pos); - proxy = proxy.substring(pos + 1); - if (user.contains(":")) { - pos = user.indexOf(":"); - password = user.substring(pos + 1); - user = user.substring(0, pos); - } - } - - if (proxy.equals(":")) { - proxy = null; - } else if (proxy.contains(":")) { - int pos = proxy.indexOf(":"); - try { - port = Integer.parseInt(proxy.substring(0, pos)); - proxy = proxy.substring(pos + 1); - } catch (Exception e) { - } - } - - if (proxy == null) { - Proxy.useSystemProxy(user, password); - } else { - Proxy.useProxy(proxy, port, user, password); - } - } - } - - /** - * Use the system proxy. - */ - static public void useSystemProxy() { - useSystemProxy(null, null); - } - - /** - * Use the system proxy with the given login/password, for authenticated - * proxies. - * - * @param user - * the user name or login - * @param password - * the password - */ - static public void useSystemProxy(String user, String password) { - System.setProperty("java.net.useSystemProxies", "true"); - auth(user, password); - } - - /** - * Use the give proxy. - * - * @param host - * the proxy host name or IP address - * @param port - * the port to use - */ - static public void useProxy(String host, int port) { - useProxy(host, port, null, null); - } - - /** - * Use the given proxy with the given login/password, for authenticated - * proxies. - * - * @param user - * the user name or login - * @param password - * the password - * @param host - * the proxy host name or IP address - * @param port - * the port to use - * @param user - * the user name or login - * @param password - * the password - */ - static public void useProxy(String host, int port, String user, - String password) { - System.setProperty("http.proxyHost", host); - System.setProperty("http.proxyPort", Integer.toString(port)); - auth(user, password); - } - - /** - * Select the default authenticator for proxy requests. - * - * @param user - * the user name or login - * @param password - * the password - */ - static private void auth(final String user, final String password) { - if (user != null && password != null) { - Authenticator proxy = new Authenticator() { - @Override - protected PasswordAuthentication getPasswordAuthentication() { - if (getRequestorType() == RequestorType.PROXY) { - return new PasswordAuthentication(user, - password.toCharArray()); - } - return null; - } - }; - Authenticator.setDefault(proxy); - } - } -} diff --git a/src/be/nikiroo/utils/StringJustifier.java b/src/be/nikiroo/utils/StringJustifier.java deleted file mode 100644 index ed20291..0000000 --- a/src/be/nikiroo/utils/StringJustifier.java +++ /dev/null @@ -1,286 +0,0 @@ -/* - * This file was taken from: - * Jexer - Java Text User Interface - * - * The MIT License (MIT) - * - * Copyright (C) 2017 Kevin Lamonte - * - * 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. - * - * @author Kevin Lamonte [kevin.lamonte@gmail.com] - * @version 1 - * - * I added some changes to integrate it here. - * @author Niki - */ -package be.nikiroo.utils; - -import java.util.LinkedList; -import java.util.List; - -/** - * StringJustifier contains methods to convert one or more long lines of strings - * into justified text paragraphs. - */ -class StringJustifier { - /** - * Process the given text into a list of left-justified lines of a given - * max-width. - * - * @param data - * the text to justify - * @param width - * the maximum width of a line - * - * @return the list of justified lines - */ - static List left(final String data, final int width) { - return left(data, width, false); - } - - /** - * Right-justify a string into a list of lines. - * - * @param str - * the string - * @param n - * the maximum number of characters in a line - * @return the list of lines - */ - static List right(final String str, final int n) { - List result = new LinkedList(); - - /* - * Same as left(), but preceed each line with spaces to make it n chars - * long. - */ - List lines = left(str, n); - for (String line : lines) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < n - line.length(); i++) { - sb.append(' '); - } - sb.append(line); - result.add(sb.toString()); - } - - return result; - } - - /** - * Center a string into a list of lines. - * - * @param str - * the string - * @param n - * the maximum number of characters in a line - * @return the list of lines - */ - static List center(final String str, final int n) { - List result = new LinkedList(); - - /* - * Same as left(), but preceed/succeed each line with spaces to make it - * n chars long. - */ - List lines = left(str, n); - for (String line : lines) { - StringBuilder sb = new StringBuilder(); - int l = (n - line.length()) / 2; - int r = n - line.length() - l; - for (int i = 0; i < l; i++) { - sb.append(' '); - } - sb.append(line); - for (int i = 0; i < r; i++) { - sb.append(' '); - } - result.add(sb.toString()); - } - - return result; - } - - /** - * Fully-justify a string into a list of lines. - * - * @param str - * the string - * @param n - * the maximum number of characters in a line - * @return the list of lines - */ - static List full(final String str, final int n) { - List result = new LinkedList(); - - /* - * Same as left(true), but insert spaces between words to make each line - * n chars long. The "algorithm" here is pretty dumb: it performs a - * split on space and then re-inserts multiples of n between words. - */ - List lines = left(str, n, true); - for (int lineI = 0; lineI < lines.size() - 1; lineI++) { - String line = lines.get(lineI); - String[] words = line.split(" "); - if (words.length > 1) { - int charCount = 0; - for (int i = 0; i < words.length; i++) { - charCount += words[i].length(); - } - int spaceCount = n - charCount; - int q = spaceCount / (words.length - 1); - int r = spaceCount % (words.length - 1); - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < words.length - 1; i++) { - sb.append(words[i]); - for (int j = 0; j < q; j++) { - sb.append(' '); - } - if (r > 0) { - sb.append(' '); - r--; - } - } - for (int j = 0; j < r; j++) { - sb.append(' '); - } - sb.append(words[words.length - 1]); - result.add(sb.toString()); - } else { - result.add(line); - } - } - if (lines.size() > 0) { - result.add(lines.get(lines.size() - 1)); - } - - return result; - } - - /** - * Process the given text into a list of left-justified lines of a given - * max-width. - * - * @param data - * the text to justify - * @param width - * the maximum width of a line - * @param minTwoWords - * use 2 words per line minimum if the text allows it - * - * @return the list of justified lines - */ - static private List left(final String data, final int width, - boolean minTwoWords) { - List lines = new LinkedList(); - - for (String dataLine : data.split("\n")) { - String line = rightTrim(dataLine.replace("\t", " ")); - - if (width > 0 && line.length() > width) { - while (line.length() > 0) { - int i = Math.min(line.length(), width - 1); // -1 for "-" - - boolean needDash = true; - // find the best space if any and if needed - int prevSpace = 0; - if (i < line.length()) { - prevSpace = -1; - int space = line.indexOf(' '); - int numOfSpaces = 0; - - while (space > -1 && space <= i) { - prevSpace = space; - space = line.indexOf(' ', space + 1); - numOfSpaces++; - } - - if (prevSpace > 0 && (!minTwoWords || numOfSpaces >= 2)) { - i = prevSpace; - needDash = false; - } - } - // - - // no dash before space/dash - if ((i + 1) < line.length()) { - char car = line.charAt(i); - char nextCar = line.charAt(i + 1); - if (car == ' ' || car == '-' || nextCar == ' ') { - needDash = false; - } else if (i > 0) { - char prevCar = line.charAt(i - 1); - if (prevCar == ' ' || prevCar == '-') { - needDash = false; - i--; - } - } - } - - // if the space freed by the removed dash allows it, or if - // it is the last char, add the next char - if (!needDash || i >= line.length() - 1) { - int checkI = Math.min(i + 1, line.length()); - if (checkI == i || checkI <= width) { - needDash = false; - i = checkI; - } - } - - // no dash before parenthesis (but cannot add one more - // after) - if ((i + 1) < line.length()) { - char nextCar = line.charAt(i + 1); - if (nextCar == '(' || nextCar == ')') { - needDash = false; - } - } - - if (needDash) { - lines.add(rightTrim(line.substring(0, i)) + "-"); - } else { - lines.add(rightTrim(line.substring(0, i))); - } - - // full trim (remove spaces when cutting) - line = line.substring(i).trim(); - } - } else { - lines.add(line); - } - } - - return lines; - } - - /** - * Trim the given {@link String} on the right only. - * - * @param data - * the source {@link String} - * @return the right-trimmed String or Empty if it was NULL - */ - static private String rightTrim(String data) { - if (data == null) - return ""; - - return ("|" + data).trim().substring(1); - } -} diff --git a/src/be/nikiroo/utils/StringUtils.java b/src/be/nikiroo/utils/StringUtils.java deleted file mode 100644 index be1c654..0000000 --- a/src/be/nikiroo/utils/StringUtils.java +++ /dev/null @@ -1,1165 +0,0 @@ -package be.nikiroo.utils; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.text.Normalizer; -import java.text.Normalizer.Form; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.List; -import java.util.Map.Entry; -import java.util.regex.Pattern; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; - -import org.unbescape.html.HtmlEscape; -import org.unbescape.html.HtmlEscapeLevel; -import org.unbescape.html.HtmlEscapeType; - -import be.nikiroo.utils.streams.Base64InputStream; -import be.nikiroo.utils.streams.Base64OutputStream; - -/** - * This class offer some utilities based around {@link String}s. - * - * @author niki - */ -public class StringUtils { - /** - * This enum type will decide the alignment of a {@link String} when padding - * or justification is applied (if there is enough horizontal space for it - * to be aligned). - */ - public enum Alignment { - /** Aligned at left. */ - LEFT, - /** Centered. */ - CENTER, - /** Aligned at right. */ - RIGHT, - /** Full justified (to both left and right). */ - JUSTIFY, - - // Old Deprecated values: - - /** DEPRECATED: please use LEFT. */ - @Deprecated - Beginning, - /** DEPRECATED: please use CENTER. */ - @Deprecated - Center, - /** DEPRECATED: please use RIGHT. */ - @Deprecated - End; - - /** - * Return the non-deprecated version of this enum if needed (or return - * self if not). - * - * @return the non-deprecated value - */ - Alignment undeprecate() { - if (this == Beginning) - return LEFT; - if (this == Center) - return CENTER; - if (this == End) - return RIGHT; - return this; - } - } - - static private Pattern marks = getMarks(); - - /** - * Fix the size of the given {@link String} either with space-padding or by - * shortening it. - * - * @param text - * the {@link String} to fix - * @param width - * the size of the resulting {@link String} or -1 for a noop - * - * @return the resulting {@link String} of size size - */ - static public String padString(String text, int width) { - return padString(text, width, true, null); - } - - /** - * Fix the size of the given {@link String} either with space-padding or by - * optionally shortening it. - * - * @param text - * the {@link String} to fix - * @param width - * the size of the resulting {@link String} if the text fits or - * if cut is TRUE or -1 for a noop - * @param cut - * cut the {@link String} shorter if needed - * @param align - * align the {@link String} in this position if we have enough - * space (default is Alignment.Beginning) - * - * @return the resulting {@link String} of size size minimum - */ - static public String padString(String text, int width, boolean cut, - Alignment align) { - - if (align == null) { - align = Alignment.LEFT; - } - - align = align.undeprecate(); - - if (width >= 0) { - if (text == null) - text = ""; - - int diff = width - text.length(); - - if (diff < 0) { - if (cut) - text = text.substring(0, width); - } else if (diff > 0) { - if (diff < 2 && align != Alignment.RIGHT) - align = Alignment.LEFT; - - switch (align) { - case RIGHT: - text = new String(new char[diff]).replace('\0', ' ') + text; - break; - case CENTER: - int pad1 = (diff) / 2; - int pad2 = (diff + 1) / 2; - text = new String(new char[pad1]).replace('\0', ' ') + text - + new String(new char[pad2]).replace('\0', ' '); - break; - case LEFT: - default: - text = text + new String(new char[diff]).replace('\0', ' '); - break; - } - } - } - - return text; - } - - /** - * Justify a text into width-sized (at the maximum) lines and return all the - * lines concatenated into a single '\\n'-separated line of text. - * - * @param text - * the {@link String} to justify - * @param width - * the maximum size of the resulting lines - * - * @return a list of justified text lines concatenated into a single - * '\\n'-separated line of text - */ - static public String justifyTexts(String text, int width) { - StringBuilder builder = new StringBuilder(); - for (String line : justifyText(text, width, null)) { - if (builder.length() > 0) { - builder.append('\n'); - } - builder.append(line); - } - - return builder.toString(); - } - - /** - * Justify a text into width-sized (at the maximum) lines. - * - * @param text - * the {@link String} to justify - * @param width - * the maximum size of the resulting lines - * - * @return a list of justified text lines - */ - static public List justifyText(String text, int width) { - return justifyText(text, width, null); - } - - /** - * Justify a text into width-sized (at the maximum) lines. - * - * @param text - * the {@link String} to justify - * @param width - * the maximum size of the resulting lines - * @param align - * align the lines in this position (default is - * Alignment.Beginning) - * - * @return a list of justified text lines - */ - static public List justifyText(String text, int width, - Alignment align) { - if (align == null) { - align = Alignment.LEFT; - } - - align = align.undeprecate(); - - switch (align) { - case CENTER: - return StringJustifier.center(text, width); - case RIGHT: - return StringJustifier.right(text, width); - case JUSTIFY: - return StringJustifier.full(text, width); - case LEFT: - default: - return StringJustifier.left(text, width); - } - } - - /** - * Justify a text into width-sized (at the maximum) lines. - * - * @param text - * the {@link String} to justify - * @param width - * the maximum size of the resulting lines - * - * @return a list of justified text lines - */ - static public List justifyText(List text, int width) { - return justifyText(text, width, null); - } - - /** - * Justify a text into width-sized (at the maximum) lines. - * - * @param text - * the {@link String} to justify - * @param width - * the maximum size of the resulting lines - * @param align - * align the lines in this position (default is - * Alignment.Beginning) - * - * @return a list of justified text lines - */ - static public List justifyText(List text, int width, - Alignment align) { - List result = new ArrayList(); - - // Content <-> Bullet spacing (null = no spacing) - List> lines = new ArrayList>(); - StringBuilder previous = null; - StringBuilder tmp = new StringBuilder(); - String previousItemBulletSpacing = null; - String itemBulletSpacing = null; - for (String inputLine : text) { - boolean previousLineComplete = true; - - String current = inputLine.replace("\t", " "); - itemBulletSpacing = getItemSpacing(current); - boolean bullet = isItemLine(current); - if ((previousItemBulletSpacing == null || itemBulletSpacing - .length() <= previousItemBulletSpacing.length()) && !bullet) { - itemBulletSpacing = null; - } - - if (itemBulletSpacing != null) { - current = current.trim(); - if (!current.isEmpty() && bullet) { - current = current.substring(1); - } - current = current.trim(); - previousLineComplete = bullet; - } else { - tmp.setLength(0); - for (String word : current.split(" ")) { - if (word.isEmpty()) { - continue; - } - - if (tmp.length() > 0) { - tmp.append(' '); - } - tmp.append(word.trim()); - } - current = tmp.toString(); - - previousLineComplete = current.isEmpty() - || previousItemBulletSpacing != null - || (previous != null && isFullLine(previous)) - || isHrLine(current) || isHrLine(previous); - } - - if (previous == null) { - previous = new StringBuilder(); - } else { - if (previousLineComplete) { - lines.add(new AbstractMap.SimpleEntry( - previous.toString(), previousItemBulletSpacing)); - previous.setLength(0); - previousItemBulletSpacing = itemBulletSpacing; - } else { - previous.append(' '); - } - } - - previous.append(current); - - } - - if (previous != null) { - lines.add(new AbstractMap.SimpleEntry(previous - .toString(), previousItemBulletSpacing)); - } - - for (Entry line : lines) { - String content = line.getKey(); - String spacing = line.getValue(); - - String bullet = "- "; - if (spacing == null) { - bullet = ""; - spacing = ""; - } - - if (spacing.length() > width + 3) { - spacing = ""; - } - - for (String subline : StringUtils.justifyText(content, width - - (spacing.length() + bullet.length()), align)) { - result.add(spacing + bullet + subline); - if (!bullet.isEmpty()) { - bullet = " "; - } - } - } - - return result; - } - - /** - * Sanitise the given input to make it more Terminal-friendly by removing - * combining characters. - * - * @param input - * the input to sanitise - * @param allowUnicode - * allow Unicode or only allow ASCII Latin characters - * - * @return the sanitised {@link String} - */ - static public String sanitize(String input, boolean allowUnicode) { - return sanitize(input, allowUnicode, !allowUnicode); - } - - /** - * Sanitise the given input to make it more Terminal-friendly by removing - * combining characters. - * - * @param input - * the input to sanitise - * @param allowUnicode - * allow Unicode or only allow ASCII Latin characters - * @param removeAllAccents - * TRUE to replace all accentuated characters by their non - * accentuated counter-parts - * - * @return the sanitised {@link String} - */ - static public String sanitize(String input, boolean allowUnicode, - boolean removeAllAccents) { - - if (removeAllAccents) { - input = Normalizer.normalize(input, Form.NFKD); - if (marks != null) { - input = marks.matcher(input).replaceAll(""); - } - } - - input = Normalizer.normalize(input, Form.NFKC); - - if (!allowUnicode) { - StringBuilder builder = new StringBuilder(); - for (int index = 0; index < input.length(); index++) { - char car = input.charAt(index); - // displayable chars in ASCII are in the range 32<->255, - // except DEL (127) - if (car >= 32 && car <= 255 && car != 127) { - builder.append(car); - } - } - input = builder.toString(); - } - - return input; - } - - /** - * Convert between the time in milliseconds to a {@link String} in a "fixed" - * way (to exchange data over the wire, for instance). - *

- * Precise to the second. - * - * @param time - * the specified number of milliseconds since the standard base - * time known as "the epoch", namely January 1, 1970, 00:00:00 - * GMT - * - * @return the time as a {@link String} - */ - static public String fromTime(long time) { - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - return sdf.format(new Date(time)); - } - - /** - * Convert between the time as a {@link String} to milliseconds in a "fixed" - * way (to exchange data over the wire, for instance). - *

- * Precise to the second. - * - * @param displayTime - * the time as a {@link String} - * - * @return the number of milliseconds since the standard base time known as - * "the epoch", namely January 1, 1970, 00:00:00 GMT, or -1 in case - * of error - * - * @throws ParseException - * in case of parse error - */ - static public long toTime(String displayTime) throws ParseException { - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - return sdf.parse(displayTime).getTime(); - } - - /** - * Return a hash of the given {@link String}. - * - * @param input - * the input data - * - * @return the hash - * - * @deprecated please use {@link HashUtils} - */ - @Deprecated - static public String getMd5Hash(String input) { - try { - MessageDigest md = MessageDigest.getInstance("MD5"); - md.update(getBytes(input)); - byte byteData[] = md.digest(); - - StringBuffer hexString = new StringBuffer(); - for (int i = 0; i < byteData.length; i++) { - String hex = Integer.toHexString(0xff & byteData[i]); - if (hex.length() == 1) - hexString.append('0'); - hexString.append(hex); - } - - return hexString.toString(); - } catch (NoSuchAlgorithmException e) { - return input; - } - } - - /** - * Remove the HTML content from the given input, and un-html-ize the rest. - * - * @param html - * the HTML-encoded content - * - * @return the HTML-free equivalent content - */ - public static String unhtml(String html) { - StringBuilder builder = new StringBuilder(); - - int inTag = 0; - for (char car : html.toCharArray()) { - if (car == '<') { - inTag++; - } else if (car == '>') { - inTag--; - } else if (inTag <= 0) { - builder.append(car); - } - } - - char nbsp = ' '; // non-breakable space (a special char) - char space = ' '; - return HtmlEscape.unescapeHtml(builder.toString()).replace(nbsp, space); - } - - /** - * Escape the given {@link String} so it can be used in XML, as content. - * - * @param input - * the input {@link String} - * - * @return the escaped {@link String} - */ - public static String xmlEscape(String input) { - if (input == null) { - return ""; - } - - return HtmlEscape.escapeHtml(input, - HtmlEscapeType.HTML4_NAMED_REFERENCES_DEFAULT_TO_HEXA, - HtmlEscapeLevel.LEVEL_1_ONLY_MARKUP_SIGNIFICANT); - } - - /** - * Escape the given {@link String} so it can be used in XML, as text content - * inside double-quotes. - * - * @param input - * the input {@link String} - * - * @return the escaped {@link String} - */ - public static String xmlEscapeQuote(String input) { - if (input == null) { - return ""; - } - - return HtmlEscape.escapeHtml(input, - HtmlEscapeType.HTML4_NAMED_REFERENCES_DEFAULT_TO_HEXA, - HtmlEscapeLevel.LEVEL_1_ONLY_MARKUP_SIGNIFICANT); - } - - /** - * Zip the data and then encode it into Base64. - * - * @param data - * the data - * - * @return the Base64 zipped version - * - * @throws IOException - * in case of I/O error - */ - public static String zip64(String data) throws IOException { - try { - return zip64(getBytes(data)); - } catch (UnsupportedEncodingException e) { - // All conforming JVM are required to support UTF-8 - e.printStackTrace(); - return null; - } - } - - /** - * Zip the data and then encode it into Base64. - * - * @param data - * the data - * - * @return the Base64 zipped version - * - * @throws IOException - * in case of I/O error - */ - public static String zip64(byte[] data) throws IOException { - // 1. compress - ByteArrayOutputStream bout = new ByteArrayOutputStream(); - try { - OutputStream out = new GZIPOutputStream(bout); - try { - out.write(data); - } finally { - out.close(); - } - } finally { - data = bout.toByteArray(); - bout.close(); - } - - // 2. base64 - InputStream in = new ByteArrayInputStream(data); - try { - in = new Base64InputStream(in, true); - return new String(IOUtils.toByteArray(in), "UTF-8"); - } finally { - in.close(); - } - } - - /** - * Unconvert from Base64 then unzip the content, which is assumed to be a - * String. - * - * @param data - * the data in Base64 format - * - * @return the raw data - * - * @throws IOException - * in case of I/O error - */ - public static String unzip64s(String data) throws IOException { - return new String(unzip64(data), "UTF-8"); - } - - /** - * Unconvert from Base64 then unzip the content. - * - * @param data - * the data in Base64 format - * - * @return the raw data - * - * @throws IOException - * in case of I/O error - */ - public static byte[] unzip64(String data) throws IOException { - InputStream in = new Base64InputStream(new ByteArrayInputStream( - getBytes(data)), false); - try { - in = new GZIPInputStream(in); - return IOUtils.toByteArray(in); - } finally { - in.close(); - } - } - - /** - * Convert the given data to Base64 format. - * - * @param data - * the data to convert - * - * @return the Base64 {@link String} representation of the data - * - * @throws IOException - * in case of I/O errors - */ - public static String base64(String data) throws IOException { - return base64(getBytes(data)); - } - - /** - * Convert the given data to Base64 format. - * - * @param data - * the data to convert - * - * @return the Base64 {@link String} representation of the data - * - * @throws IOException - * in case of I/O errors - */ - public static String base64(byte[] data) throws IOException { - Base64InputStream in = new Base64InputStream(new ByteArrayInputStream( - data), true); - try { - return new String(IOUtils.toByteArray(in), "UTF-8"); - } finally { - in.close(); - } - } - - /** - * Unconvert the given data from Base64 format back to a raw array of bytes. - * - * @param data - * the data to unconvert - * - * @return the raw data represented by the given Base64 {@link String}, - * - * @throws IOException - * in case of I/O errors - */ - public static byte[] unbase64(String data) throws IOException { - Base64InputStream in = new Base64InputStream(new ByteArrayInputStream( - getBytes(data)), false); - try { - return IOUtils.toByteArray(in); - } finally { - in.close(); - } - } - - /** - * Unonvert the given data from Base64 format back to a {@link String}. - * - * @param data - * the data to unconvert - * - * @return the {@link String} represented by the given Base64 {@link String} - * - * @throws IOException - * in case of I/O errors - */ - public static String unbase64s(String data) throws IOException { - return new String(unbase64(data), "UTF-8"); - } - - /** - * Return a display {@link String} for the given value, which can be - * suffixed with "k" or "M" depending upon the number, if it is big enough. - *

- *

- * Examples: - *

    - *
  • 8 765 becomes "8 k"
  • - *
  • 998 765 becomes "998 k"
  • - *
  • 12 987 364 becomes "12 M"
  • - *
  • 5 534 333 221 becomes "5 G"
  • - *
- * - * @param value - * the value to convert - * - * @return the display value - */ - public static String formatNumber(long value) { - return formatNumber(value, 0); - } - - /** - * Return a display {@link String} for the given value, which can be - * suffixed with "k" or "M" depending upon the number, if it is big enough. - *

- * Examples (assuming decimalPositions = 1): - *

    - *
  • 8 765 becomes "8.7 k"
  • - *
  • 998 765 becomes "998.7 k"
  • - *
  • 12 987 364 becomes "12.9 M"
  • - *
  • 5 534 333 221 becomes "5.5 G"
  • - *
- * - * @param value - * the value to convert - * @param decimalPositions - * the number of decimal positions to keep - * - * @return the display value - */ - public static String formatNumber(long value, int decimalPositions) { - long userValue = value; - String suffix = " "; - long mult = 1; - - if (value >= 1000000000l) { - mult = 1000000000l; - userValue = value / 1000000000l; - suffix = " G"; - } else if (value >= 1000000l) { - mult = 1000000l; - userValue = value / 1000000l; - suffix = " M"; - } else if (value >= 1000l) { - mult = 1000l; - userValue = value / 1000l; - suffix = " k"; - } - - String deci = ""; - if (decimalPositions > 0) { - deci = Long.toString(value % mult); - int size = Long.toString(mult).length() - 1; - while (deci.length() < size) { - deci = "0" + deci; - } - - deci = deci.substring(0, Math.min(decimalPositions, deci.length())); - while (deci.length() < decimalPositions) { - deci += "0"; - } - - deci = "." + deci; - } - - return Long.toString(userValue) + deci + suffix; - } - - /** - * The reverse operation to {@link StringUtils#formatNumber(long)}: it will - * read a "display" number that can contain a "M" or "k" suffix and return - * the full value. - *

- * Of course, the conversion to and from display form is lossy (example: - * 6870 to "6.5k" to 6500). - * - * @param value - * the value in display form with possible "M" and "k" suffixes, - * can be NULL - * - * @return the value as a number, or 0 if not possible to convert - */ - public static long toNumber(String value) { - return toNumber(value, 0l); - } - - /** - * The reverse operation to {@link StringUtils#formatNumber(long)}: it will - * read a "display" number that can contain a "M" or "k" suffix and return - * the full value. - *

- * Of course, the conversion to and from display form is lossy (example: - * 6870 to "6.5k" to 6500). - * - * @param value - * the value in display form with possible "M" and "k" suffixes, - * can be NULL - * @param def - * the default value if it is not possible to convert the given - * value to a number - * - * @return the value as a number, or 0 if not possible to convert - */ - public static long toNumber(String value, long def) { - long count = def; - if (value != null) { - value = value.trim().toLowerCase(); - try { - long mult = 1; - if (value.endsWith("g")) { - value = value.substring(0, value.length() - 1).trim(); - mult = 1000000000; - } else if (value.endsWith("m")) { - value = value.substring(0, value.length() - 1).trim(); - mult = 1000000; - } else if (value.endsWith("k")) { - value = value.substring(0, value.length() - 1).trim(); - mult = 1000; - } - - long deci = 0; - if (value.contains(".")) { - String[] tab = value.split("\\."); - if (tab.length != 2) { - throw new NumberFormatException(value); - } - double decimal = Double.parseDouble("0." - + tab[tab.length - 1]); - deci = ((long) (mult * decimal)); - value = tab[0]; - } - count = mult * Long.parseLong(value) + deci; - } catch (Exception e) { - } - } - - return count; - } - - /** - * Return the bytes array representation of the given {@link String} in - * UTF-8. - * - * @param str - * the {@link String} to transform into bytes - * @return the content in bytes - */ - static public byte[] getBytes(String str) { - try { - return str.getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - // All conforming JVM must support UTF-8 - e.printStackTrace(); - return null; - } - } - - /** - * The "remove accents" pattern. - * - * @return the pattern, or NULL if a problem happens - */ - private static Pattern getMarks() { - try { - return Pattern - .compile("[\\p{InCombiningDiacriticalMarks}\\p{IsLm}\\p{IsSk}]+"); - } catch (Exception e) { - // Can fail on Android... - return null; - } - } - - // - // justify List related: - // - - /** - * Check if this line ends as a complete line (ends with a "." or similar). - *

- * Note that we consider an empty line as full, and a line ending with - * spaces as not complete. - * - * @param line - * the line to check - * - * @return TRUE if it does - */ - static private boolean isFullLine(StringBuilder line) { - if (line.length() == 0) { - return true; - } - - char lastCar = line.charAt(line.length() - 1); - switch (lastCar) { - case '.': // points - case '?': - case '!': - - case '\'': // quotes - case '‘': - case '’': - - case '"': // double quotes - case '”': - case '“': - case '»': - case '«': - return true; - default: - return false; - } - } - - /** - * Check if this line represent an item in a list or description (i.e., - * check that the first non-space char is "-"). - * - * @param line - * the line to check - * - * @return TRUE if it is - */ - static private boolean isItemLine(String line) { - String spacing = getItemSpacing(line); - return spacing != null && !spacing.isEmpty() - && line.charAt(spacing.length()) == '-'; - } - - /** - * Return all the spaces that start this line (or Empty if none). - * - * @param line - * the line to get the starting spaces from - * - * @return the left spacing - */ - static private String getItemSpacing(String line) { - int i; - for (i = 0; i < line.length(); i++) { - if (line.charAt(i) != ' ') { - return line.substring(0, i); - } - } - - return ""; - } - - /** - * This line is an horizontal spacer line. - * - * @param line - * the line to test - * - * @return TRUE if it is - */ - static private boolean isHrLine(CharSequence line) { - int count = 0; - if (line != null) { - for (int i = 0; i < line.length(); i++) { - char car = line.charAt(i); - if (car == ' ' || car == '\t' || car == '*' || car == '-' - || car == '_' || car == '~' || car == '=' || car == '/' - || car == '\\') { - count++; - } else { - return false; - } - } - } - - return count > 2; - } - - // Deprecated functions, please do not use // - - /** - * @deprecated please use {@link StringUtils#zip64(byte[])} or - * {@link StringUtils#base64(byte[])} instead. - * - * @param data - * the data to encode - * @param zip - * TRUE to zip it before Base64 encoding it, FALSE for Base64 - * encoding only - * - * @return the encoded data - * - * @throws IOException - * in case of I/O error - */ - @Deprecated - public static String base64(String data, boolean zip) throws IOException { - return base64(getBytes(data), zip); - } - - /** - * @deprecated please use {@link StringUtils#zip64(String)} or - * {@link StringUtils#base64(String)} instead. - * - * @param data - * the data to encode - * @param zip - * TRUE to zip it before Base64 encoding it, FALSE for Base64 - * encoding only - * - * @return the encoded data - * - * @throws IOException - * in case of I/O error - */ - @Deprecated - public static String base64(byte[] data, boolean zip) throws IOException { - if (zip) { - return zip64(data); - } - - Base64InputStream b64 = new Base64InputStream(new ByteArrayInputStream( - data), true); - try { - return IOUtils.readSmallStream(b64); - } finally { - b64.close(); - } - } - - /** - * @deprecated please use {@link Base64OutputStream} and - * {@link GZIPOutputStream} instead. - * - * @param breakLines - * NOT USED ANYMORE, it is always considered FALSE now - */ - @Deprecated - public static OutputStream base64(OutputStream data, boolean zip, - boolean breakLines) throws IOException { - OutputStream out = new Base64OutputStream(data); - if (zip) { - out = new java.util.zip.GZIPOutputStream(out); - } - - return out; - } - - /** - * Unconvert the given data from Base64 format back to a raw array of bytes. - *

- * Will automatically detect zipped data and also uncompress it before - * returning, unless ZIP is false. - * - * @deprecated DO NOT USE ANYMORE (bad perf, will be dropped) - * - * @param data - * the data to unconvert - * @param zip - * TRUE to also uncompress the data from a GZIP format - * automatically; if set to FALSE, zipped data can be returned - * - * @return the raw data represented by the given Base64 {@link String}, - * optionally compressed with GZIP - * - * @throws IOException - * in case of I/O errors - */ - @Deprecated - public static byte[] unbase64(String data, boolean zip) throws IOException { - byte[] buffer = unbase64(data); - if (!zip) { - return buffer; - } - - try { - GZIPInputStream zipped = new GZIPInputStream( - new ByteArrayInputStream(buffer)); - try { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - try { - IOUtils.write(zipped, out); - return out.toByteArray(); - } finally { - out.close(); - } - } finally { - zipped.close(); - } - } catch (Exception e) { - return buffer; - } - } - - /** - * Unconvert the given data from Base64 format back to a raw array of bytes. - *

- * Will automatically detect zipped data and also uncompress it before - * returning, unless ZIP is false. - * - * @deprecated DO NOT USE ANYMORE (bad perf, will be dropped) - * - * @param data - * the data to unconvert - * @param zip - * TRUE to also uncompress the data from a GZIP format - * automatically; if set to FALSE, zipped data can be returned - * - * @return the raw data represented by the given Base64 {@link String}, - * optionally compressed with GZIP - * - * @throws IOException - * in case of I/O errors - */ - @Deprecated - public static InputStream unbase64(InputStream data, boolean zip) - throws IOException { - return new ByteArrayInputStream(unbase64(IOUtils.readSmallStream(data), - zip)); - } - - /** - * @deprecated DO NOT USE ANYMORE (bad perf, will be dropped) - */ - @Deprecated - public static byte[] unbase64(byte[] data, int offset, int count, - boolean zip) throws IOException { - byte[] dataPart = Arrays.copyOfRange(data, offset, offset + count); - return unbase64(new String(dataPart, "UTF-8"), zip); - } - - /** - * @deprecated DO NOT USE ANYMORE (bad perf, will be dropped) - */ - @Deprecated - public static String unbase64s(String data, boolean zip) throws IOException { - return new String(unbase64(data, zip), "UTF-8"); - } - - /** - * @deprecated DO NOT USE ANYMORE (bad perf, will be dropped) - */ - @Deprecated - public static String unbase64s(byte[] data, int offset, int count, - boolean zip) throws IOException { - return new String(unbase64(data, offset, count, zip), "UTF-8"); - } -} diff --git a/src/be/nikiroo/utils/TempFiles.java b/src/be/nikiroo/utils/TempFiles.java deleted file mode 100644 index b54f0bc..0000000 --- a/src/be/nikiroo/utils/TempFiles.java +++ /dev/null @@ -1,187 +0,0 @@ -package be.nikiroo.utils; - -import java.io.Closeable; -import java.io.File; -import java.io.IOException; - -/** - * A small utility class to generate auto-delete temporary files in a - * centralised location. - * - * @author niki - */ -public class TempFiles implements Closeable { - /** - * Root directory of this instance, owned by it, where all temporary files - * must reside. - */ - protected File root; - - /** - * Create a new {@link TempFiles} -- each instance is separate and have a - * dedicated sub-directory in a shared temporary root. - *

- * The whole repository will be deleted on close (if you fail to call it, - * the program will try to call it on JVM termination). - * - * @param name - * the instance name (will be part of the final directory - * name) - * - * @throws IOException - * in case of I/O error - */ - public TempFiles(String name) throws IOException { - this(null, name); - } - - /** - * Create a new {@link TempFiles} -- each instance is separate and have a - * dedicated sub-directory in a given temporary root. - *

- * The whole repository will be deleted on close (if you fail to call it, - * the program will try to call it on JVM termination). - *

- * Be careful, this instance will own the given root directory, and - * will most probably delete all its files. - * - * @param base - * the root base directory to use for all the temporary files of - * this instance (if NULL, will be the default temporary - * directory of the OS) - * @param name - * the instance name (will be part of the final directory - * name) - * - * @throws IOException - * in case of I/O error - */ - public TempFiles(File base, String name) throws IOException { - if (base == null) { - base = File.createTempFile(".temp", ""); - } - - root = base; - - if (root.exists()) { - IOUtils.deltree(root, true); - } - - root = new File(root.getParentFile(), ".temp"); - root.mkdir(); - if (!root.exists()) { - throw new IOException("Cannot create root directory: " + root); - } - - root.deleteOnExit(); - - root = createTempFile(name); - IOUtils.deltree(root, true); - - root.mkdir(); - if (!root.exists()) { - throw new IOException("Cannot create root subdirectory: " + root); - } - } - - /** - * Create an auto-delete temporary file. - * - * @param name - * a base for the final filename (only a part of said - * filename) - * - * @return the newly created file - * - * @throws IOException - * in case of I/O errors - */ - public synchronized File createTempFile(String name) throws IOException { - name += "_"; - while (name.length() < 3) { - name += "_"; - } - - while (true) { - File tmp = File.createTempFile(name, ""); - IOUtils.deltree(tmp, true); - - File test = new File(root, tmp.getName()); - if (!test.exists()) { - test.createNewFile(); - if (!test.exists()) { - throw new IOException( - "Cannot create temporary file: " + test); - } - - test.deleteOnExit(); - return test; - } - } - } - - /** - * Create an auto-delete temporary directory. - *

- * Note that creating 2 temporary directories with the same name will result - * in two different directories, even if the final name is the same - * (the absolute path will be different). - * - * @param name - * the actual directory name (not path) - * - * @return the newly created file - * - * @throws IOException - * in case of I/O errors, or if the name was a path instead of a - * name - */ - public synchronized File createTempDir(String name) throws IOException { - File localRoot = createTempFile(name); - IOUtils.deltree(localRoot, true); - - localRoot.mkdir(); - if (!localRoot.exists()) { - throw new IOException("Cannot create subdirectory: " + localRoot); - } - - File dir = new File(localRoot, name); - if (!dir.getName().equals(name)) { - throw new IOException( - "Cannot create temporary directory with a path, only names are allowed: " - + dir); - } - - dir.mkdir(); - dir.deleteOnExit(); - - if (!dir.exists()) { - throw new IOException("Cannot create subdirectory: " + dir); - } - - return dir; - } - - @Override - public synchronized void close() throws IOException { - File root = this.root; - this.root = null; - - if (root != null) { - IOUtils.deltree(root); - - // Since we allocate temp directories from a base point, - // try and remove that base point - root.getParentFile().delete(); // (only works if empty) - } - } - - @Override - protected void finalize() throws Throwable { - try { - close(); - } finally { - super.finalize(); - } - } -} diff --git a/src/be/nikiroo/utils/TraceHandler.java b/src/be/nikiroo/utils/TraceHandler.java deleted file mode 100644 index 0a09712..0000000 --- a/src/be/nikiroo/utils/TraceHandler.java +++ /dev/null @@ -1,156 +0,0 @@ -package be.nikiroo.utils; - -/** - * A handler when a trace message is sent or when a recoverable exception was - * caught by the program. - * - * @author niki - */ -public class TraceHandler { - private final boolean showErrors; - private final boolean showErrorDetails; - private final int traceLevel; - private final int maxPrintSize; - - /** - * Create a default {@link TraceHandler} that will print errors on stderr - * (without details) and no traces. - */ - public TraceHandler() { - this(true, false, false); - } - - /** - * Create a default {@link TraceHandler}. - * - * @param showErrors - * show errors on stderr - * @param showErrorDetails - * show more details when printing errors - * @param showTraces - * show level 1 traces on stderr, or no traces at all - */ - public TraceHandler(boolean showErrors, boolean showErrorDetails, - boolean showTraces) { - this(showErrors, showErrorDetails, showTraces ? 1 : 0); - } - - /** - * Create a default {@link TraceHandler}. - * - * @param showErrors - * show errors on stderr - * @param showErrorDetails - * show more details when printing errors - * @param traceLevel - * show traces of this level or lower (0 means "no traces", - * higher means more traces) - */ - public TraceHandler(boolean showErrors, boolean showErrorDetails, - int traceLevel) { - this(showErrors, showErrorDetails, traceLevel, -1); - } - - /** - * Create a default {@link TraceHandler}. - * - * @param showErrors - * show errors on stderr - * @param showErrorDetails - * show more details when printing errors - * @param traceLevel - * show traces of this level or lower (0 means "no traces", - * higher means more traces) - * @param maxPrintSize - * the maximum size at which to truncate traces data (or -1 for - * "no limit") - */ - public TraceHandler(boolean showErrors, boolean showErrorDetails, - int traceLevel, int maxPrintSize) { - this.showErrors = showErrors; - this.showErrorDetails = showErrorDetails; - this.traceLevel = Math.max(traceLevel, 0); - this.maxPrintSize = maxPrintSize; - } - - /** - * The trace level of this {@link TraceHandler}. - * - * @return the level - */ - public int getTraceLevel() { - return traceLevel; - } - - /** - * An exception happened, log it. - * - * @param e - * the exception - */ - public void error(Exception e) { - if (showErrors) { - if (showErrorDetails) { - long now = System.currentTimeMillis(); - System.err.print(StringUtils.fromTime(now) + ": "); - e.printStackTrace(); - } else { - error(e.toString()); - } - } - } - - /** - * An error happened, log it. - * - * @param message - * the error message - */ - public void error(String message) { - if (showErrors) { - long now = System.currentTimeMillis(); - System.err.println(StringUtils.fromTime(now) + ": " + message); - } - } - - /** - * A trace happened, show it. - *

- * By default, will only be effective if {@link TraceHandler#traceLevel} is - * not 0. - *

- * A call to this method is equivalent to a call to - * {@link TraceHandler#trace(String, int)} with a level of 1. - * - * @param message - * the trace message - */ - public void trace(String message) { - trace(message, 1); - } - - /** - * A trace happened, show it. - *

- * By default, will only be effective if {@link TraceHandler#traceLevel} is - * not 0 and the level is lower or equal to it. - * - * @param message - * the trace message - * @param level - * the trace level - */ - public void trace(String message, int level) { - if (traceLevel > 0 && level <= traceLevel) { - long now = System.currentTimeMillis(); - System.err.print(StringUtils.fromTime(now) + ": "); - if (maxPrintSize > 0 && message.length() > maxPrintSize) { - - System.err - .println(message.substring(0, maxPrintSize) + "[...]"); - } else { - System.err.println(message); - } - } - } -} diff --git a/src/be/nikiroo/utils/Version.java b/src/be/nikiroo/utils/Version.java deleted file mode 100644 index 269edb6..0000000 --- a/src/be/nikiroo/utils/Version.java +++ /dev/null @@ -1,366 +0,0 @@ -package be.nikiroo.utils; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; - -/** - * This class describe a program {@link Version}. - * - * @author niki - */ -public class Version implements Comparable { - private String version; - private int major; - private int minor; - private int patch; - private String tag; - private int tagVersion; - - /** - * Create a new, empty {@link Version}. - * - */ - public Version() { - } - - /** - * Create a new {@link Version} with the given values. - * - * @param major - * the major version - * @param minor - * the minor version - * @param patch - * the patch version - */ - public Version(int major, int minor, int patch) { - this(major, minor, patch, null, -1); - } - - /** - * Create a new {@link Version} with the given values. - * - * @param major - * the major version - * @param minor - * the minor version - * @param patch - * the patch version - * @param tag - * a tag name for this version - */ - public Version(int major, int minor, int patch, String tag) { - this(major, minor, patch, tag, -1); - } - - /** - * Create a new {@link Version} with the given values. - * - * @param major - * the major version - * @param minor - * the minor version - * @param patch - * the patch version the patch version - * @param tag - * a tag name for this version - * @param tagVersion - * the version of the tagged version - */ - public Version(int major, int minor, int patch, String tag, int tagVersion) { - if (tagVersion >= 0 && tag == null) { - throw new java.lang.IllegalArgumentException( - "A tag version cannot be used without a tag"); - } - - this.major = major; - this.minor = minor; - this.patch = patch; - this.tag = tag; - this.tagVersion = tagVersion; - - this.version = generateVersion(); - } - - /** - * Create a new {@link Version} with the given value, which must be in the - * form MAJOR.MINOR.PATCH(-TAG(TAG_VERSION)). - * - * @param version - * the version (MAJOR.MINOR.PATCH, - * MAJOR.MINOR.PATCH-TAG or - * MAJOR.MINOR.PATCH-TAGVERSIONTAG) - */ - public Version(String version) { - try { - String[] tab = version.split("\\."); - this.major = Integer.parseInt(tab[0].trim()); - this.minor = Integer.parseInt(tab[1].trim()); - if (tab[2].contains("-")) { - int posInVersion = version.indexOf('.'); - posInVersion = version.indexOf('.', posInVersion + 1); - String rest = version.substring(posInVersion + 1); - - int posInRest = rest.indexOf('-'); - this.patch = Integer.parseInt(rest.substring(0, posInRest) - .trim()); - - posInVersion = version.indexOf('-'); - this.tag = version.substring(posInVersion + 1).trim(); - this.tagVersion = -1; - - StringBuilder str = new StringBuilder(); - while (!tag.isEmpty() && tag.charAt(tag.length() - 1) >= '0' - && tag.charAt(tag.length() - 1) <= '9') { - str.insert(0, tag.charAt(tag.length() - 1)); - tag = tag.substring(0, tag.length() - 1); - } - - if (str.length() > 0) { - this.tagVersion = Integer.parseInt(str.toString()); - } - } else { - this.patch = Integer.parseInt(tab[2].trim()); - this.tag = null; - this.tagVersion = -1; - } - - this.version = generateVersion(); - } catch (Exception e) { - this.major = 0; - this.minor = 0; - this.patch = 0; - this.tag = null; - this.tagVersion = -1; - this.version = null; - } - } - - /** - * The 'major' version. - *

- * This version should only change when API-incompatible changes are made to - * the program. - * - * @return the major version - */ - public int getMajor() { - return major; - } - - /** - * The 'minor' version. - *

- * This version should only change when new, backwards-compatible - * functionality has been added to the program. - * - * @return the minor version - */ - public int getMinor() { - return minor; - } - - /** - * The 'patch' version. - *

- * This version should change when backwards-compatible bugfixes have been - * added to the program. - * - * @return the patch version - */ - public int getPatch() { - return patch; - } - - /** - * A tag name for this version. - * - * @return the tag - */ - public String getTag() { - return tag; - } - - /** - * The version of the tag, or -1 for no version. - * - * @return the tag version - */ - public int getTagVersion() { - return tagVersion; - } - - /** - * Check if this {@link Version} is "empty" (i.e., the version was not - * parse-able or not given). - * - * @return TRUE if it is empty - */ - public boolean isEmpty() { - return version == null; - } - - /** - * Check if we are more recent than the given {@link Version}. - *

- * Note that a tagged version is considered newer than a non-tagged version, - * but two tagged versions with different tags are not comparable. - *

- * Also, an empty version is always considered older. - * - * @param o - * the other {@link Version} - * @return TRUE if this {@link Version} is more recent than the given one - */ - public boolean isNewerThan(Version o) { - if (isEmpty()) { - return false; - } else if (o.isEmpty()) { - return true; - } - - if (major > o.major) { - return true; - } - - if (major == o.major && minor > o.minor) { - return true; - } - - if (major == o.major && minor == o.minor && patch > o.patch) { - return true; - } - - // a tagged version is considered newer than a non-tagged one - if (major == o.major && minor == o.minor && patch == o.patch - && tag != null && o.tag == null) { - return true; - } - - // 2 <> tagged versions are not comparable - boolean sameTag = (tag == null && o.tag == null) - || (tag != null && tag.equals(o.tag)); - if (major == o.major && minor == o.minor && patch == o.patch && sameTag - && tagVersion > o.tagVersion) { - return true; - } - - return false; - } - - /** - * Check if we are older than the given {@link Version}. - *

- * Note that a tagged version is considered newer than a non-tagged version, - * but two tagged versions with different tags are not comparable. - *

- * Also, an empty version is always considered older. - * - * @param o - * the other {@link Version} - * @return TRUE if this {@link Version} is older than the given one - */ - public boolean isOlderThan(Version o) { - if (o.isEmpty()) { - return false; - } else if (isEmpty()) { - return true; - } - - // 2 <> tagged versions are not comparable - boolean sameTag = (tag == null && o.tag == null) - || (tag != null && tag.equals(o.tag)); - if (major == o.major && minor == o.minor && patch == o.patch - && !sameTag) { - return false; - } - - return !equals(o) && !isNewerThan(o); - } - - /** - * Return the version of the running program if it follows the VERSION - * convention (i.e., if it has a file called VERSION containing the version - * as a {@link String} in its binary root, and if this {@link String} - * follows the Major/Minor/Patch convention). - *

- * If it does not, return an empty {@link Version} object. - * - * @return the {@link Version} of the program, or an empty {@link Version} - * (does not return NULL) - */ - public static Version getCurrentVersion() { - String version = null; - - InputStream in = IOUtils.openResource("VERSION"); - if (in != null) { - try { - ByteArrayOutputStream ba = new ByteArrayOutputStream(); - IOUtils.write(in, ba); - in.close(); - - version = ba.toString("UTF-8").trim(); - } catch (IOException e) { - } - } - - return new Version(version); - } - - @Override - public int compareTo(Version o) { - if (equals(o)) { - return 0; - } else if (isNewerThan(o)) { - return 1; - } else { - return -1; - } - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof Version) { - Version o = (Version) obj; - if (isEmpty()) { - return o.isEmpty(); - } - - boolean sameTag = (tag == null && o.tag == null) - || (tag != null && tag.equals(o.tag)); - return o.major == major && o.minor == minor && o.patch == patch - && sameTag && o.tagVersion == tagVersion; - } - - return false; - } - - @Override - public int hashCode() { - return version == null ? 0 : version.hashCode(); - } - - /** - * Return a user-readable form of this {@link Version}. - */ - @Override - public String toString() { - return version == null ? "[unknown]" : version; - } - - /** - * Generate the clean version {@link String} from the current values. - * - * @return the clean version string - */ - private String generateVersion() { - String tagSuffix = ""; - if (tag != null) { - tagSuffix = "-" + tag - + (tagVersion >= 0 ? Integer.toString(tagVersion) : ""); - } - - return String.format("%d.%d.%d%s", major, minor, patch, tagSuffix); - } -} diff --git a/src/be/nikiroo/utils/VersionCheck.java b/src/be/nikiroo/utils/VersionCheck.java deleted file mode 100644 index 0e16c2b..0000000 --- a/src/be/nikiroo/utils/VersionCheck.java +++ /dev/null @@ -1,172 +0,0 @@ -package be.nikiroo.utils; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.URL; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -/** - * Version checker: can check the current version of the program against a - * remote changelog, and list the missed updates and their description. - * - * @author niki - */ -public class VersionCheck { - private static final String base = "https://github.com/${PROJECT}/raw/master/changelog${LANG}.md"; - private static Downloader downloader = new Downloader(null); - - private Version current; - private List newer; - private Map> changes; - - /** - * Create a new {@link VersionCheck}. - * - * @param current - * the current version of the program - * @param newer - * the list of available {@link Version}s newer the current one - * @param changes - * the list of changes - */ - private VersionCheck(Version current, List newer, - Map> changes) { - this.current = current; - this.newer = newer; - this.changes = changes; - } - - /** - * Check if there are more recent {@link Version}s of this program - * available. - * - * @return TRUE if there is at least one - */ - public boolean isNewVersionAvailable() { - return !newer.isEmpty(); - } - - /** - * The current {@link Version} of the program. - * - * @return the current {@link Version} - */ - public Version getCurrentVersion() { - return current; - } - - /** - * The list of available {@link Version}s newer than the current one. - * - * @return the newer {@link Version}s - */ - public List getNewer() { - return newer; - } - - /** - * The list of changes for each available {@link Version} newer than the - * current one. - * - * @return the list of changes - */ - public Map> getChanges() { - return changes; - } - - /** - * Check if there are available {@link Version}s of this program more recent - * than the current one. - * - * @param githubProject - * the GitHub project to check on, for instance "nikiroo/fanfix" - * @param lang - * the current locale, so we can try to get the changelog in the - * correct language (can be NULL, will fetch the default - * changelog) - * - * @return a {@link VersionCheck} - * - * @throws IOException - * in case of I/O error - */ - public static VersionCheck check(String githubProject, Locale lang) - throws IOException { - Version current = Version.getCurrentVersion(); - List newer = new ArrayList(); - Map> changes = new HashMap>(); - - // Use the right project: - String base = VersionCheck.base.replace("${PROJECT}", githubProject); - - // Prepare the URLs according to the user's language (we take here - // "-fr_BE" as an example): - String fr = lang == null ? "" : "-" + lang.getLanguage(); - String BE = lang == null ? "" - : "_" + lang.getCountry().replace(".UTF8", ""); - String urlFrBE = base.replace("${LANG}", fr + BE); - String urlFr = base.replace("${LANG}", "-" + fr); - String urlDefault = base.replace("${LANG}", ""); - - InputStream in = null; - for (String url : new String[] { urlFrBE, urlFr, urlDefault }) { - try { - in = downloader.open(new URL(url), false); - break; - } catch (IOException e) { - } - } - - if (in == null) { - throw new IOException("No changelog found"); - } - - BufferedReader reader = new BufferedReader( - new InputStreamReader(in, "UTF-8")); - try { - Version version = new Version(); - for (String line = reader.readLine(); line != null; line = reader - .readLine()) { - if (line.startsWith("## Version ")) { - version = new Version( - line.substring("## Version ".length())); - if (version.isNewerThan(current)) { - newer.add(version); - changes.put(version, new ArrayList()); - } else { - version = new Version(); - } - } else if (!version.isEmpty() && !newer.isEmpty() - && !line.isEmpty()) { - List ch = changes.get(newer.get(newer.size() - 1)); - if (!ch.isEmpty() && !line.startsWith("- ")) { - int i = ch.size() - 1; - ch.set(i, ch.get(i) + " " + line.trim()); - } else { - ch.add(line.substring("- ".length()).trim()); - } - } - } - } finally { - reader.close(); - } - - return new VersionCheck(current, newer, changes); - } - - @Override - public String toString() { - return String.format( - "Version checker: version [%s], %d releases behind latest version [%s]", // - current, // - newer.size(), // - newer.isEmpty() ? current : newer.get(newer.size() - 1)// - ); - } -} diff --git a/src/be/nikiroo/utils/android/ImageUtilsAndroid.java b/src/be/nikiroo/utils/android/ImageUtilsAndroid.java deleted file mode 100644 index c2e269c..0000000 --- a/src/be/nikiroo/utils/android/ImageUtilsAndroid.java +++ /dev/null @@ -1,99 +0,0 @@ -package be.nikiroo.utils.android; - -import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; -import android.graphics.BitmapFactory; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.stream.Stream; - -import be.nikiroo.utils.Image; -import be.nikiroo.utils.ImageUtils; -import be.nikiroo.utils.StringUtils; - -/** - * This class offer some utilities based around images and uses the Android - * framework. - * - * @author niki - */ -public class ImageUtilsAndroid extends ImageUtils { - @Override - protected boolean check() { - // If we can get the class, it means we have access to it - Config c = Config.ALPHA_8; - return true; - } - - @Override - public void saveAsImage(Image img, File target, String format) - throws IOException { - FileOutputStream fos = new FileOutputStream(target); - try { - Bitmap image = fromImage(img); - - boolean ok = false; - try { - ok = image.compress( - Bitmap.CompressFormat.valueOf(format.toUpperCase()), - 90, fos); - } catch (Exception e) { - ok = false; - } - - // Some formats are not reliable - // Second chance: PNG - if (!ok && !format.equals("png")) { - ok = image.compress(Bitmap.CompressFormat.PNG, 90, fos); - } - - if (!ok) { - throw new IOException( - "Cannot find a writer for this image and format: " - + format); - } - } catch (IOException e) { - throw new IOException("Cannot write image to " + target, e); - } finally { - fos.close(); - } - } - - /** - * Convert the given {@link Image} into a {@link Bitmap} object. - * - * @param img - * the {@link Image} - * @return the {@link Image} object - * @throws IOException - * in case of IO error - */ - static public Bitmap fromImage(Image img) throws IOException { - InputStream stream = img.newInputStream(); - try { - Bitmap image = BitmapFactory.decodeStream(stream); - if (image == null) { - String extra = ""; - if (img.getSize() <= 2048) { - try { - extra = ", content: " - + new String(img.getData(), "UTF-8"); - } catch (Exception e) { - extra = ", content unavailable"; - } - } - String ssize = StringUtils.formatNumber(img.getSize()); - throw new IOException( - "Failed to convert input to image, size was: " + ssize - + extra); - } - - return image; - } finally { - stream.close(); - } - } -} diff --git a/src/be/nikiroo/utils/android/test/TestAndroid.java b/src/be/nikiroo/utils/android/test/TestAndroid.java deleted file mode 100644 index 2ded4e1..0000000 --- a/src/be/nikiroo/utils/android/test/TestAndroid.java +++ /dev/null @@ -1,7 +0,0 @@ -package be.nikiroo.utils.android.test; - -import be.nikiroo.utils.android.ImageUtilsAndroid; - -public class TestAndroid { - ImageUtilsAndroid a = new ImageUtilsAndroid(); -} diff --git a/src/be/nikiroo/utils/main/bridge.java b/src/be/nikiroo/utils/main/bridge.java deleted file mode 100644 index 1b7ab85..0000000 --- a/src/be/nikiroo/utils/main/bridge.java +++ /dev/null @@ -1,136 +0,0 @@ -package be.nikiroo.utils.main; - -import be.nikiroo.utils.TraceHandler; -import be.nikiroo.utils.serial.server.ServerBridge; - -/** - * Serialiser bridge (starts a {@link ServerBridge} and can thus intercept - * communication between a client and a server). - * - * @author niki - */ -public class bridge { - /** - * The optional options that can be passed to the program. - * - * @author niki - */ - private enum Option { - /** - * The encryption key for the input data (optional, but can also be - * empty which is different (it will then use an empty encryption - * key)). - */ - KEY, - /** - * The encryption key for the output data (optional, but can also be - * empty which is different (it will then use an empty encryption - * key)). - */ - FORWARD_KEY, - /** The trace level (1, 2, 3.. default is 1). */ - TRACE_LEVEL, - /** - * The maximum length after which to truncate data to display (the whole - * data will still be sent). - */ - MAX_DISPLAY_SIZE, - /** The help message. */ - HELP, - } - - static private String getSyntax() { - return "Syntax: (--options) (--) [NAME] [PORT] [FORWARD_HOST] [FORWARD_PORT]\n"// - + "\tNAME : the bridge name for display/debug purposes\n"// - + "\tPORT : the port to listen on\n"// - + "\tFORWARD_HOST : the host to connect to\n"// - + "\tFORWARD_PORT : the port to connect to\n"// - + "\n" // - + "\tOptions: \n" // - + "\t-- : no more options in the rest of the parameters\n" // - + "\t--help : this help message\n" // - + "\t--key : the INCOMING encryption key\n" // - + "\t--forward-key : the OUTGOING encryption key\n" // - + "\t--trace-level : the trace level (1, 2, 3... default is 1)\n" // - + "\t--max-display-size : the maximum size after which to \n"// - + "\t truncate the messages to display (the full message will still be sent)\n" // - ; - } - - /** - * Start a bridge between 2 servers. - * - * @param args - * the parameters, which can be seen by passing "--help" or just - * calling the program without parameters - */ - public static void main(String[] args) { - final TraceHandler tracer = new TraceHandler(true, false, 0); - try { - if (args.length == 0) { - tracer.error(getSyntax()); - System.exit(0); - } - - String key = null; - String fkey = null; - int traceLevel = 1; - int maxPrintSize = 0; - - int i = 0; - while (args[i].startsWith("--")) { - String arg = args[i]; - i++; - - if (arg.equals("--")) { - break; - } - - arg = arg.substring(2).toUpperCase().replace("-", "_"); - try { - Option opt = Enum.valueOf(Option.class, arg); - switch (opt) { - case HELP: - tracer.trace(getSyntax()); - System.exit(0); - break; - case FORWARD_KEY: - fkey = args[i++]; - break; - case KEY: - key = args[i++]; - break; - case MAX_DISPLAY_SIZE: - maxPrintSize = Integer.parseInt(args[i++]); - break; - case TRACE_LEVEL: - traceLevel = Integer.parseInt(args[i++]); - break; - } - } catch (Exception e) { - tracer.error(getSyntax()); - System.exit(1); - } - } - - if ((args.length - i) != 4) { - tracer.error(getSyntax()); - System.exit(2); - } - - String name = args[i++]; - int port = Integer.parseInt(args[i++]); - String fhost = args[i++]; - int fport = Integer.parseInt(args[i++]); - - ServerBridge bridge = new ServerBridge(name, port, key, fhost, - fport, fkey); - bridge.setTraceHandler(new TraceHandler(true, true, traceLevel, - maxPrintSize)); - bridge.run(); - } catch (Exception e) { - tracer.error(e); - System.exit(42); - } - } -} diff --git a/src/be/nikiroo/utils/main/img2aa.java b/src/be/nikiroo/utils/main/img2aa.java deleted file mode 100644 index 9cc6f0c..0000000 --- a/src/be/nikiroo/utils/main/img2aa.java +++ /dev/null @@ -1,137 +0,0 @@ -package be.nikiroo.utils.main; - -import java.awt.Dimension; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; - -import be.nikiroo.utils.IOUtils; -import be.nikiroo.utils.Image; -import be.nikiroo.utils.ui.ImageTextAwt; -import be.nikiroo.utils.ui.ImageTextAwt.Mode; -import be.nikiroo.utils.ui.ImageUtilsAwt; - -/** - * Image to ASCII conversion. - * - * @author niki - */ -public class img2aa { - /** - * Syntax: (--mode=MODE) (--width=WIDTH) (--height=HEIGHT) (--size=SIZE) - * (--output=OUTPUT) (--invert) (--help) - *

- * See "--help". - * - * @param args - */ - public static void main(String[] args) { - Dimension size = null; - Mode mode = null; - boolean invert = false; - List inputs = new ArrayList(); - File output = null; - - String lastArg = ""; - try { - int height = -1; - int width = -1; - - for (String arg : args) { - lastArg = arg; - - if (arg.startsWith("--mode=")) { - mode = Mode.valueOf(arg.substring("--mode=".length())); - } else if (arg.startsWith("--width=")) { - width = Integer - .parseInt(arg.substring("--width=".length())); - } else if (arg.startsWith("--height=")) { - height = Integer.parseInt(arg.substring("--height=" - .length())); - } else if (arg.startsWith("--size=")) { - String content = arg.substring("--size=".length()).replace( - "X", "x"); - width = Integer.parseInt(content.split("x")[0]); - height = Integer.parseInt(content.split("x")[1]); - } else if (arg.startsWith("--ouput=")) { - if (!arg.equals("--output=-")) { - output = new File(arg.substring("--output=".length())); - } - } else if (arg.equals("--invert")) { - invert = true; - } else if (arg.equals("--help")) { - System.out - .println("Syntax: (--mode=MODE) (--width=WIDTH) (--height=HEIGHT) (--size=SIZE) (--output=OUTPUT) (--invert) (--help)"); - System.out.println("\t --help: will show this screen"); - System.out - .println("\t --invert: will invert the 'colours'"); - System.out - .println("\t --mode: will select the rendering mode (default: ASCII):"); - System.out - .println("\t\t ASCII: ASCI output mode, that is, characters \" .-+=o8#\""); - System.out - .println("\t\t DITHERING: Use 5 different \"colours\" which are actually" - + "\n\t\t Unicode characters \" ░▒▓█\""); - System.out - .println("\t\t DOUBLE_RESOLUTION: Use \"block\" Unicode characters up to quarter" - + "\n\t\t blocks, thus in effect doubling the resolution both in vertical" - + "\n\t\t and horizontal space." - + "\n\t\t Note that since 2 characters next to each other are square," - + "\n\t\t 4 blocks per 2 blocks for w/h resolution."); - System.out - .println("\t\t DOUBLE_DITHERING: Use characters from both DOUBLE_RESOLUTION" - + "\n\t\t and DITHERING"); - return; - } else { - inputs.add(arg); - } - } - - size = new Dimension(width, height); - if (inputs.size() == 0) { - inputs.add("-"); // by default, stdin - } - } catch (Exception e) { - System.err.println("Syntax error: \"" + lastArg + "\" is invalid"); - System.exit(1); - } - - try { - if (mode == null) { - mode = Mode.ASCII; - } - - for (String input : inputs) { - InputStream in = null; - - try { - if (input.equals("-")) { - in = System.in; - } else { - in = new FileInputStream(input); - } - BufferedImage image = ImageUtilsAwt - .fromImage(new Image(in)); - ImageTextAwt img = new ImageTextAwt(image, size, mode, - invert); - if (output == null) { - System.out.println(img.getText()); - } else { - IOUtils.writeSmallFile(output, img.getText()); - } - } finally { - if (!input.equals("-")) { - in.close(); - } - } - } - } catch (IOException e) { - e.printStackTrace(); - System.exit(2); - } - } -} diff --git a/src/be/nikiroo/utils/main/justify.java b/src/be/nikiroo/utils/main/justify.java deleted file mode 100644 index 2a83389..0000000 --- a/src/be/nikiroo/utils/main/justify.java +++ /dev/null @@ -1,53 +0,0 @@ -package be.nikiroo.utils.main; - -import java.util.ArrayList; -import java.util.List; -import java.util.Scanner; - -import be.nikiroo.utils.StringUtils; -import be.nikiroo.utils.StringUtils.Alignment; - -/** - * Text justification (left, right, center, justify). - * - * @author niki - */ -public class justify { - /** - * Syntax: $0 ([left|right|center|justify]) (max width) - *

- *

    - *
  • mode: left, right, center or full justification (defaults to left)
  • - *
  • max width: the maximum width of a line, or "" for "no maximum" - * (defaults to "no maximum")
  • - *
- * - * @param args - */ - public static void main(String[] args) { - int width = -1; - StringUtils.Alignment align = Alignment.LEFT; - - if (args.length >= 1) { - align = Alignment.valueOf(args[0].toUpperCase()); - } - if (args.length >= 2) { - width = Integer.parseInt(args[1]); - } - - Scanner scan = new Scanner(System.in); - scan.useDelimiter("\r\n|[\r\n]"); - try { - List lines = new ArrayList(); - while (scan.hasNext()) { - lines.add(scan.next()); - } - - for (String line : StringUtils.justifyText(lines, width, align)) { - System.out.println(line); - } - } finally { - scan.close(); - } - } -} diff --git a/src/be/nikiroo/utils/resources/Bundle.java b/src/be/nikiroo/utils/resources/Bundle.java deleted file mode 100644 index fe3ac1a..0000000 --- a/src/be/nikiroo/utils/resources/Bundle.java +++ /dev/null @@ -1,1313 +0,0 @@ -package be.nikiroo.utils.resources; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.Reader; -import java.io.Writer; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.MissingResourceException; -import java.util.PropertyResourceBundle; -import java.util.ResourceBundle; - -import be.nikiroo.utils.resources.Meta.Format; - -/** - * This class encapsulate a {@link ResourceBundle} in UTF-8. It allows to - * retrieve values associated to an enumeration, and allows some additional - * methods. - *

- * It also sports a writable change map, and you can save back the - * {@link Bundle} to file with {@link Bundle#updateFile(String)}. - * - * @param - * the enum to use to get values out of this class - * - * @author niki - */ - -public class Bundle> { - /** The type of E. */ - protected Class type; - /** - * The {@link Enum} associated to this {@link Bundle} (all the keys used in - * this {@link Bundle} will be of this type). - */ - protected Enum keyType; - - private TransBundle descriptionBundle; - - /** R/O map */ - private Map map; - /** R/W map */ - private Map changeMap; - - /** - * Create a new {@link Bundles} of the given name. - * - * @param type - * a runtime instance of the class of E - * @param name - * the name of the {@link Bundles} - * @param descriptionBundle - * the description {@link TransBundle}, that is, a - * {@link TransBundle} dedicated to the description of the values - * of the given {@link Bundle} (can be NULL) - */ - protected Bundle(Class type, Enum name, - TransBundle descriptionBundle) { - this.type = type; - this.keyType = name; - this.descriptionBundle = descriptionBundle; - - this.map = new HashMap(); - this.changeMap = new HashMap(); - setBundle(name, Locale.getDefault(), false); - } - - /** - * Check if the setting is set into this {@link Bundle}. - * - * @param id - * the id of the setting to check - * @param includeDefaultValue - * TRUE to only return false when the setting is not set AND - * there is no default value - * - * @return TRUE if the setting is set - */ - public boolean isSet(E id, boolean includeDefaultValue) { - return isSet(id.name(), includeDefaultValue); - } - - /** - * Check if the setting is set into this {@link Bundle}. - * - * @param name - * the id of the setting to check - * @param includeDefaultValue - * TRUE to only return false when the setting is explicitly set - * to NULL (and not just "no set") in the change maps - * - * @return TRUE if the setting is set - */ - protected boolean isSet(String name, boolean includeDefaultValue) { - if (getString(name, null) == null) { - if (!includeDefaultValue || getString(name, "") == null) { - return false; - } - } - - return true; - } - - /** - * Return the value associated to the given id as a {@link String}. - * - * @param id - * the id of the value to get - * - * @return the associated value, or NULL if not found (not present in the - * resource file) - */ - public String getString(E id) { - return getString(id, null); - } - - /** - * Return the value associated to the given id as a {@link String}. - *

- * If no value is associated, take the default one if any. - * - * @param id - * the id of the value to get - * @param def - * the default value when it is not present in the config file - * - * @return the associated value, or def if not found (not present - * in the resource file) - */ - public String getString(E id, String def) { - return getString(id, def, -1); - } - - /** - * Return the value associated to the given id as a {@link String}. - *

- * If no value is associated (or if it is empty!), take the default one if - * any. - * - * @param id - * the id of the value to get - * @param def - * the default value when it is not present in the config file - * @param item - * the item number to get for an array of values, or -1 for - * non-arrays - * - * @return the associated value, def if not found (not present in - * the resource file) or NULL if the item is specified (not -1) and - * does not exist - */ - public String getString(E id, String def, int item) { - String rep = getString(id.name(), null); - if (rep == null) { - rep = getMetaDef(id.name()); - } - - if (rep.isEmpty()) { - return def; - } - - if (item >= 0) { - List values = BundleHelper.parseList(rep, item); - if (values != null && item < values.size()) { - return values.get(item); - } - - return null; - } - - return rep; - } - - /** - * Set the value associated to the given id as a {@link String}. - * - * @param id - * the id of the value to set - * @param value - * the value - * - */ - public void setString(E id, String value) { - setString(id.name(), value); - } - - /** - * Set the value associated to the given id as a {@link String}. - * - * @param id - * the id of the value to set - * @param value - * the value - * @param item - * the item number to get for an array of values, or -1 for - * non-arrays - * - */ - public void setString(E id, String value, int item) { - if (item < 0) { - setString(id.name(), value); - } else { - List values = getList(id); - setString(id.name(), BundleHelper.fromList(values, value, item)); - } - } - - /** - * Return the value associated to the given id as a {@link String} suffixed - * with the runtime value "_suffix" (that is, "_" and suffix). - *

- * Will only accept suffixes that form an existing id. - *

- * If no value is associated, take the default one if any. - * - * @param id - * the id of the value to get - * @param suffix - * the runtime suffix - * - * @return the associated value, or NULL if not found (not present in the - * resource file) - */ - public String getStringX(E id, String suffix) { - return getStringX(id, suffix, null, -1); - } - - /** - * Return the value associated to the given id as a {@link String} suffixed - * with the runtime value "_suffix" (that is, "_" and suffix). - *

- * Will only accept suffixes that form an existing id. - *

- * If no value is associated, take the default one if any. - * - * @param id - * the id of the value to get - * @param suffix - * the runtime suffix - * @param def - * the default value when it is not present in the config file - * - * @return the associated value, or NULL if not found (not present in the - * resource file) - */ - public String getStringX(E id, String suffix, String def) { - return getStringX(id, suffix, def, -1); - } - - /** - * Return the value associated to the given id as a {@link String} suffixed - * with the runtime value "_suffix" (that is, "_" and suffix). - *

- * Will only accept suffixes that form an existing id. - *

- * If no value is associated, take the default one if any. - * - * @param id - * the id of the value to get - * @param suffix - * the runtime suffix - * @param def - * the default value when it is not present in the config file - * @param item - * the item number to get for an array of values, or -1 for - * non-arrays - * - * @return the associated value, def if not found (not present in - * the resource file), NULL if the item is specified (not -1) but - * does not exist and NULL if bad key - */ - public String getStringX(E id, String suffix, String def, int item) { - String key = id.name() - + (suffix == null ? "" : "_" + suffix.toUpperCase()); - - try { - id = Enum.valueOf(type, key); - return getString(id, def, item); - } catch (IllegalArgumentException e) { - } - - return null; - } - - /** - * Set the value associated to the given id as a {@link String} suffixed - * with the runtime value "_suffix" (that is, "_" and suffix). - *

- * Will only accept suffixes that form an existing id. - * - * @param id - * the id of the value to set - * @param suffix - * the runtime suffix - * @param value - * the value - */ - public void setStringX(E id, String suffix, String value) { - setStringX(id, suffix, value, -1); - } - - /** - * Set the value associated to the given id as a {@link String} suffixed - * with the runtime value "_suffix" (that is, "_" and suffix). - *

- * Will only accept suffixes that form an existing id. - * - * @param id - * the id of the value to set - * @param suffix - * the runtime suffix - * @param value - * the value - * @param item - * the item number to get for an array of values, or -1 for - * non-arrays - */ - public void setStringX(E id, String suffix, String value, int item) { - String key = id.name() - + (suffix == null ? "" : "_" + suffix.toUpperCase()); - - try { - id = Enum.valueOf(type, key); - setString(id, value, item); - } catch (IllegalArgumentException e) { - } - } - - /** - * Return the value associated to the given id as a {@link Boolean}. - *

- * If no value is associated, take the default one if any. - * - * @param id - * the id of the value to get - * - * @return the associated value - */ - public Boolean getBoolean(E id) { - return BundleHelper.parseBoolean(getString(id), -1); - } - - /** - * Return the value associated to the given id as a {@link Boolean}. - *

- * If no value is associated, take the default one if any. - * - * @param id - * the id of the value to get - * @param def - * the default value when it is not present in the config file or - * if it is not a boolean value - * - * @return the associated value - */ - public boolean getBoolean(E id, boolean def) { - Boolean value = getBoolean(id); - if (value != null) { - return value; - } - - return def; - } - - /** - * Return the value associated to the given id as a {@link Boolean}. - *

- * If no value is associated, take the default one if any. - * - * @param id - * the id of the value to get - * @param def - * the default value when it is not present in the config file or - * if it is not a boolean value - * @param item - * the item number to get for an array of values, or -1 for - * non-arrays - * - * @return the associated value - */ - public Boolean getBoolean(E id, boolean def, int item) { - String value = getString(id); - if (value != null) { - return BundleHelper.parseBoolean(value, item); - } - - return def; - } - - /** - * Set the value associated to the given id as a {@link Boolean}. - * - * @param id - * the id of the value to set - * @param value - * the value - * - */ - public void setBoolean(E id, boolean value) { - setBoolean(id, value, -1); - } - - /** - * Set the value associated to the given id as a {@link Boolean}. - * - * @param id - * the id of the value to set - * @param value - * the value - * @param item - * the item number to get for an array of values, or -1 for - * non-arrays - * - */ - public void setBoolean(E id, boolean value, int item) { - setString(id, BundleHelper.fromBoolean(value), item); - } - - /** - * Return the value associated to the given id as an {@link Integer}. - *

- * If no value is associated, take the default one if any. - * - * @param id - * the id of the value to get - * - * @return the associated value - */ - public Integer getInteger(E id) { - String value = getString(id); - if (value != null) { - return BundleHelper.parseInteger(value, -1); - } - - return null; - } - - /** - * Return the value associated to the given id as an int. - *

- * If no value is associated, take the default one if any. - * - * @param id - * the id of the value to get - * @param def - * the default value when it is not present in the config file or - * if it is not a int value - * - * @return the associated value - */ - public int getInteger(E id, int def) { - Integer value = getInteger(id); - if (value != null) { - return value; - } - - return def; - } - - /** - * Return the value associated to the given id as an int. - *

- * If no value is associated, take the default one if any. - * - * @param id - * the id of the value to get - * @param def - * the default value when it is not present in the config file or - * if it is not a int value - * @param item - * the item number to get for an array of values, or -1 for - * non-arrays - * - * @return the associated value - */ - public Integer getInteger(E id, int def, int item) { - String value = getString(id); - if (value != null) { - return BundleHelper.parseInteger(value, item); - } - - return def; - } - - /** - * Set the value associated to the given id as a {@link Integer}. - * - * @param id - * the id of the value to set - * @param value - * the value - * - */ - public void setInteger(E id, int value) { - setInteger(id, value, -1); - } - - /** - * Set the value associated to the given id as a {@link Integer}. - * - * @param id - * the id of the value to set - * @param value - * the value - * @param item - * the item number to get for an array of values, or -1 for - * non-arrays - * - */ - public void setInteger(E id, int value, int item) { - setString(id, BundleHelper.fromInteger(value), item); - } - - /** - * Return the value associated to the given id as a {@link Character}. - *

- * If no value is associated, take the default one if any. - * - * @param id - * the id of the value to get - * - * @return the associated value - */ - public Character getCharacter(E id) { - return BundleHelper.parseCharacter(getString(id), -1); - } - - /** - * Return the value associated to the given id as a {@link Character}. - *

- * If no value is associated, take the default one if any. - * - * @param id - * the id of the value to get - * @param def - * the default value when it is not present in the config file or - * if it is not a char value - * - * @return the associated value - */ - public char getCharacter(E id, char def) { - Character value = getCharacter(id); - if (value != null) { - return value; - } - - return def; - } - - /** - * Return the value associated to the given id as a {@link Character}. - *

- * If no value is associated, take the default one if any. - * - * @param id - * the id of the value to get - * @param def - * the default value when it is not present in the config file or - * if it is not a char value - * @param item - * the item number to get for an array of values, or -1 for - * non-arrays - * - * @return the associated value - */ - public Character getCharacter(E id, char def, int item) { - String value = getString(id); - if (value != null) { - return BundleHelper.parseCharacter(value, item); - } - - return def; - } - - /** - * Set the value associated to the given id as a {@link Character}. - * - * @param id - * the id of the value to set - * @param value - * the value - * - */ - public void setCharacter(E id, char value) { - setCharacter(id, value, -1); - } - - /** - * Set the value associated to the given id as a {@link Character}. - * - * @param id - * the id of the value to set - * @param value - * the value - * @param item - * the item number to get for an array of values, or -1 for - * non-arrays - * - */ - public void setCharacter(E id, char value, int item) { - setString(id, BundleHelper.fromCharacter(value), item); - } - - /** - * Return the value associated to the given id as a colour if it is found - * and can be parsed. - *

- * The returned value is an ARGB value. - *

- * If no value is associated, take the default one if any. - * - * @param id - * the id of the value to get - * - * @return the associated value - */ - public Integer getColor(E id) { - return BundleHelper.parseColor(getString(id), -1); - } - - /** - * Return the value associated to the given id as a colour if it is found - * and can be parsed. - *

- * The returned value is an ARGB value. - *

- * If no value is associated, take the default one if any. - * - * @param id - * the id of the value to get - * @param def - * the default value when it is not present in the config file or - * if it is not a char value - * - * @return the associated value - */ - public int getColor(E id, int def) { - Integer value = getColor(id); - if (value != null) { - return value; - } - - return def; - } - - /** - * Return the value associated to the given id as a colour if it is found - * and can be parsed. - *

- * The returned value is an ARGB value. - *

- * If no value is associated, take the default one if any. - * - * @param id - * the id of the value to get - * @param def - * the default value when it is not present in the config file or - * if it is not a char value - * @param item - * the item number to get for an array of values, or -1 for - * non-arrays - * - * @return the associated value - */ - public Integer getColor(E id, int def, int item) { - String value = getString(id); - if (value != null) { - return BundleHelper.parseColor(value, item); - } - - return def; - } - - /** - * Set the value associated to the given id as a colour. - *

- * The value is a BGRA value. - * - * @param id - * the id of the value to set - * @param color - * the new colour - */ - public void setColor(E id, Integer color) { - setColor(id, color, -1); - } - - /** - * Set the value associated to the given id as a Color. - * - * @param id - * the id of the value to set - * @param value - * the value - * @param item - * the item number to get for an array of values, or -1 for - * non-arrays - * - */ - public void setColor(E id, int value, int item) { - setString(id, BundleHelper.fromColor(value), item); - } - - /** - * Return the value associated to the given id as a list of values if it is - * found and can be parsed. - *

- * If no value is associated, take the default one if any. - * - * @param id - * the id of the value to get - * - * @return the associated list, empty if the value is empty, NULL if it is - * not found or cannot be parsed as a list - */ - public List getList(E id) { - return BundleHelper.parseList(getString(id), -1); - } - - /** - * Return the value associated to the given id as a list of values if it is - * found and can be parsed. - *

- * If no value is associated, take the default one if any. - * - * @param id - * the id of the value to get - * @param def - * the default value when it is not present in the config file or - * if it is not a char value - * - * @return the associated list, empty if the value is empty, NULL if it is - * not found or cannot be parsed as a list - */ - public List getList(E id, List def) { - List value = getList(id); - if (value != null) { - return value; - } - - return def; - } - - /** - * Return the value associated to the given id as a list of values if it is - * found and can be parsed. - *

- * If no value is associated, take the default one if any. - * - * @param id - * the id of the value to get - * @param def - * the default value when it is not present in the config file or - * if it is not a char value - * @param item - * the item number to get for an array of values, or -1 for - * non-arrays - * - * @return the associated list, empty if the value is empty, NULL if it is - * not found or cannot be parsed as a list - */ - public List getList(E id, List def, int item) { - String value = getString(id); - if (value != null) { - return BundleHelper.parseList(value, item); - } - - return def; - } - - /** - * Set the value associated to the given id as a list of values. - * - * @param id - * the id of the value to set - * @param list - * the new list of values - */ - public void setList(E id, List list) { - setList(id, list, -1); - } - - /** - * Set the value associated to the given id as a {@link List}. - * - * @param id - * the id of the value to set - * @param value - * the value - * @param item - * the item number to get for an array of values, or -1 for - * non-arrays - * - */ - public void setList(E id, List value, int item) { - setString(id, BundleHelper.fromList(value), item); - } - - /** - * Create/update the .properties file. - *

- * Will use the most likely candidate as base if the file does not already - * exists and this resource is translatable (for instance, "en_US" will use - * "en" as a base if the resource is a translation file). - *

- * Will update the files in {@link Bundles#getDirectory()}; it MUST - * be set. - * - * @throws IOException - * in case of IO errors - */ - public void updateFile() throws IOException { - updateFile(Bundles.getDirectory()); - } - - /** - * Create/update the .properties file. - *

- * Will use the most likely candidate as base if the file does not already - * exists and this resource is translatable (for instance, "en_US" will use - * "en" as a base if the resource is a translation file). - * - * @param path - * the path where the .properties files are, MUST NOT be - * NULL - * - * @throws IOException - * in case of IO errors - */ - public void updateFile(String path) throws IOException { - File file = getUpdateFile(path); - - BufferedWriter writer = new BufferedWriter(new OutputStreamWriter( - new FileOutputStream(file), "UTF-8")); - - writeHeader(writer); - writer.write("\n"); - writer.write("\n"); - - for (Field field : type.getDeclaredFields()) { - Meta meta = field.getAnnotation(Meta.class); - if (meta != null) { - E id = Enum.valueOf(type, field.getName()); - String info = getMetaInfo(meta); - - if (info != null) { - writer.write(info); - writer.write("\n"); - } - - writeValue(writer, id); - } - } - - writer.close(); - } - - /** - * Delete the .properties file. - *

- * Will use the most likely candidate as base if the file does not already - * exists and this resource is translatable (for instance, "en_US" will use - * "en" as a base if the resource is a translation file). - *

- * Will delete the files in {@link Bundles#getDirectory()}; it MUST - * be set. - * - * @return TRUE if the file was deleted - */ - public boolean deleteFile() { - return deleteFile(Bundles.getDirectory()); - } - - /** - * Delete the .properties file. - *

- * Will use the most likely candidate as base if the file does not already - * exists and this resource is translatable (for instance, "en_US" will use - * "en" as a base if the resource is a translation file). - * - * @param path - * the path where the .properties files are, MUST NOT be - * NULL - * - * @return TRUE if the file was deleted - */ - public boolean deleteFile(String path) { - File file = getUpdateFile(path); - return file.delete(); - } - - /** - * The description {@link TransBundle}, that is, a {@link TransBundle} - * dedicated to the description of the values of the given {@link Bundle} - * (can be NULL). - * - * @return the description {@link TransBundle} - */ - public TransBundle getDescriptionBundle() { - return descriptionBundle; - } - - /** - * Reload the {@link Bundle} data files. - * - * @param resetToDefault - * reset to the default configuration (do not look into the - * possible user configuration files, only take the original - * configuration) - */ - public void reload(boolean resetToDefault) { - setBundle(keyType, Locale.getDefault(), resetToDefault); - } - - /** - * Check if the internal map contains the given key. - * - * @param key - * the key to check for - * - * @return true if it does - */ - protected boolean containsKey(String key) { - return changeMap.containsKey(key) || map.containsKey(key); - } - - /** - * The default {@link Meta#def()} value for the given enumeration name. - * - * @param id - * the enumeration name (the "id") - * - * @return the def value in the {@link MetaInfo} or "" if none (never NULL) - */ - protected String getMetaDef(String id) { - String rep = ""; - try { - Meta meta = type.getDeclaredField(id).getAnnotation(Meta.class); - rep = meta.def(); - } catch (NoSuchFieldException e) { - } catch (SecurityException e) { - } - - if (rep == null) { - rep = ""; - } - - return rep; - } - - /** - * Get the value for the given key if it exists in the internal map, or - * def if not. - *

- * DO NOT get the default meta value (MetaInfo.def()). - * - * @param key - * the key to check for - * @param def - * the default value when it is not present in the internal map - * - * @return the value, or def if not found - */ - protected String getString(String key, String def) { - if (changeMap.containsKey(key)) { - return changeMap.get(key); - } - - if (map.containsKey(key)) { - return map.get(key); - } - - return def; - } - - /** - * Set the value for this key, in the change map (it is kept in memory, not - * yet on disk). - * - * @param key - * the key - * @param value - * the associated value - */ - protected void setString(String key, String value) { - changeMap.put(key, value == null ? null : value.trim()); - } - - /** - * Return formated, display-able information from the {@link Meta} field - * given. Each line will always starts with a "#" character. - * - * @param meta - * the {@link Meta} field - * - * @return the information to display or NULL if none - */ - protected String getMetaInfo(Meta meta) { - String desc = meta.description(); - boolean group = meta.group(); - Meta.Format format = meta.format(); - String[] list = meta.list(); - boolean nullable = meta.nullable(); - String def = meta.def(); - boolean array = meta.array(); - - // Default, empty values -> NULL - if (desc.length() + list.length + def.length() == 0 && !group - && nullable && format == Format.STRING) { - return null; - } - - StringBuilder builder = new StringBuilder(); - for (String line : desc.split("\n")) { - builder.append("# ").append(line).append("\n"); - } - - if (group) { - builder.append("# This item is used as a group, its content is not expected to be used."); - } else { - builder.append("# (FORMAT: ").append(format) - .append(nullable ? "" : ", required"); - builder.append(") "); - - if (list.length > 0) { - builder.append("\n# ALLOWED VALUES: "); - boolean first = true; - for (String value : list) { - if (!first) { - builder.append(", "); - } - builder.append(BundleHelper.escape(value)); - first = false; - } - } - - if (array) { - builder.append("\n# (This item accepts a list of ^escaped comma-separated values)"); - } - } - - return builder.toString(); - } - - /** - * The display name used in the .properties file. - * - * @return the name - */ - protected String getBundleDisplayName() { - return keyType.toString(); - } - - /** - * Write the header found in the configuration .properties file of - * this {@link Bundles}. - * - * @param writer - * the {@link Writer} to write the header in - * - * @throws IOException - * in case of IO error - */ - protected void writeHeader(Writer writer) throws IOException { - writer.write("# " + getBundleDisplayName() + "\n"); - writer.write("#\n"); - } - - /** - * Write the given data to the config file, i.e., "MY_ID = my_curent_value" - * followed by a new line. - *

- * Will prepend a # sign if the is is not set (see - * {@link Bundle#isSet(Enum, boolean)}). - * - * @param writer - * the {@link Writer} to write into - * @param id - * the id to write - * - * @throws IOException - * in case of IO error - */ - protected void writeValue(Writer writer, E id) throws IOException { - boolean set = isSet(id, false); - writeValue(writer, id.name(), getString(id), set); - } - - /** - * Write the given data to the config file, i.e., "MY_ID = my_curent_value" - * followed by a new line. - *

- * Will prepend a # sign if the is is not set. - * - * @param writer - * the {@link Writer} to write into - * @param id - * the id to write - * @param value - * the id's value - * @param set - * the value is set in this {@link Bundle} - * - * @throws IOException - * in case of IO error - */ - protected void writeValue(Writer writer, String id, String value, - boolean set) throws IOException { - - if (!set) { - writer.write('#'); - } - - writer.write(id); - writer.write(" = "); - - if (value == null) { - value = ""; - } - - String[] lines = value.replaceAll("\t", "\\\\\\t").split("\n"); - for (int i = 0; i < lines.length; i++) { - writer.write(lines[i]); - if (i < lines.length - 1) { - writer.write("\\n\\"); - } - writer.write("\n"); - } - } - - /** - * Return the source file for this {@link Bundles} from the given path. - * - * @param path - * the path where the .properties files are - * - * @return the source {@link File} - */ - protected File getUpdateFile(String path) { - return new File(path, keyType.name() + ".properties"); - } - - /** - * Change the currently used bundle, and reset all changes. - * - * @param name - * the name of the bundle to load - * @param locale - * the {@link Locale} to use - * @param resetToDefault - * reset to the default configuration (do not look into the - * possible user configuration files, only take the original - * configuration) - */ - protected void setBundle(Enum name, Locale locale, boolean resetToDefault) { - changeMap.clear(); - String dir = Bundles.getDirectory(); - String bname = type.getPackage().getName() + "." + name.name(); - - boolean found = false; - if (!resetToDefault && dir != null) { - try { - // Look into Bundles.getDirectory() for .properties files - File file = getPropertyFile(dir, name.name(), locale); - if (file != null) { - InputStream in = new FileInputStream(file); - try { - Reader reader = new InputStreamReader(in, "UTF-8"); - try { - resetMap(new PropertyResourceBundle(reader)); - } finally { - reader.close(); - } - } finally { - in.close(); - } - found = true; - } - } catch (IOException e) { - e.printStackTrace(); - } - } - - if (!found) { - // Look into the package itself for resources - try { - resetMap(ResourceBundle - .getBundle(bname, locale, type.getClassLoader(), - new FixedResourceBundleControl())); - found = true; - } catch (MissingResourceException e) { - } catch (Exception e) { - e.printStackTrace(); - } - } - - if (!found) { - // We have no bundle for this Bundle - System.err.println("No bundle found for: " + bname); - resetMap(null); - } - } - - /** - * Reset the backing map to the content of the given bundle, or with NULL - * values if bundle is NULL. - * - * @param bundle - * the bundle to copy - */ - protected void resetMap(ResourceBundle bundle) { - this.map.clear(); - if (bundle != null) { - for (Field field : type.getDeclaredFields()) { - try { - Meta meta = field.getAnnotation(Meta.class); - if (meta != null) { - E id = Enum.valueOf(type, field.getName()); - String value = bundle.getString(id.name()); - this.map.put(id.name(), - value == null ? null : value.trim()); - } - } catch (MissingResourceException e) { - } - } - } - } - - /** - * Take a snapshot of the changes in memory in this {@link Bundle} made by - * the "set" methods ( {@link Bundle#setString(Enum, String)}...) at the - * current time. - * - * @return a snapshot to use with {@link Bundle#restoreSnapshot(Object)} - */ - public Object takeSnapshot() { - return new HashMap(changeMap); - } - - /** - * Restore a snapshot taken with {@link Bundle}, or reset the current - * changes if the snapshot is NULL. - * - * @param snap - * the snapshot or NULL - */ - @SuppressWarnings("unchecked") - public void restoreSnapshot(Object snap) { - if (snap == null) { - changeMap.clear(); - } else { - if (snap instanceof Map) { - changeMap = (Map) snap; - } else { - throw new RuntimeException( - "Restoring changes in a Bundle must be done on a changes snapshot, " - + "or NULL to discard current changes"); - } - } - } - - /** - * Return the resource file that is closer to the {@link Locale}. - * - * @param dir - * the directory to look into - * @param name - * the file base name (without .properties) - * @param locale - * the {@link Locale} - * - * @return the closest match or NULL if none - */ - private File getPropertyFile(String dir, String name, Locale locale) { - List locales = new ArrayList(); - if (locale != null) { - String country = locale.getCountry() == null ? "" : locale - .getCountry(); - String language = locale.getLanguage() == null ? "" : locale - .getLanguage(); - if (!language.isEmpty() && !country.isEmpty()) { - locales.add("_" + language + "-" + country); - } - if (!language.isEmpty()) { - locales.add("_" + language); - } - } - - locales.add(""); - - File file = null; - for (String loc : locales) { - file = new File(dir, name + loc + ".properties"); - if (file.exists()) { - break; - } - - file = null; - } - - return file; - } -} diff --git a/src/be/nikiroo/utils/resources/BundleHelper.java b/src/be/nikiroo/utils/resources/BundleHelper.java deleted file mode 100644 index c6b26c7..0000000 --- a/src/be/nikiroo/utils/resources/BundleHelper.java +++ /dev/null @@ -1,589 +0,0 @@ -package be.nikiroo.utils.resources; - -import java.util.ArrayList; -import java.util.List; - -/** - * Internal class used to convert data to/from {@link String}s in the context of - * {@link Bundle}s. - * - * @author niki - */ -class BundleHelper { - /** - * Convert the given {@link String} into a {@link Boolean} if it represents - * a {@link Boolean}, or NULL if it doesn't. - *

- * Note: null, "strange text", ""... will all be converted to NULL. - * - * @param str - * the input {@link String} - * @param item - * the item number to use for an array of values, or -1 for - * non-arrays - * - * @return the converted {@link Boolean} or NULL - */ - static public Boolean parseBoolean(String str, int item) { - str = getItem(str, item); - if (str == null) { - return null; - } - - if (str.equalsIgnoreCase("true") || str.equalsIgnoreCase("on") - || str.equalsIgnoreCase("yes")) - return true; - if (str.equalsIgnoreCase("false") || str.equalsIgnoreCase("off") - || str.equalsIgnoreCase("no")) - return false; - - return null; - } - - /** - * Return a {@link String} representation of the given {@link Boolean}. - * - * @param value - * the input value - * - * @return the raw {@link String} value that correspond to it - */ - static public String fromBoolean(boolean value) { - return Boolean.toString(value); - } - - /** - * Convert the given {@link String} into a {@link Integer} if it represents - * a {@link Integer}, or NULL if it doesn't. - *

- * Note: null, "strange text", ""... will all be converted to NULL. - * - * @param str - * the input {@link String} - * @param item - * the item number to use for an array of values, or -1 for - * non-arrays - * - * @return the converted {@link Integer} or NULL - */ - static public Integer parseInteger(String str, int item) { - str = getItem(str, item); - if (str == null) { - return null; - } - - try { - return Integer.parseInt(str); - } catch (Exception e) { - } - - return null; - } - - /** - * Return a {@link String} representation of the given {@link Integer}. - * - * @param value - * the input value - * - * @return the raw {@link String} value that correspond to it - */ - static public String fromInteger(int value) { - return Integer.toString(value); - } - - /** - * Convert the given {@link String} into a {@link Character} if it - * represents a {@link Character}, or NULL if it doesn't. - *

- * Note: null, "strange text", ""... will all be converted to NULL - * (remember: any {@link String} whose length is not 1 is not a - * {@link Character}). - * - * @param str - * the input {@link String} - * @param item - * the item number to use for an array of values, or -1 for - * non-arrays - * - * @return the converted {@link Character} or NULL - */ - static public Character parseCharacter(String str, int item) { - str = getItem(str, item); - if (str == null) { - return null; - } - - String s = str.trim(); - if (s.length() == 1) { - return s.charAt(0); - } - - return null; - } - - /** - * Return a {@link String} representation of the given {@link Boolean}. - * - * @param value - * the input value - * - * @return the raw {@link String} value that correspond to it - */ - static public String fromCharacter(char value) { - return Character.toString(value); - } - - /** - * Convert the given {@link String} into a colour (represented here as an - * {@link Integer}) if it represents a colour, or NULL if it doesn't. - *

- * The returned colour value is an ARGB value. - * - * @param str - * the input {@link String} - * @param item - * the item number to use for an array of values, or -1 for - * non-arrays - * - * @return the converted colour as an {@link Integer} value or NULL - */ - static Integer parseColor(String str, int item) { - str = getItem(str, item); - if (str == null) { - return null; - } - - Integer rep = null; - - str = str.trim(); - int r = 0, g = 0, b = 0, a = -1; - if (str.startsWith("#") && (str.length() == 7 || str.length() == 9)) { - try { - r = Integer.parseInt(str.substring(1, 3), 16); - g = Integer.parseInt(str.substring(3, 5), 16); - b = Integer.parseInt(str.substring(5, 7), 16); - if (str.length() == 9) { - a = Integer.parseInt(str.substring(7, 9), 16); - } else { - a = 255; - } - - } catch (NumberFormatException e) { - // no changes - } - } - - // Try by name if still not found - if (a == -1) { - if ("black".equalsIgnoreCase(str)) { - a = 255; - r = 0; - g = 0; - b = 0; - } else if ("white".equalsIgnoreCase(str)) { - a = 255; - r = 255; - g = 255; - b = 255; - } else if ("red".equalsIgnoreCase(str)) { - a = 255; - r = 255; - g = 0; - b = 0; - } else if ("green".equalsIgnoreCase(str)) { - a = 255; - r = 0; - g = 255; - b = 0; - } else if ("blue".equalsIgnoreCase(str)) { - a = 255; - r = 0; - g = 0; - b = 255; - } else if ("grey".equalsIgnoreCase(str) - || "gray".equalsIgnoreCase(str)) { - a = 255; - r = 128; - g = 128; - b = 128; - } else if ("cyan".equalsIgnoreCase(str)) { - a = 255; - r = 0; - g = 255; - b = 255; - } else if ("magenta".equalsIgnoreCase(str)) { - a = 255; - r = 255; - g = 0; - b = 255; - } else if ("yellow".equalsIgnoreCase(str)) { - a = 255; - r = 255; - g = 255; - b = 0; - } - } - - if (a != -1) { - rep = ((a & 0xFF) << 24) // - | ((r & 0xFF) << 16) // - | ((g & 0xFF) << 8) // - | ((b & 0xFF) << 0); - } - - return rep; - } - - /** - * Return a {@link String} representation of the given colour. - *

- * The colour value is interpreted as an ARGB value. - * - * @param color - * the ARGB colour value - * @return the raw {@link String} value that correspond to it - */ - static public String fromColor(int color) { - int a = (color >> 24) & 0xFF; - int r = (color >> 16) & 0xFF; - int g = (color >> 8) & 0xFF; - int b = (color >> 0) & 0xFF; - - String rs = Integer.toString(r, 16); - String gs = Integer.toString(g, 16); - String bs = Integer.toString(b, 16); - String as = ""; - if (a < 255) { - as = Integer.toString(a, 16); - } - - return "#" + rs + gs + bs + as; - } - - /** - * The size of this raw list (note than a NULL list is of size 0). - * - * @param raw - * the raw list - * - * @return its size if it is a list (NULL is an empty list), -1 if it is not - * a list - */ - static public int getListSize(String raw) { - if (raw == null) { - return 0; - } - - List list = parseList(raw, -1); - if (list == null) { - return -1; - } - - return list.size(); - } - - /** - * Return a {@link String} representation of the given list of values. - *

- * The list of values is comma-separated and each value is surrounded by - * double-quotes; caret (^) and double-quotes (") are escaped by a caret. - * - * @param str - * the input value - * @param item - * the item number to use for an array of values, or -1 for - * non-arrays - * - * @return the raw {@link String} value that correspond to it - */ - static public List parseList(String str, int item) { - if (str == null) { - return null; - } - - if (item >= 0) { - str = getItem(str, item); - } - - List list = new ArrayList(); - try { - boolean inQuote = false; - boolean prevIsBackSlash = false; - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < str.length(); i++) { - char car = str.charAt(i); - - if (prevIsBackSlash) { - // We don't process it here - builder.append(car); - prevIsBackSlash = false; - } else { - switch (car) { - case '"': - // We don't process it here - builder.append(car); - - if (inQuote) { - list.add(unescape(builder.toString())); - builder.setLength(0); - } - - inQuote = !inQuote; - break; - case '^': - // We don't process it here - builder.append(car); - prevIsBackSlash = true; - break; - case ' ': - case '\n': - case '\r': - if (inQuote) { - builder.append(car); - } - break; - - case ',': - if (!inQuote) { - break; - } - // continue to default - default: - if (!inQuote) { - // Bad format! - return null; - } - - builder.append(car); - break; - } - } - } - - if (inQuote || prevIsBackSlash) { - // Bad format! - return null; - } - - } catch (Exception e) { - return null; - } - - return list; - } - - /** - * Return a {@link String} representation of the given list of values. - *

- * NULL will be assimilated to an empty {@link String} if later non-null - * values exist, or just ignored if not. - *

- * Example: - *

    - *
  • 1,NULL, 3 will become 1, - * "", 3
  • - *
  • 1,NULL, NULL will become 1
  • - *
  • NULL, NULL, NULL will become an empty list - *
  • - *
- * - * @param list - * the input value - * - * @return the raw {@link String} value that correspond to it - */ - static public String fromList(List list) { - if (list == null) { - list = new ArrayList(); - } - - int last = list.size() - 1; - for (int i = 0; i < list.size(); i++) { - if (list.get(i) != null) { - last = i; - } - } - - StringBuilder builder = new StringBuilder(); - for (int i = 0; i <= last; i++) { - String item = list.get(i); - if (item == null) { - item = ""; - } - - if (builder.length() > 0) { - builder.append(", "); - } - builder.append(escape(item)); - } - - return builder.toString(); - } - - /** - * Return a {@link String} representation of the given list of values. - *

- * NULL will be assimilated to an empty {@link String} if later non-null - * values exist, or just ignored if not. - *

- * Example: - *

    - *
  • 1,NULL, 3 will become 1, - * "", 3
  • - *
  • 1,NULL, NULL will become 1
  • - *
  • NULL, NULL, NULL will become an empty list - *
  • - *
- * - * @param list - * the input value - * @param value - * the value to insert - * @param item - * the position to insert it at - * - * @return the raw {@link String} value that correspond to it - */ - static public String fromList(List list, String value, int item) { - if (list == null) { - list = new ArrayList(); - } - - while (item >= list.size()) { - list.add(null); - } - list.set(item, value); - - return fromList(list); - } - - /** - * Return a {@link String} representation of the given list of values. - *

- * NULL will be assimilated to an empty {@link String} if later non-null - * values exist, or just ignored if not. - *

- * Example: - *

    - *
  • 1,NULL, 3 will become 1, - * "", 3
  • - *
  • 1,NULL, NULL will become 1
  • - *
  • NULL, NULL, NULL will become an empty list - *
  • - *
- * - * @param list - * the input value - * @param value - * the value to insert - * @param item - * the position to insert it at - * - * @return the raw {@link String} value that correspond to it - */ - static public String fromList(String list, String value, int item) { - return fromList(parseList(list, -1), value, item); - } - - /** - * Escape the given value for list formating (no carets, no NEWLINES...). - *

- * You can unescape it with {@link BundleHelper#unescape(String)} - * - * @param value - * the value to escape - * - * @return an escaped value that can unquoted by the reverse operation - * {@link BundleHelper#unescape(String)} - */ - static public String escape(String value) { - return '"' + value// - .replace("^", "^^") // - .replace("\"", "^\"") // - .replace("\n", "^\n") // - .replace("\r", "^\r") // - + '"'; - } - - /** - * Unescape the given value for list formating (change ^n into NEWLINE and - * so on). - *

- * You can escape it with {@link BundleHelper#escape(String)} - * - * @param value - * the value to escape - * - * @return an unescaped value that can reverted by the reverse operation - * {@link BundleHelper#escape(String)}, or NULL if it was badly - * formated - */ - static public String unescape(String value) { - if (value.length() < 2 || !value.startsWith("\"") - || !value.endsWith("\"")) { - // Bad format - return null; - } - - value = value.substring(1, value.length() - 1); - - boolean prevIsBackslash = false; - StringBuilder builder = new StringBuilder(); - for (char car : value.toCharArray()) { - if (prevIsBackslash) { - switch (car) { - case 'n': - case 'N': - builder.append('\n'); - break; - case 'r': - case 'R': - builder.append('\r'); - break; - default: // includes ^ and " - builder.append(car); - break; - } - prevIsBackslash = false; - } else { - if (car == '^') { - prevIsBackslash = true; - } else { - builder.append(car); - } - } - } - - if (prevIsBackslash) { - // Bad format - return null; - } - - return builder.toString(); - } - - /** - * Retrieve the specific item in the given value, assuming it is an array. - * - * @param value - * the value to look into - * @param item - * the item number to get for an array of values, or -1 for - * non-arrays (in that case, simply return the value as-is) - * - * @return the value as-is for non arrays, the item item if found, - * NULL if not - */ - static private String getItem(String value, int item) { - if (item >= 0) { - value = null; - List values = parseList(value, -1); - if (values != null && item < values.size()) { - value = values.get(item); - } - } - - return value; - } -} diff --git a/src/be/nikiroo/utils/resources/Bundles.java b/src/be/nikiroo/utils/resources/Bundles.java deleted file mode 100644 index ad7b99d..0000000 --- a/src/be/nikiroo/utils/resources/Bundles.java +++ /dev/null @@ -1,40 +0,0 @@ -package be.nikiroo.utils.resources; - -import java.util.ResourceBundle; - -/** - * This class help you get UTF-8 bundles for this application. - * - * @author niki - */ -public class Bundles { - /** - * The configuration directory where we try to get the .properties - * in priority, or NULL to get the information from the compiled resources. - */ - static private String confDir = null; - - /** - * Set the primary configuration directory to look for .properties - * files in. - * - * All {@link ResourceBundle}s returned by this class after that point will - * respect this new directory. - * - * @param confDir - * the new directory - */ - static public void setDirectory(String confDir) { - Bundles.confDir = confDir; - } - - /** - * Get the primary configuration directory to look for .properties - * files in. - * - * @return the directory - */ - static public String getDirectory() { - return Bundles.confDir; - } -} diff --git a/src/be/nikiroo/utils/resources/FixedResourceBundleControl.java b/src/be/nikiroo/utils/resources/FixedResourceBundleControl.java deleted file mode 100644 index b53da9d..0000000 --- a/src/be/nikiroo/utils/resources/FixedResourceBundleControl.java +++ /dev/null @@ -1,60 +0,0 @@ -package be.nikiroo.utils.resources; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.URL; -import java.net.URLConnection; -import java.util.Locale; -import java.util.PropertyResourceBundle; -import java.util.ResourceBundle; -import java.util.ResourceBundle.Control; - -/** - * Fixed ResourceBundle.Control class. It will use UTF-8 for the files to load. - * - * Also support an option to first check into the given path before looking into - * the resources. - * - * @author niki - * - */ -class FixedResourceBundleControl extends Control { - @Override - public ResourceBundle newBundle(String baseName, Locale locale, - String format, ClassLoader loader, boolean reload) - throws IllegalAccessException, InstantiationException, IOException { - // The below is a copy of the default implementation. - String bundleName = toBundleName(baseName, locale); - String resourceName = toResourceName(bundleName, "properties"); - - ResourceBundle bundle = null; - InputStream stream = null; - if (reload) { - URL url = loader.getResource(resourceName); - if (url != null) { - URLConnection connection = url.openConnection(); - if (connection != null) { - connection.setUseCaches(false); - stream = connection.getInputStream(); - } - } - } else { - stream = loader.getResourceAsStream(resourceName); - } - - if (stream != null) { - try { - // This line is changed to make it to read properties files - // as UTF-8. - // How can someone use an archaic encoding such as ISO 8859-1 by - // *DEFAULT* is beyond me... - bundle = new PropertyResourceBundle(new InputStreamReader( - stream, "UTF-8")); - } finally { - stream.close(); - } - } - return bundle; - } -} \ No newline at end of file diff --git a/src/be/nikiroo/utils/resources/Meta.java b/src/be/nikiroo/utils/resources/Meta.java deleted file mode 100644 index fb4d491..0000000 --- a/src/be/nikiroo/utils/resources/Meta.java +++ /dev/null @@ -1,132 +0,0 @@ -package be.nikiroo.utils.resources; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Annotation used to give some information about the translation keys, so the - * translation .properties file can be created programmatically. - * - * @author niki - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.FIELD) -public @interface Meta { - /** - * The format of an item (the values it is expected to be of). - *

- * Note that the INI file can contain arbitrary data, but it is expected to - * be valid. - * - * @author niki - */ - public enum Format { - /** An integer value, can be negative. */ - INT, - /** true or false. */ - BOOLEAN, - /** Any text String. */ - STRING, - /** A password field. */ - PASSWORD, - /** A colour (either by name or #rrggbb or #aarrggbb). */ - COLOR, - /** A locale code (e.g., fr-BE, en-GB, es...). */ - LOCALE, - /** A path to a file. */ - FILE, - /** A path to a directory. */ - DIRECTORY, - /** A fixed list of values (see {@link Meta#list()} for the values). */ - FIXED_LIST, - /** - * A fixed list of values (see {@link Meta#list()} for the values) OR a - * custom String value (basically, a {@link Format#FIXED_LIST} with an - * option to enter a not accounted for value). - */ - COMBO_LIST, - } - - /** - * A description for this item: what it is or does, how to explain that item - * to the user including what can be used here (i.e., %s = file name, %d = - * file size...). - *

- * For group, the first line ('\\n'-separated) will be used as a title while - * the rest will be the description. - * - * @return what it is - */ - String description() default ""; - - /** - * This item should be hidden from the user (she will still be able to - * modify it if she opens the file manually). - *

- * Defaults to FALSE (visible). - * - * @return TRUE if it should stay hidden - */ - boolean hidden() default false; - - /** - * This item is only used as a group, not as an option. - *

- * For instance, you could have LANGUAGE_CODE as a group for which you won't - * use the value in the program, and LANGUAGE_CODE_FR, LANGUAGE_CODE_EN - * inside for which the value must be set. - * - * @return TRUE if it is a group - */ - boolean group() default false; - - /** - * What format should/must this key be in. - * - * @return the format it is in - */ - Format format() default Format.STRING; - - /** - * The list of fixed values this item can be (either for - * {@link Format#FIXED_LIST} or {@link Format#COMBO_LIST}). - * - * @return the list of values - */ - String[] list() default {}; - - /** - * This item can be left unspecified. - * - * @return TRUE if it can - */ - boolean nullable() default true; - - /** - * The default value of this item. - * - * @return the value - */ - String def() default ""; - - /** - * This item is a comma-separated list of values instead of a single value. - *

- * The list items are separated by a comma, each surrounded by - * double-quotes, with backslashes and double-quotes escaped by a backslash. - *

- * Example: "un", "deux" - * - * @return TRUE if it is - */ - boolean array() default false; - - /** - * @deprecated add the info into the description, as only the description - * will be translated. - */ - @Deprecated - String info() default ""; -} diff --git a/src/be/nikiroo/utils/resources/MetaInfo.java b/src/be/nikiroo/utils/resources/MetaInfo.java deleted file mode 100644 index 70c6c43..0000000 --- a/src/be/nikiroo/utils/resources/MetaInfo.java +++ /dev/null @@ -1,770 +0,0 @@ -package be.nikiroo.utils.resources; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import be.nikiroo.utils.resources.Meta.Format; - -/** - * A graphical item that reflect a configuration option from the given - * {@link Bundle}. - * - * @author niki - * - * @param - * the type of {@link Bundle} to edit - */ -public class MetaInfo> implements Iterable> { - private final Bundle bundle; - private final E id; - - private Meta meta; - private List> children = new ArrayList>(); - - private String value; - private List reloadedListeners = new ArrayList(); - private List saveListeners = new ArrayList(); - - private String name; - private boolean hidden; - private String description; - - private boolean dirty; - - /** - * Create a new {@link MetaInfo} from a value (without children). - *

- * For instance, you can call - * new MetaInfo(Config.class, configBundle, Config.MY_VALUE). - * - * @param type - * the type of enum the value is - * @param bundle - * the bundle this value belongs to - * @param id - * the value itself - */ - public MetaInfo(Class type, Bundle bundle, E id) { - this.bundle = bundle; - this.id = id; - - try { - this.meta = type.getDeclaredField(id.name()).getAnnotation( - Meta.class); - } catch (NoSuchFieldException e) { - } catch (SecurityException e) { - } - - // We consider that if a description bundle is used, everything is in it - - String description = null; - if (bundle.getDescriptionBundle() != null) { - description = bundle.getDescriptionBundle().getString(id); - if (description != null && description.trim().isEmpty()) { - description = null; - } - } - if (description == null) { - description = meta.description(); - if (description == null) { - description = ""; - } - } - - String name = idToName(id, null); - - // Special rules for groups: - if (meta.group()) { - String groupName = description.split("\n")[0]; - description = description.substring(groupName.length()).trim(); - if (!groupName.isEmpty()) { - name = groupName; - } - } - - if (meta.def() != null && !meta.def().isEmpty()) { - if (!description.isEmpty()) { - description += "\n\n"; - } - description += "(Default value: " + meta.def() + ")"; - } - - this.name = name; - this.hidden = meta.hidden(); - this.description = description; - - reload(); - } - - /** - * For normal items, this is the name of this item, deduced from its ID (or - * in other words, it is the ID but presented in a displayable form). - *

- * For group items, this is the first line of the description if it is not - * empty (else, it is the ID in the same way as normal items). - *

- * Never NULL. - * - * - * @return the name, never NULL - */ - public String getName() { - return name; - } - - /** - * This item should be hidden from the user (she will still be able to - * modify it if she opens the file manually). - * - * @return TRUE if it should stay hidden - */ - public boolean isHidden() { - return hidden; - } - - /** - * A description for this item: what it is or does, how to explain that item - * to the user including what can be used here (i.e., %s = file name, %d = - * file size...). - *

- * For group, the first line ('\\n'-separated) will be used as a title while - * the rest will be the description. - *

- * If a default value is known, it will be specified here, too. - *

- * Never NULL. - * - * @return the description, not NULL - */ - public String getDescription() { - return description; - } - - /** - * The format this item is supposed to follow - * - * @return the format - */ - public Format getFormat() { - return meta.format(); - } - - /** - * The allowed list of values that a {@link Format#FIXED_LIST} item is - * allowed to be, or a list of suggestions for {@link Format#COMBO_LIST} - * items. Also works for {@link Format#LOCALE}. - *

- * Will always allow an empty string in addition to the rest. - * - * @return the list of values - */ - public String[] getAllowedValues() { - String[] list = meta.list(); - - String[] withEmpty = new String[list.length + 1]; - withEmpty[0] = ""; - for (int i = 0; i < list.length; i++) { - withEmpty[i + 1] = list[i]; - } - - return withEmpty; - } - - /** - * Return all the languages known by the program for this bundle. - *

- * This only works for {@link TransBundle}, and will return an empty list if - * this is not a {@link TransBundle}. - * - * @return the known language codes - */ - public List getKnownLanguages() { - if (bundle instanceof TransBundle) { - return ((TransBundle) bundle).getKnownLanguages(); - } - - return new ArrayList(); - } - - /** - * This item is a comma-separated list of values instead of a single value. - *

- * The list items are separated by a comma, each surrounded by - * double-quotes, with backslashes and double-quotes escaped by a backslash. - *

- * Example: "un", "deux" - * - * @return TRUE if it is - */ - public boolean isArray() { - return meta.array(); - } - - /** - * A manual flag to specify if the data has been changed or not, which can - * be used by {@link MetaInfo#save(boolean)}. - * - * @return TRUE if it is dirty (if it has changed) - */ - public boolean isDirty() { - return dirty; - } - - /** - * A manual flag to specify that the data has been changed, which can be - * used by {@link MetaInfo#save(boolean)}. - */ - public void setDirty() { - this.dirty = true; - } - - /** - * The number of items in this item if it {@link MetaInfo#isArray()}, or -1 - * if not. - * - * @param useDefaultIfEmpty - * check the size of the default list instead if the list is - * empty - * - * @return -1 or the number of items - */ - public int getListSize(boolean useDefaultIfEmpty) { - if (!isArray()) { - return -1; - } - - return BundleHelper.getListSize(getString(-1, useDefaultIfEmpty)); - } - - /** - * This item is only used as a group, not as an option. - *

- * For instance, you could have LANGUAGE_CODE as a group for which you won't - * use the value in the program, and LANGUAGE_CODE_FR, LANGUAGE_CODE_EN - * inside for which the value must be set. - * - * @return TRUE if it is a group - */ - public boolean isGroup() { - return meta.group(); - } - - /** - * The value stored by this item, as a {@link String}. - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * @param useDefaultIfEmpty - * use the default value instead of NULL if the setting is not - * set - * - * @return the value - */ - public String getString(int item, boolean useDefaultIfEmpty) { - if (isArray() && item >= 0) { - List values = BundleHelper.parseList(value, -1); - if (values != null && item < values.size()) { - return values.get(item); - } - - if (useDefaultIfEmpty) { - return getDefaultString(item); - } - - return null; - } - - if (value == null && useDefaultIfEmpty) { - return getDefaultString(item); - } - - return value; - } - - /** - * The default value of this item, as a {@link String}. - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * - * @return the default value - */ - public String getDefaultString(int item) { - if (isArray() && item >= 0) { - List values = BundleHelper.parseList(meta.def(), item); - if (values != null && item < values.size()) { - return values.get(item); - } - - return null; - } - - return meta.def(); - } - - /** - * The value stored by this item, as a {@link Boolean}. - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * @param useDefaultIfEmpty - * use the default value instead of NULL if the setting is not - * set - * - * @return the value - */ - public Boolean getBoolean(int item, boolean useDefaultIfEmpty) { - return BundleHelper - .parseBoolean(getString(item, useDefaultIfEmpty), -1); - } - - /** - * The default value of this item, as a {@link Boolean}. - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * - * @return the default value - */ - public Boolean getDefaultBoolean(int item) { - return BundleHelper.parseBoolean(getDefaultString(item), -1); - } - - /** - * The value stored by this item, as a {@link Character}. - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * @param useDefaultIfEmpty - * use the default value instead of NULL if the setting is not - * set - * - * @return the value - */ - public Character getCharacter(int item, boolean useDefaultIfEmpty) { - return BundleHelper.parseCharacter(getString(item, useDefaultIfEmpty), - -1); - } - - /** - * The default value of this item, as a {@link Character}. - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * - * @return the default value - */ - public Character getDefaultCharacter(int item) { - return BundleHelper.parseCharacter(getDefaultString(item), -1); - } - - /** - * The value stored by this item, as an {@link Integer}. - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * @param useDefaultIfEmpty - * use the default value instead of NULL if the setting is not - * set - * - * @return the value - */ - public Integer getInteger(int item, boolean useDefaultIfEmpty) { - return BundleHelper - .parseInteger(getString(item, useDefaultIfEmpty), -1); - } - - /** - * The default value of this item, as an {@link Integer}. - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * - * @return the default value - */ - public Integer getDefaultInteger(int item) { - return BundleHelper.parseInteger(getDefaultString(item), -1); - } - - /** - * The value stored by this item, as a colour (represented here as an - * {@link Integer}) if it represents a colour, or NULL if it doesn't. - *

- * The returned colour value is an ARGB value. - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * @param useDefaultIfEmpty - * use the default value instead of NULL if the setting is not - * set - * - * @return the value - */ - public Integer getColor(int item, boolean useDefaultIfEmpty) { - return BundleHelper.parseColor(getString(item, useDefaultIfEmpty), -1); - } - - /** - * The default value stored by this item, as a colour (represented here as - * an {@link Integer}) if it represents a colour, or NULL if it doesn't. - *

- * The returned colour value is an ARGB value. - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * - * @return the value - */ - public Integer getDefaultColor(int item) { - return BundleHelper.parseColor(getDefaultString(item), -1); - } - - /** - * A {@link String} representation of the list of values. - *

- * The list of values is comma-separated and each value is surrounded by - * double-quotes; backslashes and double-quotes are escaped by a backslash. - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * @param useDefaultIfEmpty - * use the default value instead of NULL if the setting is not - * set - * - * @return the value - */ - public List getList(int item, boolean useDefaultIfEmpty) { - return BundleHelper.parseList(getString(item, useDefaultIfEmpty), -1); - } - - /** - * A {@link String} representation of the default list of values. - *

- * The list of values is comma-separated and each value is surrounded by - * double-quotes; backslashes and double-quotes are escaped by a backslash. - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * - * @return the value - */ - public List getDefaultList(int item) { - return BundleHelper.parseList(getDefaultString(item), -1); - } - - /** - * The value stored by this item, as a {@link String}. - * - * @param value - * the new value - * @param item - * the item number to set for an array of values, or -1 to set - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - */ - public void setString(String value, int item) { - if (isArray() && item >= 0) { - this.value = BundleHelper.fromList(this.value, value, item); - } else { - this.value = value; - } - } - - /** - * The value stored by this item, as a {@link Boolean}. - * - * @param value - * the new value - * @param item - * the item number to set for an array of values, or -1 to set - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - */ - public void setBoolean(boolean value, int item) { - setString(BundleHelper.fromBoolean(value), item); - } - - /** - * The value stored by this item, as a {@link Character}. - * - * @param value - * the new value - * @param item - * the item number to set for an array of values, or -1 to set - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - */ - public void setCharacter(char value, int item) { - setString(BundleHelper.fromCharacter(value), item); - } - - /** - * The value stored by this item, as an {@link Integer}. - * - * @param value - * the new value - * @param item - * the item number to set for an array of values, or -1 to set - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - */ - public void setInteger(int value, int item) { - setString(BundleHelper.fromInteger(value), item); - } - - /** - * The value stored by this item, as a colour (represented here as an - * {@link Integer}) if it represents a colour, or NULL if it doesn't. - *

- * The colour value is an ARGB value. - * - * @param value - * the value - * @param item - * the item number to set for an array of values, or -1 to set - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - */ - public void setColor(int value, int item) { - setString(BundleHelper.fromColor(value), item); - } - - /** - * A {@link String} representation of the default list of values. - *

- * The list of values is comma-separated and each value is surrounded by - * double-quotes; backslashes and double-quotes are escaped by a backslash. - * - * @param value - * the {@link String} representation - * @param item - * the item number to set for an array of values, or -1 to set - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - */ - public void setList(List value, int item) { - setString(BundleHelper.fromList(value), item); - } - - /** - * Reload the value from the {@link Bundle}, so the last value that was - * saved will be used. - */ - public void reload() { - if (bundle.isSet(id, false)) { - value = bundle.getString(id); - } else { - value = null; - } - - // Copy the list so we can create new listener in a listener - for (Runnable listener : new ArrayList(reloadedListeners)) { - try { - listener.run(); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - /** - * Add a listener that will be called after a reload operation. - *

- * You could use it to refresh the UI for instance. - * - * @param listener - * the listener - */ - public void addReloadedListener(Runnable listener) { - reloadedListeners.add(listener); - } - - /** - * Save the current value to the {@link Bundle}. - *

- * Note that listeners will be called before the dirty check and - * before saving the value. - * - * @param onlyIfDirty - * only save the data if the dirty flag is set (will reset the - * dirty flag) - */ - public void save(boolean onlyIfDirty) { - // Copy the list so we can create new listener in a listener - for (Runnable listener : new ArrayList(saveListeners)) { - try { - listener.run(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - if (!onlyIfDirty || isDirty()) { - bundle.setString(id, value); - } - } - - /** - * Add a listener that will be called before a save operation. - *

- * You could use it to make some modification to the stored value before it - * is saved. - * - * @param listener - * the listener - */ - public void addSaveListener(Runnable listener) { - saveListeners.add(listener); - } - - /** - * The sub-items if any (if no sub-items, will return an empty list). - *

- * Sub-items are declared when a {@link Meta} has an ID that starts with the - * ID of a {@link Meta#group()} {@link MetaInfo}. - *

- * For instance: - *

    - *
  • {@link Meta} MY_PREFIX is a {@link Meta#group()}
  • - *
  • {@link Meta} MY_PREFIX_DESCRIPTION is another {@link Meta}
  • - *
  • MY_PREFIX_DESCRIPTION will be a child of MY_PREFIX
  • - *
- * - * @return the sub-items if any - */ - public List> getChildren() { - return children; - } - - /** - * The number of sub-items, if any. - * - * @return the number or 0 - */ - public int size() { - return children.size(); - } - - @Override - public Iterator> iterator() { - return children.iterator(); - } - - /** - * Create a list of {@link MetaInfo}, one for each of the item in the given - * {@link Bundle}. - * - * @param - * the type of {@link Bundle} to edit - * @param type - * a class instance of the item type to work on - * @param bundle - * the {@link Bundle} to sort through - * - * @return the list - */ - static public > List> getItems(Class type, - Bundle bundle) { - List> list = new ArrayList>(); - List> shadow = new ArrayList>(); - for (E id : type.getEnumConstants()) { - MetaInfo info = new MetaInfo(type, bundle, id); - if (!info.hidden) { - list.add(info); - shadow.add(info); - } - } - - for (int i = 0; i < list.size(); i++) { - MetaInfo info = list.get(i); - - MetaInfo parent = findParent(info, shadow); - if (parent != null) { - list.remove(i--); - parent.children.add(info); - info.name = idToName(info.id, parent.id); - } - } - - return list; - } - - /** - * Find the longest parent of the given {@link MetaInfo}, which means: - *
    - *
  • the parent is a {@link Meta#group()}
  • - *
  • the parent Id is a substring of the Id of the given {@link MetaInfo}
  • - *
  • there is no other parent sharing a substring for this - * {@link MetaInfo} with a longer Id
  • - *
- * - * @param - * the kind of enum - * @param info - * the info to look for a parent for - * @param candidates - * the list of potential parents - * - * @return the longest parent or NULL if no parent is found - */ - static private > MetaInfo findParent(MetaInfo info, - List> candidates) { - String id = info.id.toString(); - MetaInfo group = null; - for (MetaInfo pcandidate : candidates) { - if (pcandidate.isGroup()) { - String candidateId = pcandidate.id.toString(); - if (!id.equals(candidateId) && id.startsWith(candidateId)) { - if (group == null - || group.id.toString().length() < candidateId - .length()) { - group = pcandidate; - } - } - } - } - - return group; - } - - static private > String idToName(E id, E prefix) { - String name = id.toString(); - if (prefix != null && name.startsWith(prefix.toString())) { - name = name.substring(prefix.toString().length()); - } - - if (name.length() > 0) { - name = name.substring(0, 1).toUpperCase() - + name.substring(1).toLowerCase(); - } - - name = name.replace("_", " "); - - return name.trim(); - } -} diff --git a/src/be/nikiroo/utils/resources/TransBundle.java b/src/be/nikiroo/utils/resources/TransBundle.java deleted file mode 100644 index 7b2edb1..0000000 --- a/src/be/nikiroo/utils/resources/TransBundle.java +++ /dev/null @@ -1,404 +0,0 @@ -package be.nikiroo.utils.resources; - -import java.io.File; -import java.io.IOException; -import java.io.Writer; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; -import java.util.regex.Pattern; - -/** - * This class manages a translation-dedicated Bundle. - *

- * Two special cases are handled for the used enum: - *

    - *
  • NULL will always will return an empty {@link String}
  • - *
  • DUMMY will return "[DUMMY]" (maybe with a suffix and/or "NOUTF")
  • - *
- * - * @param - * the enum to use to get values out of this class - * - * @author niki - */ -public class TransBundle> extends Bundle { - private boolean utf = true; - private Locale locale; - private boolean defaultLocale = false; - - /** - * Create a translation service with the default language. - * - * @param type - * a runtime instance of the class of E - * @param name - * the name of the {@link Bundles} - */ - public TransBundle(Class type, Enum name) { - this(type, name, (Locale) null); - } - - /** - * Create a translation service for the given language (will fall back to - * the default one i not found). - * - * @param type - * a runtime instance of the class of E - * @param name - * the name of the {@link Bundles} - * @param language - * the language to use, can be NULL for default - */ - public TransBundle(Class type, Enum name, String language) { - super(type, name, null); - setLocale(language); - } - - /** - * Create a translation service for the given language (will fall back to - * the default one i not found). - * - * @param type - * a runtime instance of the class of E - * @param name - * the name of the {@link Bundles} - * @param language - * the language to use, can be NULL for default - */ - public TransBundle(Class type, Enum name, Locale language) { - super(type, name, null); - setLocale(language); - } - - /** - * Translate the given id into user text. - * - * @param stringId - * the ID to translate - * @param values - * the values to insert instead of the place holders in the - * translation - * - * @return the translated text with the given value where required or NULL - * if not found (not present in the resource file) - */ - public String getString(E stringId, Object... values) { - return getStringX(stringId, "", values); - } - - /** - * Translate the given id into user text. - * - * @param stringId - * the ID to translate - * @param values - * the values to insert instead of the place holders in the - * translation - * - * @return the translated text with the given value where required or NULL - * if not found (not present in the resource file) - */ - public String getStringNOUTF(E stringId, Object... values) { - return getStringX(stringId, "NOUTF", values); - } - - /** - * Translate the given id suffixed with the runtime value "_suffix" (that - * is, "_" and suffix) into user text. - * - * @param stringId - * the ID to translate - * @param values - * the values to insert instead of the place holders in the - * translation - * @param suffix - * the runtime suffix - * - * @return the translated text with the given value where required or NULL - * if not found (not present in the resource file) - */ - public String getStringX(E stringId, String suffix, Object... values) { - E id = stringId; - String result = ""; - - String key = id.name() - + ((suffix == null || suffix.isEmpty()) ? "" : "_" - + suffix.toUpperCase()); - - if (!isUnicode()) { - if (containsKey(key + "_NOUTF")) { - key += "_NOUTF"; - } - } - - if ("NULL".equals(id.name().toUpperCase())) { - result = ""; - } else if ("DUMMY".equals(id.name().toUpperCase())) { - result = "[" + key.toLowerCase() + "]"; - } else if (containsKey(key)) { - result = getString(key, null); - if (result == null) { - result = getMetaDef(id.name()); - } - } else { - result = null; - } - - if (values != null && values.length > 0 && result != null) { - return String.format(locale, result, values); - } - - return result; - } - - /** - * Check if unicode characters should be used. - * - * @return TRUE to allow unicode - */ - public boolean isUnicode() { - return utf; - } - - /** - * Allow or disallow unicode characters in the program. - * - * @param utf - * TRUE to allow unuciode, FALSE to only allow ASCII characters - */ - public void setUnicode(boolean utf) { - this.utf = utf; - } - - /** - * Return all the languages known by the program for this bundle. - * - * @return the known language codes - */ - public List getKnownLanguages() { - return getKnownLanguages(keyType); - } - - /** - * The current language (which can be the default one, but NOT NULL). - * - * @return the language, not NULL - */ - public Locale getLocale() { - return locale; - } - - /** - * The current language (which can be the default one, but NOT NULL). - * - * @return the language, not NULL, in a display format (fr-BE, en-GB, es, - * de...) - */ - public String getLocaleString() { - String lang = locale.getLanguage(); - String country = locale.getCountry(); - if (country != null && !country.isEmpty()) { - return lang + "-" + country; - } - return lang; - } - - /** - * Initialise the translation mappings for the given language. - * - * @param language - * the language to initialise, in the form "en-GB" or "fr" for - * instance - */ - private void setLocale(String language) { - setLocale(getLocaleFor(language)); - } - - /** - * Initialise the translation mappings for the given language. - * - * @param language - * the language to initialise, or NULL for default - */ - private void setLocale(Locale language) { - if (language != null) { - defaultLocale = false; - locale = language; - } else { - defaultLocale = true; - locale = Locale.getDefault(); - } - - setBundle(keyType, locale, false); - } - - @Override - public void reload(boolean resetToDefault) { - setBundle(keyType, locale, resetToDefault); - } - - @Override - public String getString(E id) { - return getString(id, (Object[]) null); - } - - /** - * Create/update the .properties files for each supported language and for - * the default language. - *

- * Note: this method is NOT thread-safe. - * - * @param path - * the path where the .properties files are - * - * @throws IOException - * in case of IO errors - */ - @Override - public void updateFile(String path) throws IOException { - String prev = locale.getLanguage(); - Object status = takeSnapshot(); - - // default locale - setLocale((Locale) null); - if (prev.equals(Locale.getDefault().getLanguage())) { - // restore snapshot if default locale = current locale - restoreSnapshot(status); - } - super.updateFile(path); - - for (String lang : getKnownLanguages()) { - setLocale(lang); - if (lang.equals(prev)) { - restoreSnapshot(status); - } - super.updateFile(path); - } - - setLocale(prev); - restoreSnapshot(status); - } - - @Override - protected File getUpdateFile(String path) { - String code = locale.toString(); - File file = null; - if (!defaultLocale && code.length() > 0) { - file = new File(path, keyType.name() + "_" + code + ".properties"); - } else { - // Default properties file: - file = new File(path, keyType.name() + ".properties"); - } - - return file; - } - - @Override - protected void writeHeader(Writer writer) throws IOException { - String code = locale.toString(); - String name = locale.getDisplayCountry(locale); - - if (name.length() == 0) { - name = locale.getDisplayLanguage(locale); - } - - if (name.length() == 0) { - name = "default"; - } - - if (code.length() > 0) { - name = name + " (" + code + ")"; - } - - name = (name + " " + getBundleDisplayName()).trim(); - - writer.write("# " + name + " translation file (UTF-8)\n"); - writer.write("# \n"); - writer.write("# Note that any key can be doubled with a _NOUTF suffix\n"); - writer.write("# to use when the NOUTF env variable is set to 1\n"); - writer.write("# \n"); - writer.write("# Also, the comments always refer to the key below them.\n"); - writer.write("# \n"); - } - - @Override - protected void writeValue(Writer writer, E id) throws IOException { - super.writeValue(writer, id); - - String name = id.name() + "_NOUTF"; - if (containsKey(name)) { - String value = getString(name, null); - if (value == null) { - value = getMetaDef(id.name()); - } - boolean set = isSet(id, false); - writeValue(writer, name, value, set); - } - } - - /** - * Return the {@link Locale} representing the given language. - * - * @param language - * the language to initialise, in the form "en-GB" or "fr" for - * instance - * - * @return the corresponding {@link Locale} or NULL if it is not known - */ - static private Locale getLocaleFor(String language) { - Locale locale; - - if (language == null || language.trim().isEmpty()) { - return null; - } - - language = language.replaceAll("_", "-"); - String lang = language; - String country = null; - if (language.contains("-")) { - lang = language.split("-")[0]; - country = language.split("-")[1]; - } - - if (country != null) - locale = new Locale(lang, country); - else - locale = new Locale(lang); - - return locale; - } - - /** - * Return all the languages known by the program. - * - * @param name - * the enumeration on which we translate - * - * @return the known language codes - */ - static protected List getKnownLanguages(Enum name) { - List resources = new LinkedList(); - - String regex = ".*" + name.name() + "[_a-zA-Za]*\\.properties$"; - - for (String res : TransBundle_ResourceList.getResources(Pattern - .compile(regex))) { - String resource = res; - int index = resource.lastIndexOf('/'); - if (index >= 0 && index < (resource.length() - 1)) - resource = resource.substring(index + 1); - if (resource.startsWith(name.name())) { - resource = resource.substring(0, resource.length() - - ".properties".length()); - resource = resource.substring(name.name().length()); - if (resource.startsWith("_")) { - resource = resource.substring(1); - resources.add(resource); - } - } - } - - return resources; - } -} diff --git a/src/be/nikiroo/utils/resources/TransBundle_ResourceList.java b/src/be/nikiroo/utils/resources/TransBundle_ResourceList.java deleted file mode 100644 index 9983b8b..0000000 --- a/src/be/nikiroo/utils/resources/TransBundle_ResourceList.java +++ /dev/null @@ -1,125 +0,0 @@ -package be.nikiroo.utils.resources; - -// code copied from from: -// http://forums.devx.com/showthread.php?t=153784, -// via: -// http://stackoverflow.com/questions/3923129/get-a-list-of-resources-from-classpath-directory - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Enumeration; -import java.util.List; -import java.util.regex.Pattern; -import java.util.zip.ZipEntry; -import java.util.zip.ZipException; -import java.util.zip.ZipFile; - -/** - * list resources available from the classpath @ * - */ -class TransBundle_ResourceList { - - /** - * for all elements of java.class.path get a Collection of resources Pattern - * pattern = Pattern.compile(".*"); gets all resources - * - * @param pattern - * the pattern to match - * @return the resources in the order they are found - */ - public static Collection getResources(final Pattern pattern) { - final ArrayList retval = new ArrayList(); - final String classPath = System.getProperty("java.class.path", "."); - final String[] classPathElements = classPath.split(System - .getProperty("path.separator")); - for (final String element : classPathElements) { - retval.addAll(getResources(element, pattern)); - } - - return retval; - } - - private static Collection getResources(final String element, - final Pattern pattern) { - final ArrayList retval = new ArrayList(); - final File file = new File(element); - if (file.isDirectory()) { - retval.addAll(getResourcesFromDirectory(file, pattern)); - } else { - retval.addAll(getResourcesFromJarFile(file, pattern)); - } - - return retval; - } - - private static Collection getResourcesFromJarFile(final File file, - final Pattern pattern) { - final ArrayList retval = new ArrayList(); - ZipFile zf; - try { - zf = new ZipFile(file); - } catch (final ZipException e) { - throw new Error(e); - } catch (final IOException e) { - throw new Error(e); - } - final Enumeration e = zf.entries(); - while (e.hasMoreElements()) { - final ZipEntry ze = e.nextElement(); - final String fileName = ze.getName(); - final boolean accept = pattern.matcher(fileName).matches(); - if (accept) { - retval.add(fileName); - } - } - try { - zf.close(); - } catch (final IOException e1) { - throw new Error(e1); - } - - return retval; - } - - private static Collection getResourcesFromDirectory( - final File directory, final Pattern pattern) { - List acc = new ArrayList(); - List dirs = new ArrayList(); - getResourcesFromDirectory(acc, dirs, directory, pattern); - - List rep = new ArrayList(); - for (String value : acc) { - if (pattern.matcher(value).matches()) { - rep.add(value); - } - } - - return rep; - } - - private static void getResourcesFromDirectory(List acc, - List dirs, final File directory, final Pattern pattern) { - final File[] fileList = directory.listFiles(); - if (fileList != null) { - for (final File file : fileList) { - if (!dirs.contains(file)) { - try { - String key = file.getCanonicalPath(); - if (!acc.contains(key)) { - if (file.isDirectory()) { - dirs.add(file); - getResourcesFromDirectory(acc, dirs, file, - pattern); - } else { - acc.add(key); - } - } - } catch (IOException e) { - } - } - } - } - } -} diff --git a/src/be/nikiroo/utils/resources/package-info.java b/src/be/nikiroo/utils/resources/package-info.java deleted file mode 100644 index bda940b..0000000 --- a/src/be/nikiroo/utils/resources/package-info.java +++ /dev/null @@ -1,14 +0,0 @@ -/** - * This package encloses the classes needed to use - * {@link be.nikiroo.utils.resources.Bundle}s - *

- * Those are basically a .properties resource linked to an enumeration - * listing all the fields you can use. The classes can also be used to update - * the linked .properties files (or export them, which is useful when - * you work from a JAR file). - *

- * All those classes expect UTF-8 content only. - * - * @author niki - */ -package be.nikiroo.utils.resources; \ No newline at end of file diff --git a/src/be/nikiroo/utils/serial/CustomSerializer.java b/src/be/nikiroo/utils/serial/CustomSerializer.java deleted file mode 100644 index e58ccf2..0000000 --- a/src/be/nikiroo/utils/serial/CustomSerializer.java +++ /dev/null @@ -1,150 +0,0 @@ -package be.nikiroo.utils.serial; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import be.nikiroo.utils.streams.BufferedInputStream; -import be.nikiroo.utils.streams.ReplaceInputStream; -import be.nikiroo.utils.streams.ReplaceOutputStream; - -/** - * A {@link CustomSerializer} supports and generates values in the form: - *

    - *
  • custom^TYPE^ENCODED_VALUE
  • - *
- *

- * In this scheme, the values are: - *

    - *
  • custom: a fixed keyword
  • - *
  • ^: a fixed separator character (the - * ENCODED_VALUE can still use it inside its content, though
  • - *
  • TYPE: the object type of this value
  • - *
  • ENCODED_VALUE: the custom encoded value
  • - *
- *

- * To create a new {@link CustomSerializer}, you are expected to implement the - * abstract methods of this class. The rest should be taken care of bythe - * system. - * - * @author niki - */ -public abstract class CustomSerializer { - /** - * Generate the custom ENCODED_VALUE from this - * value. - *

- * The value will always be of the supported type. - * - * @param out - * the {@link OutputStream} to write the value to - * @param value - * the value to serialize - * - * @throws IOException - * in case of I/O error - */ - protected abstract void toStream(OutputStream out, Object value) - throws IOException; - - /** - * Regenerate the value from the custom ENCODED_VALUE. - *

- * The value in the {@link InputStream} in will always be of the - * supported type. - * - * @param in - * the {@link InputStream} containing the - * ENCODED_VALUE - * - * @return the regenerated object - * - * @throws IOException - * in case of I/O error - */ - protected abstract Object fromStream(InputStream in) throws IOException; - - /** - * Return the supported type name. - *

- * It must be the name returned by {@link Object#getClass() - * #getCanonicalName()}. - * - * @return the supported class name - */ - protected abstract String getType(); - - /** - * Encode the object into the given {@link OutputStream}, i.e., generate the - * ENCODED_VALUE part. - *

- * Use whatever scheme you wish, the system shall ensure that the content is - * correctly encoded and that you will receive the same content at decode - * time. - * - * @param out - * the builder to append to - * @param value - * the object to encode - * - * @throws IOException - * in case of I/O error - */ - public void encode(OutputStream out, Object value) throws IOException { - ReplaceOutputStream replace = new ReplaceOutputStream(out, // - new String[] { "\\", "\n" }, // - new String[] { "\\\\", "\\n" }); - - try { - SerialUtils.write(replace, "custom^"); - SerialUtils.write(replace, getType()); - SerialUtils.write(replace, "^"); - toStream(replace, value); - } finally { - replace.close(false); - } - } - - /** - * Decode the value back into the supported object type. - *

- * We do not expect the full content here but only: - *

    - *
  • ENCODED_VALUE - *
  • - *
- * That is, we do not expect the "custom^TYPE^" - * part. - * - * @param in - * the encoded value - * - * @return the object - * - * @throws IOException - * in case of I/O error - */ - public Object decode(InputStream in) throws IOException { - ReplaceInputStream replace = new ReplaceInputStream(in, // - new String[] { "\\\\", "\\n" }, // - new String[] { "\\", "\n" }); - - try { - return fromStream(replace); - } finally { - replace.close(false); - } - } - - public static boolean isCustom(BufferedInputStream in) throws IOException { - return in.startsWith("custom^"); - } - - public static String typeOf(String encodedValue) { - int pos1 = encodedValue.indexOf('^'); - int pos2 = encodedValue.indexOf('^', pos1 + 1); - String type = encodedValue.substring(pos1 + 1, pos2); - - return type; - } -} diff --git a/src/be/nikiroo/utils/serial/Exporter.java b/src/be/nikiroo/utils/serial/Exporter.java deleted file mode 100644 index 2470bde..0000000 --- a/src/be/nikiroo/utils/serial/Exporter.java +++ /dev/null @@ -1,60 +0,0 @@ -package be.nikiroo.utils.serial; - -import java.io.IOException; -import java.io.NotSerializableException; -import java.io.OutputStream; -import java.util.HashMap; -import java.util.Map; - -/** - * A simple class to serialise objects to {@link String}. - *

- * This class does not support inner classes (it does support nested classes, - * though). - * - * @author niki - */ -public class Exporter { - private Map map; - private OutputStream out; - - /** - * Create a new {@link Exporter}. - * - * @param out - * export the data to this stream - */ - public Exporter(OutputStream out) { - if (out == null) { - throw new NullPointerException( - "Cannot create an be.nikiroo.utils.serials.Exporter that will export to NULL"); - } - - this.out = out; - map = new HashMap(); - } - - /** - * Serialise the given object and add it to the list. - *

- * Important: If the operation fails (with a - * {@link NotSerializableException}), the {@link Exporter} will be corrupted - * (will contain bad, most probably not importable data). - * - * @param o - * the object to serialise - * @return this (for easier appending of multiple values) - * - * @throws NotSerializableException - * if the object cannot be serialised (in this case, the - * {@link Exporter} can contain bad, most probably not - * importable data) - * @throws IOException - * in case of I/O error - */ - public Exporter append(Object o) throws NotSerializableException, - IOException { - SerialUtils.append(out, o, map); - return this; - } -} \ No newline at end of file diff --git a/src/be/nikiroo/utils/serial/Importer.java b/src/be/nikiroo/utils/serial/Importer.java deleted file mode 100644 index 81814df..0000000 --- a/src/be/nikiroo/utils/serial/Importer.java +++ /dev/null @@ -1,288 +0,0 @@ -package be.nikiroo.utils.serial; - -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.Map; -import java.util.zip.GZIPInputStream; - -import be.nikiroo.utils.IOUtils; -import be.nikiroo.utils.streams.Base64InputStream; -import be.nikiroo.utils.streams.BufferedInputStream; -import be.nikiroo.utils.streams.NextableInputStream; -import be.nikiroo.utils.streams.NextableInputStreamStep; - -/** - * A simple class that can accept the output of {@link Exporter} to recreate - * objects as they were sent to said exporter. - *

- * This class requires the objects (and their potential enclosing objects) to - * have an empty constructor, and does not support inner classes (it does - * support nested classes, though). - * - * @author niki - */ -public class Importer { - private Boolean link; - private Object me; - private Importer child; - private Map map; - - private String currentFieldName; - - /** - * Create a new {@link Importer}. - */ - public Importer() { - map = new HashMap(); - map.put("NULL", null); - } - - private Importer(Map map) { - this.map = map; - } - - /** - * Read some data into this {@link Importer}: it can be the full serialised - * content, or a number of lines of it (any given line MUST be - * complete though) and accumulate it with the already present data. - * - * @param in - * the data to parse - * - * @return itself so it can be chained - * - * @throws NoSuchFieldException - * if the serialised data contains information about a field - * which does actually not exist in the class we know of - * @throws NoSuchMethodException - * if a class described in the serialised data cannot be created - * because it is not compatible with this code - * @throws ClassNotFoundException - * if a class described in the serialised data cannot be found - * @throws IOException - * if the content cannot be read (for instance, corrupt data) - * @throws NullPointerException - * if the stream is empty - */ - public Importer read(InputStream in) throws NoSuchFieldException, - NoSuchMethodException, ClassNotFoundException, IOException, - NullPointerException { - - NextableInputStream stream = new NextableInputStream(in, - new NextableInputStreamStep('\n')); - - try { - if (in == null) { - throw new NullPointerException("InputStream is null"); - } - - boolean first = true; - while (stream.next()) { - if (stream.eof()) { - if (first) { - throw new NullPointerException( - "InputStream empty, normal termination"); - } - return this; - } - first = false; - - boolean zip = stream.startsWith("ZIP:"); - boolean b64 = stream.startsWith("B64:"); - - if (zip || b64) { - stream.skip("XXX:".length()); - - InputStream decoded = stream.open(); - if (zip) { - decoded = new GZIPInputStream(decoded); - } - decoded = new Base64InputStream(decoded, false); - - try { - read(decoded); - } finally { - decoded.close(); - } - } else { - processLine(stream); - } - } - } finally { - stream.close(false); - } - - return this; - } - - /** - * Read a single (whole) line of serialised data into this {@link Importer} - * and accumulate it with the already present data. - * - * @param in - * the line to parse - * - * @return TRUE if we are just done with one object or sub-object - * - * @throws NoSuchFieldException - * if the serialised data contains information about a field - * which does actually not exist in the class we know of - * @throws NoSuchMethodException - * if a class described in the serialised data cannot be created - * because it is not compatible with this code - * @throws ClassNotFoundException - * if a class described in the serialised data cannot be found - * @throws IOException - * if the content cannot be read (for instance, corrupt data) - */ - private boolean processLine(BufferedInputStream in) - throws NoSuchFieldException, NoSuchMethodException, - ClassNotFoundException, IOException { - - // Defer to latest child if any - if (child != null) { - if (child.processLine(in)) { - if (currentFieldName != null) { - setField(currentFieldName, child.getValue()); - currentFieldName = null; - } - child = null; - } - - return false; - } - - // Start/Stop object - if (in.is("{")) { // START: new child if needed - if (link != null) { - child = new Importer(map); - } - in.end(); - return false; - } else if (in.is("}")) { // STOP: report self to parent - in.end(); - return true; - } - - // Custom objects - if (CustomSerializer.isCustom(in)) { - // not a field value but a direct value - me = SerialUtils.decode(in); - return false; - } - - // REF: (object) - if (in.startsWith("REF ")) { // REF: create/link self - // here, line is REF type@999:xxx - // xxx is optional - - NextableInputStream stream = new NextableInputStream(in, - new NextableInputStreamStep(':')); - try { - stream.next(); - - stream.skip("REF ".length()); - String header = IOUtils.readSmallStream(stream); - - String[] tab = header.split("@"); - if (tab.length != 2) { - throw new IOException("Bad import header line: " + header); - } - String type = tab[0]; - String ref = tab[1]; - - stream.nextAll(); - - link = map.containsKey(ref); - if (link) { - me = map.get(ref); - stream.end(); - } else { - if (stream.eof()) { - // construct - me = SerialUtils.createObject(type); - } else { - // direct value - me = SerialUtils.decode(stream); - } - map.put(ref, me); - } - } finally { - stream.close(false); - } - - return false; - } - - if (SerialUtils.isDirectValue(in)) { - // not a field value but a direct value - me = SerialUtils.decode(in); - return false; - } - - if (in.startsWith("^")) { - in.skip(1); - - NextableInputStream nameThenContent = new NextableInputStream(in, - new NextableInputStreamStep(':')); - - try { - nameThenContent.next(); - String fieldName = IOUtils.readSmallStream(nameThenContent); - - if (nameThenContent.nextAll() && !nameThenContent.eof()) { - // field value is direct or custom - Object value = null; - value = SerialUtils.decode(nameThenContent); - - // To support simple types directly: - if (me == null) { - me = value; - } else { - setField(fieldName, value); - } - } else { - // field value is compound - currentFieldName = fieldName; - } - } finally { - nameThenContent.close(false); - } - - return false; - } - - String line = IOUtils.readSmallStream(in); - throw new IOException("Line cannot be processed: <" + line + ">"); - } - - private void setField(String name, Object value) - throws NoSuchFieldException { - - try { - Field field = me.getClass().getDeclaredField(name); - - field.setAccessible(true); - field.set(me, value); - } catch (NoSuchFieldException e) { - throw new NoSuchFieldException(String.format( - "Field \"%s\" was not found in object of type \"%s\".", - name, me.getClass().getCanonicalName())); - } catch (Exception e) { - throw new NoSuchFieldException(String.format( - "Internal error when setting \"%s.%s\": %s", me.getClass() - .getCanonicalName(), name, e.getMessage())); - } - } - - /** - * Return the current deserialised value. - * - * @return the current value - */ - public Object getValue() { - return me; - } -} \ No newline at end of file diff --git a/src/be/nikiroo/utils/serial/SerialUtils.java b/src/be/nikiroo/utils/serial/SerialUtils.java deleted file mode 100644 index ad3b5d4..0000000 --- a/src/be/nikiroo/utils/serial/SerialUtils.java +++ /dev/null @@ -1,733 +0,0 @@ -package be.nikiroo.utils.serial; - -import java.io.IOException; -import java.io.InputStream; -import java.io.NotSerializableException; -import java.io.OutputStream; -import java.lang.reflect.Array; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.net.URL; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UnknownFormatConversionException; - -import be.nikiroo.utils.IOUtils; -import be.nikiroo.utils.Image; -import be.nikiroo.utils.StringUtils; -import be.nikiroo.utils.streams.Base64InputStream; -import be.nikiroo.utils.streams.Base64OutputStream; -import be.nikiroo.utils.streams.BufferedInputStream; -import be.nikiroo.utils.streams.NextableInputStream; -import be.nikiroo.utils.streams.NextableInputStreamStep; - -/** - * Small class to help with serialisation. - *

- * Note that we do not support inner classes (but we do support nested classes) - * and all objects require an empty constructor to be deserialised. - *

- * It is possible to add support to custom types (both the encoder and the - * decoder will require the custom classes) -- see {@link CustomSerializer}. - *

- * Default supported types are: - *

    - *
  • NULL (as a null value)
  • - *
  • String
  • - *
  • Boolean
  • - *
  • Byte
  • - *
  • Character
  • - *
  • Short
  • - *
  • Long
  • - *
  • Float
  • - *
  • Double
  • - *
  • Integer
  • - *
  • Enum (any enum whose name and value is known by the caller)
  • - *
  • java.awt.image.BufferedImage (as a {@link CustomSerializer})
  • - *
  • An array of the above (as a {@link CustomSerializer})
  • - *
  • URL
  • - *
- * - * @author niki - */ -public class SerialUtils { - private static Map customTypes; - - static { - customTypes = new HashMap(); - - // Array types: - customTypes.put("[]", new CustomSerializer() { - @Override - protected void toStream(OutputStream out, Object value) - throws IOException { - - String type = value.getClass().getCanonicalName(); - type = type.substring(0, type.length() - 2); // remove the [] - - write(out, type); - try { - for (int i = 0; true; i++) { - Object item = Array.get(value, i); - - // encode it normally if direct value - write(out, "\r"); - if (!SerialUtils.encode(out, item)) { - try { - write(out, "B64:"); - OutputStream out64 = new Base64OutputStream( - out, true); - new Exporter(out64).append(item); - out64.flush(); - } catch (NotSerializableException e) { - throw new UnknownFormatConversionException(e - .getMessage()); - } - } - } - } catch (ArrayIndexOutOfBoundsException e) { - // Done. - } - } - - @Override - protected Object fromStream(InputStream in) throws IOException { - NextableInputStream stream = new NextableInputStream(in, - new NextableInputStreamStep('\r')); - - try { - List list = new ArrayList(); - stream.next(); - String type = IOUtils.readSmallStream(stream); - - while (stream.next()) { - Object value = new Importer().read(stream).getValue(); - list.add(value); - } - - Object array = Array.newInstance( - SerialUtils.getClass(type), list.size()); - for (int i = 0; i < list.size(); i++) { - Array.set(array, i, list.get(i)); - } - - return array; - } catch (Exception e) { - if (e instanceof IOException) { - throw (IOException) e; - } - throw new IOException(e.getMessage()); - } - } - - @Override - protected String getType() { - return "[]"; - } - }); - - // URL: - customTypes.put("java.net.URL", new CustomSerializer() { - @Override - protected void toStream(OutputStream out, Object value) - throws IOException { - String val = ""; - if (value != null) { - val = ((URL) value).toString(); - } - - out.write(StringUtils.getBytes(val)); - } - - @Override - protected Object fromStream(InputStream in) throws IOException { - String val = IOUtils.readSmallStream(in); - if (!val.isEmpty()) { - return new URL(val); - } - - return null; - } - - @Override - protected String getType() { - return "java.net.URL"; - } - }); - - // Images (this is currently the only supported image type by default) - customTypes.put("be.nikiroo.utils.Image", new CustomSerializer() { - @Override - protected void toStream(OutputStream out, Object value) - throws IOException { - Image img = (Image) value; - OutputStream encoded = new Base64OutputStream(out, true); - try { - InputStream in = img.newInputStream(); - try { - IOUtils.write(in, encoded); - } finally { - in.close(); - } - } finally { - encoded.flush(); - // Cannot close! - } - } - - @Override - protected String getType() { - return "be.nikiroo.utils.Image"; - } - - @Override - protected Object fromStream(InputStream in) throws IOException { - try { - // Cannot close it! - InputStream decoded = new Base64InputStream(in, false); - return new Image(decoded); - } catch (IOException e) { - throw new UnknownFormatConversionException(e.getMessage()); - } - } - }); - } - - /** - * Create an empty object of the given type. - * - * @param type - * the object type (its class name) - * - * @return the new object - * - * @throws ClassNotFoundException - * if the class cannot be found - * @throws NoSuchMethodException - * if the given class is not compatible with this code - */ - public static Object createObject(String type) - throws ClassNotFoundException, NoSuchMethodException { - - String desc = null; - try { - Class clazz = getClass(type); - String className = clazz.getName(); - List args = new ArrayList(); - List> classes = new ArrayList>(); - Constructor ctor = null; - if (className.contains("$")) { - for (String parentName = className.substring(0, - className.lastIndexOf('$'));; parentName = parentName - .substring(0, parentName.lastIndexOf('$'))) { - Object parent = createObject(parentName); - args.add(parent); - classes.add(parent.getClass()); - - if (!parentName.contains("$")) { - break; - } - } - - // Better error description in case there is no empty - // constructor: - desc = ""; - String end = ""; - for (Class parent = clazz; parent != null - && !parent.equals(Object.class); parent = parent - .getSuperclass()) { - if (!desc.isEmpty()) { - desc += " [:"; - end += "]"; - } - desc += parent; - } - desc += end; - // - - try { - ctor = clazz.getDeclaredConstructor(classes - .toArray(new Class[] {})); - } catch (NoSuchMethodException nsme) { - // TODO: it seems we do not always need a parameter for each - // level, so we currently try "ALL" levels or "FIRST" level - // only -> we should check the actual rule and use it - ctor = clazz.getDeclaredConstructor(classes.get(0)); - Object firstParent = args.get(0); - args.clear(); - args.add(firstParent); - } - desc = null; - } else { - ctor = clazz.getDeclaredConstructor(); - } - - ctor.setAccessible(true); - return ctor.newInstance(args.toArray()); - } catch (ClassNotFoundException e) { - throw e; - } catch (NoSuchMethodException e) { - if (desc != null) { - throw new NoSuchMethodException("Empty constructor not found: " - + desc); - } - throw e; - } catch (Exception e) { - throw new NoSuchMethodException("Cannot instantiate: " + type); - } - } - - /** - * Insert a custom serialiser that will take precedence over the default one - * or the target class. - * - * @param serializer - * the custom serialiser - */ - static public void addCustomSerializer(CustomSerializer serializer) { - customTypes.put(serializer.getType(), serializer); - } - - /** - * Serialise the given object into this {@link OutputStream}. - *

- * Important: If the operation fails (with a - * {@link NotSerializableException}), the {@link StringBuilder} will be - * corrupted (will contain bad, most probably not importable data). - * - * @param out - * the output {@link OutputStream} to serialise to - * @param o - * the object to serialise - * @param map - * the map of already serialised objects (if the given object or - * one of its descendant is already present in it, only an ID - * will be serialised) - * - * @throws NotSerializableException - * if the object cannot be serialised (in this case, the - * {@link StringBuilder} can contain bad, most probably not - * importable data) - * @throws IOException - * in case of I/O errors - */ - static void append(OutputStream out, Object o, Map map) - throws NotSerializableException, IOException { - - Field[] fields = new Field[] {}; - String type = ""; - String id = "NULL"; - - if (o != null) { - int hash = System.identityHashCode(o); - fields = o.getClass().getDeclaredFields(); - type = o.getClass().getCanonicalName(); - if (type == null) { - // Anonymous inner classes support - type = o.getClass().getName(); - } - id = Integer.toString(hash); - if (map.containsKey(hash)) { - fields = new Field[] {}; - } else { - map.put(hash, o); - } - } - - write(out, "{\nREF "); - write(out, type); - write(out, "@"); - write(out, id); - write(out, ":"); - - if (!encode(out, o)) { // check if direct value - try { - for (Field field : fields) { - field.setAccessible(true); - - if (field.getName().startsWith("this$") - || field.isSynthetic() - || (field.getModifiers() & Modifier.STATIC) == Modifier.STATIC) { - // Do not keep this links of nested classes - // Do not keep synthetic fields - // Do not keep final fields - continue; - } - - write(out, "\n^"); - write(out, field.getName()); - write(out, ":"); - - Object value = field.get(o); - - if (!encode(out, value)) { - write(out, "\n"); - append(out, value, map); - } - } - } catch (IllegalArgumentException e) { - e.printStackTrace(); // should not happen (see - // setAccessible) - } catch (IllegalAccessException e) { - e.printStackTrace(); // should not happen (see - // setAccessible) - } - - write(out, "\n}"); - } - } - - /** - * Encode the object into the given {@link OutputStream} if possible and if - * supported. - *

- * A supported object in this context means an object we can directly - * encode, like an Integer or a String. Custom objects and arrays are also - * considered supported, but compound objects are not supported here. - *

- * For compound objects, you should use {@link Exporter}. - * - * @param out - * the {@link OutputStream} to append to - * @param value - * the object to encode (can be NULL, which will be encoded) - * - * @return TRUE if success, FALSE if not (the content of the - * {@link OutputStream} won't be changed in case of failure) - * - * @throws IOException - * in case of I/O error - */ - static boolean encode(OutputStream out, Object value) throws IOException { - if (value == null) { - write(out, "NULL"); - } else if (value.getClass().getSimpleName().endsWith("[]")) { - // Simple name does support [] suffix and do not return NULL for - // inner anonymous classes - customTypes.get("[]").encode(out, value); - } else if (customTypes.containsKey(value.getClass().getCanonicalName())) { - customTypes.get(value.getClass().getCanonicalName())// - .encode(out, value); - } else if (value instanceof String) { - encodeString(out, (String) value); - } else if (value instanceof Boolean) { - write(out, value); - } else if (value instanceof Byte) { - write(out, "b"); - write(out, value); - } else if (value instanceof Character) { - write(out, "c"); - encodeString(out, "" + value); - } else if (value instanceof Short) { - write(out, "s"); - write(out, value); - } else if (value instanceof Integer) { - write(out, "i"); - write(out, value); - } else if (value instanceof Long) { - write(out, "l"); - write(out, value); - } else if (value instanceof Float) { - write(out, "f"); - write(out, value); - } else if (value instanceof Double) { - write(out, "d"); - write(out, value); - } else if (value instanceof Enum) { - write(out, "E:"); - String type = value.getClass().getCanonicalName(); - write(out, type); - write(out, "."); - write(out, ((Enum) value).name()); - write(out, ";"); - } else { - return false; - } - - return true; - } - - static boolean isDirectValue(BufferedInputStream encodedValue) - throws IOException { - if (CustomSerializer.isCustom(encodedValue)) { - return false; - } - - for (String fullValue : new String[] { "NULL", "null", "true", "false" }) { - if (encodedValue.is(fullValue)) { - return true; - } - } - - for (String prefix : new String[] { "c\"", "\"", "b", "s", "i", "l", - "f", "d", "E:" }) { - if (encodedValue.startsWith(prefix)) { - return true; - } - } - - return false; - } - - /** - * Decode the data into an equivalent supported source object. - *

- * A supported object in this context means an object we can directly - * encode, like an Integer or a String (see - * {@link SerialUtils#decode(String)}. - *

- * Custom objects and arrays are also considered supported here, but - * compound objects are not. - *

- * For compound objects, you should use {@link Importer}. - * - * @param encodedValue - * the encoded data, cannot be NULL - * - * @return the object (can be NULL for NULL encoded values) - * - * @throws IOException - * if the content cannot be converted - */ - static Object decode(BufferedInputStream encodedValue) throws IOException { - if (CustomSerializer.isCustom(encodedValue)) { - // custom^TYPE^ENCODED_VALUE - NextableInputStream content = new NextableInputStream(encodedValue, - new NextableInputStreamStep('^')); - try { - content.next(); - @SuppressWarnings("unused") - String custom = IOUtils.readSmallStream(content); - content.next(); - String type = IOUtils.readSmallStream(content); - content.nextAll(); - if (customTypes.containsKey(type)) { - return customTypes.get(type).decode(content); - } - content.end(); - throw new IOException("Unknown custom type: " + type); - } finally { - content.close(false); - encodedValue.end(); - } - } - - String encodedString = IOUtils.readSmallStream(encodedValue); - return decode(encodedString); - } - - /** - * Decode the data into an equivalent supported source object. - *

- * A supported object in this context means an object we can directly - * encode, like an Integer or a String. - *

- * For custom objects and arrays, you should use - * {@link SerialUtils#decode(InputStream)} or directly {@link Importer}. - *

- * For compound objects, you should use {@link Importer}. - * - * @param encodedValue - * the encoded data, cannot be NULL - * - * @return the object (can be NULL for NULL encoded values) - * - * @throws IOException - * if the content cannot be converted - */ - static Object decode(String encodedValue) throws IOException { - try { - String cut = ""; - if (encodedValue.length() > 1) { - cut = encodedValue.substring(1); - } - - if (encodedValue.equals("NULL") || encodedValue.equals("null")) { - return null; - } else if (encodedValue.startsWith("\"")) { - return decodeString(encodedValue); - } else if (encodedValue.equals("true")) { - return true; - } else if (encodedValue.equals("false")) { - return false; - } else if (encodedValue.startsWith("b")) { - return Byte.parseByte(cut); - } else if (encodedValue.startsWith("c")) { - return decodeString(cut).charAt(0); - } else if (encodedValue.startsWith("s")) { - return Short.parseShort(cut); - } else if (encodedValue.startsWith("l")) { - return Long.parseLong(cut); - } else if (encodedValue.startsWith("f")) { - return Float.parseFloat(cut); - } else if (encodedValue.startsWith("d")) { - return Double.parseDouble(cut); - } else if (encodedValue.startsWith("i")) { - return Integer.parseInt(cut); - } else if (encodedValue.startsWith("E:")) { - cut = cut.substring(1); - return decodeEnum(cut); - } else { - throw new IOException("Unrecognized value: " + encodedValue); - } - } catch (Exception e) { - if (e instanceof IOException) { - throw (IOException) e; - } - throw new IOException(e.getMessage(), e); - } - } - - /** - * Write the given {@link String} into the given {@link OutputStream} in - * UTF-8. - * - * @param out - * the {@link OutputStream} - * @param data - * the data to write, cannot be NULL - * - * @throws IOException - * in case of I/O error - */ - static void write(OutputStream out, Object data) throws IOException { - out.write(StringUtils.getBytes(data.toString())); - } - - /** - * Return the corresponding class or throw an {@link Exception} if it - * cannot. - * - * @param type - * the class name to look for - * - * @return the class (will never be NULL) - * - * @throws ClassNotFoundException - * if the class cannot be found - * @throws NoSuchMethodException - * if the class cannot be created (usually because it or its - * enclosing class doesn't have an empty constructor) - */ - static private Class getClass(String type) - throws ClassNotFoundException, NoSuchMethodException { - Class clazz = null; - try { - clazz = Class.forName(type); - } catch (ClassNotFoundException e) { - int pos = type.length(); - pos = type.lastIndexOf(".", pos); - if (pos >= 0) { - String parentType = type.substring(0, pos); - String nestedType = type.substring(pos + 1); - Class javaParent = null; - try { - javaParent = getClass(parentType); - parentType = javaParent.getName(); - clazz = Class.forName(parentType + "$" + nestedType); - } catch (Exception ee) { - } - - if (javaParent == null) { - throw new NoSuchMethodException( - "Class not found: " - + type - + " (the enclosing class cannot be created: maybe it doesn't have an empty constructor?)"); - } - } - } - - if (clazz == null) { - throw new ClassNotFoundException("Class not found: " + type); - } - - return clazz; - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - static private Enum decodeEnum(String escaped) { - // escaped: be.xxx.EnumType.VALUE; - int pos = escaped.lastIndexOf("."); - String type = escaped.substring(0, pos); - String name = escaped.substring(pos + 1, escaped.length() - 1); - - try { - return Enum.valueOf((Class) getClass(type), name); - } catch (Exception e) { - throw new UnknownFormatConversionException("Unknown enum: <" + type - + "> " + name); - } - } - - // aa bb -> "aa\tbb" - static void encodeString(OutputStream out, String raw) throws IOException { - // TODO: not. efficient. - out.write('\"'); - for (char car : raw.toCharArray()) { - encodeString(out, car); - } - out.write('\"'); - } - - // for encoding string, NOT to encode a char by itself! - static void encodeString(OutputStream out, char raw) throws IOException { - switch (raw) { - case '\\': - out.write('\\'); - out.write('\\'); - break; - case '\r': - out.write('\\'); - out.write('r'); - break; - case '\n': - out.write('\\'); - out.write('n'); - break; - case '"': - out.write('\\'); - out.write('\"'); - break; - default: - out.write(raw); - break; - } - } - - // "aa\tbb" -> aa bb - static String decodeString(String escaped) { - StringBuilder builder = new StringBuilder(); - - boolean escaping = false; - for (char car : escaped.toCharArray()) { - if (!escaping) { - if (car == '\\') { - escaping = true; - } else { - builder.append(car); - } - } else { - switch (car) { - case '\\': - builder.append('\\'); - break; - case 'r': - builder.append('\r'); - break; - case 'n': - builder.append('\n'); - break; - case '"': - builder.append('"'); - break; - } - escaping = false; - } - } - - return builder.substring(1, builder.length() - 1); - } -} diff --git a/src/be/nikiroo/utils/serial/server/ConnectAction.java b/src/be/nikiroo/utils/serial/server/ConnectAction.java deleted file mode 100644 index 6a19368..0000000 --- a/src/be/nikiroo/utils/serial/server/ConnectAction.java +++ /dev/null @@ -1,474 +0,0 @@ -package be.nikiroo.utils.serial.server; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.Socket; - -import javax.net.ssl.SSLException; - -import be.nikiroo.utils.CryptUtils; -import be.nikiroo.utils.IOUtils; -import be.nikiroo.utils.StringUtils; -import be.nikiroo.utils.Version; -import be.nikiroo.utils.serial.Exporter; -import be.nikiroo.utils.serial.Importer; -import be.nikiroo.utils.streams.BufferedOutputStream; -import be.nikiroo.utils.streams.NextableInputStream; -import be.nikiroo.utils.streams.NextableInputStreamStep; -import be.nikiroo.utils.streams.ReplaceInputStream; -import be.nikiroo.utils.streams.ReplaceOutputStream; - -/** - * Base class used for the client/server basic handling. - *

- * It represents a single action: a client is expected to only execute one - * action, while a server is expected to execute one action for each client - * action. - * - * @author niki - */ -abstract class ConnectAction { - // We separate each "packet" we send with this character and make sure it - // does not occurs in the message itself. - static private char STREAM_SEP = '\b'; - static private String[] STREAM_RAW = new String[] { "\\", "\b" }; - static private String[] STREAM_CODED = new String[] { "\\\\", "\\b" }; - - private Socket s; - private boolean server; - - private Version clientVersion; - private Version serverVersion; - - private CryptUtils crypt; - - private Object lock = new Object(); - private NextableInputStream in; - private BufferedOutputStream out; - private boolean contentToSend; - - /** - * Method that will be called when an action is performed on either the - * client or server this {@link ConnectAction} represent. - * - * @param version - * the version on the other side of the communication (client or - * server) - * - * @throws Exception - * in case of I/O error - */ - abstract protected void action(Version version) throws Exception; - - /** - * Method called when we negotiate the version with the client. - *

- * Thus, it is only called on the server. - *

- * Will return the actual server version by default. - * - * @param clientVersion - * the client version - * - * @return the version to send to the client - */ - abstract protected Version negotiateVersion(Version clientVersion); - - /** - * Handler called when an unexpected error occurs in the code. - * - * @param e - * the exception that occurred, SSLException usually denotes a - * crypt error - */ - abstract protected void onError(Exception e); - - /** - * Create a new {@link ConnectAction}. - * - * @param s - * the socket to bind to - * @param server - * TRUE for a server action, FALSE for a client action (will - * impact the process) - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - * @param version - * the client-or-server version (depending upon the boolean - * parameter server) - */ - protected ConnectAction(Socket s, boolean server, String key, - Version version) { - this.s = s; - this.server = server; - if (key != null) { - crypt = new CryptUtils(key); - } - - if (version == null) { - version = new Version(); - } - - if (server) { - serverVersion = version; - } else { - clientVersion = version; - } - } - - /** - * The version of this client-or-server. - * - * @return the version - */ - public Version getVersion() { - if (server) { - return serverVersion; - } - - return clientVersion; - } - - /** - * The total amount of bytes received. - * - * @return the amount of bytes received - */ - public long getBytesReceived() { - return in.getBytesRead(); - } - - /** - * The total amount of bytes sent. - * - * @return the amount of bytes sent - */ - public long getBytesWritten() { - return out.getBytesWritten(); - } - - /** - * Actually start the process (this is synchronous). - */ - public void connect() { - try { - in = new NextableInputStream(s.getInputStream(), - new NextableInputStreamStep(STREAM_SEP)); - try { - out = new BufferedOutputStream(s.getOutputStream()); - try { - // Negotiate version - Version version; - if (server) { - String HELLO = recString(); - if (HELLO == null || !HELLO.startsWith("VERSION ")) { - throw new SSLException( - "Client used bad encryption key"); - } - version = negotiateVersion(new Version( - HELLO.substring("VERSION ".length()))); - sendString("VERSION " + version); - } else { - String HELLO = sendString("VERSION " + clientVersion); - if (HELLO == null || !HELLO.startsWith("VERSION ")) { - throw new SSLException( - "Server did not accept the encryption key"); - } - version = new Version(HELLO.substring("VERSION " - .length())); - } - - // Actual code - action(version); - } finally { - out.close(); - } - } finally { - in.close(); - } - } catch (Exception e) { - onError(e); - } finally { - try { - s.close(); - } catch (Exception e) { - onError(e); - } - } - } - - /** - * Serialise and send the given object to the counter part (and, only for - * client, return the deserialised answer -- the server will always receive - * NULL). - * - * @param data - * the data to send - * - * @return the answer (which can be NULL if no answer, or NULL for an answer - * which is NULL) if this action is a client, always NULL if it is a - * server - * - * @throws IOException - * in case of I/O error - * @throws NoSuchFieldException - * if the serialised data contains information about a field - * which does actually not exist in the class we know of - * @throws NoSuchMethodException - * if a class described in the serialised data cannot be created - * because it is not compatible with this code - * @throws ClassNotFoundException - * if a class described in the serialised data cannot be found - */ - protected Object sendObject(Object data) throws IOException, - NoSuchFieldException, NoSuchMethodException, ClassNotFoundException { - return send(out, data, false); - } - - /** - * Reserved for the server: flush the data to the client and retrieve its - * answer. - *

- * Also used internally for the client (only do something if there is - * contentToSend). - *

- * Will only flush the data if there is contentToSend. - * - * @return the deserialised answer (which can actually be NULL) - * - * @throws IOException - * in case of I/O error - * @throws NoSuchFieldException - * if the serialised data contains information about a field - * which does actually not exist in the class we know of - * @throws NoSuchMethodException - * if a class described in the serialised data cannot be created - * because it is not compatible with this code - * @throws ClassNotFoundException - * if a class described in the serialised data cannot be found - * @throws java.lang.NullPointerException - * if the counter part has no data to send - */ - protected Object recObject() throws IOException, NoSuchFieldException, - NoSuchMethodException, ClassNotFoundException, - java.lang.NullPointerException { - return rec(false); - } - - /** - * Send the given string to the counter part (and, only for client, return - * the answer -- the server will always receive NULL). - * - * @param line - * the data to send (we will add a line feed) - * - * @return the answer if this action is a client (without the added line - * feed), NULL if it is a server - * - * @throws IOException - * in case of I/O error - * @throws SSLException - * in case of crypt error - */ - protected String sendString(String line) throws IOException { - try { - return (String) send(out, line, true); - } catch (NoSuchFieldException e) { - // Cannot happen - e.printStackTrace(); - } catch (NoSuchMethodException e) { - // Cannot happen - e.printStackTrace(); - } catch (ClassNotFoundException e) { - // Cannot happen - e.printStackTrace(); - } - - return null; - } - - /** - * Reserved for the server (externally): flush the data to the client and - * retrieve its answer. - *

- * Also used internally for the client (only do something if there is - * contentToSend). - *

- * Will only flush the data if there is contentToSend. - * - * @return the answer (which can be NULL if no more content) - * - * @throws IOException - * in case of I/O error - * @throws SSLException - * in case of crypt error - */ - protected String recString() throws IOException { - try { - return (String) rec(true); - } catch (NoSuchFieldException e) { - // Cannot happen - e.printStackTrace(); - } catch (NoSuchMethodException e) { - // Cannot happen - e.printStackTrace(); - } catch (ClassNotFoundException e) { - // Cannot happen - e.printStackTrace(); - } catch (NullPointerException e) { - // Should happen - e.printStackTrace(); - } - - return null; - } - - /** - * Serialise and send the given object to the counter part (and, only for - * client, return the deserialised answer -- the server will always receive - * NULL). - * - * @param out - * the stream to write to - * @param data - * the data to write - * @param asString - * TRUE to write it as a String, FALSE to write it as an Object - * - * @return the answer (which can be NULL if no answer, or NULL for an answer - * which is NULL) if this action is a client, always NULL if it is a - * server - * - * @throws IOException - * in case of I/O error - * @throws SSLException - * in case of crypt error - * @throws IOException - * in case of I/O error - * @throws NoSuchFieldException - * if the serialised data contains information about a field - * which does actually not exist in the class we know of - * @throws NoSuchMethodException - * if a class described in the serialised data cannot be created - * because it is not compatible with this code - * @throws ClassNotFoundException - * if a class described in the serialised data cannot be found - */ - private Object send(BufferedOutputStream out, Object data, boolean asString) - throws IOException, NoSuchFieldException, NoSuchMethodException, - ClassNotFoundException, java.lang.NullPointerException { - - synchronized (lock) { - OutputStream sub; - if (crypt != null) { - sub = crypt.encrypt64(out.open()); - } else { - sub = out.open(); - } - - sub = new ReplaceOutputStream(sub, STREAM_RAW, STREAM_CODED); - try { - if (asString) { - sub.write(StringUtils.getBytes(data.toString())); - } else { - new Exporter(sub).append(data); - } - } finally { - sub.close(); - } - - out.write(STREAM_SEP); - - if (server) { - out.flush(); - return null; - } - - contentToSend = true; - try { - return rec(asString); - } catch (NullPointerException e) { - // We accept no data here for Objects - } - - return null; - } - } - - /** - * Reserved for the server: flush the data to the client and retrieve its - * answer. - *

- * Also used internally for the client (only do something if there is - * contentToSend). - *

- * Will only flush the data if there is contentToSend. - *

- * Note that the behaviour is slightly different for String and Object - * reading regarding exceptions: - *

    - *
  • NULL means that the counter part has no more data to send
  • - *
  • All the exceptions except {@link IOException} are there for Object - * conversion
  • - *
- * - * @param asString - * TRUE for String reading, FALSE for Object reading (which can - * still be a String) - * - * @return the deserialised answer (which can actually be NULL) - * - * @throws IOException - * in case of I/O error - * @throws NoSuchFieldException - * if the serialised data contains information about a field - * which does actually not exist in the class we know of - * @throws NoSuchMethodException - * if a class described in the serialised data cannot be created - * because it is not compatible with this code - * @throws ClassNotFoundException - * if a class described in the serialised data cannot be found - * @throws java.lang.NullPointerException - * for Objects only: if the counter part has no data to send - */ - @SuppressWarnings("resource") - private Object rec(boolean asString) throws IOException, - NoSuchFieldException, NoSuchMethodException, - ClassNotFoundException, java.lang.NullPointerException { - - synchronized (lock) { - if (server || contentToSend) { - if (contentToSend) { - out.flush(); - contentToSend = false; - } - - if (in.next() && !in.eof()) { - InputStream read = new ReplaceInputStream(in.open(), - STREAM_CODED, STREAM_RAW); - try { - if (crypt != null) { - read = crypt.decrypt64(read); - } - - if (asString) { - return IOUtils.readSmallStream(read); - } - - return new Importer().read(read).getValue(); - } finally { - read.close(); - } - } - - if (!asString) { - throw new NullPointerException(); - } - } - - return null; - } - } -} \ No newline at end of file diff --git a/src/be/nikiroo/utils/serial/server/ConnectActionClient.java b/src/be/nikiroo/utils/serial/server/ConnectActionClient.java deleted file mode 100644 index cb6bef3..0000000 --- a/src/be/nikiroo/utils/serial/server/ConnectActionClient.java +++ /dev/null @@ -1,166 +0,0 @@ -package be.nikiroo.utils.serial.server; - -import java.io.IOException; -import java.net.Socket; -import java.net.UnknownHostException; - -import be.nikiroo.utils.Version; - -/** - * Base class used for the client basic handling. - *

- * It represents a single action: a client is expected to only execute one - * action. - * - * @author niki - */ -abstract class ConnectActionClient { - /** - * The underlying {@link ConnectAction}. - *

- * Cannot be NULL. - */ - protected ConnectAction action; - - /** - * Create a new {@link ConnectActionClient}, using the current version of - * the program. - * - * @param host - * the host to bind to - * @param port - * the port to bind to - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - * - * - * @throws IOException - * in case of I/O error - * @throws UnknownHostException - * if the host is not known - * @throws IllegalArgumentException - * if the port parameter is outside the specified range of valid - * port values, which is between 0 and 65535, inclusive - */ - public ConnectActionClient(String host, int port, String key) - throws IOException { - this(host, port, key, Version.getCurrentVersion()); - } - - /** - * Create a new {@link ConnectActionClient}. - * - * @param host - * the host to bind to - * @param port - * the port to bind to - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - * @param clientVersion - * the client version - * - * - * @throws IOException - * in case of I/O error - * @throws UnknownHostException - * if the host is not known - * @throws IllegalArgumentException - * if the port parameter is outside the specified range of valid - * port values, which is between 0 and 65535, inclusive - */ - public ConnectActionClient(String host, int port, String key, - Version clientVersion) throws IOException { - this(new Socket(host, port), key, clientVersion); - } - - /** - * Create a new {@link ConnectActionClient}, using the current version of - * the program. - * - * @param s - * the socket to bind to - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - */ - public ConnectActionClient(Socket s, String key) { - this(s, key, Version.getCurrentVersion()); - } - - /** - * Create a new {@link ConnectActionClient}. - * - * @param s - * the socket to bind to - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - * @param clientVersion - * the client version - */ - public ConnectActionClient(Socket s, String key, Version clientVersion) { - action = new ConnectAction(s, false, key, clientVersion) { - @Override - protected void action(Version serverVersion) throws Exception { - ConnectActionClient.this.action(serverVersion); - } - - @Override - protected void onError(Exception e) { - ConnectActionClient.this.onError(e); - } - - @Override - protected Version negotiateVersion(Version clientVersion) { - new Exception("Should never be called on a client") - .printStackTrace(); - return null; - } - }; - } - - /** - * Actually start the process and call the action (synchronous). - */ - public void connect() { - action.connect(); - } - - /** - * Actually start the process and call the action (asynchronous). - */ - public void connectAsync() { - new Thread(new Runnable() { - @Override - public void run() { - connect(); - } - }).start(); - } - - /** - * Method that will be called when an action is performed on the client. - * - * @param serverVersion - * the version of the server connected to this client - * - * @throws Exception - * in case of I/O error - */ - @SuppressWarnings("unused") - public void action(Version serverVersion) throws Exception { - } - - /** - * Handler called when an unexpected error occurs in the code. - *

- * Will just ignore the error by default. - * - * @param e - * the exception that occurred - */ - protected void onError(@SuppressWarnings("unused") Exception e) { - } -} \ No newline at end of file diff --git a/src/be/nikiroo/utils/serial/server/ConnectActionClientObject.java b/src/be/nikiroo/utils/serial/server/ConnectActionClientObject.java deleted file mode 100644 index 9385645..0000000 --- a/src/be/nikiroo/utils/serial/server/ConnectActionClientObject.java +++ /dev/null @@ -1,175 +0,0 @@ -package be.nikiroo.utils.serial.server; - -import java.io.IOException; -import java.net.Socket; -import java.net.UnknownHostException; - -import be.nikiroo.utils.Version; - -/** - * Class used for the client basic handling. - *

- * It represents a single action: a client is expected to only execute one - * action. - * - * @author niki - */ -public class ConnectActionClientObject extends ConnectActionClient { - /** - * Create a new {@link ConnectActionClientObject} . - * - * @param s - * the socket to bind to - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - */ - public ConnectActionClientObject(Socket s, String key) { - super(s, key); - } - - /** - * Create a new {@link ConnectActionClientObject} . - * - * @param s - * the socket to bind to - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - * @param clientVersion - * the version of the client - */ - public ConnectActionClientObject(Socket s, String key, Version clientVersion) { - super(s, key, clientVersion); - } - - /** - * Create a new {@link ConnectActionClientObject}. - * - * @param host - * the host to bind to - * @param port - * the port to bind to - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - * - * @throws IOException - * in case of I/O error - * @throws UnknownHostException - * if the IP address of the host could not be determined - * @throws IllegalArgumentException - * if the port parameter is outside the specified range of valid - * port values, which is between 0 and 65535, inclusive - */ - public ConnectActionClientObject(String host, int port, String key) - throws IOException { - super(host, port, key); - } - - /** - * Create a new {@link ConnectActionClientObject}. - * - * @param host - * the host to bind to - * @param port - * the port to bind to - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - * @param clientVersion - * the version of the client - * - * @throws IOException - * in case of I/O error - * @throws UnknownHostException - * if the IP address of the host could not be determined - * @throws IllegalArgumentException - * if the port parameter is outside the specified range of valid - * port values, which is between 0 and 65535, inclusive - */ - public ConnectActionClientObject(String host, int port, String key, - Version clientVersion) throws IOException { - super(host, port, key, clientVersion); - } - - /** - * Serialise and send the given object to the server (and return the - * deserialised answer). - * - * @param data - * the data to send - * - * @return the answer, which can be NULL - * - * @throws IOException - * in case of I/O error - * @throws NoSuchFieldException - * if the serialised data contains information about a field - * which does actually not exist in the class we know of - * @throws NoSuchMethodException - * if a class described in the serialised data cannot be created - * because it is not compatible with this code - * @throws ClassNotFoundException - * if a class described in the serialised data cannot be found - */ - public Object send(Object data) throws IOException, NoSuchFieldException, - NoSuchMethodException, ClassNotFoundException { - return action.sendObject(data); - } - - // Deprecated // - - /** - * @deprecated SSL support has been replaced by key-based encryption. - *

- * Please use the version with key encryption (this deprecated - * version uses an empty key when ssl is TRUE and no - * key (NULL) when ssl is FALSE). - */ - @Deprecated - public ConnectActionClientObject(String host, int port, boolean ssl) - throws IOException { - this(host, port, ssl ? "" : null); - } - - /** - * @deprecated SSL support has been replaced by key-based encryption. - *

- * Please use the version with key encryption (this deprecated - * version uses an empty key when ssl is TRUE and no - * key (NULL) when ssl is FALSE). - */ - @Deprecated - public ConnectActionClientObject(String host, int port, boolean ssl, - Version version) throws IOException { - this(host, port, ssl ? "" : null, version); - } - - /** - * @deprecated SSL support has been replaced by key-based encryption. - *

- * Please use the version with key encryption (this deprecated - * version uses an empty key when ssl is TRUE and no - * key (NULL) when ssl is FALSE). - */ - @SuppressWarnings("unused") - @Deprecated - public ConnectActionClientObject(Socket s, boolean ssl) throws IOException { - this(s, ssl ? "" : null); - } - - /** - * @deprecated SSL support has been replaced by key-based encryption. - *

- * Please use the version with key encryption (this deprecated - * version uses an empty key when ssl is TRUE and no - * key (NULL) when ssl is FALSE). - */ - @SuppressWarnings("unused") - @Deprecated - public ConnectActionClientObject(Socket s, boolean ssl, Version version) - throws IOException { - this(s, ssl ? "" : null, version); - } -} \ No newline at end of file diff --git a/src/be/nikiroo/utils/serial/server/ConnectActionClientString.java b/src/be/nikiroo/utils/serial/server/ConnectActionClientString.java deleted file mode 100644 index 3005cee..0000000 --- a/src/be/nikiroo/utils/serial/server/ConnectActionClientString.java +++ /dev/null @@ -1,165 +0,0 @@ -package be.nikiroo.utils.serial.server; - -import java.io.IOException; -import java.net.Socket; -import java.net.UnknownHostException; - -import be.nikiroo.utils.Version; - -/** - * Class used for the client basic handling. - *

- * It represents a single action: a client is expected to only execute one - * action. - * - * @author niki - */ -public class ConnectActionClientString extends ConnectActionClient { - /** - * Create a new {@link ConnectActionClientString}. - * - * @param s - * the socket to bind to - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - */ - public ConnectActionClientString(Socket s, String key) { - super(s, key); - } - - /** - * Create a new {@link ConnectActionClientString}. - * - * @param s - * the socket to bind to - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - * @param clientVersion - * the version of this client - */ - public ConnectActionClientString(Socket s, String key, Version clientVersion) { - super(s, key, clientVersion); - } - - /** - * Create a new {@link ConnectActionClientString}. - * - * @param host - * the host to bind to - * @param port - * the port to bind to - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - * - * @throws IOException - * in case of I/O error - * @throws UnknownHostException - * if the IP address of the host could not be determined - * @throws IllegalArgumentException - * if the port parameter is outside the specified range of valid - * port values, which is between 0 and 65535, inclusive - */ - public ConnectActionClientString(String host, int port, String key) - throws IOException { - super(host, port, key); - } - - /** - * Create a new {@link ConnectActionClientString}. - * - * @param host - * the host to bind to - * @param port - * the port to bind to - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - * @param clientVersion - * the version of this client - * - * @throws IOException - * in case of I/O error - * @throws UnknownHostException - * if the IP address of the host could not be determined - * @throws IllegalArgumentException - * if the port parameter is outside the specified range of valid - * port values, which is between 0 and 65535, inclusive - */ - public ConnectActionClientString(String host, int port, String key, - Version clientVersion) throws IOException { - super(host, port, key, clientVersion); - } - - /** - * Send the given object to the server (and return the answer). - * - * @param data - * the data to send - * - * @return the answer, which can be NULL - * - * @throws IOException - * in case of I/O error - */ - public String send(String data) throws IOException { - return action.sendString(data); - } - - // Deprecated // - - /** - * @deprecated SSL support has been replaced by key-based encryption. - *

- * Please use the version with key encryption (this deprecated - * version uses an empty key when ssl is TRUE and no - * key (NULL) when ssl is FALSE). - */ - @Deprecated - public ConnectActionClientString(String host, int port, boolean ssl) - throws IOException { - this(host, port, ssl ? "" : null); - } - - /** - * @deprecated SSL support has been replaced by key-based encryption. - *

- * Please use the version with key encryption (this deprecated - * version uses an empty key when ssl is TRUE and no - * key (NULL) when ssl is FALSE). - */ - @Deprecated - public ConnectActionClientString(String host, int port, boolean ssl, - Version version) throws IOException { - this(host, port, ssl ? "" : null, version); - } - - /** - * @deprecated SSL support has been replaced by key-based encryption. - *

- * Please use the version with key encryption (this deprecated - * version uses an empty key when ssl is TRUE and no - * key (NULL) when ssl is FALSE). - */ - @SuppressWarnings("unused") - @Deprecated - public ConnectActionClientString(Socket s, boolean ssl) throws IOException { - this(s, ssl ? "" : null); - } - - /** - * @deprecated SSL support has been replaced by key-based encryption. - *

- * Please use the version with key encryption (this deprecated - * version uses an empty key when ssl is TRUE and no - * key (NULL) when ssl is FALSE). - */ - @SuppressWarnings("unused") - @Deprecated - public ConnectActionClientString(Socket s, boolean ssl, Version version) - throws IOException { - this(s, ssl ? "" : null, version); - } -} \ No newline at end of file diff --git a/src/be/nikiroo/utils/serial/server/ConnectActionServer.java b/src/be/nikiroo/utils/serial/server/ConnectActionServer.java deleted file mode 100644 index 350d3fe..0000000 --- a/src/be/nikiroo/utils/serial/server/ConnectActionServer.java +++ /dev/null @@ -1,171 +0,0 @@ -package be.nikiroo.utils.serial.server; - -import java.net.Socket; - -import be.nikiroo.utils.Version; - -/** - * Base class used for the server basic handling. - *

- * It represents a single action: a server is expected to execute one action for - * each client action. - * - * @author niki - */ -abstract class ConnectActionServer { - private boolean closing; - - /** - * The underlying {@link ConnectAction}. - *

- * Cannot be NULL. - */ - protected ConnectAction action; - - /** - * Create a new {@link ConnectActionServer}, using the current version. - * - * @param s - * the socket to bind to - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - */ - public ConnectActionServer(Socket s, String key) { - this(s, key, Version.getCurrentVersion()); - } - - /** - * Create a new {@link ConnectActionServer}. - * - * @param s - * the socket to bind to - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - * @param serverVersion - * the version of this server,that will be sent to the client - */ - public ConnectActionServer(Socket s, String key, Version serverVersion) { - action = new ConnectAction(s, true, key, serverVersion) { - @Override - protected void action(Version clientVersion) throws Exception { - ConnectActionServer.this.action(clientVersion); - } - - @Override - protected void onError(Exception e) { - ConnectActionServer.this.onError(e); - } - - @Override - protected Version negotiateVersion(Version clientVersion) { - return ConnectActionServer.this.negotiateVersion(clientVersion); - } - }; - } - - /** - * Actually start the process and call the action (synchronous). - */ - public void connect() { - action.connect(); - } - - /** - * Actually start the process and call the action (asynchronous). - */ - public void connectAsync() { - new Thread(new Runnable() { - @Override - public void run() { - connect(); - } - }).start(); - } - - /** - * Stop the client/server connection on behalf of the server (usually, the - * client connects then is allowed to send as many requests as it wants; in - * some cases, though, the server may wish to forcefully close the - * connection and can do via this value, when it is set to TRUE). - *

- * Example of usage: the client failed an authentication check, cut the - * connection here and now. - * - * @return TRUE when it is - */ - public boolean isClosing() { - return closing; - } - - /** - * Can be called to stop the client/server connection on behalf of the - * server (usually, the client connects then is allowed to send as many - * requests as it wants; in some cases, though, the server may wish to - * forcefully close the connection and can do so by calling this method). - *

- * Example of usage: the client failed an authentication check, cut the - * connection here and now. - */ - public void close() { - closing = true; - } - - /** - * The total amount of bytes received. - * - * @return the amount of bytes received - */ - public long getBytesReceived() { - return action.getBytesReceived(); - } - - /** - * The total amount of bytes sent. - * - * @return the amount of bytes sent - */ - public long getBytesSent() { - return action.getBytesWritten(); - } - - /** - * Method that will be called when an action is performed on the server. - * - * @param clientVersion - * the version of the client connected to this server - * - * @throws Exception - * in case of I/O error - */ - @SuppressWarnings("unused") - public void action(Version clientVersion) throws Exception { - } - - /** - * Handler called when an unexpected error occurs in the code. - *

- * Will just ignore the error by default. - * - * @param e - * the exception that occurred - */ - protected void onError(@SuppressWarnings("unused") Exception e) { - } - - /** - * Method called when we negotiate the version with the client. - *

- * Will return the actual server version by default. - * - * @param clientVersion - * the client version - * - * @return the version to send to the client - */ - protected Version negotiateVersion( - @SuppressWarnings("unused") Version clientVersion) { - return action.getVersion(); - } -} \ No newline at end of file diff --git a/src/be/nikiroo/utils/serial/server/ConnectActionServerObject.java b/src/be/nikiroo/utils/serial/server/ConnectActionServerObject.java deleted file mode 100644 index 07d9867..0000000 --- a/src/be/nikiroo/utils/serial/server/ConnectActionServerObject.java +++ /dev/null @@ -1,72 +0,0 @@ -package be.nikiroo.utils.serial.server; - -import java.io.IOException; -import java.net.Socket; - -/** - * Class used for the server basic handling. - *

- * It represents a single action: a server is expected to execute one action for - * each client action. - * - * @author niki - */ -public class ConnectActionServerObject extends ConnectActionServer { - /** - * Create a new {@link ConnectActionServerObject} as the server version. - * - * @param s - * the socket to bind to - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - */ - public ConnectActionServerObject(Socket s, String key) { - super(s, key); - } - - /** - * Serialise and send the given object to the client. - * - * @param data - * the data to send - * - * @throws IOException - * in case of I/O error - * @throws NoSuchFieldException - * if the serialised data contains information about a field - * which does actually not exist in the class we know of - * @throws NoSuchMethodException - * if a class described in the serialised data cannot be created - * because it is not compatible with this code - * @throws ClassNotFoundException - * if a class described in the serialised data cannot be found - */ - public void send(Object data) throws IOException, NoSuchFieldException, - NoSuchMethodException, ClassNotFoundException { - action.sendObject(data); - } - - /** - * (Flush the data to the client if needed and) retrieve its answer. - * - * @return the deserialised answer (which can actually be NULL) - * - * @throws IOException - * in case of I/O error - * @throws NoSuchFieldException - * if the serialised data contains information about a field - * which does actually not exist in the class we know of - * @throws NoSuchMethodException - * if a class described in the serialised data cannot be created - * because it is not compatible with this code - * @throws ClassNotFoundException - * if a class described in the serialised data cannot be found - * @throws java.lang.NullPointerException - * if the counter part has no data to send - */ - public Object rec() throws NoSuchFieldException, NoSuchMethodException, - ClassNotFoundException, IOException, java.lang.NullPointerException { - return action.recObject(); - } -} diff --git a/src/be/nikiroo/utils/serial/server/ConnectActionServerString.java b/src/be/nikiroo/utils/serial/server/ConnectActionServerString.java deleted file mode 100644 index 8d113c1..0000000 --- a/src/be/nikiroo/utils/serial/server/ConnectActionServerString.java +++ /dev/null @@ -1,52 +0,0 @@ -package be.nikiroo.utils.serial.server; - -import java.io.IOException; -import java.net.Socket; - -/** - * Class used for the server basic handling. - *

- * It represents a single action: a server is expected to execute one action for - * each client action. - * - * @author niki - */ -public class ConnectActionServerString extends ConnectActionServer { - /** - * Create a new {@link ConnectActionServerString} as the server version. - * - * @param s - * the socket to bind to - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - */ - public ConnectActionServerString(Socket s, String key) { - super(s, key); - } - - /** - * Serialise and send the given object to the client. - * - * @param data - * the data to send - * - * @throws IOException - * in case of I/O error - */ - public void send(String data) throws IOException { - action.sendString(data); - } - - /** - * (Flush the data to the client if needed and) retrieve its answer. - * - * @return the answer if it is available, or NULL if not - * - * @throws IOException - * in case of I/O error - */ - public String rec() throws IOException { - return action.recString(); - } -} diff --git a/src/be/nikiroo/utils/serial/server/Server.java b/src/be/nikiroo/utils/serial/server/Server.java deleted file mode 100644 index 0470159..0000000 --- a/src/be/nikiroo/utils/serial/server/Server.java +++ /dev/null @@ -1,419 +0,0 @@ -package be.nikiroo.utils.serial.server; - -import java.io.IOException; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.UnknownHostException; - -import be.nikiroo.utils.TraceHandler; - -/** - * This class implements a simple server that can listen for connections and - * send/receive objects. - *

- * Note: this {@link Server} has to be discarded after use (cannot be started - * twice). - * - * @author niki - */ -abstract class Server implements Runnable { - protected final String key; - protected long id = 0; - - private final String name; - private final Object lock = new Object(); - private final Object counterLock = new Object(); - - private ServerSocket ss; - private int port; - - private boolean started; - private boolean exiting = false; - private int counter; - - private long bytesReceived; - private long bytesSent; - - private TraceHandler tracer = new TraceHandler(); - - /** - * Create a new {@link ConnectActionServer} to handle a request. - * - * @param s - * the socket to service - * - * @return the action - */ - abstract ConnectActionServer createConnectActionServer(Socket s); - - /** - * Create a new server that will start listening on the network when - * {@link Server#start()} is called. - * - * @param port - * the port to listen on, or 0 to assign any unallocated port - * found (which can later on be queried via - * {@link Server#getPort()} - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - * - * @throws IOException - * in case of I/O error - * @throws UnknownHostException - * if the IP address of the host could not be determined - * @throws IllegalArgumentException - * if the port parameter is outside the specified range of valid - * port values, which is between 0 and 65535, inclusive - */ - public Server(int port, String key) throws IOException { - this((String) null, port, key); - } - - /** - * Create a new server that will start listening on the network when - * {@link Server#start()} is called. - *

- * All the communications will happen in plain text. - * - * @param name - * the server name (only used for debug info and traces) - * @param port - * the port to listen on - * - * @throws IOException - * in case of I/O error - * @throws UnknownHostException - * if the IP address of the host could not be determined - * @throws IllegalArgumentException - * if the port parameter is outside the specified range of valid - * port values, which is between 0 and 65535, inclusive - */ - public Server(String name, int port) throws IOException { - this(name, port, null); - } - - /** - * Create a new server that will start listening on the network when - * {@link Server#start()} is called. - * - * @param name - * the server name (only used for debug info and traces) - * @param port - * the port to listen on - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - * - * @throws IOException - * in case of I/O error - * @throws UnknownHostException - * if the IP address of the host could not be determined - * @throws IllegalArgumentException - * if the port parameter is outside the specified range of valid - * port values, which is between 0 and 65535, inclusive - */ - public Server(String name, int port, String key) throws IOException { - this.name = name; - this.port = port; - this.key = key; - this.ss = new ServerSocket(port); - - if (this.port == 0) { - this.port = this.ss.getLocalPort(); - } - } - - /** - * The traces handler for this {@link Server}. - * - * @return the traces handler - */ - public TraceHandler getTraceHandler() { - return tracer; - } - - /** - * The traces handler for this {@link Server}. - * - * @param tracer - * the new traces handler - */ - public void setTraceHandler(TraceHandler tracer) { - if (tracer == null) { - tracer = new TraceHandler(false, false, false); - } - - this.tracer = tracer; - } - - /** - * The name of this {@link Server} if any. - *

- * Used for traces and debug purposes only. - * - * @return the name or NULL - */ - public String getName() { - return name; - } - - /** - * Return the assigned port. - * - * @return the assigned port - */ - public int getPort() { - return port; - } - - /** - * The total amount of bytes received. - * - * @return the amount of bytes received - */ - public long getBytesReceived() { - return bytesReceived; - } - - /** - * The total amount of bytes sent. - * - * @return the amount of bytes sent - */ - public long getBytesSent() { - return bytesSent; - } - - /** - * Start the server (listen on the network for new connections). - *

- * Can only be called once. - *

- * This call is asynchronous, and will just start a new {@link Thread} on - * itself (see {@link Server#run()}). - */ - public void start() { - new Thread(this).start(); - } - - /** - * Start the server (listen on the network for new connections). - *

- * Can only be called once. - *

- * You may call it via {@link Server#start()} for an asynchronous call, too. - */ - @Override - public void run() { - ServerSocket ss = null; - boolean alreadyStarted = false; - synchronized (lock) { - ss = this.ss; - if (!started && ss != null) { - started = true; - } else { - alreadyStarted = started; - } - } - - if (alreadyStarted) { - tracer.error(name + ": cannot start server on port " + port - + ", it is already started"); - return; - } - - if (ss == null) { - tracer.error(name + ": cannot start server on port " + port - + ", it has already been used"); - return; - } - - try { - tracer.trace(name + ": server starting on port " + port + " (" - + (key != null ? "encrypted" : "plain text") + ")"); - - while (started && !exiting) { - count(1); - final Socket s = ss.accept(); - new Thread(new Runnable() { - @Override - public void run() { - ConnectActionServer action = null; - try { - action = createConnectActionServer(s); - action.connect(); - } finally { - count(-1); - if (action != null) { - bytesReceived += action.getBytesReceived(); - bytesSent += action.getBytesSent(); - } - } - } - }).start(); - } - - // Will be covered by @link{Server#stop(long)} for timeouts - while (counter > 0) { - Thread.sleep(10); - } - } catch (Exception e) { - if (counter > 0) { - onError(e); - } - } finally { - try { - ss.close(); - } catch (Exception e) { - onError(e); - } - - this.ss = null; - - started = false; - exiting = false; - counter = 0; - - tracer.trace(name + ": client terminated on port " + port); - } - } - - /** - * Will stop the server, synchronously and without a timeout. - */ - public void stop() { - tracer.trace(name + ": stopping server"); - stop(0, true); - } - - /** - * Stop the server. - * - * @param timeout - * the maximum timeout to wait for existing actions to complete, - * or 0 for "no timeout" - * @param wait - * wait for the server to be stopped before returning - * (synchronous) or not (asynchronous) - */ - public void stop(final long timeout, final boolean wait) { - if (wait) { - stop(timeout); - } else { - new Thread(new Runnable() { - @Override - public void run() { - stop(timeout); - } - }).start(); - } - } - - /** - * Stop the server (synchronous). - * - * @param timeout - * the maximum timeout to wait for existing actions to complete, - * or 0 for "no timeout" - */ - private void stop(long timeout) { - tracer.trace(name + ": server stopping on port " + port); - synchronized (lock) { - if (started && !exiting) { - exiting = true; - - try { - getConnectionToMe().connect(); - long time = 0; - while (ss != null && timeout > 0 && timeout > time) { - Thread.sleep(10); - time += 10; - } - } catch (Exception e) { - if (ss != null) { - counter = 0; // will stop the main thread - onError(e); - } - } - } - } - - // return only when stopped - while (started || exiting) { - try { - Thread.sleep(10); - } catch (InterruptedException e) { - } - } - } - - /** - * Return a connection to this server (used by the Exit code to send an exit - * message). - * - * @return the connection - * - * @throws UnknownHostException - * the host should always be NULL (localhost) - * @throws IOException - * in case of I/O error - */ - abstract protected ConnectActionClient getConnectionToMe() - throws UnknownHostException, IOException; - - /** - * Change the number of currently serviced actions. - * - * @param change - * the number to increase or decrease - * - * @return the current number after this operation - */ - private int count(int change) { - synchronized (counterLock) { - counter += change; - return counter; - } - } - - /** - * This method will be called on errors. - *

- * By default, it will only call the trace handler (so you may want to call - * super {@link Server#onError} if you override it). - * - * @param e - * the error - */ - protected void onError(Exception e) { - tracer.error(e); - } - - /** - * Return the next ID to use. - * - * @return the next ID - */ - protected synchronized long getNextId() { - return id++; - } - - /** - * Method called when - * {@link ServerObject#onRequest(ConnectActionServerObject, Object, long)} - * has successfully finished. - *

- * Can be used to know how much data was transmitted. - * - * @param id - * the ID used to identify the request - * @param bytesReceived - * the bytes received during the request - * @param bytesSent - * the bytes sent during the request - */ - @SuppressWarnings("unused") - protected void onRequestDone(long id, long bytesReceived, long bytesSent) { - } -} diff --git a/src/be/nikiroo/utils/serial/server/ServerBridge.java b/src/be/nikiroo/utils/serial/server/ServerBridge.java deleted file mode 100644 index 0b734c6..0000000 --- a/src/be/nikiroo/utils/serial/server/ServerBridge.java +++ /dev/null @@ -1,292 +0,0 @@ -package be.nikiroo.utils.serial.server; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Array; -import java.net.Socket; -import java.net.UnknownHostException; - -import be.nikiroo.utils.StringUtils; -import be.nikiroo.utils.TraceHandler; -import be.nikiroo.utils.Version; -import be.nikiroo.utils.serial.Importer; - -/** - * This class implements a simple server that can bridge two other - * {@link Server}s. - *

- * It can, of course, inspect the data that goes through it (by default, it - * prints traces of the data). - *

- * Note: this {@link ServerBridge} has to be discarded after use (cannot be - * started twice). - * - * @author niki - */ -public class ServerBridge extends Server { - private final String forwardToHost; - private final int forwardToPort; - private final String forwardToKey; - - /** - * Create a new server that will start listening on the network when - * {@link ServerBridge#start()} is called. - * - * @param port - * the port to listen on, or 0 to assign any unallocated port - * found (which can later on be queried via - * {@link ServerBridge#getPort()} - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - * @param forwardToHost - * the host server to forward the calls to - * @param forwardToPort - * the host port to forward the calls to - * @param forwardToKey - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - * - * @throws IOException - * in case of I/O error - * @throws UnknownHostException - * if the IP address of the host could not be determined - * @throws IllegalArgumentException - * if the port parameter is outside the specified range of valid - * port values, which is between 0 and 65535, inclusive - */ - public ServerBridge(int port, String key, String forwardToHost, - int forwardToPort, String forwardToKey) throws IOException { - super(port, key); - this.forwardToHost = forwardToHost; - this.forwardToPort = forwardToPort; - this.forwardToKey = forwardToKey; - } - - /** - * Create a new server that will start listening on the network when - * {@link ServerBridge#start()} is called. - * - * @param name - * the server name (only used for debug info and traces) - * @param port - * the port to listen on - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - * @param forwardToHost - * the host server to forward the calls to - * @param forwardToPort - * the host port to forward the calls to - * @param forwardToKey - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) use an SSL connection - * for the forward server or not - * - * @throws IOException - * in case of I/O error - * @throws UnknownHostException - * if the IP address of the host could not be determined - * @throws IllegalArgumentException - * if the port parameter is outside the specified range of valid - * port values, which is between 0 and 65535, inclusive - */ - public ServerBridge(String name, int port, String key, - String forwardToHost, int forwardToPort, String forwardToKey) - throws IOException { - super(name, port, key); - this.forwardToHost = forwardToHost; - this.forwardToPort = forwardToPort; - this.forwardToKey = forwardToKey; - } - - /** - * The traces handler for this {@link Server}. - *

- * The trace levels are handled as follow: - *

    - *
  • 1: it will only print basic IN/OUT messages with length
  • - *
  • 2: it will try to interpret it as an object (SLOW) and print the - * object class if possible
  • - *
  • 3: it will try to print the {@link Object#toString()} value, or the - * data if it is not an object
  • - *
  • 4: it will also print the unzipped serialised value if it is an - * object
  • - *
- * - * @param tracer - * the new traces handler - */ - @Override - public void setTraceHandler(TraceHandler tracer) { - super.setTraceHandler(tracer); - } - - @Override - protected ConnectActionServer createConnectActionServer(Socket s) { - // Bad impl, not up to date (should work, but not efficient) - return new ConnectActionServerString(s, key) { - @Override - public void action(Version clientVersion) throws Exception { - onClientContact(clientVersion); - final ConnectActionServerString bridge = this; - - try { - new ConnectActionClientString(forwardToHost, forwardToPort, - forwardToKey) { - @Override - public void action(Version serverVersion) - throws Exception { - onServerContact(serverVersion); - - for (String fromClient = bridge.rec(); fromClient != null; fromClient = bridge - .rec()) { - onRec(fromClient); - String fromServer = send(fromClient); - onSend(fromServer); - bridge.send(fromServer); - } - - getTraceHandler().trace("=== DONE", 1); - getTraceHandler().trace("", 1); - } - - @Override - protected void onError(Exception e) { - ServerBridge.this.onError(e); - } - }.connect(); - } catch (Exception e) { - ServerBridge.this.onError(e); - } - } - }; - } - - /** - * This is the method that is called each time a client contact us. - */ - protected void onClientContact(Version clientVersion) { - getTraceHandler().trace(">>> CLIENT " + clientVersion); - } - - /** - * This is the method that is called each time a client contact us. - */ - protected void onServerContact(Version serverVersion) { - getTraceHandler().trace("<<< SERVER " + serverVersion); - getTraceHandler().trace(""); - } - - /** - * This is the method that is called each time a client contact us. - * - * @param data - * the data sent by the client - */ - protected void onRec(String data) { - trace(">>> CLIENT", data); - } - - /** - * This is the method that is called each time the forwarded server contact - * us. - * - * @param data - * the data sent by the client - */ - protected void onSend(String data) { - trace("<<< SERVER", data); - } - - @Override - protected ConnectActionClient getConnectionToMe() - throws UnknownHostException, IOException { - return new ConnectActionClientString(new Socket((String) null, - getPort()), key); - } - - @Override - public void run() { - getTraceHandler().trace( - getName() + ": will forward to " + forwardToHost + ":" - + forwardToPort + " (" - + (forwardToKey != null ? "encrypted" : "plain text") - + ")"); - super.run(); - } - - /** - * Trace the data with the given prefix. - * - * @param prefix - * the prefix (client, server, version...) - * @param data - * the data to trace - */ - private void trace(String prefix, String data) { - int size = data == null ? 0 : data.length(); - String ssize = StringUtils.formatNumber(size) + "bytes"; - - getTraceHandler().trace(prefix + ": " + ssize, 1); - - if (getTraceHandler().getTraceLevel() >= 2) { - try { - while (data.startsWith("ZIP:") || data.startsWith("B64:")) { - if (data.startsWith("ZIP:")) { - data = StringUtils.unzip64s(data.substring(4)); - } else if (data.startsWith("B64:")) { - data = StringUtils.unzip64s(data.substring(4)); - } - } - - InputStream stream = new ByteArrayInputStream( - StringUtils.getBytes(data)); - try { - Object obj = new Importer().read(stream).getValue(); - if (obj == null) { - getTraceHandler().trace("NULL", 2); - getTraceHandler().trace("NULL", 3); - getTraceHandler().trace("NULL", 4); - } else { - if (obj.getClass().isArray()) { - getTraceHandler().trace( - "(" + obj.getClass() + ") with " - + Array.getLength(obj) - + "element(s)", 3); - } else { - getTraceHandler().trace("(" + obj.getClass() + ")", - 2); - } - getTraceHandler().trace("" + obj.toString(), 3); - getTraceHandler().trace(data, 4); - } - } finally { - stream.close(); - } - } catch (NoSuchMethodException e) { - getTraceHandler().trace("(not an object)", 2); - getTraceHandler().trace(data, 3); - getTraceHandler().trace("", 4); - } catch (NoSuchFieldException e) { - getTraceHandler().trace( - "(incompatible: " + e.getMessage() + ")", 2); - getTraceHandler().trace(data, 3); - getTraceHandler().trace("", 4); - } catch (ClassNotFoundException e) { - getTraceHandler().trace( - "(unknown object: " + e.getMessage() + ")", 2); - getTraceHandler().trace(data, 3); - getTraceHandler().trace("", 4); - } catch (Exception e) { - getTraceHandler().trace( - "(decode error: " + e.getMessage() + ")", 2); - getTraceHandler().trace(data, 3); - getTraceHandler().trace("", 4); - } - - getTraceHandler().trace("", 2); - } - } -} diff --git a/src/be/nikiroo/utils/serial/server/ServerObject.java b/src/be/nikiroo/utils/serial/server/ServerObject.java deleted file mode 100644 index a6a5dd1..0000000 --- a/src/be/nikiroo/utils/serial/server/ServerObject.java +++ /dev/null @@ -1,180 +0,0 @@ -package be.nikiroo.utils.serial.server; - -import java.io.IOException; -import java.net.Socket; -import java.net.UnknownHostException; - -import be.nikiroo.utils.Version; - -/** - * This class implements a simple server that can listen for connections and - * send/receive objects. - *

- * Note: this {@link ServerObject} has to be discarded after use (cannot be - * started twice). - * - * @author niki - */ -abstract public class ServerObject extends Server { - /** - * Create a new server that will start listening on the network when - * {@link ServerObject#start()} is called. - * - * @param port - * the port to listen on, or 0 to assign any unallocated port - * found (which can later on be queried via - * {@link ServerObject#getPort()} - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - * - * @throws IOException - * in case of I/O error - * @throws UnknownHostException - * if the IP address of the host could not be determined - * @throws IllegalArgumentException - * if the port parameter is outside the specified range of valid - * port values, which is between 0 and 65535, inclusive - */ - public ServerObject(int port, String key) throws IOException { - super(port, key); - } - - /** - * Create a new server that will start listening on the network when - * {@link ServerObject#start()} is called. - * - * @param name - * the server name (only used for debug info and traces) - * @param port - * the port to listen on - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - * - * @throws IOException - * in case of I/O error - * @throws UnknownHostException - * if the IP address of the host could not be determined - * @throws IllegalArgumentException - * if the port parameter is outside the specified range of valid - * port values, which is between 0 and 65535, inclusive - */ - public ServerObject(String name, int port, String key) throws IOException { - super(name, port, key); - } - - @Override - protected ConnectActionServer createConnectActionServer(Socket s) { - return new ConnectActionServerObject(s, key) { - @Override - public void action(Version clientVersion) throws Exception { - long id = getNextId(); - try { - for (Object data = rec(); true; data = rec()) { - Object rep = null; - try { - rep = onRequest(this, clientVersion, data, id); - if (isClosing()) { - return; - } - } catch (Exception e) { - onError(e); - } - - send(rep); - } - } catch (NullPointerException e) { - // Client has no data any more, we quit - onRequestDone(id, getBytesReceived(), getBytesSent()); - } - } - - @Override - protected void onError(Exception e) { - ServerObject.this.onError(e); - } - }; - } - - @Override - protected ConnectActionClient getConnectionToMe() - throws UnknownHostException, IOException { - return new ConnectActionClientObject(new Socket((String) null, - getPort()), key); - } - - /** - * This is the method that is called on each client request. - *

- * You are expected to react to it and return an answer (which can be NULL). - * - * @param action - * the client action - * @param data - * the data sent by the client (which can be NULL) - * @param id - * an ID to identify this request (will also be re-used for - * {@link ServerObject#onRequestDone(long, long, long)}. - * - * @return the answer to return to the client (which can be NULL) - * - * @throws Exception - * in case of an exception, the error will only be logged - */ - protected Object onRequest(ConnectActionServerObject action, - Version clientVersion, Object data, - @SuppressWarnings("unused") long id) throws Exception { - // TODO: change to abstract when deprecated method is removed - // Default implementation for compat - return onRequest(action, clientVersion, data); - } - - // Deprecated // - - /** - * @deprecated SSL support has been replaced by key-based encryption. - *

- * Please use the version with key encryption (this deprecated - * version uses an empty key when ssl is TRUE and no - * key (NULL) when ssl is FALSE). - */ - @Deprecated - public ServerObject(int port, boolean ssl) throws IOException { - this(port, ssl ? "" : null); - } - - /** - * @deprecated SSL support has been replaced by key-based encryption. - *

- * Please use the version with key encryption (this deprecated - * version uses an empty key when ssl is TRUE and no - * key (NULL) when ssl is FALSE). - */ - @Deprecated - public ServerObject(String name, int port, boolean ssl) throws IOException { - this(name, port, ssl ? "" : null); - } - - /** - * Will be called if the correct version is not overrided. - * - * @deprecated use the version with the id. - * - * @param action - * the client action - * @param data - * the data sent by the client - * - * @return the answer to return to the client - * - * @throws Exception - * in case of an exception, the error will only be logged - */ - @Deprecated - @SuppressWarnings("unused") - protected Object onRequest(ConnectActionServerObject action, - Version version, Object data) throws Exception { - return null; - } -} diff --git a/src/be/nikiroo/utils/serial/server/ServerString.java b/src/be/nikiroo/utils/serial/server/ServerString.java deleted file mode 100644 index 3c982fd..0000000 --- a/src/be/nikiroo/utils/serial/server/ServerString.java +++ /dev/null @@ -1,183 +0,0 @@ -package be.nikiroo.utils.serial.server; - -import java.io.IOException; -import java.net.Socket; -import java.net.UnknownHostException; - -import be.nikiroo.utils.Version; - -/** - * This class implements a simple server that can listen for connections and - * send/receive Strings. - *

- * Note: this {@link ServerString} has to be discarded after use (cannot be - * started twice). - * - * @author niki - */ -abstract public class ServerString extends Server { - /** - * Create a new server that will start listening on the network when - * {@link ServerString#start()} is called. - * - * @param port - * the port to listen on, or 0 to assign any unallocated port - * found (which can later on be queried via - * {@link ServerString#getPort()} - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - * - * @throws IOException - * in case of I/O error - * @throws UnknownHostException - * if the IP address of the host could not be determined - * @throws IllegalArgumentException - * if the port parameter is outside the specified range of valid - * port values, which is between 0 and 65535, inclusive - */ - public ServerString(int port, String key) throws IOException { - super(port, key); - } - - /** - * Create a new server that will start listening on the network when - * {@link ServerString#start()} is called. - * - * @param name - * the server name (only used for debug info and traces) - * @param port - * the port to listen on - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - * - * @throws IOException - * in case of I/O error - * @throws UnknownHostException - * if the IP address of the host could not be determined - * @throws IllegalArgumentException - * if the port parameter is outside the specified range of valid - * port values, which is between 0 and 65535, inclusive - */ - public ServerString(String name, int port, String key) throws IOException { - super(name, port, key); - } - - @Override - protected ConnectActionServer createConnectActionServer(Socket s) { - return new ConnectActionServerString(s, key) { - @Override - public void action(Version clientVersion) throws Exception { - long id = getNextId(); - for (String data = rec(); data != null; data = rec()) { - String rep = null; - try { - rep = onRequest(this, clientVersion, data, id); - if (isClosing()) { - return; - } - } catch (Exception e) { - onError(e); - } - - if (rep == null) { - rep = ""; - } - send(rep); - } - - onRequestDone(id, getBytesReceived(), getBytesSent()); - } - - @Override - protected void onError(Exception e) { - ServerString.this.onError(e); - } - }; - } - - @Override - protected ConnectActionClient getConnectionToMe() - throws UnknownHostException, IOException { - return new ConnectActionClientString(new Socket((String) null, - getPort()), key); - } - - /** - * This is the method that is called on each client request. - *

- * You are expected to react to it and return an answer (NULL will be - * converted to an empty {@link String}). - * - * @param action - * the client action - * @param clientVersion - * the client version - * @param data - * the data sent by the client - * @param id - * an ID to identify this request (will also be re-used for - * {@link ServerObject#onRequestDone(long, long, long)}. - * - * @return the answer to return to the client - * - * @throws Exception - * in case of an exception, the error will only be logged - */ - protected String onRequest(ConnectActionServerString action, - Version clientVersion, String data, - @SuppressWarnings("unused") long id) throws Exception { - // TODO: change to abstract when deprecated method is removed - // Default implementation for compat - return onRequest(action, clientVersion, data); - } - - // Deprecated // - - /** - * @deprecated SSL support has been replaced by key-based encryption. - *

- * Please use the version with key encryption (this deprecated - * version uses an empty key when ssl is TRUE and no - * key (NULL) when ssl is FALSE). - */ - @Deprecated - public ServerString(int port, boolean ssl) throws IOException { - this(port, ssl ? "" : null); - } - - /** - * @deprecated SSL support has been replaced by key-based encryption. - *

- * Please use the version with key encryption (this deprecated - * version uses an empty key when ssl is TRUE and no - * key (NULL) when ssl is FALSE). - */ - @Deprecated - public ServerString(String name, int port, boolean ssl) throws IOException { - this(name, port, ssl ? "" : null); - } - - /** - * Will be called if the correct version is not overrided. - * - * @deprecated use the version with the id. - * - * @param action - * the client action - * @param data - * the data sent by the client - * - * @return the answer to return to the client - * - * @throws Exception - * in case of an exception, the error will only be logged - */ - @Deprecated - @SuppressWarnings("unused") - protected String onRequest(ConnectActionServerString action, - Version version, String data) throws Exception { - return null; - } -} diff --git a/src/be/nikiroo/utils/streams/Base64.java b/src/be/nikiroo/utils/streams/Base64.java deleted file mode 100644 index d54794b..0000000 --- a/src/be/nikiroo/utils/streams/Base64.java +++ /dev/null @@ -1,752 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * 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. - */ - -/* - * Changes (@author niki): - * - default charset -> UTF-8 - */ - -package be.nikiroo.utils.streams; - -import java.io.UnsupportedEncodingException; - -import be.nikiroo.utils.StringUtils; - -/** - * Utilities for encoding and decoding the Base64 representation of - * binary data. See RFCs 2045 and 3548. - */ -class Base64 { - /** - * Default values for encoder/decoder flags. - */ - public static final int DEFAULT = 0; - - /** - * Encoder flag bit to omit the padding '=' characters at the end - * of the output (if any). - */ - public static final int NO_PADDING = 1; - - /** - * Encoder flag bit to omit all line terminators (i.e., the output - * will be on one long line). - */ - public static final int NO_WRAP = 2; - - /** - * Encoder flag bit to indicate lines should be terminated with a - * CRLF pair instead of just an LF. Has no effect if {@code - * NO_WRAP} is specified as well. - */ - public static final int CRLF = 4; - - /** - * Encoder/decoder flag bit to indicate using the "URL and - * filename safe" variant of Base64 (see RFC 3548 section 4) where - * {@code -} and {@code _} are used in place of {@code +} and - * {@code /}. - */ - public static final int URL_SAFE = 8; - - /** - * Flag to pass to {@link Base64OutputStream} to indicate that it - * should not close the output stream it is wrapping when it - * itself is closed. - */ - public static final int NO_CLOSE = 16; - - // -------------------------------------------------------- - // shared code - // -------------------------------------------------------- - - /* package */ static abstract class Coder { - public byte[] output; - public int op; - - /** - * Encode/decode another block of input data. this.output is - * provided by the caller, and must be big enough to hold all - * the coded data. On exit, this.opwill be set to the length - * of the coded data. - * - * @param finish true if this is the final call to process for - * this object. Will finalize the coder state and - * include any final bytes in the output. - * - * @return true if the input so far is good; false if some - * error has been detected in the input stream.. - */ - public abstract boolean process(byte[] input, int offset, int len, boolean finish); - - /** - * @return the maximum number of bytes a call to process() - * could produce for the given number of input bytes. This may - * be an overestimate. - */ - public abstract int maxOutputSize(int len); - } - - // -------------------------------------------------------- - // decoding - // -------------------------------------------------------- - - /** - * Decode the Base64-encoded data in input and return the data in - * a new byte array. - * - *

The padding '=' characters at the end are considered optional, but - * if any are present, there must be the correct number of them. - * - * @param str the input String to decode, which is converted to - * bytes using the default charset - * @param flags controls certain features of the decoded output. - * Pass {@code DEFAULT} to decode standard Base64. - * - * @throws IllegalArgumentException if the input contains - * incorrect padding - */ - public static byte[] decode(String str, int flags) { - return decode(StringUtils.getBytes(str), flags); - } - - /** - * Decode the Base64-encoded data in input and return the data in - * a new byte array. - * - *

The padding '=' characters at the end are considered optional, but - * if any are present, there must be the correct number of them. - * - * @param input the input array to decode - * @param flags controls certain features of the decoded output. - * Pass {@code DEFAULT} to decode standard Base64. - * - * @throws IllegalArgumentException if the input contains - * incorrect padding - */ - public static byte[] decode(byte[] input, int flags) { - return decode(input, 0, input.length, flags); - } - - /** - * Decode the Base64-encoded data in input and return the data in - * a new byte array. - * - *

The padding '=' characters at the end are considered optional, but - * if any are present, there must be the correct number of them. - * - * @param input the data to decode - * @param offset the position within the input array at which to start - * @param len the number of bytes of input to decode - * @param flags controls certain features of the decoded output. - * Pass {@code DEFAULT} to decode standard Base64. - * - * @throws IllegalArgumentException if the input contains - * incorrect padding - */ - public static byte[] decode(byte[] input, int offset, int len, int flags) { - // Allocate space for the most data the input could represent. - // (It could contain less if it contains whitespace, etc.) - Decoder decoder = new Decoder(flags, new byte[len*3/4]); - - if (!decoder.process(input, offset, len, true)) { - throw new IllegalArgumentException("bad base-64"); - } - - // Maybe we got lucky and allocated exactly enough output space. - if (decoder.op == decoder.output.length) { - return decoder.output; - } - - // Need to shorten the array, so allocate a new one of the - // right size and copy. - byte[] temp = new byte[decoder.op]; - System.arraycopy(decoder.output, 0, temp, 0, decoder.op); - return temp; - } - - /* package */ static class Decoder extends Coder { - /** - * Lookup table for turning bytes into their position in the - * Base64 alphabet. - */ - private static final int DECODE[] = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, - -1, 0, 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, -1, -1, -1, -1, -1, - -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - }; - - /** - * Decode lookup table for the "web safe" variant (RFC 3548 - * sec. 4) where - and _ replace + and /. - */ - private static final int DECODE_WEBSAFE[] = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, - -1, 0, 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, -1, -1, -1, -1, 63, - -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - }; - - /** Non-data values in the DECODE arrays. */ - private static final int SKIP = -1; - private static final int EQUALS = -2; - - /** - * States 0-3 are reading through the next input tuple. - * State 4 is having read one '=' and expecting exactly - * one more. - * State 5 is expecting no more data or padding characters - * in the input. - * State 6 is the error state; an error has been detected - * in the input and no future input can "fix" it. - */ - private int state; // state number (0 to 6) - private int value; - - final private int[] alphabet; - - public Decoder(int flags, byte[] output) { - this.output = output; - - alphabet = ((flags & URL_SAFE) == 0) ? DECODE : DECODE_WEBSAFE; - state = 0; - value = 0; - } - - /** - * @return an overestimate for the number of bytes {@code - * len} bytes could decode to. - */ - @Override - public int maxOutputSize(int len) { - return len * 3/4 + 10; - } - - /** - * Decode another block of input data. - * - * @return true if the state machine is still healthy. false if - * bad base-64 data has been detected in the input stream. - */ - @Override - public boolean process(byte[] input, int offset, int len, boolean finish) { - if (this.state == 6) return false; - - int p = offset; - len += offset; - - // Using local variables makes the decoder about 12% - // faster than if we manipulate the member variables in - // the loop. (Even alphabet makes a measurable - // difference, which is somewhat surprising to me since - // the member variable is final.) - int state = this.state; - int value = this.value; - int op = 0; - final byte[] output = this.output; - final int[] alphabet = this.alphabet; - - while (p < len) { - // Try the fast path: we're starting a new tuple and the - // next four bytes of the input stream are all data - // bytes. This corresponds to going through states - // 0-1-2-3-0. We expect to use this method for most of - // the data. - // - // If any of the next four bytes of input are non-data - // (whitespace, etc.), value will end up negative. (All - // the non-data values in decode are small negative - // numbers, so shifting any of them up and or'ing them - // together will result in a value with its top bit set.) - // - // You can remove this whole block and the output should - // be the same, just slower. - if (state == 0) { - while (p+4 <= len && - (value = ((alphabet[input[p] & 0xff] << 18) | - (alphabet[input[p+1] & 0xff] << 12) | - (alphabet[input[p+2] & 0xff] << 6) | - (alphabet[input[p+3] & 0xff]))) >= 0) { - output[op+2] = (byte) value; - output[op+1] = (byte) (value >> 8); - output[op] = (byte) (value >> 16); - op += 3; - p += 4; - } - if (p >= len) break; - } - - // The fast path isn't available -- either we've read a - // partial tuple, or the next four input bytes aren't all - // data, or whatever. Fall back to the slower state - // machine implementation. - - int d = alphabet[input[p++] & 0xff]; - - switch (state) { - case 0: - if (d >= 0) { - value = d; - ++state; - } else if (d != SKIP) { - this.state = 6; - return false; - } - break; - - case 1: - if (d >= 0) { - value = (value << 6) | d; - ++state; - } else if (d != SKIP) { - this.state = 6; - return false; - } - break; - - case 2: - if (d >= 0) { - value = (value << 6) | d; - ++state; - } else if (d == EQUALS) { - // Emit the last (partial) output tuple; - // expect exactly one more padding character. - output[op++] = (byte) (value >> 4); - state = 4; - } else if (d != SKIP) { - this.state = 6; - return false; - } - break; - - case 3: - if (d >= 0) { - // Emit the output triple and return to state 0. - value = (value << 6) | d; - output[op+2] = (byte) value; - output[op+1] = (byte) (value >> 8); - output[op] = (byte) (value >> 16); - op += 3; - state = 0; - } else if (d == EQUALS) { - // Emit the last (partial) output tuple; - // expect no further data or padding characters. - output[op+1] = (byte) (value >> 2); - output[op] = (byte) (value >> 10); - op += 2; - state = 5; - } else if (d != SKIP) { - this.state = 6; - return false; - } - break; - - case 4: - if (d == EQUALS) { - ++state; - } else if (d != SKIP) { - this.state = 6; - return false; - } - break; - - case 5: - if (d != SKIP) { - this.state = 6; - return false; - } - break; - } - } - - if (!finish) { - // We're out of input, but a future call could provide - // more. - this.state = state; - this.value = value; - this.op = op; - return true; - } - - // Done reading input. Now figure out where we are left in - // the state machine and finish up. - - switch (state) { - case 0: - // Output length is a multiple of three. Fine. - break; - case 1: - // Read one extra input byte, which isn't enough to - // make another output byte. Illegal. - this.state = 6; - return false; - case 2: - // Read two extra input bytes, enough to emit 1 more - // output byte. Fine. - output[op++] = (byte) (value >> 4); - break; - case 3: - // Read three extra input bytes, enough to emit 2 more - // output bytes. Fine. - output[op++] = (byte) (value >> 10); - output[op++] = (byte) (value >> 2); - break; - case 4: - // Read one padding '=' when we expected 2. Illegal. - this.state = 6; - return false; - case 5: - // Read all the padding '='s we expected and no more. - // Fine. - break; - } - - this.state = state; - this.op = op; - return true; - } - } - - // -------------------------------------------------------- - // encoding - // -------------------------------------------------------- - - /** - * Base64-encode the given data and return a newly allocated - * String with the result. - * - * @param input the data to encode - * @param flags controls certain features of the encoded output. - * Passing {@code DEFAULT} results in output that - * adheres to RFC 2045. - */ - public static String encodeToString(byte[] input, int flags) { - try { - return new String(encode(input, flags), "US-ASCII"); - } catch (UnsupportedEncodingException e) { - // US-ASCII is guaranteed to be available. - throw new AssertionError(e); - } - } - - /** - * Base64-encode the given data and return a newly allocated - * String with the result. - * - * @param input the data to encode - * @param offset the position within the input array at which to - * start - * @param len the number of bytes of input to encode - * @param flags controls certain features of the encoded output. - * Passing {@code DEFAULT} results in output that - * adheres to RFC 2045. - */ - public static String encodeToString(byte[] input, int offset, int len, int flags) { - try { - return new String(encode(input, offset, len, flags), "US-ASCII"); - } catch (UnsupportedEncodingException e) { - // US-ASCII is guaranteed to be available. - throw new AssertionError(e); - } - } - - /** - * Base64-encode the given data and return a newly allocated - * byte[] with the result. - * - * @param input the data to encode - * @param flags controls certain features of the encoded output. - * Passing {@code DEFAULT} results in output that - * adheres to RFC 2045. - */ - public static byte[] encode(byte[] input, int flags) { - return encode(input, 0, input.length, flags); - } - - /** - * Base64-encode the given data and return a newly allocated - * byte[] with the result. - * - * @param input the data to encode - * @param offset the position within the input array at which to - * start - * @param len the number of bytes of input to encode - * @param flags controls certain features of the encoded output. - * Passing {@code DEFAULT} results in output that - * adheres to RFC 2045. - */ - public static byte[] encode(byte[] input, int offset, int len, int flags) { - Encoder encoder = new Encoder(flags, null); - - // Compute the exact length of the array we will produce. - int output_len = len / 3 * 4; - - // Account for the tail of the data and the padding bytes, if any. - if (encoder.do_padding) { - if (len % 3 > 0) { - output_len += 4; - } - } else { - switch (len % 3) { - case 0: break; - case 1: output_len += 2; break; - case 2: output_len += 3; break; - } - } - - // Account for the newlines, if any. - if (encoder.do_newline && len > 0) { - output_len += (((len-1) / (3 * Encoder.LINE_GROUPS)) + 1) * - (encoder.do_cr ? 2 : 1); - } - - encoder.output = new byte[output_len]; - encoder.process(input, offset, len, true); - - assert encoder.op == output_len; - - return encoder.output; - } - - /* package */ static class Encoder extends Coder { - /** - * Emit a new line every this many output tuples. Corresponds to - * a 76-character line length (the maximum allowable according to - * RFC 2045). - */ - public static final int LINE_GROUPS = 19; - - /** - * Lookup table for turning Base64 alphabet positions (6 bits) - * into output bytes. - */ - private static final byte ENCODE[] = { - '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', '+', '/', - }; - - /** - * Lookup table for turning Base64 alphabet positions (6 bits) - * into output bytes. - */ - private static final byte ENCODE_WEBSAFE[] = { - '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', '-', '_', - }; - - final private byte[] tail; - /* package */ int tailLen; - private int count; - - final public boolean do_padding; - final public boolean do_newline; - final public boolean do_cr; - final private byte[] alphabet; - - public Encoder(int flags, byte[] output) { - this.output = output; - - do_padding = (flags & NO_PADDING) == 0; - do_newline = (flags & NO_WRAP) == 0; - do_cr = (flags & CRLF) != 0; - alphabet = ((flags & URL_SAFE) == 0) ? ENCODE : ENCODE_WEBSAFE; - - tail = new byte[2]; - tailLen = 0; - - count = do_newline ? LINE_GROUPS : -1; - } - - /** - * @return an overestimate for the number of bytes {@code - * len} bytes could encode to. - */ - @Override - public int maxOutputSize(int len) { - return len * 8/5 + 10; - } - - @Override - public boolean process(byte[] input, int offset, int len, boolean finish) { - // Using local variables makes the encoder about 9% faster. - final byte[] alphabet = this.alphabet; - final byte[] output = this.output; - int op = 0; - int count = this.count; - - int p = offset; - len += offset; - int v = -1; - - // First we need to concatenate the tail of the previous call - // with any input bytes available now and see if we can empty - // the tail. - - switch (tailLen) { - case 0: - // There was no tail. - break; - - case 1: - if (p+2 <= len) { - // A 1-byte tail with at least 2 bytes of - // input available now. - v = ((tail[0] & 0xff) << 16) | - ((input[p++] & 0xff) << 8) | - (input[p++] & 0xff); - tailLen = 0; - } - break; - - case 2: - if (p+1 <= len) { - // A 2-byte tail with at least 1 byte of input. - v = ((tail[0] & 0xff) << 16) | - ((tail[1] & 0xff) << 8) | - (input[p++] & 0xff); - tailLen = 0; - } - break; - } - - if (v != -1) { - output[op++] = alphabet[(v >> 18) & 0x3f]; - output[op++] = alphabet[(v >> 12) & 0x3f]; - output[op++] = alphabet[(v >> 6) & 0x3f]; - output[op++] = alphabet[v & 0x3f]; - if (--count == 0) { - if (do_cr) output[op++] = '\r'; - output[op++] = '\n'; - count = LINE_GROUPS; - } - } - - // At this point either there is no tail, or there are fewer - // than 3 bytes of input available. - - // The main loop, turning 3 input bytes into 4 output bytes on - // each iteration. - while (p+3 <= len) { - v = ((input[p] & 0xff) << 16) | - ((input[p+1] & 0xff) << 8) | - (input[p+2] & 0xff); - output[op] = alphabet[(v >> 18) & 0x3f]; - output[op+1] = alphabet[(v >> 12) & 0x3f]; - output[op+2] = alphabet[(v >> 6) & 0x3f]; - output[op+3] = alphabet[v & 0x3f]; - p += 3; - op += 4; - if (--count == 0) { - if (do_cr) output[op++] = '\r'; - output[op++] = '\n'; - count = LINE_GROUPS; - } - } - - if (finish) { - // Finish up the tail of the input. Note that we need to - // consume any bytes in tail before any bytes - // remaining in input; there should be at most two bytes - // total. - - if (p-tailLen == len-1) { - int t = 0; - v = ((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 4; - tailLen -= t; - output[op++] = alphabet[(v >> 6) & 0x3f]; - output[op++] = alphabet[v & 0x3f]; - if (do_padding) { - output[op++] = '='; - output[op++] = '='; - } - if (do_newline) { - if (do_cr) output[op++] = '\r'; - output[op++] = '\n'; - } - } else if (p-tailLen == len-2) { - int t = 0; - v = (((tailLen > 1 ? tail[t++] : input[p++]) & 0xff) << 10) | - (((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 2); - tailLen -= t; - output[op++] = alphabet[(v >> 12) & 0x3f]; - output[op++] = alphabet[(v >> 6) & 0x3f]; - output[op++] = alphabet[v & 0x3f]; - if (do_padding) { - output[op++] = '='; - } - if (do_newline) { - if (do_cr) output[op++] = '\r'; - output[op++] = '\n'; - } - } else if (do_newline && op > 0 && count != LINE_GROUPS) { - if (do_cr) output[op++] = '\r'; - output[op++] = '\n'; - } - - assert tailLen == 0; - assert p == len; - } else { - // Save the leftovers in tail to be consumed on the next - // call to encodeInternal. - - if (p == len-1) { - tail[tailLen++] = input[p]; - } else if (p == len-2) { - tail[tailLen++] = input[p]; - tail[tailLen++] = input[p+1]; - } - } - - this.op = op; - this.count = count; - - return true; - } - } - - private Base64() { } // don't instantiate -} diff --git a/src/be/nikiroo/utils/streams/Base64InputStream.java b/src/be/nikiroo/utils/streams/Base64InputStream.java deleted file mode 100644 index a3afaef..0000000 --- a/src/be/nikiroo/utils/streams/Base64InputStream.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * 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 be.nikiroo.utils.streams; - -import java.io.FilterInputStream; -import java.io.IOException; -import java.io.InputStream; - -/** - * An InputStream that does Base64 decoding on the data read through - * it. - */ -public class Base64InputStream extends FilterInputStream { - private final Base64.Coder coder; - - private static byte[] EMPTY = new byte[0]; - - private static final int BUFFER_SIZE = 2048; - private boolean eof; - private byte[] inputBuffer; - private int outputStart; - private int outputEnd; - - /** - * An InputStream that performs Base64 decoding on the data read - * from the wrapped stream. - * - * @param in the InputStream to read the source data from - */ - public Base64InputStream(InputStream in) { - this(in, false); - } - - /** - * Performs Base64 encoding or decoding on the data read from the - * wrapped InputStream. - * - * @param in the InputStream to read the source data from - * @param flags bit flags for controlling the decoder; see the - * constants in {@link Base64} - * @param encode true to encode, false to decode - * - * @hide - */ - public Base64InputStream(InputStream in, boolean encode) { - super(in); - eof = false; - inputBuffer = new byte[BUFFER_SIZE]; - if (encode) { - coder = new Base64.Encoder(Base64.NO_WRAP, null); - } else { - coder = new Base64.Decoder(Base64.NO_WRAP, null); - } - coder.output = new byte[coder.maxOutputSize(BUFFER_SIZE)]; - outputStart = 0; - outputEnd = 0; - } - - @Override - public boolean markSupported() { - return false; - } - - @SuppressWarnings("sync-override") - @Override - public void mark(int readlimit) { - throw new UnsupportedOperationException(); - } - - @SuppressWarnings("sync-override") - @Override - public void reset() { - throw new UnsupportedOperationException(); - } - - @Override - public void close() throws IOException { - in.close(); - inputBuffer = null; - } - - @Override - public int available() { - return outputEnd - outputStart; - } - - @Override - public long skip(long n) throws IOException { - if (outputStart >= outputEnd) { - refill(); - } - if (outputStart >= outputEnd) { - return 0; - } - long bytes = Math.min(n, outputEnd-outputStart); - outputStart += bytes; - return bytes; - } - - @Override - public int read() throws IOException { - if (outputStart >= outputEnd) { - refill(); - } - if (outputStart >= outputEnd) { - return -1; - } - - return coder.output[outputStart++] & 0xff; - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - if (outputStart >= outputEnd) { - refill(); - } - if (outputStart >= outputEnd) { - return -1; - } - int bytes = Math.min(len, outputEnd-outputStart); - System.arraycopy(coder.output, outputStart, b, off, bytes); - outputStart += bytes; - return bytes; - } - - /** - * Read data from the input stream into inputBuffer, then - * decode/encode it into the empty coder.output, and reset the - * outputStart and outputEnd pointers. - */ - private void refill() throws IOException { - if (eof) return; - int bytesRead = in.read(inputBuffer); - boolean success; - if (bytesRead == -1) { - eof = true; - success = coder.process(EMPTY, 0, 0, true); - } else { - success = coder.process(inputBuffer, 0, bytesRead, false); - } - if (!success) { - throw new IOException("bad base-64"); - } - outputEnd = coder.op; - outputStart = 0; - } -} diff --git a/src/be/nikiroo/utils/streams/Base64OutputStream.java b/src/be/nikiroo/utils/streams/Base64OutputStream.java deleted file mode 100644 index ab4e457..0000000 --- a/src/be/nikiroo/utils/streams/Base64OutputStream.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * 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 be.nikiroo.utils.streams; - -import java.io.FilterOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -/** - * An OutputStream that does Base64 encoding on the data written to - * it, writing the resulting data to another OutputStream. - */ -public class Base64OutputStream extends FilterOutputStream { - private final Base64.Coder coder; - private final int flags; - - private byte[] buffer = null; - private int bpos = 0; - - private static byte[] EMPTY = new byte[0]; - - /** - * Performs Base64 encoding on the data written to the stream, - * writing the encoded data to another OutputStream. - * - * @param out the OutputStream to write the encoded data to - */ - public Base64OutputStream(OutputStream out) { - this(out, true); - } - - /** - * Performs Base64 encoding or decoding on the data written to the - * stream, writing the encoded/decoded data to another - * OutputStream. - * - * @param out the OutputStream to write the encoded data to - * @param encode true to encode, false to decode - * - * @hide - */ - public Base64OutputStream(OutputStream out, boolean encode) { - super(out); - this.flags = Base64.NO_WRAP; - if (encode) { - coder = new Base64.Encoder(flags, null); - } else { - coder = new Base64.Decoder(flags, null); - } - } - - @Override - public void write(int b) throws IOException { - // To avoid invoking the encoder/decoder routines for single - // bytes, we buffer up calls to write(int) in an internal - // byte array to transform them into writes of decently-sized - // arrays. - - if (buffer == null) { - buffer = new byte[1024]; - } - if (bpos >= buffer.length) { - // internal buffer full; write it out. - internalWrite(buffer, 0, bpos, false); - bpos = 0; - } - buffer[bpos++] = (byte) b; - } - - /** - * Flush any buffered data from calls to write(int). Needed - * before doing a write(byte[], int, int) or a close(). - */ - private void flushBuffer() throws IOException { - if (bpos > 0) { - internalWrite(buffer, 0, bpos, false); - bpos = 0; - } - } - - @Override - public void write(byte[] b, int off, int len) throws IOException { - if (len <= 0) return; - flushBuffer(); - internalWrite(b, off, len, false); - } - - @Override - public void close() throws IOException { - IOException thrown = null; - try { - flushBuffer(); - internalWrite(EMPTY, 0, 0, true); - } catch (IOException e) { - thrown = e; - } - - try { - if ((flags & Base64.NO_CLOSE) == 0) { - out.close(); - } else { - out.flush(); - } - } catch (IOException e) { - if (thrown != null) { - thrown = e; - } - } - - if (thrown != null) { - throw thrown; - } - } - - /** - * Write the given bytes to the encoder/decoder. - * - * @param finish true if this is the last batch of input, to cause - * encoder/decoder state to be finalized. - */ - private void internalWrite(byte[] b, int off, int len, boolean finish) throws IOException { - coder.output = embiggen(coder.output, coder.maxOutputSize(len)); - if (!coder.process(b, off, len, finish)) { - throw new IOException("bad base-64"); - } - out.write(coder.output, 0, coder.op); - } - - /** - * If b.length is at least len, return b. Otherwise return a new - * byte array of length len. - */ - private byte[] embiggen(byte[] b, int len) { - if (b == null || b.length < len) { - return new byte[len]; - } - return b; - } -} diff --git a/src/be/nikiroo/utils/streams/BufferedInputStream.java b/src/be/nikiroo/utils/streams/BufferedInputStream.java deleted file mode 100644 index 3cad70d..0000000 --- a/src/be/nikiroo/utils/streams/BufferedInputStream.java +++ /dev/null @@ -1,610 +0,0 @@ -package be.nikiroo.utils.streams; - -import java.io.IOException; -import java.io.InputStream; -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.List; -import java.util.Map.Entry; - -import be.nikiroo.utils.StringUtils; - -/** - * A simple {@link InputStream} that is buffered with a bytes array. - *

- * It is mostly intended to be used as a base class to create new - * {@link InputStream}s with special operation modes, and to give some default - * methods. - * - * @author niki - */ -public class BufferedInputStream extends InputStream { - /** - * The size of the internal buffer (can be different if you pass your own - * buffer, of course, and can also expand to search for longer "startsWith" - * data). - *

- * Note that special "push-back" buffers can also be created during the life - * of this stream. - */ - static private final int BUFFER_SIZE = 4096; - - /** The current position in the buffer. */ - protected int start; - /** The index of the last usable position of the buffer. */ - protected int stop; - /** The buffer itself. */ - protected byte[] buffer; - /** An End-Of-File (or {@link InputStream}, here) marker. */ - protected boolean eof; - - private boolean closed; - private InputStream in; - private int openCounter; - private byte[] singleByteReader = new byte[1]; - - /** array + offset of pushed-back buffers */ - private List> backBuffers; - - private long bytesRead; - - /** - * Create a new {@link BufferedInputStream} that wraps the given - * {@link InputStream}. - * - * @param in - * the {@link InputStream} to wrap - */ - public BufferedInputStream(InputStream in) { - this.in = in; - - this.buffer = new byte[BUFFER_SIZE]; - this.start = 0; - this.stop = 0; - this.backBuffers = new ArrayList>(); - } - - /** - * Create a new {@link BufferedInputStream} that wraps the given bytes array - * as a data source. - * - * @param in - * the array to wrap, cannot be NULL - */ - public BufferedInputStream(byte[] in) { - this(in, 0, in.length); - } - - /** - * Create a new {@link BufferedInputStream} that wraps the given bytes array - * as a data source. - * - * @param in - * the array to wrap, cannot be NULL - * @param offset - * the offset to start the reading at - * @param length - * the number of bytes to take into account in the array, - * starting from the offset - * - * @throws NullPointerException - * if the array is NULL - * @throws IndexOutOfBoundsException - * if the offset and length do not correspond to the given array - */ - public BufferedInputStream(byte[] in, int offset, int length) { - if (in == null) { - throw new NullPointerException(); - } else if (offset < 0 || length < 0 || length > in.length - offset) { - throw new IndexOutOfBoundsException(); - } - - this.in = null; - - this.buffer = in; - this.start = offset; - this.stop = length; - this.backBuffers = new ArrayList>(); - } - - /** - * Return this very same {@link BufferedInputStream}, but keep a counter of - * how many streams were open this way. When calling - * {@link BufferedInputStream#close()}, decrease this counter if it is not - * already zero instead of actually closing the stream. - *

- * You are now responsible for it — you must close it. - *

- * This method allows you to use a wrapping stream around this one and still - * close the wrapping stream. - * - * @return the same stream, but you are now responsible for closing it - * - * @throws IOException - * in case of I/O error or if the stream is closed - */ - public synchronized InputStream open() throws IOException { - checkClose(); - openCounter++; - return this; - } - - /** - * Check if the current content (until eof) is equal to the given search - * term. - *

- * Note: the search term size must be smaller or equal the internal - * buffer size. - * - * @param search - * the term to search for - * - * @return TRUE if the content that will be read starts with it - * - * @throws IOException - * in case of I/O error or if the size of the search term is - * greater than the internal buffer - */ - public boolean is(String search) throws IOException { - return is(StringUtils.getBytes(search)); - } - - /** - * Check if the current content (until eof) is equal to the given search - * term. - *

- * Note: the search term size must be smaller or equal the internal - * buffer size. - * - * @param search - * the term to search for - * - * @return TRUE if the content that will be read starts with it - * - * @throws IOException - * in case of I/O error or if the size of the search term is - * greater than the internal buffer - */ - public boolean is(byte[] search) throws IOException { - if (startsWith(search)) { - return available() == search.length; - } - - return false; - } - - /** - * Check if the current content (what will be read next) starts with the - * given search term. - *

- * Note: the search term size must be smaller or equal the internal - * buffer size. - * - * @param search - * the term to search for - * - * @return TRUE if the content that will be read starts with it - * - * @throws IOException - * in case of I/O error or if the size of the search term is - * greater than the internal buffer - */ - public boolean startsWith(String search) throws IOException { - return startsWith(StringUtils.getBytes(search)); - } - - /** - * Check if the current content (what will be read next) starts with the - * given search term. - *

- * An empty string will always return true (unless the stream is closed, - * which would throw an {@link IOException}). - *

- * Note: the search term size must be smaller or equal the internal - * buffer size. - * - * @param search - * the term to search for - * - * @return TRUE if the content that will be read starts with it - * - * @throws IOException - * in case of I/O error or if the size of the search term is - * greater than the internal buffer - */ - public boolean startsWith(byte[] search) throws IOException { - checkClose(); - - while (consolidatePushBack(search.length) < search.length) { - preRead(); - if (start >= stop) { - // Not enough data left to start with that - return false; - } - - byte[] newBuffer = new byte[stop - start]; - System.arraycopy(buffer, start, newBuffer, 0, stop - start); - pushback(newBuffer, 0); - start = stop; - } - - Entry bb = backBuffers.get(backBuffers.size() - 1); - byte[] bbBuffer = bb.getKey(); - int bbOffset = bb.getValue(); - - return StreamUtils.startsWith(search, bbBuffer, bbOffset, - bbBuffer.length); - } - - /** - * The number of bytes read from the under-laying {@link InputStream}. - * - * @return the number of bytes - */ - public long getBytesRead() { - return bytesRead; - } - - /** - * Check if this stream is spent (no more data to read or to process). - * - * @return TRUE if it is - * - * @throws IOException - * in case of I/O error - */ - public boolean eof() throws IOException { - if (closed) { - return true; - } - - preRead(); - return !hasMoreData(); - } - - /** - * Read the whole {@link InputStream} until the end and return the number of - * bytes read. - * - * @return the number of bytes read - * - * @throws IOException - * in case of I/O error - */ - public long end() throws IOException { - long skipped = 0; - while (hasMoreData()) { - skipped += skip(buffer.length); - } - - return skipped; - } - - @Override - public int read() throws IOException { - if (read(singleByteReader) < 0) { - return -1; - } - - return singleByteReader[0]; - } - - @Override - public int read(byte[] b) throws IOException { - return read(b, 0, b.length); - } - - @Override - public int read(byte[] b, int boff, int blen) throws IOException { - checkClose(); - - if (b == null) { - throw new NullPointerException(); - } else if (boff < 0 || blen < 0 || blen > b.length - boff) { - throw new IndexOutOfBoundsException(); - } else if (blen == 0) { - return 0; - } - - // Read from the pushed-back buffers if any - if (backBuffers.isEmpty()) { - preRead(); // an implementation could pushback in preRead() - } - - if (!backBuffers.isEmpty()) { - int read = 0; - - Entry bb = backBuffers - .remove(backBuffers.size() - 1); - byte[] bbBuffer = bb.getKey(); - int bbOffset = bb.getValue(); - int bbSize = bbBuffer.length - bbOffset; - - if (bbSize > blen) { - read = blen; - System.arraycopy(bbBuffer, bbOffset, b, boff, read); - pushback(bbBuffer, bbOffset + read); - } else { - read = bbSize; - System.arraycopy(bbBuffer, bbOffset, b, boff, read); - } - - return read; - } - - int done = 0; - while (hasMoreData() && done < blen) { - preRead(); - if (hasMoreData()) { - int now = Math.min(blen - done, stop - start); - if (now > 0) { - System.arraycopy(buffer, start, b, boff + done, now); - start += now; - done += now; - } - } - } - - return done > 0 ? done : -1; - } - - @Override - public long skip(long n) throws IOException { - if (n <= 0) { - return 0; - } - - long skipped = 0; - while (!backBuffers.isEmpty() && n > 0) { - Entry bb = backBuffers - .remove(backBuffers.size() - 1); - byte[] bbBuffer = bb.getKey(); - int bbOffset = bb.getValue(); - int bbSize = bbBuffer.length - bbOffset; - - int localSkip = 0; - localSkip = (int) Math.min(n, bbSize); - - n -= localSkip; - bbSize -= localSkip; - - if (bbSize > 0) { - pushback(bbBuffer, bbOffset + localSkip); - } - } - while (hasMoreData() && n > 0) { - preRead(); - - long inBuffer = Math.min(n, available()); - start += inBuffer; - n -= inBuffer; - skipped += inBuffer; - } - - return skipped; - } - - @Override - public int available() { - if (closed) { - return 0; - } - - int avail = 0; - for (Entry entry : backBuffers) { - avail += entry.getKey().length - entry.getValue(); - } - - return avail + Math.max(0, stop - start); - } - - /** - * Closes this stream and releases any system resources associated with the - * stream. - *

- * Including the under-laying {@link InputStream}. - *

- * Note: if you called the {@link BufferedInputStream#open()} method - * prior to this one, it will just decrease the internal count of how many - * open streams it held and do nothing else. The stream will actually be - * closed when you have called {@link BufferedInputStream#close()} once more - * than {@link BufferedInputStream#open()}. - * - * @exception IOException - * in case of I/O error - */ - @Override - public synchronized void close() throws IOException { - close(true); - } - - /** - * Closes this stream and releases any system resources associated with the - * stream. - *

- * Including the under-laying {@link InputStream} if - * incudingSubStream is true. - *

- * You can call this method multiple times, it will not cause an - * {@link IOException} for subsequent calls. - *

- * Note: if you called the {@link BufferedInputStream#open()} method - * prior to this one, it will just decrease the internal count of how many - * open streams it held and do nothing else. The stream will actually be - * closed when you have called {@link BufferedInputStream#close()} once more - * than {@link BufferedInputStream#open()}. - * - * @param includingSubStream - * also close the under-laying stream - * - * @exception IOException - * in case of I/O error - */ - public synchronized void close(boolean includingSubStream) - throws IOException { - if (!closed) { - if (openCounter > 0) { - openCounter--; - } else { - closed = true; - if (includingSubStream && in != null) { - in.close(); - } - } - } - } - - /** - * Consolidate the push-back buffers so the last one is at least the given - * size, if possible. - *

- * If there is not enough data in the push-back buffers, they will all be - * consolidated. - * - * @param size - * the minimum size of the consolidated buffer, or -1 to force - * the consolidation of all push-back buffers - * - * @return the size of the last, consolidated buffer; can be less than the - * requested size if not enough data - */ - protected int consolidatePushBack(int size) { - int bbIndex = -1; - int bbUpToSize = 0; - for (Entry entry : backBuffers) { - bbIndex++; - bbUpToSize += entry.getKey().length - entry.getValue(); - - if (size >= 0 && bbUpToSize >= size) { - break; - } - } - - // Index 0 means "the last buffer is already big enough" - if (bbIndex > 0) { - byte[] consolidatedBuffer = new byte[bbUpToSize]; - int consolidatedPos = 0; - for (int i = 0; i <= bbIndex; i++) { - Entry bb = backBuffers - .remove(backBuffers.size() - 1); - byte[] bbBuffer = bb.getKey(); - int bbOffset = bb.getValue(); - int bbSize = bbBuffer.length - bbOffset; - System.arraycopy(bbBuffer, bbOffset, consolidatedBuffer, - consolidatedPos, bbSize); - } - - pushback(consolidatedBuffer, 0); - } - - return bbUpToSize; - } - - /** - * Check if we still have some data in the buffer and, if not, fetch some. - * - * @return TRUE if we fetched some data, FALSE if there are still some in - * the buffer - * - * @throws IOException - * in case of I/O error - */ - protected boolean preRead() throws IOException { - boolean hasRead = false; - if (in != null && !eof && start >= stop) { - start = 0; - stop = read(in, buffer); - if (stop > 0) { - bytesRead += stop; - } - - hasRead = true; - } - - if (start >= stop) { - eof = true; - } - - return hasRead; - } - - /** - * Push back some data that will be read again at the next read call. - * - * @param buffer - * the buffer to push back - * @param offset - * the offset at which to start reading in the buffer - */ - protected void pushback(byte[] buffer, int offset) { - backBuffers.add( - new AbstractMap.SimpleEntry(buffer, offset)); - } - - /** - * Push back some data that will be read again at the next read call. - * - * @param buffer - * the buffer to push back - * @param offset - * the offset at which to start reading in the buffer - * @param len - * the length to copy - */ - protected void pushback(byte[] buffer, int offset, int len) { - // TODO: not efficient! - if (buffer.length != len) { - byte[] lenNotSupportedYet = new byte[len]; - System.arraycopy(buffer, offset, lenNotSupportedYet, 0, len); - buffer = lenNotSupportedYet; - offset = 0; - } - - pushback(buffer, offset); - } - - /** - * Read the under-laying stream into the given local buffer. - * - * @param in - * the under-laying {@link InputStream} - * @param buffer - * the buffer we use in this {@link BufferedInputStream} - * - * @return the number of bytes read - * - * @throws IOException - * in case of I/O error - */ - protected int read(InputStream in, byte[] buffer) throws IOException { - return in.read(buffer, 0, buffer.length); - } - - /** - * We have more data available in the buffer or we can, maybe, fetch - * more. - * - * @return TRUE if it is the case, FALSE if not - */ - protected boolean hasMoreData() { - if (closed) { - return false; - } - - return !backBuffers.isEmpty() || (start < stop) || !eof; - } - - /** - * Check that the stream was not closed, and throw an {@link IOException} if - * it was. - * - * @throws IOException - * if it was closed - */ - protected void checkClose() throws IOException { - if (closed) { - throw new IOException( - "This BufferedInputStream was closed, you cannot use it anymore."); - } - } -} diff --git a/src/be/nikiroo/utils/streams/BufferedOutputStream.java b/src/be/nikiroo/utils/streams/BufferedOutputStream.java deleted file mode 100644 index 1442534..0000000 --- a/src/be/nikiroo/utils/streams/BufferedOutputStream.java +++ /dev/null @@ -1,260 +0,0 @@ -package be.nikiroo.utils.streams; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * A simple {@link OutputStream} that is buffered with a bytes array. - *

- * It is mostly intended to be used as a base class to create new - * {@link OutputStream}s with special operation modes, and to give some default - * methods. - * - * @author niki - */ -public class BufferedOutputStream extends OutputStream { - /** The current position in the buffer. */ - protected int start; - /** The index of the last usable position of the buffer. */ - protected int stop; - /** The buffer itself. */ - protected byte[] buffer; - /** An End-Of-File (or buffer, here) marker. */ - protected boolean eof; - /** The actual under-laying stream. */ - protected OutputStream out; - /** The number of bytes written to the under-laying stream. */ - protected long bytesWritten; - /** - * Can bypass the flush process for big writes (will directly write to the - * under-laying buffer if the array to write is > the internal buffer - * size). - *

- * By default, this is true. - */ - protected boolean bypassFlush = true; - - private boolean closed; - private int openCounter; - private byte[] b1; - - /** - * Create a new {@link BufferedInputStream} that wraps the given - * {@link InputStream}. - * - * @param out - * the {@link OutputStream} to wrap - */ - public BufferedOutputStream(OutputStream out) { - this.out = out; - - this.buffer = new byte[4096]; - this.b1 = new byte[1]; - this.start = 0; - this.stop = 0; - } - - @Override - public void write(int b) throws IOException { - b1[0] = (byte) b; - write(b1, 0, 1); - } - - @Override - public void write(byte[] b) throws IOException { - write(b, 0, b.length); - } - - @Override - public void write(byte[] source, int sourceOffset, int sourceLength) - throws IOException { - - checkClose(); - - if (source == null) { - throw new NullPointerException(); - } else if ((sourceOffset < 0) || (sourceOffset > source.length) - || (sourceLength < 0) - || ((sourceOffset + sourceLength) > source.length) - || ((sourceOffset + sourceLength) < 0)) { - throw new IndexOutOfBoundsException(); - } else if (sourceLength == 0) { - return; - } - - if (bypassFlush && sourceLength >= buffer.length) { - /* - * If the request length exceeds the size of the output buffer, - * flush the output buffer and then write the data directly. In this - * way buffered streams will cascade harmlessly. - */ - flush(false); - out.write(source, sourceOffset, sourceLength); - bytesWritten += (sourceLength - sourceOffset); - return; - } - - int done = 0; - while (done < sourceLength) { - if (available() <= 0) { - flush(false); - } - - int now = Math.min(sourceLength - done, available()); - System.arraycopy(source, sourceOffset + done, buffer, stop, now); - stop += now; - done += now; - } - } - - /** - * The available space in the buffer. - * - * @return the space in bytes - */ - private int available() { - if (closed) { - return 0; - } - - return Math.max(0, buffer.length - stop - 1); - } - - /** - * The number of bytes written to the under-laying {@link OutputStream}. - * - * @return the number of bytes - */ - public long getBytesWritten() { - return bytesWritten; - } - - /** - * Return this very same {@link BufferedInputStream}, but keep a counter of - * how many streams were open this way. When calling - * {@link BufferedInputStream#close()}, decrease this counter if it is not - * already zero instead of actually closing the stream. - *

- * You are now responsible for it — you must close it. - *

- * This method allows you to use a wrapping stream around this one and still - * close the wrapping stream. - * - * @return the same stream, but you are now responsible for closing it - * - * @throws IOException - * in case of I/O error or if the stream is closed - */ - public synchronized OutputStream open() throws IOException { - checkClose(); - openCounter++; - return this; - } - - /** - * Check that the stream was not closed, and throw an {@link IOException} if - * it was. - * - * @throws IOException - * if it was closed - */ - protected void checkClose() throws IOException { - if (closed) { - throw new IOException( - "This BufferedInputStream was closed, you cannot use it anymore."); - } - } - - @Override - public void flush() throws IOException { - flush(true); - } - - /** - * Flush the {@link BufferedOutputStream}, write the current buffered data - * to (and optionally also flush) the under-laying stream. - *

- * If {@link BufferedOutputStream#bypassFlush} is false, all writes to the - * under-laying stream are done in this method. - *

- * This can be used if you want to write some data in the under-laying - * stream yourself (in that case, flush this {@link BufferedOutputStream} - * with or without flushing the under-laying stream, then you can write to - * the under-laying stream). - * - * @param includingSubStream - * also flush the under-laying stream - * @throws IOException - * in case of I/O error - */ - public void flush(boolean includingSubStream) throws IOException { - if (stop > start) { - out.write(buffer, start, stop - start); - bytesWritten += (stop - start); - } - start = 0; - stop = 0; - - if (includingSubStream) { - out.flush(); - } - } - - /** - * Closes this stream and releases any system resources associated with the - * stream. - *

- * Including the under-laying {@link InputStream}. - *

- * Note: if you called the {@link BufferedInputStream#open()} method - * prior to this one, it will just decrease the internal count of how many - * open streams it held and do nothing else. The stream will actually be - * closed when you have called {@link BufferedInputStream#close()} once more - * than {@link BufferedInputStream#open()}. - * - * @exception IOException - * in case of I/O error - */ - @Override - public synchronized void close() throws IOException { - close(true); - } - - /** - * Closes this stream and releases any system resources associated with the - * stream. - *

- * Including the under-laying {@link InputStream} if - * incudingSubStream is true. - *

- * You can call this method multiple times, it will not cause an - * {@link IOException} for subsequent calls. - *

- * Note: if you called the {@link BufferedInputStream#open()} method - * prior to this one, it will just decrease the internal count of how many - * open streams it held and do nothing else. The stream will actually be - * closed when you have called {@link BufferedInputStream#close()} once more - * than {@link BufferedInputStream#open()}. - * - * @param includingSubStream - * also close the under-laying stream - * - * @exception IOException - * in case of I/O error - */ - public synchronized void close(boolean includingSubStream) - throws IOException { - if (!closed) { - if (openCounter > 0) { - openCounter--; - } else { - closed = true; - flush(includingSubStream); - if (includingSubStream && out != null) { - out.close(); - } - } - } - } -} diff --git a/src/be/nikiroo/utils/streams/MarkableFileInputStream.java b/src/be/nikiroo/utils/streams/MarkableFileInputStream.java deleted file mode 100644 index 7622b24..0000000 --- a/src/be/nikiroo/utils/streams/MarkableFileInputStream.java +++ /dev/null @@ -1,66 +0,0 @@ -package be.nikiroo.utils.streams; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FilterInputStream; -import java.io.IOException; -import java.nio.channels.FileChannel; - -/** - * This is a markable (and thus reset-able) stream that you can create from a - * FileInputStream. - * - * @author niki - */ -public class MarkableFileInputStream extends FilterInputStream { - private FileChannel channel; - private long mark = 0; - - /** - * Create a new {@link MarkableFileInputStream} from this file. - * - * @param file - * the {@link File} to wrap - * - * @throws FileNotFoundException - * if the {@link File} cannot be found - */ - public MarkableFileInputStream(File file) throws FileNotFoundException { - this(new FileInputStream(file)); - } - - /** - * Create a new {@link MarkableFileInputStream} from this stream. - * - * @param in - * the original {@link FileInputStream} to wrap - */ - public MarkableFileInputStream(FileInputStream in) { - super(in); - channel = in.getChannel(); - } - - @Override - public boolean markSupported() { - return true; - } - - @Override - public synchronized void mark(int readlimit) { - try { - mark = channel.position(); - } catch (IOException ex) { - ex.printStackTrace(); - mark = -1; - } - } - - @Override - public synchronized void reset() throws IOException { - if (mark < 0) { - throw new IOException("mark position not valid"); - } - channel.position(mark); - } -} \ No newline at end of file diff --git a/src/be/nikiroo/utils/streams/NextableInputStream.java b/src/be/nikiroo/utils/streams/NextableInputStream.java deleted file mode 100644 index dcab472..0000000 --- a/src/be/nikiroo/utils/streams/NextableInputStream.java +++ /dev/null @@ -1,279 +0,0 @@ -package be.nikiroo.utils.streams; - -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.util.Arrays; - -/** - * This {@link InputStream} can be separated into sub-streams (you can process - * it as a normal {@link InputStream} but, when it is spent, you can call - * {@link NextableInputStream#next()} on it to unlock new data). - *

- * The separation in sub-streams is done via {@link NextableInputStreamStep}. - * - * @author niki - */ -public class NextableInputStream extends BufferedInputStream { - private NextableInputStreamStep step; - private boolean started; - private boolean stopped; - - /** - * Create a new {@link NextableInputStream} that wraps the given - * {@link InputStream}. - * - * @param in - * the {@link InputStream} to wrap - * @param step - * how to separate it into sub-streams (can be NULL, but in that - * case it will behave as a normal {@link InputStream}) - */ - public NextableInputStream(InputStream in, NextableInputStreamStep step) { - super(in); - this.step = step; - } - - /** - * Create a new {@link NextableInputStream} that wraps the given bytes array - * as a data source. - * - * @param in - * the array to wrap, cannot be NULL - * @param step - * how to separate it into sub-streams (can be NULL, but in that - * case it will behave as a normal {@link InputStream}) - */ - public NextableInputStream(byte[] in, NextableInputStreamStep step) { - this(in, step, 0, in.length); - } - - /** - * Create a new {@link NextableInputStream} that wraps the given bytes array - * as a data source. - * - * @param in - * the array to wrap, cannot be NULL - * @param step - * how to separate it into sub-streams (can be NULL, but in that - * case it will behave as a normal {@link InputStream}) - * @param offset - * the offset to start the reading at - * @param length - * the number of bytes to take into account in the array, - * starting from the offset - * - * @throws NullPointerException - * if the array is NULL - * @throws IndexOutOfBoundsException - * if the offset and length do not correspond to the given array - */ - public NextableInputStream(byte[] in, NextableInputStreamStep step, - int offset, int length) { - super(in, offset, length); - this.step = step; - checkBuffer(true); - } - - /** - * Unblock the processing of the next sub-stream. - *

- * It can only be called when the "current" stream is spent (i.e., you must - * first process the stream until it is spent). - *

- * {@link IOException}s can happen when we have no data available in the - * buffer; in that case, we fetch more data to know if we can have a next - * sub-stream or not. - *

- * This is can be a blocking call when data need to be fetched. - * - * @return TRUE if we unblocked the next sub-stream, FALSE if not (i.e., - * FALSE when there are no more sub-streams to fetch) - * - * @throws IOException - * in case of I/O error or if the stream is closed - */ - public boolean next() throws IOException { - return next(false); - } - - /** - * Unblock the next sub-stream as would have done - * {@link NextableInputStream#next()}, but disable the sub-stream systems. - *

- * That is, the next stream, if any, will be the last one and will not be - * subject to the {@link NextableInputStreamStep}. - *

- * This is can be a blocking call when data need to be fetched. - * - * @return TRUE if we unblocked the next sub-stream, FALSE if not - * - * @throws IOException - * in case of I/O error or if the stream is closed - */ - public boolean nextAll() throws IOException { - return next(true); - } - - /** - * Check if this stream is totally spent (no more data to read or to - * process). - *

- * Note: when the stream is divided into sub-streams, each sub-stream will - * report its own eof when spent. - * - * @return TRUE if it is - * - * @throws IOException - * in case of I/O error - */ - @Override - public boolean eof() throws IOException { - return super.eof(); - } - - /** - * Check if we still have some data in the buffer and, if not, fetch some. - * - * @return TRUE if we fetched some data, FALSE if there are still some in - * the buffer - * - * @throws IOException - * in case of I/O error - */ - @Override - protected boolean preRead() throws IOException { - if (!stopped) { - boolean bufferChanged = super.preRead(); - checkBuffer(bufferChanged); - return bufferChanged; - } - - if (start >= stop) { - eof = true; - } - - return false; - } - - @Override - protected boolean hasMoreData() { - return started && super.hasMoreData(); - } - - /** - * Check that the buffer didn't overshot to the next item, and fix - * {@link NextableInputStream#stop} if needed. - *

- * If {@link NextableInputStream#stop} is fixed, - * {@link NextableInputStream#eof} and {@link NextableInputStream#stopped} - * are set to TRUE. - * - * @param newBuffer - * we changed the buffer, we need to clear some information in - * the {@link NextableInputStreamStep} - */ - private void checkBuffer(boolean newBuffer) { - if (step != null && stop >= 0) { - if (newBuffer) { - step.clearBuffer(); - } - - int stopAt = step.stop(buffer, start, stop, eof); - if (stopAt >= 0) { - stop = stopAt; - eof = true; - stopped = true; - } - } - } - - /** - * The implementation of {@link NextableInputStream#next()} and - * {@link NextableInputStream#nextAll()}. - *

- * This is can be a blocking call when data need to be fetched. - * - * @param all - * TRUE for {@link NextableInputStream#nextAll()}, FALSE for - * {@link NextableInputStream#next()} - * - * @return TRUE if we unblocked the next sub-stream, FALSE if not (i.e., - * FALSE when there are no more sub-streams to fetch) - * - * @throws IOException - * in case of I/O error or if the stream is closed - */ - private boolean next(boolean all) throws IOException { - checkClose(); - - if (!started) { - // First call before being allowed to read - started = true; - - if (all) { - step = null; - } - - return true; - } - - // If started, must be stopped and no more data to continue - // i.e., sub-stream must be spent - if (!stopped || hasMoreData()) { - return false; - } - - if (step != null) { - stop = step.getResumeLen(); - start += step.getResumeSkip(); - eof = step.getResumeEof(); - stopped = false; - - if (all) { - step = null; - } - - checkBuffer(false); - - return true; - } - - return false; - - // // consider that if EOF, there is no next - // if (start >= stop) { - // // Make sure, block if necessary - // preRead(); - // - // return hasMoreData(); - // } - // - // return true; - } - - /** - * Display a DEBUG {@link String} representation of this object. - *

- * Do not use for release code. - */ - @Override - public String toString() { - String data = ""; - if (stop > 0) { - try { - data = new String(Arrays.copyOfRange(buffer, 0, stop), "UTF-8"); - } catch (UnsupportedEncodingException e) { - } - if (data.length() > 200) { - data = data.substring(0, 197) + "..."; - } - } - String rep = String.format( - "Nextable %s: %d -> %d [eof: %s] [more data: %s]: %s", - (stopped ? "stopped" : "running"), start, stop, "" + eof, "" - + hasMoreData(), data); - - return rep; - } -} diff --git a/src/be/nikiroo/utils/streams/NextableInputStreamStep.java b/src/be/nikiroo/utils/streams/NextableInputStreamStep.java deleted file mode 100644 index fda998d..0000000 --- a/src/be/nikiroo/utils/streams/NextableInputStreamStep.java +++ /dev/null @@ -1,112 +0,0 @@ -package be.nikiroo.utils.streams; - -import java.io.InputStream; - -/** - * Divide an {@link InputStream} into sub-streams. - * - * @author niki - */ -public class NextableInputStreamStep { - private int stopAt; - private int last = -1; - private int resumeLen; - private int resumeSkip; - private boolean resumeEof; - - /** - * Create a new divider that will separate the sub-streams each time it sees - * this byte. - *

- * Note that the byte will be bypassed by the {@link InputStream} as far as - * the consumers will be aware. - * - * @param byt - * the byte at which to separate two sub-streams - */ - public NextableInputStreamStep(int byt) { - stopAt = byt; - } - - /** - * Check if we need to stop the {@link InputStream} reading at some point in - * the current buffer. - *

- * If we do, return the index at which to stop; if not, return -1. - *

- * This method will not return the same index a second time (unless - * we cleared the buffer). - * - * @param buffer - * the buffer to check - * @param pos - * the current position of what was read in the buffer - * @param len - * the maximum index to use in the buffer (anything above that is - * not to be used) - * @param eof - * the current state of the under-laying stream - * - * @return the index at which to stop, or -1 - */ - public int stop(byte[] buffer, int pos, int len, boolean eof) { - for (int i = pos; i < len; i++) { - if (buffer[i] == stopAt) { - if (i > this.last) { - // we skip the sep - this.resumeSkip = 1; - - this.resumeLen = len; - this.resumeEof = eof; - this.last = i; - return i; - } - } - } - - return -1; - } - - /** - * Get the maximum index to use in the buffer used in - * {@link NextableInputStreamStep#stop(byte[], int, int, boolean)} at resume - * time. - * - * @return the index - */ - public int getResumeLen() { - return resumeLen; - } - - /** - * Get the number of bytes to skip at resume time. - * - * @return the number of bytes to skip - */ - public int getResumeSkip() { - return resumeSkip; - } - - /** - * Get the under-laying stream state at resume time. - * - * @return the EOF state - */ - public boolean getResumeEof() { - return resumeEof; - } - - /** - * Clear the information we may have kept about the current buffer - *

- * You should call this method each time you change the content of the - * buffer used in - * {@link NextableInputStreamStep#stop(byte[], int, int, boolean)}. - */ - public void clearBuffer() { - this.last = -1; - this.resumeSkip = 0; - this.resumeLen = 0; - this.resumeEof = false; - } -} diff --git a/src/be/nikiroo/utils/streams/ReplaceInputStream.java b/src/be/nikiroo/utils/streams/ReplaceInputStream.java deleted file mode 100644 index 0860f78..0000000 --- a/src/be/nikiroo/utils/streams/ReplaceInputStream.java +++ /dev/null @@ -1,217 +0,0 @@ -package be.nikiroo.utils.streams; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; - -import be.nikiroo.utils.StringUtils; - -/** - * This {@link InputStream} will change some of its content by replacing it with - * something else. - * - * @author niki - */ -public class ReplaceInputStream extends BufferedInputStream { - /** - * The minimum size of the internal buffer (could be more if at least one of - * the 'FROM' bytes arrays is > 2048 bytes — in that case the - * buffer will be twice the largest size of the 'FROM' bytes arrays). - *

- * This is a different buffer than the one from the inherited class. - */ - static private final int MIN_BUFFER_SIZE = 4096; - - private byte[][] froms; - private byte[][] tos; - private int bufferSize; - private int maxFromSize; - - /** - * Create a {@link ReplaceInputStream} that will replace from with - * to. - * - * @param in - * the under-laying {@link InputStream} - * @param from - * the {@link String} to replace - * @param to - * the {@link String} to replace with - */ - public ReplaceInputStream(InputStream in, String from, String to) { - this(in, StringUtils.getBytes(from), StringUtils.getBytes(to)); - } - - /** - * Create a {@link ReplaceInputStream} that will replace from with - * to. - * - * @param in - * the under-laying {@link InputStream} - * @param from - * the value to replace - * @param to - * the value to replace with - */ - public ReplaceInputStream(InputStream in, byte[] from, byte[] to) { - this(in, new byte[][] { from }, new byte[][] { to }); - } - - /** - * Create a {@link ReplaceInputStream} that will replace all froms - * with tos. - *

- * Note that they will be replaced in order, and that for each from - * a to must correspond. - * - * @param in - * the under-laying {@link InputStream} - * @param froms - * the values to replace - * @param tos - * the values to replace with - */ - public ReplaceInputStream(InputStream in, String[] froms, String[] tos) { - this(in, StreamUtils.getBytes(froms), StreamUtils.getBytes(tos)); - } - - /** - * Create a {@link ReplaceInputStream} that will replace all froms - * with tos. - *

- * Note that they will be replaced in order, and that for each from - * a to must correspond. - * - * @param in - * the under-laying {@link InputStream} - * @param froms - * the values to replace - * @param tos - * the values to replace with - */ - public ReplaceInputStream(InputStream in, byte[][] froms, byte[][] tos) { - super(in); - - if (froms.length != tos.length) { - throw new IllegalArgumentException( - "For replacing, each FROM must have a corresponding TO"); - } - - this.froms = froms; - this.tos = tos; - - maxFromSize = 0; - for (int i = 0; i < froms.length; i++) { - maxFromSize = Math.max(maxFromSize, froms[i].length); - } - - int maxToSize = 0; - for (int i = 0; i < tos.length; i++) { - maxToSize = Math.max(maxToSize, tos[i].length); - } - - // We need at least maxFromSize so we can iterate and replace - bufferSize = Math.max(4 * Math.max(maxToSize, maxFromSize), - MIN_BUFFER_SIZE); - } - - @Override - protected boolean preRead() throws IOException { - boolean rep = super.preRead(); - start = stop; - return rep; - } - - @Override - protected int read(InputStream in, byte[] buffer) throws IOException { - buffer = null; // do not use the buffer. - - byte[] newBuffer = new byte[bufferSize]; - int read = 0; - while (read < bufferSize / 2) { - int thisTime = in.read(newBuffer, read, bufferSize / 2 - read); - if (thisTime <= 0) { - break; - } - read += thisTime; - } - - List bbBuffers = new ArrayList(); - List bbOffsets = new ArrayList(); - List bbLengths = new ArrayList(); - - int offset = 0; - for (int i = 0; i < read; i++) { - for (int fromIndex = 0; fromIndex < froms.length; fromIndex++) { - byte[] from = froms[fromIndex]; - byte[] to = tos[fromIndex]; - - if (from.length > 0 - && StreamUtils.startsWith(from, newBuffer, i, read)) { - if (i - offset > 0) { - bbBuffers.add(newBuffer); - bbOffsets.add(offset); - bbLengths.add(i - offset); - } - - if (to.length > 0) { - bbBuffers.add(to); - bbOffsets.add(0); - bbLengths.add(to.length); - } - - i += from.length; - offset = i; - } - } - } - - if (offset < read) { - bbBuffers.add(newBuffer); - bbOffsets.add(offset); - bbLengths.add(read - offset); - } - - for (int i = bbBuffers.size() - 1; i >= 0; i--) { - // DEBUG("pushback", bbBuffers.get(i), bbOffsets.get(i), - // bbLengths.get(i)); - pushback(bbBuffers.get(i), bbOffsets.get(i), bbLengths.get(i)); - } - - return read; - } - - // static public void DEBUG(String title, byte[] b, int off, int len) { - // String str = new String(b,off,len); - // if(str.length()>20) { - // str=str.substring(0,10)+" ... - // "+str.substring(str.length()-10,str.length()); - // } - // } - - @Override - public String toString() { - StringBuilder rep = new StringBuilder(); - rep.append(getClass().getSimpleName()).append("\n"); - - for (int i = 0; i < froms.length; i++) { - byte[] from = froms[i]; - byte[] to = tos[i]; - - rep.append("\t"); - rep.append("bytes[").append(from.length).append("]"); - if (from.length <= 20) { - rep.append(" (").append(new String(from)).append(")"); - } - rep.append(" -> "); - rep.append("bytes[").append(to.length).append("]"); - if (to.length <= 20) { - rep.append(" (").append(new String(to)).append(")"); - } - rep.append("\n"); - } - - return "[" + rep + "]"; - } -} diff --git a/src/be/nikiroo/utils/streams/ReplaceOutputStream.java b/src/be/nikiroo/utils/streams/ReplaceOutputStream.java deleted file mode 100644 index c6679cc..0000000 --- a/src/be/nikiroo/utils/streams/ReplaceOutputStream.java +++ /dev/null @@ -1,148 +0,0 @@ -package be.nikiroo.utils.streams; - -import java.io.IOException; -import java.io.OutputStream; - -import be.nikiroo.utils.StringUtils; - -/** - * This {@link OutputStream} will change some of its content by replacing it - * with something else. - * - * @author niki - */ -public class ReplaceOutputStream extends BufferedOutputStream { - private byte[][] froms; - private byte[][] tos; - - /** - * Create a {@link ReplaceOutputStream} that will replace from with - * to. - * - * @param out - * the under-laying {@link OutputStream} - * @param from - * the {@link String} to replace - * @param to - * the {@link String} to replace with - */ - public ReplaceOutputStream(OutputStream out, String from, String to) { - this(out, StringUtils.getBytes(from), StringUtils.getBytes(to)); - } - - /** - * Create a {@link ReplaceOutputStream} that will replace from with - * to. - * - * @param out - * the under-laying {@link OutputStream} - * @param from - * the value to replace - * @param to - * the value to replace with - */ - public ReplaceOutputStream(OutputStream out, byte[] from, byte[] to) { - this(out, new byte[][] { from }, new byte[][] { to }); - } - - /** - * Create a {@link ReplaceOutputStream} that will replace all froms - * with tos. - *

- * Note that they will be replaced in order, and that for each from - * a to must correspond. - * - * @param out - * the under-laying {@link OutputStream} - * @param froms - * the values to replace - * @param tos - * the values to replace with - */ - public ReplaceOutputStream(OutputStream out, String[] froms, String[] tos) { - this(out, StreamUtils.getBytes(froms), StreamUtils.getBytes(tos)); - } - - /** - * Create a {@link ReplaceOutputStream} that will replace all froms - * with tos. - *

- * Note that they will be replaced in order, and that for each from - * a to must correspond. - * - * @param out - * the under-laying {@link OutputStream} - * @param froms - * the values to replace - * @param tos - * the values to replace with - */ - public ReplaceOutputStream(OutputStream out, byte[][] froms, byte[][] tos) { - super(out); - bypassFlush = false; - - if (froms.length != tos.length) { - throw new IllegalArgumentException( - "For replacing, each FROM must have a corresponding TO"); - } - - this.froms = froms; - this.tos = tos; - } - - /** - * Flush the {@link BufferedOutputStream}, write the current buffered data - * to (and optionally also flush) the under-laying stream. - *

- * If {@link BufferedOutputStream#bypassFlush} is false, all writes to the - * under-laying stream are done in this method. - *

- * This can be used if you want to write some data in the under-laying - * stream yourself (in that case, flush this {@link BufferedOutputStream} - * with or without flushing the under-laying stream, then you can write to - * the under-laying stream). - *

- * But be careful! If a replacement could be done with the end o the - * currently buffered data and the start of the data to come, we obviously - * will not be able to do it. - * - * @param includingSubStream - * also flush the under-laying stream - * @throws IOException - * in case of I/O error - */ - @Override - public void flush(boolean includingSubStream) throws IOException { - // Note: very simple, not efficient implementation; sorry. - while (start < stop) { - boolean replaced = false; - for (int i = 0; i < froms.length; i++) { - if (froms[i] != null - && froms[i].length > 0 - && StreamUtils - .startsWith(froms[i], buffer, start, stop)) { - if (tos[i] != null && tos[i].length > 0) { - out.write(tos[i]); - bytesWritten += tos[i].length; - } - - start += froms[i].length; - replaced = true; - break; - } - } - - if (!replaced) { - out.write(buffer[start++]); - bytesWritten++; - } - } - - start = 0; - stop = 0; - - if (includingSubStream) { - out.flush(); - } - } -} diff --git a/src/be/nikiroo/utils/streams/StreamUtils.java b/src/be/nikiroo/utils/streams/StreamUtils.java deleted file mode 100644 index dc75090..0000000 --- a/src/be/nikiroo/utils/streams/StreamUtils.java +++ /dev/null @@ -1,69 +0,0 @@ -package be.nikiroo.utils.streams; - -import be.nikiroo.utils.StringUtils; - -/** - * Some non-public utilities used in the stream classes. - * - * @author niki - */ -class StreamUtils { - /** - * Check if the buffer starts with the given search term (given as an array, - * a start position and an end position). - *

- * Note: the parameter stop is the index of the last - * position, not the length. - *

- * Note: the search term size must be smaller or equal the internal - * buffer size. - * - * @param search - * the term to search for - * @param buffer - * the buffer to look into - * @param start - * the offset at which to start the search - * @param stop - * the maximum index of the data to check (this is not a - * length, but an index) - * - * @return TRUE if the search content is present at the given location and - * does not exceed the len index - */ - static public boolean startsWith(byte[] search, byte[] buffer, int start, - int stop) { - - // Check if there even is enough space for it - if (search.length > (stop - start)) { - return false; - } - - boolean same = true; - for (int i = 0; i < search.length; i++) { - if (search[i] != buffer[start + i]) { - same = false; - break; - } - } - - return same; - } - - /** - * Return the bytes array representation of the given {@link String} in - * UTF-8. - * - * @param strs - * the {@link String}s to transform into bytes - * @return the content in bytes - */ - static public byte[][] getBytes(String[] strs) { - byte[][] bytes = new byte[strs.length][]; - for (int i = 0; i < strs.length; i++) { - bytes[i] = StringUtils.getBytes(strs[i]); - } - - return bytes; - } -} diff --git a/src/be/nikiroo/utils/test/TestCase.java b/src/be/nikiroo/utils/test/TestCase.java deleted file mode 100644 index fe7b9af..0000000 --- a/src/be/nikiroo/utils/test/TestCase.java +++ /dev/null @@ -1,535 +0,0 @@ -package be.nikiroo.utils.test; - -import java.io.File; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import be.nikiroo.utils.IOUtils; - -/** - * A {@link TestCase} that can be run with {@link TestLauncher}. - * - * @author niki - */ -abstract public class TestCase { - /** - * The type of {@link Exception} used to signal a failed assertion or a - * force-fail. - * - * @author niki - */ - class AssertException extends Exception { - private static final long serialVersionUID = 1L; - - public AssertException(String reason, Exception source) { - super(reason, source); - } - - public AssertException(String reason) { - super(reason); - } - } - - private String name; - - /** - * Create a new {@link TestCase}. - * - * @param name - * the test name - */ - public TestCase(String name) { - this.name = name; - } - - /** - * This constructor can be used if you require a no-param constructor. In - * this case, you are allowed to set the name manually via - * {@link TestCase#setName}. - */ - protected TestCase() { - this("no name"); - } - - /** - * Setup the test (called before the test is run). - * - * @throws Exception - * in case of error - */ - public void setUp() throws Exception { - } - - /** - * Tear-down the test (called when the test has been ran). - * - * @throws Exception - * in case of error - */ - public void tearDown() throws Exception { - } - - /** - * The test name. - * - * @return the name - */ - public String getName() { - return name; - } - - /** - * The test name. - * - * @param name - * the new name (internal use only) - * - * @return this (so we can chain and so we can initialize it in a member - * variable if this is an anonymous inner class) - */ - protected TestCase setName(String name) { - this.name = name; - return this; - } - - /** - * Actually do the test. - * - * @throws Exception - * in case of error - */ - abstract public void test() throws Exception; - - /** - * Force a failure. - * - * @throws AssertException - * every time - */ - public void fail() throws AssertException { - fail(null); - } - - /** - * Force a failure. - * - * @param reason - * the failure reason - * - * @throws AssertException - * every time - */ - public void fail(String reason) throws AssertException { - fail(reason, null); - } - - /** - * Force a failure. - * - * @param reason - * the failure reason - * @param e - * the exception that caused the failure (can be NULL) - * - * @throws AssertException - * every time - */ - public void fail(String reason, Exception e) throws AssertException { - throw new AssertException("Failed!" + // - reason != null ? "\n" + reason : "", e); - } - - /** - * Check that 2 {@link Object}s are equals. - * - * @param expected - * the expected value - * @param actual - * the actual value - * - * @throws AssertException - * in case they differ - */ - public void assertEquals(Object expected, Object actual) - throws AssertException { - assertEquals(null, expected, actual); - } - - /** - * Check that 2 {@link Object}s are equals. - * - * @param errorMessage - * the error message to display if they differ - * @param expected - * the expected value - * @param actual - * the actual value - * - * @throws AssertException - * in case they differ - */ - public void assertEquals(String errorMessage, Object expected, Object actual) - throws AssertException { - if ((expected == null && actual != null) - || (expected != null && !expected.equals(actual))) { - if (errorMessage == null) { - throw new AssertException(generateAssertMessage(expected, - actual)); - } - - throw new AssertException(errorMessage, new AssertException( - generateAssertMessage(expected, actual))); - } - } - - /** - * Check that 2 longs are equals. - * - * @param expected - * the expected value - * @param actual - * the actual value - * - * @throws AssertException - * in case they differ - */ - public void assertEquals(long expected, long actual) throws AssertException { - assertEquals(Long.valueOf(expected), Long.valueOf(actual)); - } - - /** - * Check that 2 longs are equals. - * - * @param errorMessage - * the error message to display if they differ - * @param expected - * the expected value - * @param actual - * the actual value - * - * @throws AssertException - * in case they differ - */ - public void assertEquals(String errorMessage, long expected, long actual) - throws AssertException { - assertEquals(errorMessage, Long.valueOf(expected), Long.valueOf(actual)); - } - - /** - * Check that 2 booleans are equals. - * - * @param expected - * the expected value - * @param actual - * the actual value - * - * @throws AssertException - * in case they differ - */ - public void assertEquals(boolean expected, boolean actual) - throws AssertException { - assertEquals(Boolean.valueOf(expected), Boolean.valueOf(actual)); - } - - /** - * Check that 2 booleans are equals. - * - * @param errorMessage - * the error message to display if they differ - * @param expected - * the expected value - * @param actual - * the actual value - * - * @throws AssertException - * in case they differ - */ - public void assertEquals(String errorMessage, boolean expected, - boolean actual) throws AssertException { - assertEquals(errorMessage, Boolean.valueOf(expected), - Boolean.valueOf(actual)); - } - - /** - * Check that 2 doubles are equals. - * - * @param expected - * the expected value - * @param actual - * the actual value - * - * @throws AssertException - * in case they differ - */ - public void assertEquals(double expected, double actual) - throws AssertException { - assertEquals(Double.valueOf(expected), Double.valueOf(actual)); - } - - /** - * Check that 2 doubles are equals. - * - * @param errorMessage - * the error message to display if they differ - * @param expected - * the expected value - * @param actual - * the actual value - * - * @throws AssertException - * in case they differ - */ - public void assertEquals(String errorMessage, double expected, double actual) - throws AssertException { - assertEquals(errorMessage, Double.valueOf(expected), - Double.valueOf(actual)); - } - - /** - * Check that 2 {@link List}s are equals. - * - * @param errorMessage - * the error message to display if they differ - * @param expected - * the expected value - * @param actual - * the actual value - * - * @throws AssertException - * in case they differ - */ - public void assertEquals(List expected, List actual) - throws AssertException { - assertEquals("Assertion failed", expected, actual); - } - - /** - * Check that 2 {@link List}s are equals. - * - * @param errorMessage - * the error message to display if they differ - * @param expected - * the expected value - * @param actual - * the actual value - * - * @throws AssertException - * in case they differ - */ - public void assertEquals(String errorMessage, List expected, - List actual) throws AssertException { - - if (expected.size() != actual.size()) { - assertEquals(errorMessage + ": not same number of items", - list(expected), list(actual)); - } - - int size = expected.size(); - for (int i = 0; i < size; i++) { - assertEquals(errorMessage + ": item " + i - + " (0-based) is not correct", expected.get(i), - actual.get(i)); - } - } - - /** - * Check that 2 {@link File}s are equals, by doing a line-by-line - * comparison. - * - * @param expected - * the expected value - * @param actual - * the actual value - * @param errorMessage - * the error message to display if they differ - * - * @throws AssertException - * in case they differ - */ - public void assertEquals(File expected, File actual) throws AssertException { - assertEquals(generateAssertMessage(expected, actual), expected, actual); - } - - /** - * Check that 2 {@link File}s are equals, by doing a line-by-line - * comparison. - * - * @param errorMessage - * the error message to display if they differ - * @param expected - * the expected value - * @param actual - * the actual value - * - * @throws AssertException - * in case they differ - */ - public void assertEquals(String errorMessage, File expected, File actual) - throws AssertException { - assertEquals(errorMessage, expected, actual, null); - } - - /** - * Check that 2 {@link File}s are equals, by doing a line-by-line - * comparison. - * - * @param errorMessage - * the error message to display if they differ - * @param expected - * the expected value - * @param actual - * the actual value - * @param skipCompare - * skip the lines starting with some values for the given files - * (relative path from base directory in recursive mode) - * - * @throws AssertException - * in case they differ - */ - public void assertEquals(String errorMessage, File expected, File actual, - Map> skipCompare) throws AssertException { - assertEquals(errorMessage, expected, actual, skipCompare, null); - } - - private void assertEquals(String errorMessage, File expected, File actual, - Map> skipCompare, String removeFromName) - throws AssertException { - - if (expected.isDirectory() || actual.isDirectory()) { - assertEquals(errorMessage + ": type mismatch: expected a " - + (expected.isDirectory() ? "directory" : "file") - + ", received a " - + (actual.isDirectory() ? "directory" : "file"), - expected.isDirectory(), actual.isDirectory()); - - List expectedFiles = Arrays.asList(expected.list()); - Collections.sort(expectedFiles); - List actualFiles = Arrays.asList(actual.list()); - Collections.sort(actualFiles); - - assertEquals(errorMessage, expectedFiles, actualFiles); - for (int i = 0; i < actualFiles.size(); i++) { - File expectedFile = new File(expected, expectedFiles.get(i)); - File actualFile = new File(actual, actualFiles.get(i)); - - assertEquals(errorMessage, expectedFile, actualFile, - skipCompare, expected.getAbsolutePath()); - } - } else { - try { - List expectedLines = Arrays.asList(IOUtils - .readSmallFile(expected).split("\n")); - List resultLines = Arrays.asList(IOUtils.readSmallFile( - actual).split("\n")); - - String name = expected.getAbsolutePath(); - if (removeFromName != null && name.startsWith(removeFromName)) { - name = expected.getName() - + name.substring(removeFromName.length()); - } - - assertEquals(errorMessage + ": " + name - + ": the number of lines is not the same", - expectedLines.size(), resultLines.size()); - - for (int j = 0; j < expectedLines.size(); j++) { - String expectedLine = expectedLines.get(j); - String resultLine = resultLines.get(j); - - boolean skip = false; - if (skipCompare != null) { - for (Entry> skipThose : skipCompare - .entrySet()) { - for (String skipStart : skipThose.getValue()) { - if (name.endsWith(skipThose.getKey()) - && expectedLine.startsWith(skipStart) - && resultLine.startsWith(skipStart)) { - skip = true; - } - } - } - } - - if (skip) { - continue; - } - - assertEquals(errorMessage + ": line " + (j + 1) - + " is not the same in file " + name, expectedLine, - resultLine); - } - } catch (Exception e) { - throw new AssertException(errorMessage, e); - } - } - } - - /** - * Check that given {@link Object} is not NULL. - * - * @param errorMessage - * the error message to display if it is NULL - * @param actual - * the actual value - * - * @throws AssertException - * in case they differ - */ - public void assertNotNull(String errorMessage, Object actual) - throws AssertException { - if (actual == null) { - String defaultReason = String.format("" // - + "Assertion failed!%n" // - + "Object should not have been NULL"); - - if (errorMessage == null) { - throw new AssertException(defaultReason); - } - - throw new AssertException(errorMessage, new AssertException( - defaultReason)); - } - } - - /** - * Generate the default assert message for 2 different values that were - * supposed to be equals. - * - * @param expected - * the expected value - * @param actual - * the actual value - * - * @return the message - */ - public static String generateAssertMessage(Object expected, Object actual) { - return String.format("" // - + "Assertion failed!%n" // - + "Expected value: [%s]%n" // - + "Actual value: [%s]", expected, actual); - } - - private static String list(List items) { - StringBuilder builder = new StringBuilder(); - for (Object item : items) { - if (builder.length() == 0) { - builder.append(items.size() + " item(s): "); - } else { - builder.append(", "); - } - - builder.append("" + item); - - if (builder.length() > 60) { - builder.setLength(57); - builder.append("..."); - break; - } - } - - return builder.toString(); - } -} diff --git a/src/be/nikiroo/utils/test/TestLauncher.java b/src/be/nikiroo/utils/test/TestLauncher.java deleted file mode 100644 index 895b565..0000000 --- a/src/be/nikiroo/utils/test/TestLauncher.java +++ /dev/null @@ -1,434 +0,0 @@ -package be.nikiroo.utils.test; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.List; - -/** - * A {@link TestLauncher} starts a series of {@link TestCase}s and displays the - * result to the user. - * - * @author niki - */ -public class TestLauncher { - /** - * {@link Exception} happening during the setup process. - * - * @author niki - */ - private class SetupException extends Exception { - private static final long serialVersionUID = 1L; - - public SetupException(Throwable e) { - super(e); - } - } - - /** - * {@link Exception} happening during the tear-down process. - * - * @author niki - */ - private class TearDownException extends Exception { - private static final long serialVersionUID = 1L; - - public TearDownException(Throwable e) { - super(e); - } - } - - private List series; - private List tests; - private TestLauncher parent; - - private int columns; - private String okString; - private String koString; - private String name; - private boolean cont; - - protected int executed; - protected int total; - - private int currentSeries = 0; - private boolean details = false; - - /** - * Create a new {@link TestLauncher} with default parameters. - * - * @param name - * the test suite name - * @param args - * the arguments to configure the number of columns and the ok/ko - * {@link String}s - */ - public TestLauncher(String name, String[] args) { - this.name = name; - - int cols = 80; - if (args != null && args.length >= 1) { - try { - cols = Integer.parseInt(args[0]); - } catch (NumberFormatException e) { - System.err.println("Test configuration: given number " - + "of columns is not parseable: " + args[0]); - } - } - - setColumns(cols); - - String okString = "[ ok ]"; - String koString = "[ !! ]"; - if (args != null && args.length >= 3) { - okString = args[1]; - koString = args[2]; - } - - setOkString(okString); - setKoString(koString); - - series = new ArrayList(); - tests = new ArrayList(); - cont = true; - } - - /** - * Display the details of the errors - * - * @return TRUE to display them, false to simply mark the test as failed - */ - public boolean isDetails() { - if (parent != null) { - return parent.isDetails(); - } - - return details; - } - - /** - * Display the details of the errors - * - * @param details - * TRUE to display them, false to simply mark the test as failed - */ - public void setDetails(boolean details) { - if (parent != null) { - parent.setDetails(details); - } - - this.details = details; - } - - /** - * Called before actually starting the tests themselves. - * - * @throws Exception - * in case of error - */ - protected void start() throws Exception { - } - - /** - * Called when the tests are passed (or failed to do so). - * - * @throws Exception - * in case of error - */ - protected void stop() throws Exception { - } - - protected void addTest(TestCase test) { - tests.add(test); - } - - protected void addSeries(TestLauncher series) { - this.series.add(series); - series.parent = this; - } - - /** - * Launch the series of {@link TestCase}s and the {@link TestCase}s. - * - * @return the number of errors - */ - public int launch() { - return launch(0); - } - - /** - * Launch the series of {@link TestCase}s and the {@link TestCase}s. - * - * @param depth - * the level at which is the launcher (0 = main launcher) - * - * @return the number of errors - */ - public int launch(int depth) { - int errors = 0; - executed = 0; - total = tests.size(); - - print(depth); - - try { - start(); - - errors += launchTests(depth); - if (tests.size() > 0 && depth == 0) { - System.out.println(""); - } - - currentSeries = 0; - for (TestLauncher serie : series) { - errors += serie.launch(depth + 1); - executed += serie.executed; - total += serie.total; - currentSeries++; - } - } catch (Exception e) { - print(depth, "__start"); - print(depth, e); - } finally { - try { - stop(); - } catch (Exception e) { - print(depth, "__stop"); - print(depth, e); - } - } - - print(depth, executed, errors, total); - - return errors; - } - - /** - * Launch the {@link TestCase}s. - * - * @param depth - * the level at which is the launcher (0 = main launcher) - * - * @return the number of errors - */ - protected int launchTests(int depth) { - int errors = 0; - for (TestCase test : tests) { - print(depth, test.getName()); - - Throwable ex = null; - try { - try { - test.setUp(); - } catch (Throwable e) { - throw new SetupException(e); - } - test.test(); - try { - test.tearDown(); - } catch (Throwable e) { - throw new TearDownException(e); - } - } catch (Throwable e) { - ex = e; - } - - if (ex != null) { - errors++; - } - - print(depth, ex); - - executed++; - - if (ex != null && !cont) { - break; - } - } - - return errors; - } - - /** - * Specify a custom number of columns to use for the display of messages. - * - * @param columns - * the number of columns - */ - public void setColumns(int columns) { - this.columns = columns; - } - - /** - * Continue to run the tests when an error is detected. - * - * @param cont - * yes or no - */ - public void setContinueAfterFail(boolean cont) { - this.cont = cont; - } - - /** - * Set a custom "[ ok ]" {@link String} when a test passed. - * - * @param okString - * the {@link String} to display at the end of a success - */ - public void setOkString(String okString) { - this.okString = okString; - } - - /** - * Set a custom "[ !! ]" {@link String} when a test failed. - * - * @param koString - * the {@link String} to display at the end of a failure - */ - public void setKoString(String koString) { - this.koString = koString; - } - - /** - * Print the test suite header. - * - * @param depth - * the level at which is the launcher (0 = main launcher) - */ - protected void print(int depth) { - if (depth == 0) { - System.out.println("[ Test suite: " + name + " ]"); - System.out.println(""); - } else { - System.out.println(prefix(depth, false) + name + ":"); - } - } - - /** - * Print the name of the {@link TestCase} we will start immediately after. - * - * @param depth - * the level at which is the launcher (0 = main launcher) - * @param name - * the {@link TestCase} name - */ - protected void print(int depth, String name) { - name = prefix(depth, false) - + (name == null ? "" : name).replace("\t", " "); - - StringBuilder dots = new StringBuilder(); - while ((name.length() + dots.length()) < columns - 11) { - dots.append('.'); - } - - System.out.print(name + dots.toString()); - } - - /** - * Print the result of the {@link TestCase} we just ran. - * - * @param depth - * the level at which is the launcher (0 = main launcher) - * @param error - * the {@link Exception} it ran into if any - */ - private void print(int depth, Throwable error) { - if (error != null) { - System.out.println(" " + koString); - if (isDetails()) { - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - error.printStackTrace(pw); - String lines = sw.toString(); - for (String line : lines.split("\n")) { - System.out.println(prefix(depth, false) + "\t\t" + line); - } - } - } else { - System.out.println(" " + okString); - } - } - - /** - * Print the total result for this test suite. - * - * @param depth - * the level at which is the launcher (0 = main launcher) - * @param executed - * the number of tests actually ran - * @param errors - * the number of errors encountered - * @param total - * the total number of tests in the suite - */ - private void print(int depth, int executed, int errors, int total) { - int ok = executed - errors; - int pc = (int) ((100.0 * ok) / executed); - if (pc == 0 && ok > 0) { - pc = 1; - } - int pcTotal = (int) ((100.0 * ok) / total); - if (pcTotal == 0 && ok > 0) { - pcTotal = 1; - } - - String resume = "Tests passed: " + ok + "/" + executed + " (" + pc - + "%) on a total of " + total + " (" + pcTotal + "% total)"; - if (depth == 0) { - System.out.println(resume); - } else { - String arrow = "┗▶ "; - System.out.println(prefix(depth, currentSeries == 0) + arrow - + resume); - System.out.println(prefix(depth, currentSeries == 0)); - } - } - - private int last = -1; - - /** - * Return the prefix to print before the current line. - * - * @param depth - * the current depth - * @param first - * this line is the first of its tabulation level - * - * @return the prefix - */ - private String prefix(int depth, boolean first) { - String space = tabs(depth - 1); - - String line = ""; - if (depth > 0) { - if (depth > 1) { - if (depth != last && first) { - line = "╻"; // first line - } else { - line = "┃"; // continuation - } - } - - space += line + tabs(1); - } - - last = depth; - return space; - } - - /** - * Return the given number of space-converted tabs in a {@link String}. - * - * @param depth - * the number of tabs to return - * - * @return the string - */ - private String tabs(int depth) { - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < depth; i++) { - builder.append(" "); - } - return builder.toString(); - } -} diff --git a/src/be/nikiroo/utils/ui/BreadCrumbsBar.java b/src/be/nikiroo/utils/ui/BreadCrumbsBar.java deleted file mode 100644 index ed7e0bb..0000000 --- a/src/be/nikiroo/utils/ui/BreadCrumbsBar.java +++ /dev/null @@ -1,230 +0,0 @@ - -package be.nikiroo.utils.ui; - -import java.awt.BorderLayout; -import java.awt.Dimension; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.ComponentAdapter; -import java.awt.event.ComponentEvent; -import java.util.ArrayList; -import java.util.List; - -import javax.swing.AbstractAction; -import javax.swing.BoxLayout; -import javax.swing.JPanel; -import javax.swing.JPopupMenu; -import javax.swing.JToggleButton; -import javax.swing.SwingWorker; -import javax.swing.event.PopupMenuEvent; -import javax.swing.event.PopupMenuListener; - -public class BreadCrumbsBar extends ListenerPanel { - private class BreadCrumb extends JPanel { - private JToggleButton button; - private JToggleButton down; - - public BreadCrumb(final DataNode node) { - this.setLayout(new BorderLayout()); - - if (!node.isRoot()) { - button = new JToggleButton(node.toString()); - button.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - button.setSelected(false); - if (!node.isRoot()) { - // TODO: allow clicking on root? option? - setSelectedNode(node); - } - } - }); - - this.add(button, BorderLayout.CENTER); - } - - if ((node.isRoot() && node.getChildren().isEmpty()) - || !node.getChildren().isEmpty()) { - // TODO allow an image or ">", viewer - down = new JToggleButton(">"); - final JPopupMenu popup = new JPopupMenu(); - - for (final DataNode child : node.getChildren()) { - popup.add(new AbstractAction(child.toString()) { - private static final long serialVersionUID = 1L; - - @Override - public void actionPerformed(ActionEvent e) { - setSelectedNode(child); - } - }); - } - - down.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent ev) { - if (down.isSelected()) { - popup.show(down, 0, down.getBounds().height); - } else { - popup.setVisible(false); - } - } - }); - - popup.addPopupMenuListener(new PopupMenuListener() { - @Override - public void popupMenuWillBecomeVisible(PopupMenuEvent e) { - } - - @Override - public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { - down.setSelected(false); - } - - @Override - public void popupMenuCanceled(PopupMenuEvent e) { - } - }); - - this.add(down, BorderLayout.EAST); - } - } - } - - static public final String CHANGE_ACTION = "change"; - - private boolean vertical; - private DataNode node; - private List crumbs = new ArrayList(); - - public BreadCrumbsBar(final DataTree tree) { - vertical = true; // to force an update - setVertical(false); - - addComponentListener(new ComponentAdapter() { - @Override - public void componentResized(ComponentEvent e) { - super.componentResized(e); - synchronized (crumbs) { - for (BreadCrumb crumb : crumbs) { - setCrumbSize(crumb); - } - } - } - }); - - setSelectedNode(new DataNode(null, null)); - - new SwingWorker, Void>() { - @Override - protected DataNode doInBackground() throws Exception { - tree.loadData(); - return tree.getRoot(); - } - - @Override - protected void done() { - try { - DataNode node = get(); - - setSelectedNode(null); - BreadCrumbsBar.this.node = node; - addCrumb(node); - - // TODO: option? - if (node.size() > 0) { - setSelectedNode(node.getChildren().get(0)); - } else { - revalidate(); - repaint(); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - }.execute(); - } - - public void setVertical(boolean vertical) { - if (vertical != this.vertical) { - synchronized (crumbs) { - this.vertical = vertical; - - for (BreadCrumb crumb : crumbs) { - this.remove(crumb); - } - - if (vertical) { - this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); - } else { - this.setLayout(new WrapLayout(WrapLayout.LEADING)); - } - - for (BreadCrumb crumb : crumbs) { - this.add(crumb); - setCrumbSize(crumb); - } - } - - this.revalidate(); - this.repaint(); - } - } - - public DataNode getSelectedNode() { - return node; - } - - public void setSelectedNode(DataNode node) { - if (this.node == node) { - return; - } - - synchronized (crumbs) { - // clear until common ancestor (can clear all!) - while (this.node != null && !this.node.isParentOf(node)) { - this.node = this.node.getParent(); - this.remove(crumbs.remove(crumbs.size() - 1)); - } - - // switch root if needed and possible - if (this.node == null && node != null) { - this.node = node.getRoot(); - addCrumb(this.node); - } - - // re-create until node - while (node != null && this.node != node) { - DataNode ancestorOrNode = node; - for (DataNode child : this.node.getChildren()) { - if (child.isParentOf(node)) - ancestorOrNode = child; - } - - this.node = ancestorOrNode; - addCrumb(this.node); - } - } - - this.revalidate(); - this.repaint(); - - fireActionPerformed(CHANGE_ACTION); - } - - private void addCrumb(DataNode node) { - BreadCrumb crumb = new BreadCrumb(node); - this.crumbs.add(crumb); - setCrumbSize(crumb); - this.add(crumb); - } - - private void setCrumbSize(BreadCrumb crumb) { - if (vertical) { - crumb.setMaximumSize(new Dimension(this.getWidth(), - crumb.getMinimumSize().height)); - } else { - crumb.setMaximumSize(null); - } - } -} diff --git a/src/be/nikiroo/utils/ui/ConfigEditor.java b/src/be/nikiroo/utils/ui/ConfigEditor.java deleted file mode 100644 index c687c98..0000000 --- a/src/be/nikiroo/utils/ui/ConfigEditor.java +++ /dev/null @@ -1,165 +0,0 @@ -package be.nikiroo.utils.ui; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import javax.swing.BoxLayout; -import javax.swing.JButton; -import javax.swing.JComponent; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTextArea; -import javax.swing.border.EmptyBorder; -import javax.swing.border.TitledBorder; - -import be.nikiroo.utils.StringUtils; -import be.nikiroo.utils.resources.Bundle; -import be.nikiroo.utils.resources.MetaInfo; - -/** - * A configuration panel for a {@link Bundle}. - *

- * All the items in the given {@link Bundle} will be displayed in editable - * controls, with options to Save, Reset and/or Reset to the application default - * values. - * - * @author niki - * - * @param - * the type of {@link Bundle} to edit - */ -public class ConfigEditor> extends JPanel { - private static final long serialVersionUID = 1L; - private List> items; - - /** - * Create a new {@link ConfigEditor} for this {@link Bundle}. - * - * @param type - * a class instance of the item type to work on - * @param bundle - * the {@link Bundle} to sort through - * @param title - * the title to display before the options - */ - public ConfigEditor(Class type, final Bundle bundle, String title) { - this.setLayout(new BorderLayout()); - - JPanel main = new JPanel(); - main.setLayout(new BoxLayout(main, BoxLayout.PAGE_AXIS)); - main.setBorder(new EmptyBorder(5, 5, 5, 5)); - - main.add(new JLabel(title)); - - items = new ArrayList>(); - List> groupedItems = MetaInfo.getItems(type, bundle); - for (MetaInfo item : groupedItems) { - // will init this.items - addItem(main, item, 0); - } - - JPanel buttons = new JPanel(); - buttons.setLayout(new BoxLayout(buttons, BoxLayout.PAGE_AXIS)); - buttons.setBorder(new EmptyBorder(5, 5, 5, 5)); - - buttons.add(createButton("Reset", new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - for (MetaInfo item : items) { - item.reload(); - } - } - })); - - buttons.add(createButton("Default", new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - Object snap = bundle.takeSnapshot(); - bundle.reload(true); - for (MetaInfo item : items) { - item.reload(); - } - bundle.reload(false); - bundle.restoreSnapshot(snap); - } - })); - - buttons.add(createButton("Save", new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - for (MetaInfo item : items) { - item.save(true); - } - - try { - bundle.updateFile(); - } catch (IOException e1) { - e1.printStackTrace(); - } - } - })); - - JScrollPane scroll = new JScrollPane(main); - scroll.getVerticalScrollBar().setUnitIncrement(16); - - this.add(scroll, BorderLayout.CENTER); - this.add(buttons, BorderLayout.SOUTH); - } - - private void addItem(JPanel main, MetaInfo item, int nhgap) { - if (item.isGroup()) { - JPanel bpane = new JPanel(new BorderLayout()); - bpane.setBorder(new TitledBorder(item.getName())); - JPanel pane = new JPanel(); - pane.setBorder(new EmptyBorder(5, 5, 5, 5)); - pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS)); - - String info = item.getDescription(); - info = StringUtils.justifyTexts(info, 100); - if (!info.isEmpty()) { - info = info + "\n"; - JTextArea text = new JTextArea(info); - text.setWrapStyleWord(true); - text.setOpaque(false); - text.setForeground(new Color(100, 100, 180)); - text.setEditable(false); - pane.add(text); - } - - for (MetaInfo subitem : item) { - addItem(pane, subitem, nhgap + 11); - } - bpane.add(pane, BorderLayout.CENTER); - main.add(bpane); - } else { - items.add(item); - main.add(ConfigItem.createItem(item, nhgap)); - } - } - - /** - * Add an action button for this action. - * - * @param title - * the action title - * @param listener - * the action - */ - private JComponent createButton(String title, ActionListener listener) { - JButton button = new JButton(title); - button.addActionListener(listener); - - JPanel panel = new JPanel(); - panel.setLayout(new BorderLayout()); - panel.setBorder(new EmptyBorder(2, 10, 2, 10)); - panel.add(button, BorderLayout.CENTER); - - return panel; - } -} diff --git a/src/be/nikiroo/utils/ui/ConfigItem.java b/src/be/nikiroo/utils/ui/ConfigItem.java deleted file mode 100644 index 3ae029e..0000000 --- a/src/be/nikiroo/utils/ui/ConfigItem.java +++ /dev/null @@ -1,574 +0,0 @@ -package be.nikiroo.utils.ui; - -import java.awt.BorderLayout; -import java.awt.Cursor; -import java.awt.Dimension; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.util.List; - -import javax.swing.BoxLayout; -import javax.swing.ImageIcon; -import javax.swing.JButton; -import javax.swing.JComponent; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JTextField; - -import be.nikiroo.utils.Image; -import be.nikiroo.utils.StringUtils; -import be.nikiroo.utils.StringUtils.Alignment; -import be.nikiroo.utils.resources.Bundle; -import be.nikiroo.utils.resources.MetaInfo; - -/** - * A graphical item that reflect a configuration option from the given - * {@link Bundle}. - *

- * This graphical item can be edited, and the result will be saved back into the - * linked {@link MetaInfo}; you still have to save the {@link MetaInfo} should - * you wish to, of course. - * - * @author niki - * - * @param - * the type of {@link Bundle} to edit - */ -public abstract class ConfigItem> extends JPanel { - private static final long serialVersionUID = 1L; - - private static int minimumHeight = -1; - - /** A small 16x16 "?" blue in PNG, base64 encoded. */ - private static String img64info = // - "" - + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI" - + "WXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4wURFRg6IrtcdgAAATdJREFUOMvtkj8sQ1EUxr9z/71G" - + "m1RDogYxq7WDDYMYTSajSG4n6YRYzSaSLibWbiaDIGwdiLIYDFKDNJEgKu969xi8UNHy7H7LPcN3" - + "v/Odcy+hG9oOIeIcBCJS9MAvlZtOMtHxsrFrJHGqe0RVGnHAHpcIbPlng8BS3HmKBJYzabGUzcrJ" - + "XK+ckIrqANYR2JEv2nYDEVck0WKGfHzyq82Go+btxoX3XAcAIqTj8wPqOH6mtMeM4bGCLhyfhTMA" - + "qlLhKHqujCfaweCAmV0p50dPzsNpEKpK01V/n55HIvTnfDC2odKlfeYadZN/T+AqDACUsnkhqaU1" - + "LRIVuX1x7ciuSWQxVIrunONrfq3dI6oh+T94Z8453vEem/HTqT8ZpFJ0qDXtGkPbAGAMeSRngQCA" - + "eUvgn195AwlZWyvjtQdhAAAAAElFTkSuQmCC"; - - /** A small 16x16 "+" image with colours */ - private static String img64add = // - "" - + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI" - + "WXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4wUeES0QBFvvnAAAAB1pVFh0Q29tbWVudAAAAAAAQ3Jl" - + "YXRlZCB3aXRoIEdJTVBkLmUHAAACH0lEQVQ4y42Tz0sVURTHP+fMmC7CQMpH1EjgIimCsEVBEIg/" - + "qIbcBAW2Uai1m/oH2rlJXLQpeRJt2gQhTO0iTTKC1I2JBf5gKCJCRPvhPOed22LmvV70Fn7hwr3c" - + "+z3ne+73HCFHEClxaASRHgduA91AW369BkwDI3Foy0GkEofmACQnSxyaCyItAkMClMzYdeCAJgVP" - + "tJJrPA7tVoUjNZlngXMAiRmXClfoK/Tjq09x7T6LW+8RxOVJ5+LQzgSRojm5WCEDlMrQVbjIQNtN" - + "rh0d5FTzaTLBmWKgM4h0Ig4NzWseohYCJUuqx123Sx0MBpF2+MAdyWUnlqX4lf4bIDHjR+rwJJPR" - + "qNCgCjDsA10lM/oKIRcO9lByCYklnG/pqQa4euQ6J5tPoKI0yD6ef33Ku40Z80R7CSJNWyZxT+Ki" - + "2ytGP911hyZxQaRp1RtPPPYKD4+sGJwPrDUp7Q9Xxnj9fYrUUnaszEAwQHfrZQAerT/g7cYMiuCp" - + "z8LmLI0qBqz6wLQn2v5he57FrXkAtlPH2ZZOuskCzG2+4dnnx3iSuSgCKqLAlAIjmXPiVIRsgYjU" - + "usrfO0Gq7cA9jUNbBsZrmiQnac1e6n3FeBzakpf39OSBG9IPHAZwzlFoagVg5edHXn57wZed9dpA" - + "C3FoYRDpf8M0AQwKwu9yubxjeA7Y72ENqlp3mOqMcwcwDPQCx8gGchV4BYzGoS1V3gL8AVA5C5/0" - + "oRFoAAAAAElFTkSuQmCC"; - - /** A small 32x32 "-" image with colours */ - private static String img64remove = // - "" - + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI" - + "WXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4wUeESw5X/JGsQAAAB1pVFh0Q29tbWVudAAAAAAAQ3Jl" - + "YXRlZCB3aXRoIEdJTVBkLmUHAAACKUlEQVQ4y5WTO2iTYRSG3+//v/+SJrG5SSABh1JQBHFJNUNR" - + "YodCLoMoTkK0YKhQtBmsl01wKVZRBwcrgosg3SwFW9Cippe0VmlpB6uYqYIaNSZtbv/lOKRx0iR9" - + "4YOzvOc8vOd8wLbG4nYGAKP9tshKr3Pq0zFXORt0UzbopvUeZ2ml1/niUcIWAYBzwwqr+xgAjCSt" - + "wpXjWzx105Ha+1XsMgT8U6IJfPAacyfO50OXJi3VwbtbxMbidtZ3tiClbzi/eAuCmxgai4AfNvNn" - + "KJn3X5xWKgwA0lHHYud3MdDUXMcmIOMx0oGJXJCN9tuiJ98p4//DbtTk2cFKhB/OSBcMgQHVMkir" - + "AqwJBhGYrIIkCQc2eJK3aewI9Crko2FIh0K1Jo0mcwmV6XFUlmfRXhK7eXuRKaRVIYdiUGKnW8Kn" - + "0ia0t6/hKHJVqCcLzncQgLhtIvBfbWbZZahq+cl96AuvQLre2Mw59NUlkCwjZ6USL0uYgSj26B/X" - + "oK+vtkYgMAhMRF4x5oWlPdod0UQtfUFo7YEBBKz59BEGAAtRx1xHVgzu5AYyHmMmMJHrZolhhU3t" - + "05XJe7s2PJuCq9k1MgKyNjOXiBf8kWW5JDy4XKHBl2ql6+pvX8ZjzDOqrcWsFQAAE/T3H3z2GG/6" - + "zhT8sfdKeehWkUQAeJ7WcH23xTz1uPBwf1hclA3mBZjPojFOIOSsVPpmN1OznfpA+Gn+2kCHqg/d" - + "LhIA/AFU5d0V6gTjtQAAAABJRU5ErkJggg=="; - - /** The code base */ - private final ConfigItemBase base; - - /** The main panel with all the fields in it. */ - private JPanel main; - - /** - * Prepare a new {@link ConfigItem} instance, linked to the given - * {@link MetaInfo}. - * - * @param info - * the info - * @param autoDirtyHandling - * TRUE to automatically manage the setDirty/Save operations, - * FALSE if you want to do it yourself via - * {@link ConfigItem#setDirtyItem(int)} - */ - protected ConfigItem(MetaInfo info, boolean autoDirtyHandling) { - base = new ConfigItemBase(info, autoDirtyHandling) { - @Override - protected JComponent createEmptyField(int item) { - return ConfigItem.this.createEmptyField(item); - } - - @Override - protected Object getFromInfo(int item) { - return ConfigItem.this.getFromInfo(item); - } - - @Override - protected void setToInfo(Object value, int item) { - ConfigItem.this.setToInfo(value, item); - } - - @Override - protected Object getFromField(int item) { - return ConfigItem.this.getFromField(item); - } - - @Override - protected void setToField(Object value, int item) { - ConfigItem.this.setToField(value, item); - } - - @Override - public JComponent createField(int item) { - JComponent field = super.createField(item); - - int height = Math.max(getMinimumHeight(), - field.getMinimumSize().height); - field.setPreferredSize(new Dimension(200, height)); - - return field; - } - - @Override - public List reload() { - List removed = base.reload(); - if (!removed.isEmpty()) { - for (JComponent c : removed) { - main.remove(c); - } - main.revalidate(); - main.repaint(); - } - - return removed; - } - - @Override - protected JComponent removeItem(int item) { - JComponent removed = super.removeItem(item); - main.remove(removed); - main.revalidate(); - main.repaint(); - - return removed; - } - }; - } - - /** - * Create a new {@link ConfigItem} for the given {@link MetaInfo}. - * - * @param nhgap - * negative horisontal gap in pixel to use for the label, i.e., - * the step lock sized labels will start smaller by that amount - * (the use case would be to align controls that start at a - * different horisontal position) - */ - public void init(int nhgap) { - if (getInfo().isArray()) { - this.setLayout(new BorderLayout()); - add(label(nhgap), BorderLayout.WEST); - - main = new JPanel(); - - main.setLayout(new BoxLayout(main, BoxLayout.Y_AXIS)); - int size = getInfo().getListSize(false); - for (int i = 0; i < size; i++) { - addItemWithMinusPanel(i); - } - main.revalidate(); - main.repaint(); - - final JButton add = new JButton(); - setImage(add, img64add, "+"); - - add.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - addItemWithMinusPanel(base.getFieldsSize()); - main.revalidate(); - main.repaint(); - } - }); - - JPanel tmp = new JPanel(new BorderLayout()); - tmp.add(add, BorderLayout.WEST); - - JPanel mainPlus = new JPanel(new BorderLayout()); - mainPlus.add(main, BorderLayout.CENTER); - mainPlus.add(tmp, BorderLayout.SOUTH); - - add(mainPlus, BorderLayout.CENTER); - } else { - this.setLayout(new BorderLayout()); - add(label(nhgap), BorderLayout.WEST); - - JComponent field = base.createField(-1); - add(field, BorderLayout.CENTER); - } - } - - /** The {@link MetaInfo} linked to the field. */ - public MetaInfo getInfo() { - return base.getInfo(); - } - - /** - * Retrieve the associated graphical component that was created with - * {@link ConfigItemBase#createEmptyField(int)}. - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * - * @return the graphical component - */ - protected JComponent getField(int item) { - return base.getField(item); - } - - /** - * Manually specify that the given item is "dirty" and thus should be saved - * when asked. - *

- * Has no effect if the class is using automatic dirty handling (see - * {@link ConfigItemBase#ConfigItem(MetaInfo, boolean)}). - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - */ - protected void setDirtyItem(int item) { - base.setDirtyItem(item); - } - - /** - * Check if the value changed since the last load/save into the linked - * {@link MetaInfo}. - *

- * Note that we consider NULL and an Empty {@link String} to be equals. - * - * @param value - * the value to test - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * - * @return TRUE if it has - */ - protected boolean hasValueChanged(Object value, int item) { - return base.hasValueChanged(value, item); - } - - private void addItemWithMinusPanel(int item) { - JPanel minusPanel = createMinusPanel(item); - JComponent field = base.addItem(item, minusPanel); - minusPanel.add(field, BorderLayout.CENTER); - } - - private JPanel createMinusPanel(final int item) { - JPanel minusPanel = new JPanel(new BorderLayout()); - - final JButton remove = new JButton(); - setImage(remove, img64remove, "-"); - - remove.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - base.removeItem(item); - } - }); - - minusPanel.add(remove, BorderLayout.EAST); - - main.add(minusPanel); - main.revalidate(); - main.repaint(); - - return minusPanel; - } - - /** - * Create an empty graphical component to be used later by - * {@link ConfigItem#createField(int)}. - *

- * Note that {@link ConfigItem#reload(int)} will be called after it was - * created by {@link ConfigItem#createField(int)}. - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * - * @return the graphical component - */ - abstract protected JComponent createEmptyField(int item); - - /** - * Get the information from the {@link MetaInfo} in the subclass preferred - * format. - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * - * @return the information in the subclass preferred format - */ - abstract protected Object getFromInfo(int item); - - /** - * Set the value to the {@link MetaInfo}. - * - * @param value - * the value in the subclass preferred format - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - */ - abstract protected void setToInfo(Object value, int item); - - /** - * The value present in the given item's related field in the subclass - * preferred format. - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * - * @return the value present in the given item's related field in the - * subclass preferred format - */ - abstract protected Object getFromField(int item); - - /** - * Set the value (in the subclass preferred format) into the field. - * - * @param value - * the value in the subclass preferred format - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - */ - abstract protected void setToField(Object value, int item); - - /** - * Create a label which width is constrained in lock steps. - * - * @param nhgap - * negative horisontal gap in pixel to use for the label, i.e., - * the step lock sized labels will start smaller by that amount - * (the use case would be to align controls that start at a - * different horisontal position) - * - * @return the label - */ - protected JComponent label(int nhgap) { - final JLabel label = new JLabel(getInfo().getName()); - - Dimension ps = label.getPreferredSize(); - if (ps == null) { - ps = label.getSize(); - } - - ps.height = Math.max(ps.height, getMinimumHeight()); - - int w = ps.width; - int step = 150; - for (int i = 2 * step - nhgap; i < 10 * step; i += step) { - if (w < i) { - w = i; - break; - } - } - - final Runnable showInfo = new Runnable() { - @Override - public void run() { - StringBuilder builder = new StringBuilder(); - String text = (getInfo().getDescription().replace("\\n", "\n")) - .trim(); - for (String line : StringUtils.justifyText(text, 80, - Alignment.LEFT)) { - if (builder.length() > 0) { - builder.append("\n"); - } - builder.append(line); - } - text = builder.toString(); - JOptionPane.showMessageDialog(ConfigItem.this, text, getInfo() - .getName(), JOptionPane.INFORMATION_MESSAGE); - } - }; - - JLabel help = new JLabel(""); - help.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - setImage(help, img64info, "?"); - - help.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - showInfo.run(); - } - }); - - JPanel pane2 = new JPanel(new BorderLayout()); - pane2.add(help, BorderLayout.WEST); - pane2.add(new JLabel(" "), BorderLayout.CENTER); - - JPanel contentPane = new JPanel(new BorderLayout()); - contentPane.add(label, BorderLayout.WEST); - contentPane.add(pane2, BorderLayout.CENTER); - - ps.width = w + 30; // 30 for the (?) sign - contentPane.setSize(ps); - contentPane.setPreferredSize(ps); - - JPanel pane = new JPanel(new BorderLayout()); - pane.add(contentPane, BorderLayout.NORTH); - - return pane; - } - - /** - * Create a new {@link ConfigItem} for the given {@link MetaInfo}. - * - * @param - * the type of {@link Bundle} to edit - * - * @param info - * the {@link MetaInfo} - * @param nhgap - * negative horisontal gap in pixel to use for the label, i.e., - * the step lock sized labels will start smaller by that amount - * (the use case would be to align controls that start at a - * different horisontal position) - * - * @return the new {@link ConfigItem} - */ - static public > ConfigItem createItem( - MetaInfo info, int nhgap) { - - ConfigItem configItem; - switch (info.getFormat()) { - case BOOLEAN: - configItem = new ConfigItemBoolean(info); - break; - case COLOR: - configItem = new ConfigItemColor(info); - break; - case FILE: - configItem = new ConfigItemBrowse(info, false); - break; - case DIRECTORY: - configItem = new ConfigItemBrowse(info, true); - break; - case COMBO_LIST: - configItem = new ConfigItemCombobox(info, true); - break; - case FIXED_LIST: - configItem = new ConfigItemCombobox(info, false); - break; - case INT: - configItem = new ConfigItemInteger(info); - break; - case PASSWORD: - configItem = new ConfigItemPassword(info); - break; - case LOCALE: - configItem = new ConfigItemLocale(info); - break; - case STRING: - default: - configItem = new ConfigItemString(info); - break; - } - - configItem.init(nhgap); - return configItem; - } - - /** - * Set an image to the given {@link JButton}, with a fallback text if it - * fails. - * - * @param button - * the button to set - * @param image64 - * the image in BASE64 (should be PNG or similar) - * @param fallbackText - * text to use in case the image cannot be created - */ - static protected void setImage(JLabel button, String image64, - String fallbackText) { - try { - Image img = new Image(image64); - try { - BufferedImage bImg = ImageUtilsAwt.fromImage(img); - button.setIcon(new ImageIcon(bImg)); - } finally { - img.close(); - } - } catch (IOException e) { - // This is an hard-coded image, should not happen - button.setText(fallbackText); - } - } - - /** - * Set an image to the given {@link JButton}, with a fallback text if it - * fails. - * - * @param button - * the button to set - * @param image64 - * the image in BASE64 (should be PNG or similar) - * @param fallbackText - * text to use in case the image cannot be created - */ - static protected void setImage(JButton button, String image64, - String fallbackText) { - try { - Image img = new Image(image64); - try { - BufferedImage bImg = ImageUtilsAwt.fromImage(img); - button.setIcon(new ImageIcon(bImg)); - } finally { - img.close(); - } - } catch (IOException e) { - // This is an hard-coded image, should not happen - button.setText(fallbackText); - } - } - - static private int getMinimumHeight() { - if (minimumHeight < 0) { - minimumHeight = new JTextField("Test").getMinimumSize().height; - } - - return minimumHeight; - } -} diff --git a/src/be/nikiroo/utils/ui/ConfigItemBase.java b/src/be/nikiroo/utils/ui/ConfigItemBase.java deleted file mode 100644 index 21b5755..0000000 --- a/src/be/nikiroo/utils/ui/ConfigItemBase.java +++ /dev/null @@ -1,467 +0,0 @@ -package be.nikiroo.utils.ui; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import be.nikiroo.utils.resources.Bundle; -import be.nikiroo.utils.resources.MetaInfo; - -/** - * A graphical item that reflect a configuration option from the given - * {@link Bundle}. - *

- * This graphical item can be edited, and the result will be saved back into the - * linked {@link MetaInfo}; you still have to save the {@link MetaInfo} should - * you wish to, of course. - * - * @author niki - * - * @param - * the graphical base type to use (i.e., T or TWidget) - * @param - * the type of {@link Bundle} to edit - */ -public abstract class ConfigItemBase> { - /** The original value before current changes. */ - private Object orig; - private List origs = new ArrayList(); - private List dirtyBits; - - /** The fields (one for non-array, a list for arrays). */ - private T field; - private List fields = new ArrayList(); - - /** The fields to panel map to get the actual item added to 'main'. */ - private Map itemFields = new HashMap(); - - /** The {@link MetaInfo} linked to the field. */ - private MetaInfo info; - - /** The {@link MetaInfo} linked to the field. */ - public MetaInfo getInfo() { - return info; - } - - /** - * The number of fields, for arrays. - * - * @return - */ - public int getFieldsSize() { - return fields.size(); - } - - /** - * The number of fields to panel map to get the actual item added to 'main'. - */ - public int getItemFieldsSize() { - return itemFields.size(); - } - - /** - * Add a new item in an array-value {@link MetaInfo}. - * - * @param item - * the index of the new item - * @param panel - * a linked T, if we want to link it into the itemFields (can be - * NULL) -- that way, we can get it back later on - * {@link ConfigItemBase#removeItem(int)} - * - * @return the newly created graphical field - */ - public T addItem(final int item, T panel) { - if (panel != null) { - itemFields.put(item, panel); - } - return createField(item); - } - - /** - * The counter-part to {@link ConfigItemBase#addItem(int, Object)}, to - * remove a specific item of an array-values {@link MetaInfo}; all the - * remaining items will be shifted as required (so, always the last - * graphical object will be removed). - * - * @param item - * the index of the item to remove - * - * @return the linked graphical T to remove if any (always the latest - * graphical object if any) - */ - protected T removeItem(int item) { - int last = itemFields.size() - 1; - - for (int i = item; i <= last; i++) { - Object value = null; - if (i < last) { - value = getFromField(i + 1); - } - setToField(value, i); - setToInfo(value, i); - setDirtyItem(i); - } - - return itemFields.remove(last); - } - - /** - * Prepare a new {@link ConfigItemBase} instance, linked to the given - * {@link MetaInfo}. - * - * @param info - * the info - * @param autoDirtyHandling - * TRUE to automatically manage the setDirty/Save operations, - * FALSE if you want to do it yourself via - * {@link ConfigItemBase#setDirtyItem(int)} - */ - protected ConfigItemBase(MetaInfo info, boolean autoDirtyHandling) { - this.info = info; - if (!autoDirtyHandling) { - dirtyBits = new ArrayList(); - } - } - - /** - * Create an empty graphical component to be used later by - * {@link ConfigItemBase#createField(int)}. - *

- * Note that {@link ConfigItemBase#reload(int)} will be called after it was - * created by {@link ConfigItemBase#createField(int)}. - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * - * @return the graphical component - */ - abstract protected T createEmptyField(int item); - - /** - * Get the information from the {@link MetaInfo} in the subclass preferred - * format. - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * - * @return the information in the subclass preferred format - */ - abstract protected Object getFromInfo(int item); - - /** - * Set the value to the {@link MetaInfo}. - * - * @param value - * the value in the subclass preferred format - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - */ - abstract protected void setToInfo(Object value, int item); - - /** - * The value present in the given item's related field in the subclass - * preferred format. - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * - * @return the value present in the given item's related field in the - * subclass preferred format - */ - abstract protected Object getFromField(int item); - - /** - * Set the value (in the subclass preferred format) into the field. - * - * @param value - * the value in the subclass preferred format - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - */ - abstract protected void setToField(Object value, int item); - - /** - * Create a new field for the given graphical component at the given index - * (note that the component is usually created by - * {@link ConfigItemBase#createEmptyField(int)}). - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * @param field - * the graphical component - */ - private void setField(int item, T field) { - if (item < 0) { - this.field = field; - return; - } - - for (int i = fields.size(); i <= item; i++) { - fields.add(null); - } - - fields.set(item, field); - } - - /** - * Retrieve the associated graphical component that was created with - * {@link ConfigItemBase#createEmptyField(int)}. - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * - * @return the graphical component - */ - public T getField(int item) { - if (item < 0) { - return field; - } - - if (item < fields.size()) { - return fields.get(item); - } - - return null; - } - - /** - * The original value (before any changes to the {@link MetaInfo}) for this - * item. - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * - * @return the original value - */ - private Object getOrig(int item) { - if (item < 0) { - return orig; - } - - if (item < origs.size()) { - return origs.get(item); - } - - return null; - } - - /** - * The original value (before any changes to the {@link MetaInfo}) for this - * item. - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * @param value - * the new original value - */ - private void setOrig(Object value, int item) { - if (item < 0) { - orig = value; - } else { - while (item >= origs.size()) { - origs.add(null); - } - - origs.set(item, value); - } - } - - /** - * Manually specify that the given item is "dirty" and thus should be saved - * when asked. - *

- * Has no effect if the class is using automatic dirty handling (see - * {@link ConfigItemBase#ConfigItem(MetaInfo, boolean)}). - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - */ - public void setDirtyItem(int item) { - if (dirtyBits != null) { - dirtyBits.add(item); - } - } - - /** - * Check if the value changed since the last load/save into the linked - * {@link MetaInfo}. - *

- * Note that we consider NULL and an Empty {@link String} to be equals. - * - * @param value - * the value to test - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * - * @return TRUE if it has - */ - public boolean hasValueChanged(Object value, int item) { - // We consider "" and NULL to be equals - Object orig = getOrig(item); - if (orig == null) { - orig = ""; - } - return !orig.equals(value == null ? "" : value); - } - - /** - * Reload the values to what they currently are in the {@link MetaInfo}. - * - * @return for arrays, the list of graphical T objects we don't need any - * more (never NULL, but can be empty) - */ - public List reload() { - List removed = new ArrayList(); - if (info.isArray()) { - while (!itemFields.isEmpty()) { - removed.add(itemFields.remove(itemFields.size() - 1)); - } - for (int item = 0; item < info.getListSize(false); item++) { - reload(item); - } - } else { - reload(-1); - } - - return removed; - } - - /** - * Reload the values to what they currently are in the {@link MetaInfo}. - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - */ - private void reload(int item) { - if (item >= 0 && !itemFields.containsKey(item)) { - addItem(item, null); - } - - Object value = getFromInfo(item); - setToField(value, item); - setOrig(value == null ? "" : value, item); - } - - /** - * If the item has been modified, set the {@link MetaInfo} to dirty then - * modify it to, reflect the changes so it can be saved later. - *

- * This method does not call {@link MetaInfo#save(boolean)}. - */ - private void save() { - if (info.isArray()) { - boolean dirty = itemFields.size() != info.getListSize(false); - for (int item = 0; item < itemFields.size(); item++) { - if (getDirtyBit(item)) { - dirty = true; - } - } - - if (dirty) { - info.setDirty(); - info.setString(null, -1); - - for (int item = 0; item < itemFields.size(); item++) { - Object value = null; - if (getField(item) != null) { - value = getFromField(item); - if ("".equals(value)) { - value = null; - } - } - - setToInfo(value, item); - setOrig(value, item); - } - } - } else { - if (getDirtyBit(-1)) { - Object value = getFromField(-1); - - info.setDirty(); - setToInfo(value, -1); - setOrig(value, -1); - } - } - } - - /** - * Check if the item is dirty, and clear the dirty bit if set. - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * - * @return TRUE if it was dirty, FALSE if not - */ - private boolean getDirtyBit(int item) { - if (dirtyBits != null) { - return dirtyBits.remove((Integer) item); - } - - Object value = null; - if (getField(item) != null) { - value = getFromField(item); - } - - return hasValueChanged(value, item); - } - - /** - * Create a new field for the given item. - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * - * @return the newly created field - */ - public T createField(final int item) { - T field = createEmptyField(item); - setField(item, field); - reload(item); - - info.addReloadedListener(new Runnable() { - @Override - public void run() { - reload(); - } - }); - info.addSaveListener(new Runnable() { - @Override - public void run() { - save(); - } - }); - - return field; - } -} diff --git a/src/be/nikiroo/utils/ui/ConfigItemBoolean.java b/src/be/nikiroo/utils/ui/ConfigItemBoolean.java deleted file mode 100644 index de89f68..0000000 --- a/src/be/nikiroo/utils/ui/ConfigItemBoolean.java +++ /dev/null @@ -1,67 +0,0 @@ -package be.nikiroo.utils.ui; - -import javax.swing.JCheckBox; -import javax.swing.JComponent; - -import be.nikiroo.utils.resources.MetaInfo; - -class ConfigItemBoolean> extends ConfigItem { - private static final long serialVersionUID = 1L; - - /** - * Create a new {@link ConfigItemBoolean} for the given {@link MetaInfo}. - * - * @param info - * the {@link MetaInfo} - */ - public ConfigItemBoolean(MetaInfo info) { - super(info, true); - } - - @Override - protected Object getFromField(int item) { - JCheckBox field = (JCheckBox) getField(item); - if (field != null) { - return field.isSelected(); - } - - return null; - } - - @Override - protected Object getFromInfo(int item) { - return getInfo().getBoolean(item, true); - } - - @Override - protected void setToField(Object value, int item) { - JCheckBox field = (JCheckBox) getField(item); - if (field != null) { - // Should not happen if config enum is correct - // (but this is not enforced) - if (value == null) { - value = false; - } - - field.setSelected((Boolean) value); - } - } - - @Override - protected void setToInfo(Object value, int item) { - getInfo().setBoolean((Boolean) value, item); - } - - @Override - protected JComponent createEmptyField(int item) { - // Should not happen! - if (getFromInfo(item) == null) { - System.err - .println("No default value given for BOOLEAN parameter \"" - + getInfo().getName() - + "\", we consider it is FALSE"); - } - - return new JCheckBox(); - } -} diff --git a/src/be/nikiroo/utils/ui/ConfigItemBrowse.java b/src/be/nikiroo/utils/ui/ConfigItemBrowse.java deleted file mode 100644 index 9a54e52..0000000 --- a/src/be/nikiroo/utils/ui/ConfigItemBrowse.java +++ /dev/null @@ -1,116 +0,0 @@ -package be.nikiroo.utils.ui; - -import java.awt.BorderLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.io.File; -import java.util.HashMap; -import java.util.Map; - -import javax.swing.JButton; -import javax.swing.JComponent; -import javax.swing.JFileChooser; -import javax.swing.JPanel; -import javax.swing.JTextField; - -import be.nikiroo.utils.resources.MetaInfo; - -class ConfigItemBrowse> extends ConfigItem { - private static final long serialVersionUID = 1L; - - private boolean dir; - private Map fields = new HashMap(); - - /** - * Create a new {@link ConfigItemBrowse} for the given {@link MetaInfo}. - * - * @param info - * the {@link MetaInfo} - * @param dir - * TRUE for directory browsing, FALSE for file browsing - */ - public ConfigItemBrowse(MetaInfo info, boolean dir) { - super(info, false); - this.dir = dir; - } - - @Override - protected Object getFromField(int item) { - JTextField field = fields.get(getField(item)); - if (field != null) { - return new File(field.getText()); - } - - return null; - } - - @Override - protected Object getFromInfo(int item) { - String path = getInfo().getString(item, false); - if (path != null && !path.isEmpty()) { - return new File(path); - } - - return null; - } - - @Override - protected void setToField(Object value, int item) { - JTextField field = fields.get(getField(item)); - if (field != null) { - field.setText(value == null ? "" : ((File) value).getPath()); - } - } - - @Override - protected void setToInfo(Object value, int item) { - getInfo().setString(((File) value).getPath(), item); - } - - @Override - protected JComponent createEmptyField(final int item) { - final JPanel pane = new JPanel(new BorderLayout()); - final JTextField field = new JTextField(); - field.addKeyListener(new KeyAdapter() { - @Override - public void keyTyped(KeyEvent e) { - File file = null; - if (!field.getText().isEmpty()) { - file = new File(field.getText()); - } - - if (hasValueChanged(file, item)) { - setDirtyItem(item); - } - } - }); - - final JButton browseButton = new JButton("..."); - browseButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - JFileChooser chooser = new JFileChooser(); - chooser.setCurrentDirectory((File) getFromInfo(item)); - chooser.setFileSelectionMode(dir ? JFileChooser.DIRECTORIES_ONLY - : JFileChooser.FILES_ONLY); - if (chooser.showOpenDialog(ConfigItemBrowse.this) == JFileChooser.APPROVE_OPTION) { - File file = chooser.getSelectedFile(); - if (file != null) { - setToField(file, item); - if (hasValueChanged(file, item)) { - setDirtyItem(item); - } - } - } - } - }); - - pane.add(browseButton, BorderLayout.WEST); - pane.add(field, BorderLayout.CENTER); - - fields.put(pane, field); - return pane; - } -} diff --git a/src/be/nikiroo/utils/ui/ConfigItemColor.java b/src/be/nikiroo/utils/ui/ConfigItemColor.java deleted file mode 100644 index 951ff45..0000000 --- a/src/be/nikiroo/utils/ui/ConfigItemColor.java +++ /dev/null @@ -1,169 +0,0 @@ -package be.nikiroo.utils.ui; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.awt.image.BufferedImage; -import java.util.HashMap; -import java.util.Map; - -import javax.swing.Icon; -import javax.swing.ImageIcon; -import javax.swing.JButton; -import javax.swing.JColorChooser; -import javax.swing.JComponent; -import javax.swing.JPanel; -import javax.swing.JTextField; - -import be.nikiroo.utils.resources.MetaInfo; - -class ConfigItemColor> extends ConfigItem { - private static final long serialVersionUID = 1L; - - private Map fields = new HashMap(); - private Map panels = new HashMap(); - - /** - * Create a new {@link ConfigItemColor} for the given {@link MetaInfo}. - * - * @param info - * the {@link MetaInfo} - */ - public ConfigItemColor(MetaInfo info) { - super(info, true); - } - - @Override - protected Object getFromField(int item) { - JTextField field = fields.get(getField(item)); - if (field != null) { - return field.getText(); - } - - return null; - } - - @Override - protected Object getFromInfo(int item) { - return getInfo().getString(item, true); - } - - @Override - protected void setToField(Object value, int item) { - JTextField field = fields.get(getField(item)); - if (field != null) { - field.setText(value == null ? "" : value.toString()); - } - - JButton colorWheel = panels.get(getField(item)); - if (colorWheel != null) { - colorWheel.setIcon(getIcon(17, getFromInfoColor(item))); - } - } - - @Override - protected void setToInfo(Object value, int item) { - getInfo().setString((String) value, item); - } - - /** - * Get the colour currently present in the linked info for the given item. - * - * @param item - * the item number to get for an array of values, or -1 to get - * the whole value (has no effect if {@link MetaInfo#isArray()} - * is FALSE) - * - * @return a colour - */ - private int getFromInfoColor(int item) { - Integer color = getInfo().getColor(item, true); - if (color == null) { - return new Color(255, 255, 255, 255).getRGB(); - } - - return color; - } - - @Override - protected JComponent createEmptyField(final int item) { - final JPanel pane = new JPanel(new BorderLayout()); - final JTextField field = new JTextField(); - - final JButton colorWheel = new JButton(); - colorWheel.setIcon(getIcon(17, getFromInfoColor(item))); - colorWheel.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - int icol = getFromInfoColor(item); - Color initialColor = new Color(icol, true); - Color newColor = JColorChooser.showDialog(ConfigItemColor.this, - getInfo().getName(), initialColor); - if (newColor != null) { - getInfo().setColor(newColor.getRGB(), item); - field.setText(getInfo().getString(item, false)); - colorWheel.setIcon(getIcon(17, - getInfo().getColor(item, true))); - } - } - }); - - field.addKeyListener(new KeyAdapter() { - @Override - public void keyTyped(KeyEvent e) { - getInfo().setString(field.getText() + e.getKeyChar(), item); - int color = getFromInfoColor(item); - colorWheel.setIcon(getIcon(17, color)); - } - }); - - pane.add(colorWheel, BorderLayout.WEST); - pane.add(field, BorderLayout.CENTER); - - fields.put(pane, field); - panels.put(pane, colorWheel); - return pane; - } - - /** - * Return an {@link Icon} to use as a colour badge for the colour field - * controls. - * - * @param size - * the size of the badge - * @param color - * the colour of the badge, which can be NULL (will return - * transparent white) - * - * @return the badge - */ - static private Icon getIcon(int size, Integer color) { - // Allow null values - if (color == null) { - color = new Color(255, 255, 255, 255).getRGB(); - } - - Color c = new Color(color, true); - int avg = (c.getRed() + c.getGreen() + c.getBlue()) / 3; - Color border = (avg >= 128 ? Color.BLACK : Color.WHITE); - - BufferedImage img = new BufferedImage(size, size, - BufferedImage.TYPE_4BYTE_ABGR); - - Graphics2D g = img.createGraphics(); - try { - g.setColor(c); - g.fillRect(0, 0, img.getWidth(), img.getHeight()); - g.setColor(border); - g.drawRect(0, 0, img.getWidth() - 1, img.getHeight() - 1); - } finally { - g.dispose(); - } - - return new ImageIcon(img); - } -} diff --git a/src/be/nikiroo/utils/ui/ConfigItemCombobox.java b/src/be/nikiroo/utils/ui/ConfigItemCombobox.java deleted file mode 100644 index 07a6115..0000000 --- a/src/be/nikiroo/utils/ui/ConfigItemCombobox.java +++ /dev/null @@ -1,68 +0,0 @@ -package be.nikiroo.utils.ui; - -import javax.swing.JComboBox; -import javax.swing.JComponent; - -import be.nikiroo.utils.resources.MetaInfo; - -class ConfigItemCombobox> extends ConfigItem { - private static final long serialVersionUID = 1L; - - private boolean editable; - private String[] allowedValues; - - /** - * Create a new {@link ConfigItemCombobox} for the given {@link MetaInfo}. - * - * @param info - * the {@link MetaInfo} - * @param editable - * allows the user to type in another value not in the list - */ - public ConfigItemCombobox(MetaInfo info, boolean editable) { - super(info, true); - this.editable = editable; - this.allowedValues = info.getAllowedValues(); - } - - @Override - protected Object getFromField(int item) { - // rawtypes for Java 1.6 (and 1.7 ?) support - @SuppressWarnings("rawtypes") - JComboBox field = (JComboBox) getField(item); - if (field != null) { - return field.getSelectedItem(); - } - - return null; - } - - @Override - protected Object getFromInfo(int item) { - return getInfo().getString(item, false); - } - - @Override - protected void setToField(Object value, int item) { - // rawtypes for Java 1.6 (and 1.7 ?) support - @SuppressWarnings("rawtypes") - JComboBox field = (JComboBox) getField(item); - if (field != null) { - field.setSelectedItem(value); - } - } - - @Override - protected void setToInfo(Object value, int item) { - getInfo().setString((String) value, item); - } - - // rawtypes for Java 1.6 (and 1.7 ?) support - @SuppressWarnings({ "unchecked", "rawtypes" }) - @Override - protected JComponent createEmptyField(int item) { - JComboBox field = new JComboBox(allowedValues); - field.setEditable(editable); - return field; - } -} diff --git a/src/be/nikiroo/utils/ui/ConfigItemInteger.java b/src/be/nikiroo/utils/ui/ConfigItemInteger.java deleted file mode 100644 index 10c5d9d..0000000 --- a/src/be/nikiroo/utils/ui/ConfigItemInteger.java +++ /dev/null @@ -1,53 +0,0 @@ -package be.nikiroo.utils.ui; - -import javax.swing.JComponent; -import javax.swing.JSpinner; - -import be.nikiroo.utils.resources.MetaInfo; - -class ConfigItemInteger> extends ConfigItem { - private static final long serialVersionUID = 1L; - - /** - * Create a new {@link ConfigItemInteger} for the given {@link MetaInfo}. - * - * @param info - * the {@link MetaInfo} - */ - public ConfigItemInteger(MetaInfo info) { - super(info, true); - } - - @Override - protected Object getFromField(int item) { - JSpinner field = (JSpinner) getField(item); - if (field != null) { - return field.getValue(); - } - - return null; - } - - @Override - protected Object getFromInfo(int item) { - return getInfo().getInteger(item, true); - } - - @Override - protected void setToField(Object value, int item) { - JSpinner field = (JSpinner) getField(item); - if (field != null) { - field.setValue(value == null ? 0 : (Integer) value); - } - } - - @Override - protected void setToInfo(Object value, int item) { - getInfo().setInteger((Integer) value, item); - } - - @Override - protected JComponent createEmptyField(int item) { - return new JSpinner(); - } -} diff --git a/src/be/nikiroo/utils/ui/ConfigItemLocale.java b/src/be/nikiroo/utils/ui/ConfigItemLocale.java deleted file mode 100644 index eef8da0..0000000 --- a/src/be/nikiroo/utils/ui/ConfigItemLocale.java +++ /dev/null @@ -1,62 +0,0 @@ -package be.nikiroo.utils.ui; - -import java.awt.Component; -import java.util.Locale; - -import javax.swing.DefaultListCellRenderer; -import javax.swing.JComboBox; -import javax.swing.JComponent; -import javax.swing.JList; - -import be.nikiroo.utils.resources.MetaInfo; - -class ConfigItemLocale> extends ConfigItemCombobox { - private static final long serialVersionUID = 1L; - - /** - * Create a new {@link ConfigItemLocale} for the given {@link MetaInfo}. - * - * @param info - * the {@link MetaInfo} - */ - public ConfigItemLocale(MetaInfo info) { - super(info, true); - } - - // rawtypes for Java 1.6 (and 1.7 ?) support - @SuppressWarnings({ "unchecked", "rawtypes" }) - @Override - protected JComponent createEmptyField(int item) { - JComboBox field = (JComboBox) super.createEmptyField(item); - field.setRenderer(new DefaultListCellRenderer() { - private static final long serialVersionUID = 1L; - - @Override - public Component getListCellRendererComponent(JList list, - Object value, int index, boolean isSelected, - boolean cellHasFocus) { - - String svalue = value == null ? "" : value.toString(); - String[] tab = svalue.split("-"); - Locale locale = null; - if (tab.length == 1) { - locale = new Locale(tab[0]); - } else if (tab.length == 2) { - locale = new Locale(tab[0], tab[1]); - } else if (tab.length == 3) { - locale = new Locale(tab[0], tab[1], tab[2]); - } - - String displayValue = svalue; - if (locale != null) { - displayValue = locale.getDisplayName(); - } - - return super.getListCellRendererComponent(list, displayValue, - index, isSelected, cellHasFocus); - } - }); - - return field; - } -} diff --git a/src/be/nikiroo/utils/ui/ConfigItemPassword.java b/src/be/nikiroo/utils/ui/ConfigItemPassword.java deleted file mode 100644 index e8ad2f2..0000000 --- a/src/be/nikiroo/utils/ui/ConfigItemPassword.java +++ /dev/null @@ -1,109 +0,0 @@ -package be.nikiroo.utils.ui; - -import java.awt.BorderLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.util.HashMap; -import java.util.Map; - -import javax.swing.JButton; -import javax.swing.JComponent; -import javax.swing.JPanel; -import javax.swing.JPasswordField; - -import be.nikiroo.utils.resources.MetaInfo; - -class ConfigItemPassword> extends ConfigItem { - private static final long serialVersionUID = 1L; - /** A small 16x16 pass-protecet icon in PNG, base64 encoded. */ - private static String img64passProtected = // - "" - + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAAnNCSVQICFXsRgQAAAD5SURBVCjP" - + "ndG9LoNxGIbxHxJTG9U0IsJAdCSNqZEa9BR87BaHYfW5ESYkmjQh4giwIU00MWFwAPWRSmpgaf6G" - + "6ts36eZ+xuu+lvuhlTGjOFHAsXldWVDRa82WhE9pZFxrtmBeUY87+yqCH3UzMh4E1VYhp2ZVVfi7" - + "C0PuBc9G2v6KoOlIQUoyhovyLb+uZla/TbsRHnOgJkfSi4YpbDiXjuwJDS+SlASLYC9mw5KgxJlg" - + "CWJ4OyqckvKkIWswwmXrmPbl0QBkHcbsHRv6Fbz6MNnesWMnpMw51vRmphuXo7FujHf+cCt4NGza" - + "lbp3l5b1xR/1rWrYf/MLWpplWwswQpMAAAAASUVORK5CYII="; - - /** A small 16x16 pass-unprotecet icon in PNG, base64 encoded. */ - private static String img64passUnprotected = // - "" - + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAAmJLR0QAAKqNIzIAAAAJcEhZcwAA" - + "CxMAAAsTAQCanBgAAAAHdElNRQfjBR8MIilwhCwdAAABK0lEQVQoz5XQv0uUAQCH8c/7qod4nect" - + "gop3BIKDFBIiRyiKtATmcEiBDW7+Ae5ODt5gW0SLigouKTg6SJvkjw4Co8mcNeWgc+o839dBBXPz" - + "+Y7PM33r3NCpWcWKM1lfHapJq0B4G/TbEDoyZlyHQxuGtdw6eSMC33yyJxa79MW+wIj8TdDrxJSS" - + "+N5KppQNEchrkrMosmzRT0/0eGdSaFrob6DXloSqgu9mNWlUNqPPpmYNJkg5UvEMResystYVpbwW" - + "qWpjVWwcfNQqLS1rAXwQOw4N4SWoqZeUVFMGuzgg65/IqIw5a3LarZnDcxd+ScMrkcikhB8+m1eU" - + "MODUua67q967EttR0KHFoCVX/nhxp1N4o/rfUTueekC332KRM9veqnuoAwQyHs81DiddylUvrecA" - + "AAAASUVORK5CYII="; - - private Map fields = new HashMap(); - - /** - * Create a new {@link ConfigItemPassword} for the given {@link MetaInfo}. - * - * @param info - * the {@link MetaInfo} - */ - public ConfigItemPassword(MetaInfo info) { - super(info, true); - } - - @Override - protected Object getFromField(int item) { - JPasswordField field = fields.get(getField(item)); - if (field != null) { - return new String(field.getPassword()); - } - - return null; - } - - @Override - protected Object getFromInfo(int item) { - return getInfo().getString(item, false); - } - - @Override - protected void setToField(Object value, int item) { - JPasswordField field = fields.get(getField(item)); - if (field != null) { - field.setText(value == null ? "" : value.toString()); - } - } - - @Override - protected void setToInfo(Object value, int item) { - getInfo().setString((String) value, item); - } - - @Override - protected JComponent createEmptyField(int item) { - JPanel pane = new JPanel(new BorderLayout()); - final JPasswordField field = new JPasswordField(); - field.setEchoChar('*'); - - final JButton show = new JButton(); - final Boolean[] visible = new Boolean[] { false }; - setImage(show, img64passProtected, "/"); - show.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - visible[0] = !visible[0]; - if (visible[0]) { - field.setEchoChar((char) 0); - setImage(show, img64passUnprotected, "o"); - } else { - field.setEchoChar('*'); - setImage(show, img64passProtected, "/"); - } - } - }); - - pane.add(field, BorderLayout.CENTER); - pane.add(show, BorderLayout.EAST); - - fields.put(pane, field); - return pane; - } -} diff --git a/src/be/nikiroo/utils/ui/ConfigItemString.java b/src/be/nikiroo/utils/ui/ConfigItemString.java deleted file mode 100644 index 46333c0..0000000 --- a/src/be/nikiroo/utils/ui/ConfigItemString.java +++ /dev/null @@ -1,53 +0,0 @@ -package be.nikiroo.utils.ui; - -import javax.swing.JComponent; -import javax.swing.JTextField; - -import be.nikiroo.utils.resources.MetaInfo; - -class ConfigItemString> extends ConfigItem { - private static final long serialVersionUID = 1L; - - /** - * Create a new {@link ConfigItemString} for the given {@link MetaInfo}. - * - * @param info - * the {@link MetaInfo} - */ - public ConfigItemString(MetaInfo info) { - super(info, true); - } - - @Override - protected Object getFromField(int item) { - JTextField field = (JTextField) getField(item); - if (field != null) { - return field.getText(); - } - - return null; - } - - @Override - protected Object getFromInfo(int item) { - return getInfo().getString(item, false); - } - - @Override - protected void setToField(Object value, int item) { - JTextField field = (JTextField) getField(item); - if (field != null) { - field.setText(value == null ? "" : value.toString()); - } - } - - @Override - protected void setToInfo(Object value, int item) { - getInfo().setString((String) value, item); - } - - @Override - protected JComponent createEmptyField(int item) { - return new JTextField(); - } -} diff --git a/src/be/nikiroo/utils/ui/DataNode.java b/src/be/nikiroo/utils/ui/DataNode.java deleted file mode 100644 index b4dbe7b..0000000 --- a/src/be/nikiroo/utils/ui/DataNode.java +++ /dev/null @@ -1,104 +0,0 @@ -package be.nikiroo.utils.ui; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import javax.swing.Icon; - -public class DataNode { - private DataNode parent; - private List> children; - private T userData; - - public DataNode(List> children, T userData) { - if (children == null) { - children = new ArrayList>(); - } - - this.children = children; - this.userData = userData; - - for (DataNode child : children) { - child.parent = this; - } - } - - public DataNode getRoot() { - DataNode root = this; - while (root.parent != null) { - root = root.parent; - } - - return root; - } - - public DataNode getParent() { - return parent; - } - - public List> getChildren() { - return children; - } - - public int size() { - return children.size(); - } - - public boolean isRoot() { - return this == getRoot(); - } - - public boolean isSiblingOf(DataNode node) { - if (this == node) { - return true; - } - - return node != null && parent != null && parent.children.contains(node); - } - - public boolean isParentOf(DataNode node) { - if (node == null || node.parent == null) - return false; - - if (this == node.parent) - return true; - - return isParentOf(node.parent); - } - - public boolean isChildOf(DataNode node) { - if (node == null || node.size() == 0) - return false; - - return node.isParentOf(this); - } - - public T getUserData() { - return userData; - } - - /** - * The total number of nodes present in this {@link DataNode} (including - * itself and descendants). - * - * @return the number - */ - public int count() { - int s = 1; - for (DataNode child : children) { - s += child.count(); - } - - return s; - } - - @Override - public String toString() { - if (userData == null) { - return ""; - } - - return userData.toString(); - } -} \ No newline at end of file diff --git a/src/be/nikiroo/utils/ui/DataTree.java b/src/be/nikiroo/utils/ui/DataTree.java deleted file mode 100644 index 6b3657d..0000000 --- a/src/be/nikiroo/utils/ui/DataTree.java +++ /dev/null @@ -1,68 +0,0 @@ -package be.nikiroo.utils.ui; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.MutableTreeNode; - -public abstract class DataTree { - protected DataNode data; - - public DataNode loadData() throws IOException { - return this.data = extractData(); - } - - public DataNode getRoot() { - return getRoot(null); - } - - public DataNode getRoot(String filter) { - return filterNode(data, filter); - } - - protected abstract DataNode extractData() throws IOException; - - // filter cannot be null nor empty - protected abstract boolean checkFilter(String filter, E userData); - - protected boolean checkFilter(DataNode node, String filter) { - if (filter == null || filter.isEmpty()) { - return true; - } - - if (checkFilter(filter, node.getUserData())) - return true; - - for (DataNode child : node.getChildren()) { - if (checkFilter(child, filter)) - return true; - } - - return false; - } - - protected void sort(List values) { - Collections.sort(values, new Comparator() { - @Override - public int compare(String o1, String o2) { - return ("" + o1).compareToIgnoreCase("" + o2); - } - }); - } - - // note: we always send TAHT node, but filter children - private DataNode filterNode(DataNode source, String filter) { - List> children = new ArrayList>(); - for (DataNode child : source.getChildren()) { - if (checkFilter(child, filter)) { - children.add(filterNode(child, filter)); - } - } - - return new DataNode(children, source.getUserData()); - } -} diff --git a/src/be/nikiroo/utils/ui/DelayWorker.java b/src/be/nikiroo/utils/ui/DelayWorker.java deleted file mode 100644 index 2a16c98..0000000 --- a/src/be/nikiroo/utils/ui/DelayWorker.java +++ /dev/null @@ -1,220 +0,0 @@ -package be.nikiroo.utils.ui; - -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; -import java.util.TreeSet; - -import javax.swing.SwingWorker; - -/** - * This class helps you delay some graphical actions and execute the most recent - * ones when under contention. - *

- * How does it work? - *

    - *
  • it takes an ID and an associated {@link SwingWorker} and will call - * {@link SwingWorker#execute()} after a small delay (see - * {@link DelayWorker#DelayWorker(int)})
  • - *
  • if a second call to {@link DelayWorker#delay(String, SwingWorker)} comes - * with the same ID before the first one is done, it will be put on a waiting - * queue
  • - *
  • if a third call still with the same ID comes, its associated worker will - * replace the one in the queue (only one worker per ID in the queue, - * always the latest one)
  • - *
  • when the first worker is done, it will check the waiting queue and - * execute that latest worker if any
  • - *
- * - * @author niki - * - */ -@SuppressWarnings("rawtypes") -public class DelayWorker { - private Map lazyEnCours; - private Object lazyEnCoursLock; - - private TreeSet wip; - - private Object waiter; - - private boolean cont; - private boolean paused; - private Thread loop; - - /** - * Create a new {@link DelayWorker} with the given delay (in milliseconds) - * before each drain of the queue. - * - * @param delayMs - * the delay in milliseconds (can be 0, cannot be negative) - */ - public DelayWorker(final int delayMs) { - if (delayMs < 0) { - throw new IllegalArgumentException( - "A waiting delay cannot be negative"); - } - - lazyEnCours = new HashMap(); - lazyEnCoursLock = new Object(); - wip = new TreeSet(); - waiter = new Object(); - cont = true; - paused = false; - - loop = new Thread(new Runnable() { - @Override - public void run() { - while (cont) { - try { - Thread.sleep(delayMs); - } catch (InterruptedException e) { - } - - Map workers = new HashMap(); - synchronized (lazyEnCoursLock) { - for (String key : new ArrayList( - lazyEnCours.keySet())) { - if (!wip.contains(key)) { - workers.put(key, lazyEnCours.remove(key)); - } - } - } - - for (final String key : workers.keySet()) { - SwingWorker worker = workers.get(key); - - synchronized (lazyEnCoursLock) { - wip.add(key); - } - - worker.addPropertyChangeListener( - new PropertyChangeListener() { - @Override - public void propertyChange( - PropertyChangeEvent evt) { - synchronized (lazyEnCoursLock) { - wip.remove(key); - } - wakeup(); - } - }); - - // Start it, at last - worker.execute(); - } - - synchronized (waiter) { - do { - try { - if (cont) - waiter.wait(); - } catch (InterruptedException e) { - } - } while (cont && paused); - } - } - } - }); - - loop.setDaemon(true); - loop.setName("Loop for DelayWorker"); - } - - /** - * Start the internal loop that will drain the processing queue. MUST - * NOT be started twice (but see {@link DelayWorker#pause()} and - * {@link DelayWorker#resume()} instead). - */ - public void start() { - loop.start(); - } - - /** - * Pause the system until {@link DelayWorker#resume()} is called -- note - * that it will still continue on the processes currently scheduled to run, - * but will pause after that. - *

- * Can be called even if already paused, will just do nothing in that - * context. - */ - public void pause() { - paused = true; - } - - /** - * Check if the {@link DelayWorker} is currently paused. - * - * @return TRUE if it is - */ - public boolean isPaused() { - return paused; - } - - /** - * Resume the system after a pause. - *

- * Can be called even if already running, will just do nothing in that - * context. - */ - public void resume() { - synchronized (waiter) { - paused = false; - wakeup(); - } - } - - /** - * Stop the system. - *

- * Note: this is final, you MUST NOT call {@link DelayWorker#start()} - * a second time (but see {@link DelayWorker#pause()} and - * {@link DelayWorker#resume()} instead). - */ - public void stop() { - synchronized (waiter) { - cont = false; - wakeup(); - } - } - - /** - * Clear all the processes that were put on the queue but not yet scheduled - * to be executed -- note that it will still continue on the processes - * currently scheduled to run. - */ - public void clear() { - synchronized (lazyEnCoursLock) { - lazyEnCours.clear(); - wip.clear(); - } - } - - /** - * Put a new process in the delay queue. - * - * @param id - * the ID of this process (if you want to skip workers when they - * are superseded by a new one, you need to use the same ID key) - * @param worker - * the process to delay - */ - public void delay(String id, SwingWorker worker) { - synchronized (lazyEnCoursLock) { - lazyEnCours.put(id, worker); - } - - wakeup(); - } - - /** - * Wake up the loop thread. - */ - private void wakeup() { - synchronized (waiter) { - waiter.notifyAll(); - } - } -} diff --git a/src/be/nikiroo/utils/ui/ImageTextAwt.java b/src/be/nikiroo/utils/ui/ImageTextAwt.java deleted file mode 100644 index 4c0c824..0000000 --- a/src/be/nikiroo/utils/ui/ImageTextAwt.java +++ /dev/null @@ -1,512 +0,0 @@ -package be.nikiroo.utils.ui; - -import java.awt.Color; -import java.awt.Dimension; -import java.awt.Graphics; -import java.awt.Image; -import java.awt.image.BufferedImage; -import java.awt.image.ImageObserver; - -/** - * This class converts an {@link Image} into a textual representation that can - * be displayed to the user in a TUI. - * - * @author niki - */ -public class ImageTextAwt { - private Image image; - private Dimension size; - private String text; - private boolean ready; - private Mode mode; - private boolean invert; - - /** - * The rendering modes supported by this {@link ImageTextAwt} to convert - * {@link Image}s into text. - * - * @author niki - * - */ - public enum Mode { - /** - * Use 5 different "colours" which are actually Unicode - * {@link Character}s representing - *

    - *
  • space (blank)
  • - *
  • low shade (░)
  • - *
  • medium shade (▒)
  • - *
  • high shade (▓)
  • - *
  • full block (█)
  • - *
- */ - DITHERING, - /** - * Use "block" Unicode {@link Character}s up to quarter blocks, thus in - * effect doubling the resolution both in vertical and horizontal space. - * Note that since 2 {@link Character}s next to each other are square, - * we will use 4 blocks per 2 blocks for w/h resolution. - */ - DOUBLE_RESOLUTION, - /** - * Use {@link Character}s from both {@link Mode#DOUBLE_RESOLUTION} and - * {@link Mode#DITHERING}. - */ - DOUBLE_DITHERING, - /** - * Only use ASCII {@link Character}s. - */ - ASCII, - } - - /** - * Create a new {@link ImageTextAwt} with the given parameters. Defaults to - * {@link Mode#DOUBLE_DITHERING} and no colour inversion. - * - * @param image - * the source {@link Image} - * @param size - * the final text size to target - */ - public ImageTextAwt(Image image, Dimension size) { - this(image, size, Mode.DOUBLE_DITHERING, false); - } - - /** - * Create a new {@link ImageTextAwt} with the given parameters. - * - * @param image - * the source {@link Image} - * @param size - * the final text size to target - * @param mode - * the mode of conversion - * @param invert - * TRUE to invert colours rendering - */ - public ImageTextAwt(Image image, Dimension size, Mode mode, boolean invert) { - setImage(image); - setSize(size); - setMode(mode); - setColorInvert(invert); - } - - /** - * Change the source {@link Image}. - * - * @param image - * the new {@link Image} - */ - public void setImage(Image image) { - this.text = null; - this.ready = false; - this.image = image; - } - - /** - * Change the target size of this {@link ImageTextAwt}. - * - * @param size - * the new size - */ - public void setSize(Dimension size) { - this.text = null; - this.ready = false; - this.size = size; - } - - /** - * Change the image-to-text mode. - * - * @param mode - * the new {@link Mode} - */ - public void setMode(Mode mode) { - this.mode = mode; - this.text = null; - this.ready = false; - } - - /** - * Set the colour-invert mode. - * - * @param invert - * TRUE to inverse the colours - */ - public void setColorInvert(boolean invert) { - this.invert = invert; - this.text = null; - this.ready = false; - } - - /** - * Check if the colours are inverted. - * - * @return TRUE if the colours are inverted - */ - public boolean isColorInvert() { - return invert; - } - - /** - * Return the textual representation of the included {@link Image}. - * - * @return the {@link String} representation - */ - public String getText() { - if (text == null) { - if (image == null || size == null || size.width == 0 - || size.height == 0) { - return ""; - } - - int mult = 1; - if (mode == Mode.DOUBLE_RESOLUTION || mode == Mode.DOUBLE_DITHERING) { - mult = 2; - } - - Dimension srcSize = getSize(image); - srcSize = new Dimension(srcSize.width * 2, srcSize.height); - int x = 0; - int y = 0; - - int w = size.width * mult; - int h = size.height * mult; - - // Default = original ratio or original size if none - if (w < 0 || h < 0) { - if (w < 0 && h < 0) { - w = srcSize.width * mult; - h = srcSize.height * mult; - } else { - double ratioSrc = (double) srcSize.width - / (double) srcSize.height; - if (w < 0) { - w = (int) Math.round(h * ratioSrc); - } else { - h = (int) Math.round(w / ratioSrc); - } - } - } - - // Fail safe: we consider this to be too much - if (w > 1000 || h > 1000) { - return "[IMAGE TOO BIG]"; - } - - BufferedImage buff = new BufferedImage(w, h, - BufferedImage.TYPE_INT_ARGB); - - Graphics gfx = buff.getGraphics(); - - double ratioAsked = (double) (w) / (double) (h); - double ratioSrc = (double) srcSize.height / (double) srcSize.width; - double ratio = ratioAsked * ratioSrc; - if (srcSize.width < srcSize.height) { - h = (int) Math.round(ratio * h); - y = (buff.getHeight() - h) / 2; - } else { - w = (int) Math.round(w / ratio); - x = (buff.getWidth() - w) / 2; - } - - if (gfx.drawImage(image, x, y, w, h, new ImageObserver() { - @Override - public boolean imageUpdate(Image img, int infoflags, int x, - int y, int width, int height) { - ImageTextAwt.this.ready = true; - return true; - } - })) { - ready = true; - } - - while (!ready) { - try { - Thread.sleep(100); - } catch (InterruptedException e) { - } - } - - gfx.dispose(); - - StringBuilder builder = new StringBuilder(); - - for (int row = 0; row + (mult - 1) < buff.getHeight(); row += mult) { - if (row > 0) { - builder.append('\n'); - } - - for (int col = 0; col + (mult - 1) < buff.getWidth(); col += mult) { - if (mult == 1) { - char car = ' '; - float brightness = getBrightness(buff.getRGB(col, row)); - if (mode == Mode.DITHERING) - car = getDitheringChar(brightness, " ░▒▓█"); - if (mode == Mode.ASCII) - car = getDitheringChar(brightness, " .-+=o8#"); - - builder.append(car); - } else if (mult == 2) { - builder.append(getBlockChar( // - buff.getRGB(col, row),// - buff.getRGB(col + 1, row),// - buff.getRGB(col, row + 1),// - buff.getRGB(col + 1, row + 1),// - mode == Mode.DOUBLE_DITHERING// - )); - } - } - } - - text = builder.toString(); - } - - return text; - } - - @Override - public String toString() { - return getText(); - } - - /** - * Return the size of the given {@link Image}. - * - * @param img - * the image to measure - * - * @return the size - */ - static private Dimension getSize(Image img) { - Dimension size = null; - while (size == null) { - int w = img.getWidth(null); - int h = img.getHeight(null); - if (w > -1 && h > -1) { - size = new Dimension(w, h); - } else { - try { - Thread.sleep(100); - } catch (InterruptedException e) { - } - } - } - - return size; - } - - /** - * Return the {@link Character} corresponding to the given brightness level - * from the evenly-separated given {@link Character}s. - * - * @param brightness - * the brightness level - * @param cars - * the {@link Character}s to choose from, from less bright to - * most bright; MUST contain at least one - * {@link Character} - * - * @return the {@link Character} to use - */ - private char getDitheringChar(float brightness, String cars) { - int index = Math.round(brightness * (cars.length() - 1)); - return cars.charAt(index); - } - - /** - * Return the {@link Character} corresponding to the 4 given colours in - * {@link Mode#DOUBLE_RESOLUTION} or {@link Mode#DOUBLE_DITHERING} mode. - * - * @param upperleft - * the upper left colour - * @param upperright - * the upper right colour - * @param lowerleft - * the lower left colour - * @param lowerright - * the lower right colour - * @param dithering - * TRUE to use {@link Mode#DOUBLE_DITHERING}, FALSE for - * {@link Mode#DOUBLE_RESOLUTION} - * - * @return the {@link Character} to use - */ - private char getBlockChar(int upperleft, int upperright, int lowerleft, - int lowerright, boolean dithering) { - int choice = 0; - - if (getBrightness(upperleft) > 0.5f) { - choice += 1; - } - if (getBrightness(upperright) > 0.5f) { - choice += 2; - } - if (getBrightness(lowerleft) > 0.5f) { - choice += 4; - } - if (getBrightness(lowerright) > 0.5f) { - choice += 8; - } - - switch (choice) { - case 0: - return ' '; - case 1: - return '▘'; - case 2: - return '▝'; - case 3: - return '▀'; - case 4: - return '▖'; - case 5: - return '▌'; - case 6: - return '▞'; - case 7: - return '▛'; - case 8: - return '▗'; - case 9: - return '▚'; - case 10: - return '▐'; - case 11: - return '▜'; - case 12: - return '▄'; - case 13: - return '▙'; - case 14: - return '▟'; - case 15: - if (dithering) { - float avg = 0; - avg += getBrightness(upperleft); - avg += getBrightness(upperright); - avg += getBrightness(lowerleft); - avg += getBrightness(lowerright); - avg /= 4; - - // Since all the quarters are > 0.5, avg is between 0.5 and 1.0 - // So, expand the range of the value - avg = (avg - 0.5f) * 2; - - // Do not use the " " char, as it would make a - // "all quarters > 0.5" pixel go black - return getDitheringChar(avg, "░▒▓█"); - } - - return '█'; - } - - return ' '; - } - - /** - * Temporary array used so not to create a lot of new ones. - */ - private float[] tmp = new float[4]; - - /** - * Return the brightness value to use from the given ARGB colour. - * - * @param argb - * the argb colour - * - * @return the brightness to sue for computations - */ - private float getBrightness(int argb) { - if (invert) { - return 1 - rgb2hsb(argb, tmp)[2]; - } - - return rgb2hsb(argb, tmp)[2]; - } - - /** - * Convert the given ARGB colour in HSL/HSB, either into the supplied array - * or into a new one if array is NULL. - * - *

- * ARGB pixels are given in 0xAARRGGBB format, while the returned array will - * contain Hue, Saturation, Lightness/Brightness, Alpha, in this order. H, - * S, L and A are all ranging from 0 to 1 (indeed, H is in 1/360th). - *

- * pixel - * - * @param argb - * the ARGB colour pixel to convert - * @param array - * the array to convert into or NULL to create a new one - * - * @return the array containing the HSL/HSB converted colour - */ - static float[] rgb2hsb(int argb, float[] array) { - int a, r, g, b; - a = ((argb & 0xff000000) >> 24); - r = ((argb & 0x00ff0000) >> 16); - g = ((argb & 0x0000ff00) >> 8); - b = ((argb & 0x000000ff)); - - if (array == null) { - array = new float[4]; - } - - Color.RGBtoHSB(r, g, b, array); - - array[3] = a; - - return array; - - // // other implementation: - // - // float a, r, g, b; - // a = ((argb & 0xff000000) >> 24) / 255.0f; - // r = ((argb & 0x00ff0000) >> 16) / 255.0f; - // g = ((argb & 0x0000ff00) >> 8) / 255.0f; - // b = ((argb & 0x000000ff)) / 255.0f; - // - // float rgbMin, rgbMax; - // rgbMin = Math.min(r, Math.min(g, b)); - // rgbMax = Math.max(r, Math.max(g, b)); - // - // float l; - // l = (rgbMin + rgbMax) / 2; - // - // float s; - // if (rgbMin == rgbMax) { - // s = 0; - // } else { - // if (l <= 0.5) { - // s = (rgbMax - rgbMin) / (rgbMax + rgbMin); - // } else { - // s = (rgbMax - rgbMin) / (2.0f - rgbMax - rgbMin); - // } - // } - // - // float h; - // if (r > g && r > b) { - // h = (g - b) / (rgbMax - rgbMin); - // } else if (g > b) { - // h = 2.0f + (b - r) / (rgbMax - rgbMin); - // } else { - // h = 4.0f + (r - g) / (rgbMax - rgbMin); - // } - // h /= 6; // from 0 to 1 - // - // return new float[] { h, s, l, a }; - // - // // // natural mode: - // // - // // int aa = (int) Math.round(100 * a); - // // int hh = (int) (360 * h); - // // if (hh < 0) - // // hh += 360; - // // int ss = (int) Math.round(100 * s); - // // int ll = (int) Math.round(100 * l); - // // - // // return new int[] { hh, ss, ll, aa }; - } -} diff --git a/src/be/nikiroo/utils/ui/ImageUtilsAwt.java b/src/be/nikiroo/utils/ui/ImageUtilsAwt.java deleted file mode 100644 index c273e0d..0000000 --- a/src/be/nikiroo/utils/ui/ImageUtilsAwt.java +++ /dev/null @@ -1,334 +0,0 @@ -package be.nikiroo.utils.ui; - -import java.awt.Dimension; -import java.awt.Graphics2D; -import java.awt.geom.AffineTransform; -import java.awt.image.AffineTransformOp; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; - -import javax.imageio.ImageIO; - -import be.nikiroo.utils.IOUtils; -import be.nikiroo.utils.Image; -import be.nikiroo.utils.ImageUtils; -import be.nikiroo.utils.StringUtils; - -/** - * This class offer some utilities based around images and uses java.awt. - * - * @author niki - */ -public class ImageUtilsAwt extends ImageUtils { - /** - * A rotation to perform on an image. - * - * @author niki - */ - public enum Rotation { - /** No rotation */ - NONE, - /** Rotate the image to the right */ - RIGHT, - /** Rotate the image to the left */ - LEFT, - /** Rotate the image by 180° */ - UTURN - } - - @Override - protected boolean check() { - // Will not work if ImageIO is not available - ImageIO.getCacheDirectory(); - return true; - } - - @Override - public void saveAsImage(Image img, File target, String format) - throws IOException { - try { - BufferedImage image = fromImage(img); - - boolean ok = false; - try { - - ok = ImageIO.write(image, format, target); - } catch (IOException e) { - ok = false; - } - - // Some formats are not reliable - // Second chance: PNG - if (!ok && !format.equals("png")) { - try { - ok = ImageIO.write(image, "png", target); - } catch (IllegalArgumentException e) { - throw e; - } catch (Exception e) { - throw new IOException("Undocumented exception occured, " - + "converting to IOException", e); - } - } - - if (!ok) { - throw new IOException( - "Cannot find a writer for this image and format: " - + format); - } - } catch (IOException e) { - throw new IOException("Cannot write image to " + target, e); - } - } - - /** - * Convert the given {@link Image} into a {@link BufferedImage} object, - * respecting the EXIF transformations if any. - * - * @param img - * the {@link Image} - * - * @return the {@link Image} object - * - * @throws IOException - * in case of IO error - */ - public static BufferedImage fromImage(Image img) throws IOException { - return fromImage(img, Rotation.NONE); - } - - /** - * Convert the given {@link Image} into a {@link BufferedImage} object, - * respecting the EXIF transformations if any. - * - * @param img - * the {@link Image} - * @param rotation - * the rotation to apply, if any (can be null, same as - * {@link Rotation#NONE}) - * - * @return the {@link Image} object - * - * @throws IOException - * in case of IO error - */ - public static BufferedImage fromImage(Image img, Rotation rotation) - throws IOException { - InputStream in = img.newInputStream(); - BufferedImage image; - try { - int orientation; - try { - orientation = getExifTransorm(in); - } catch (Exception e) { - // no EXIF transform, ok - orientation = -1; - } - - in.reset(); - - try { - image = ImageIO.read(in); - } catch (IllegalArgumentException e) { - throw e; - } catch (Exception e) { - throw new IOException("Undocumented exception occured, " - + "converting to IOException", e); - } - - if (image == null) { - String extra = ""; - if (img.getSize() <= 2048) { - try { - byte[] data = null; - InputStream inData = img.newInputStream(); - try { - data = IOUtils.toByteArray(inData); - } finally { - inData.close(); - } - extra = ", content: " + new String(data, "UTF-8"); - } catch (Exception e) { - extra = ", content unavailable"; - } - } - String ssize = StringUtils.formatNumber(img.getSize()); - throw new IOException( - "Failed to convert input to image, size was: " + ssize - + extra); - } - - // Note: this code has been found on Internet; - // thank you anonymous coder. - int width = image.getWidth(); - int height = image.getHeight(); - AffineTransform affineTransform = new AffineTransform(); - - switch (orientation) { - case 1: - affineTransform = null; - break; - case 2: // Flip X - affineTransform.scale(-1.0, 1.0); - affineTransform.translate(-width, 0); - break; - case 3: // PI rotation - affineTransform.translate(width, height); - affineTransform.rotate(Math.PI); - break; - case 4: // Flip Y - affineTransform.scale(1.0, -1.0); - affineTransform.translate(0, -height); - break; - case 5: // - PI/2 and Flip X - affineTransform.rotate(-Math.PI / 2); - affineTransform.scale(-1.0, 1.0); - break; - case 6: // -PI/2 and -width - affineTransform.translate(height, 0); - affineTransform.rotate(Math.PI / 2); - break; - case 7: // PI/2 and Flip - affineTransform.scale(-1.0, 1.0); - affineTransform.translate(-height, 0); - affineTransform.translate(0, width); - affineTransform.rotate(3 * Math.PI / 2); - break; - case 8: // PI / 2 - affineTransform.translate(0, width); - affineTransform.rotate(3 * Math.PI / 2); - break; - default: - affineTransform = null; - break; - } - - if (rotation == null) - rotation = Rotation.NONE; - - switch (rotation) { - case RIGHT: - if (affineTransform == null) { - affineTransform = new AffineTransform(); - } - affineTransform.translate(height, 0); - affineTransform.rotate(Math.PI / 2); - - int tmp = width; - width = height; - height = tmp; - - break; - case LEFT: - if (affineTransform == null) { - affineTransform = new AffineTransform(); - } - affineTransform.translate(0, width); - affineTransform.rotate(3 * Math.PI / 2); - - int temp = width; - width = height; - height = temp; - - break; - case UTURN: - if (affineTransform == null) { - affineTransform = new AffineTransform(); - } - affineTransform.translate(width, height); - affineTransform.rotate(Math.PI); - break; - default: - break; - } - - if (affineTransform != null) { - AffineTransformOp affineTransformOp = new AffineTransformOp( - affineTransform, AffineTransformOp.TYPE_BILINEAR); - - BufferedImage transformedImage = new BufferedImage(width, - height, image.getType()); - transformedImage = affineTransformOp.filter(image, - transformedImage); - - image = transformedImage; - } - // - } finally { - in.close(); - } - - return image; - } - - /** - * Scale a dimension. - * - * @param imageSize - * the actual image size - * @param areaSize - * the base size of the target to get snap sizes for - * @param zoom - * the zoom factor (ignored on snap mode) - * @param snapMode - * NULL for no snap mode, TRUE to snap to width and FALSE for - * snap to height) - * - * @return the scaled (minimum is 1x1) - */ - public static Dimension scaleSize(Dimension imageSize, Dimension areaSize, - double zoom, Boolean snapMode) { - Integer[] sz = scaleSize(imageSize.width, imageSize.height, - areaSize.width, areaSize.height, zoom, snapMode); - return new Dimension(sz[0], sz[1]); - } - - /** - * Resize the given image. - * - * @param image - * the image to resize - * @param areaSize - * the base size of the target dimension for snap sizes - * @param zoom - * the zoom factor (ignored on snap mode) - * @param snapMode - * NULL for no snap mode, TRUE to snap to width and FALSE for - * snap to height) - * - * @return a new, resized image - */ - public static BufferedImage scaleImage(BufferedImage image, - Dimension areaSize, double zoom, Boolean snapMode) { - Dimension scaledSize = scaleSize( - new Dimension(image.getWidth(), image.getHeight()), areaSize, - zoom, snapMode); - - return scaleImage(image, scaledSize); - } - - /** - * Resize the given image. - * - * @param image - * the image to resize - * @param targetSize - * the target size - * - * @return a new, resized image - */ - public static BufferedImage scaleImage(BufferedImage image, - Dimension targetSize) { - BufferedImage resizedImage = new BufferedImage(targetSize.width, - targetSize.height, BufferedImage.TYPE_4BYTE_ABGR); - Graphics2D g = resizedImage.createGraphics(); - try { - g.drawImage(image, 0, 0, targetSize.width, targetSize.height, null); - } finally { - g.dispose(); - } - - return resizedImage; - } -} diff --git a/src/be/nikiroo/utils/ui/Item.java b/src/be/nikiroo/utils/ui/Item.java deleted file mode 100644 index c8afc7f..0000000 --- a/src/be/nikiroo/utils/ui/Item.java +++ /dev/null @@ -1,479 +0,0 @@ -package be.nikiroo.utils.ui; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Image; -import java.awt.Rectangle; -import java.awt.image.BufferedImage; -import java.util.HashMap; -import java.util.Map; - -import javax.swing.BorderFactory; -import javax.swing.JComponent; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.SwingConstants; - -/** - * A graphical item that can be presented in a list and supports user - * interaction. - *

- * Can be selected, hovered... - * - * @author niki - */ -abstract public class Item extends JPanel { - static private final long serialVersionUID = 1L; - - static private Map empty = new HashMap(); - static private Map error = new HashMap(); - static private Map statuses = new HashMap(); - - private String id; - private boolean selected; - private boolean hovered; - - private String mainTemplate; - private String secondaryTemplate; - - private boolean hasImage; - private JLabel title; - private JLabel secondary; - private JLabel statusIndicatorOn; - private JLabel statusIndicatorOff; - private JLabel statusIndicatorUnknown; - private Image image; - private boolean imageError; - - private String cachedMain; - private String cachedOptSecondary; - private Integer cachedStatus; - - /** - * Create a new {@link Item} - * - * @param id - * an ID that represents this {@link Item} (can be NULL) - * @param hasImage - * this {@link Item} will contain an image - */ - public Item(String id, boolean hasImage) { - this.id = id; - this.hasImage = hasImage; - init(hasImage); - } - - // Configuration : - - protected int getMaxDisplaySize() { - return 40; - } - - protected int getCoverWidth() { - return 100; - } - - protected int getCoverHeight() { - return 150; - } - - protected int getTextWidth() { - return getCoverWidth() + 40; - } - - protected int getTextHeight() { - return 50; - } - - protected int getCoverVOffset() { - return 20; - } - - protected int getCoverHOffset() { - return 0; - } - - protected int getHGap() { - return 10; - } - - /** Colour used for the secondary item (author/word count). */ - protected Color getSecondaryColor() { - return new Color(128, 128, 128); - } - - /** - * Return a display-ready version of the main information to show. - *

- * Note that you can make use of {@link Item#limit(String)}. - * - * @return the main info in a ready-to-display version, cannot be NULL - */ - abstract protected String getMainInfoDisplay(); - - /** - * Return a display-ready version of the secondary information to show. - *

- * Note that you can make use of {@link Item#limit(String)}. - * - * @return the main info in a ready-to-display version, cannot be NULL - */ - abstract protected String getSecondaryInfoDisplay(); - - /** - * The current status for the status indicator. - *

- * Note that NULL and negative values will create "hollow" indicators, while - * other values will create "filled" indicators. - * - * @return the status which can be NULL, presumably for "Unknown" - */ - abstract protected Integer getStatus(); - - /** - * Get the background colour to use according to the given state. - *

- * Since it is an overlay, an opaque colour will of course mask everything. - * - * @param enabled - * the item is enabled - * @param selected - * the item is selected - * @param hovered - * the mouse cursor currently hovers over the item - * - * @return the correct background colour to use - */ - abstract protected Color getOverlayColor(boolean enabled, boolean selected, - boolean hovered); - - /** - * Get the colour to use for the status indicator. - *

- * Return NULL if you don't want a status indicator for this state. - * - * @param status - * the current status as returned by {@link Item#getStatus()} - * - * @return the base colour to use, or NULL for no status indicator - */ - abstract protected Color getStatusIndicatorColor(Integer status); - - /** - * Initialise this {@link Item}. - */ - private void init(boolean hasImage) { - if (!hasImage) { - title = new JLabel(); - mainTemplate = "${MAIN}"; - secondary = new JLabel(); - secondaryTemplate = "${SECONDARY}"; - secondary.setForeground(getSecondaryColor()); - - JPanel idTitle = null; - if (id != null && !id.isEmpty()) { - JLabel idLabel = new JLabel(id); - idLabel.setPreferredSize(new JLabel(" 999 ").getPreferredSize()); - idLabel.setForeground(Color.gray); - idLabel.setHorizontalAlignment(SwingConstants.CENTER); - - idTitle = new JPanel(new BorderLayout()); - idTitle.setOpaque(false); - idTitle.add(idLabel, BorderLayout.WEST); - idTitle.add(title, BorderLayout.CENTER); - } - - setLayout(new BorderLayout()); - if (idTitle != null) - add(idTitle, BorderLayout.CENTER); - add(secondary, BorderLayout.EAST); - } else { - image = null; - title = new JLabel(); - secondary = new JLabel(); - secondaryTemplate = ""; - - String color = String.format("#%X%X%X", getSecondaryColor() - .getRed(), getSecondaryColor().getGreen(), - getSecondaryColor().getBlue()); - mainTemplate = String - .format("" - + "" - + "${MAIN}" + "
" + "" - + "${SECONDARY}" + "" + "" - + "", getTextWidth(), getTextHeight(), color); - - int ww = Math.max(getCoverWidth(), getTextWidth()); - int hh = getCoverHeight() + getCoverVOffset() + getHGap() - + getTextHeight(); - - JPanel placeholder = new JPanel(); - placeholder - .setPreferredSize(new Dimension(ww, hh - getTextHeight())); - placeholder.setOpaque(false); - - JPanel titlePanel = new JPanel(new BorderLayout()); - titlePanel.setOpaque(false); - titlePanel.add(title, BorderLayout.NORTH); - - titlePanel.setBorder(BorderFactory.createEmptyBorder()); - - setLayout(new BorderLayout()); - add(placeholder, BorderLayout.NORTH); - add(titlePanel, BorderLayout.CENTER); - } - - // Cached values are NULL, so it will be updated - updateData(); - } - - /** - * The book current selection state. - * - * @return the selection state - */ - public boolean isSelected() { - return selected; - } - - /** - * The book current selection state, - * - * @param selected - * TRUE if it is selected - */ - public void setSelected(boolean selected) { - if (this.selected != selected) { - this.selected = selected; - repaint(); - } - } - - /** - * The item mouse-hover state. - * - * @return TRUE if it is mouse-hovered - */ - public boolean isHovered() { - return this.hovered; - } - - /** - * The item mouse-hover state. - * - * @param hovered - * TRUE if it is mouse-hovered - */ - public void setHovered(boolean hovered) { - if (this.hovered != hovered) { - this.hovered = hovered; - repaint(); - } - } - - /** - * Update the title, paint the item. - */ - @Override - public void paint(Graphics g) { - Rectangle clip = g.getClipBounds(); - if (clip == null || clip.getWidth() <= 0 || clip.getHeight() <= 0) { - return; - } - - updateData(); - - super.paint(g); - if (hasImage) { - Image img = image == null ? getBlank(false) : image; - if (isImageError()) - img = getBlank(true); - - int xOff = getCoverHOffset() + (getWidth() - getCoverWidth()) / 2; - g.drawImage(img, xOff, getCoverVOffset(), null); - - Integer status = getStatus(); - boolean filled = status != null && status > 0; - Color indicatorColor = getStatusIndicatorColor(status); - if (indicatorColor != null) { - UIUtils.drawEllipse3D(g, indicatorColor, getCoverWidth() + xOff - + 10, 10, 20, 20, filled); - } - } - - Color bg = getOverlayColor(isEnabled(), isSelected(), isHovered()); - g.setColor(bg); - g.fillRect(clip.x, clip.y, clip.width, clip.height); - } - - /** - * The image to display on image {@link Item} (NULL for non-image - * {@link Item}s). - * - * @return the image or NULL for the empty image or for non image - * {@link Item}s - */ - public Image getImage() { - return hasImage ? image : null; - } - - /** - * Change the image to display (does not work for non-image {@link Item}s). - *

- * NULL is allowed, an empty image will then be shown. - * - * @param image - * the new {@link Image} or NULL - * - */ - public void setImage(Image image) { - this.image = hasImage ? image : null; - } - - /** - * Use the ERROR image instead of the real one or the empty one. - * - * @return TRUE if we force use the error image - */ - public boolean isImageError() { - return imageError; - } - - /** - * Use the ERROR image instead of the real one or the empty one. - * - * @param imageError - * TRUE to force use the error image - */ - public void setImageError(boolean imageError) { - this.imageError = imageError; - } - - /** - * Make the given {@link String} display-ready (i.e., shorten it if it is - * too long). - * - * @param value - * the full value - * - * @return the display-ready value - */ - protected String limit(String value) { - if (value == null) - value = ""; - - if (value.length() > getMaxDisplaySize()) { - value = value.substring(0, getMaxDisplaySize() - 3) + "..."; - } - - return value; - } - - /** - * Update the title with the currently registered information. - */ - private void updateData() { - String main = getMainInfoDisplay(); - String optSecondary = getSecondaryInfoDisplay(); - Integer status = getStatus(); - - // Cached values can be NULL the first time - if (!main.equals(cachedMain) - || !optSecondary.equals(cachedOptSecondary) - || status != cachedStatus) { - title.setText(mainTemplate // - .replace("${MAIN}", main) // - .replace("${SECONDARY}", optSecondary) // - ); - secondary.setText(secondaryTemplate// - .replace("${MAIN}", main) // - .replace("${SECONDARY}", optSecondary) // - + " "); - - Color bg = getOverlayColor(isEnabled(), isSelected(), isHovered()); - setBackground(bg); - - if (!hasImage) { - remove(statusIndicatorUnknown); - remove(statusIndicatorOn); - remove(statusIndicatorOff); - - Color k = getStatusIndicatorColor(getStatus()); - JComponent statusIndicator = statuses.get(k); - if (!statuses.containsKey(k)) { - statusIndicator = generateStatusIndicator(k); - statuses.put(k, statusIndicator); - } - - if (statusIndicator != null) - add(statusIndicator, BorderLayout.WEST); - } - - validate(); - } - - this.cachedMain = main; - this.cachedOptSecondary = optSecondary; - this.cachedStatus = status; - } - - /** - * Generate a status indicator for the given colour. - * - * @param color - * the colour to use - * - * @return a status indicator ready to be used - */ - private JLabel generateStatusIndicator(Color color) { - JLabel indicator = new JLabel(" ") { - private static final long serialVersionUID = 1L; - - @Override - public void paint(Graphics g) { - super.paint(g); - - if (color != null) { - Dimension sz = statusIndicatorOn.getSize(); - int s = Math.min(sz.width, sz.height); - int x = Math.max(0, (sz.width - sz.height) / 2); - int y = Math.max(0, (sz.height - sz.width) / 2); - - UIUtils.drawEllipse3D(g, color, x, y, s, s, true); - } - } - }; - - indicator.setBackground(color); - return indicator; - } - - private Image getBlank(boolean error) { - Dimension key = new Dimension(getCoverWidth(), getCoverHeight()); - Map images = error ? Item.error : Item.empty; - - BufferedImage blank = images.get(key); - if (blank == null) { - blank = new BufferedImage(getCoverWidth(), getCoverHeight(), - BufferedImage.TYPE_4BYTE_ABGR); - - Graphics2D g = blank.createGraphics(); - try { - g.setColor(Color.white); - g.fillRect(0, 0, getCoverWidth(), getCoverHeight()); - - g.setColor(error ? Color.red : Color.black); - g.drawLine(0, 0, getCoverWidth(), getCoverHeight()); - g.drawLine(getCoverWidth(), 0, 0, getCoverHeight()); - } finally { - g.dispose(); - } - images.put(key, blank); - } - - return blank; - } -} diff --git a/src/be/nikiroo/utils/ui/ListModel.java b/src/be/nikiroo/utils/ui/ListModel.java deleted file mode 100644 index 7cc23b8..0000000 --- a/src/be/nikiroo/utils/ui/ListModel.java +++ /dev/null @@ -1,570 +0,0 @@ -package be.nikiroo.utils.ui; - -import java.awt.Component; -import java.awt.Point; -import java.awt.Window; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import javax.swing.JList; -import javax.swing.JPopupMenu; -import javax.swing.ListCellRenderer; -import javax.swing.SwingWorker; - -import be.nikiroo.utils.ui.compat.DefaultListModel6; -import be.nikiroo.utils.ui.compat.JList6; -import be.nikiroo.utils.ui.compat.ListCellRenderer6; - -/** - * A {@link javax.swing.ListModel} that can maintain 2 lists; one with the - * actual data (the elements), and a second one with the items that are - * currently displayed (the items). - *

- * It also offers filter options, supports hovered changes and some more utility - * functions. - * - * @author niki - * - * @param - * the type of elements and items (the same type) - */ -public class ListModel extends DefaultListModel6 { - private static final long serialVersionUID = 1L; - - /** How long to wait before displaying a tooltip, in milliseconds. */ - private static final int DELAY_TOOLTIP_MS = 1000; - - /** - * A filter interface, to check for a condition (note that a Predicate class - * already exists in Java 1.8+, and is compatible with this one if you - * change the signatures -- but I support java 1.6+). - * - * @author niki - * - * @param - * the type of elements and items (the same type) - */ - public interface Predicate { - /** - * Check if an item or an element pass a filter. - * - * @param item - * the item to test - * - * @return TRUE if the test passed, FALSE if not - */ - public boolean test(T item); - } - - /** - * A simple interface your elements must implement if you want to use - * {@link ListModel#generateRenderer(ListModel)}. - * - * @author niki - */ - public interface Hoverable { - /** - * The element is currently selected. - * - * @param selected - * TRUE for selected, FALSE for unselected - */ - public void setSelected(boolean selected); - - /** - * The element is currently under the mouse cursor. - * - * @param hovered - * TRUE if it is, FALSE if not - */ - public void setHovered(boolean hovered); - } - - /** - * An interface required to support tooltips on this {@link ListModel}. - * - * @author niki - * - * @param - * the type of elements and items (the same type) - */ - public interface TooltipCreator { - /** - * Generate a tooltip {@link Window} for this element. - *

- * Note that the tooltip can be of two modes: undecorated or standalone. - * An undecorated tooltip will be taken care of by this - * {@link ListModel}, but a standalone one is supposed to be its own - * Dialog or Frame (it won't be automatically closed). - * - * @param t - * the element to generate a tooltip for - * @param undecorated - * TRUE for undecorated tooltip, FALSE for standalone - * tooltips - * - * @return the generated tooltip or NULL for none - */ - public Window generateTooltip(T t, boolean undecorated); - } - - private int hoveredIndex; - private List items = new ArrayList(); - private boolean keepSelection = true; - - private DelayWorker tooltipWatcher; - private JPopupMenu popup; - private TooltipCreator tooltipCreator; - private Window tooltip; - - @SuppressWarnings("rawtypes") // JList not compatible Java 1.6 - private JList list; - - /** - * Create a new {@link ListModel}. - * - * @param list - * the {@link JList6} we will handle the data of (cannot be NULL) - */ - @SuppressWarnings("rawtypes") // JList not compatible Java 1.6 - public ListModel(JList6 list) { - this((JList) list); - } - - /** - * Create a new {@link ListModel}. - *

- * Note that you must take care of passing a {@link JList} that only handles - * elements of the type of this {@link ListModel} -- you can also use - * {@link ListModel#ListModel(JList6)} instead. - * - * @param list - * the {@link JList} we will handle the data of (cannot be NULL, - * must only contain elements of the type of this - * {@link ListModel}) - */ - @SuppressWarnings({ "unchecked", "rawtypes" }) // JList not in Java 1.6 - public ListModel(final JList list) { - this.list = list; - - list.setModel(this); - - // We always have it ready - tooltipWatcher = new DelayWorker(DELAY_TOOLTIP_MS); - tooltipWatcher.start(); - - list.addMouseMotionListener(new MouseAdapter() { - @Override - public void mouseMoved(final MouseEvent me) { - if (ListModel.this.popup != null - && ListModel.this.popup.isShowing()) - return; - - Point p = new Point(me.getX(), me.getY()); - final int index = list.locationToIndex(p); - if (index != hoveredIndex) { - int oldIndex = hoveredIndex; - hoveredIndex = index; - fireElementChanged(oldIndex); - fireElementChanged(index); - - if (ListModel.this.tooltipCreator != null) { - showTooltip(null); - - tooltipWatcher.delay("tooltip", - new SwingWorker() { - @Override - protected Void doInBackground() - throws Exception { - return null; - } - - @Override - protected void done() { - showTooltip(null); - - if (index < 0 - || index != hoveredIndex) { - return; - } - - if (ListModel.this.popup != null - && ListModel.this.popup - .isShowing()) { - return; - } - - showTooltip(newTooltip(index, me)); - } - }); - } - } - } - }); - - list.addMouseListener(new MouseAdapter() { - @Override - public void mousePressed(MouseEvent e) { - check(e); - } - - @Override - public void mouseReleased(MouseEvent e) { - check(e); - } - - @Override - public void mouseExited(MouseEvent e) { - if (ListModel.this.popup != null - && ListModel.this.popup.isShowing()) - return; - - if (hoveredIndex > -1) { - int oldIndex = hoveredIndex; - hoveredIndex = -1; - fireElementChanged(oldIndex); - } - } - - private void check(MouseEvent e) { - if (ListModel.this.popup == null) { - return; - } - - if (e.isPopupTrigger()) { - if (list.getSelectedIndices().length <= 1) { - list.setSelectedIndex( - list.locationToIndex(e.getPoint())); - } - - showTooltip(null); - ListModel.this.popup.show(list, e.getX(), e.getY()); - } - } - - }); - } - - /** - * (Try and) keep the elements that were selected when filtering. - *

- * This will use toString on the elements to identify them, and can be a bit - * resource intensive. - * - * @return TRUE if we do - */ - public boolean isKeepSelection() { - return keepSelection; - } - - /** - * (Try and) keep the elements that were selected when filtering. - *

- * This will use toString on the elements to identify them, and can be a bit - * resource intensive. - * - * @param keepSelection - * TRUE to try and keep them selected - */ - public void setKeepSelection(boolean keepSelection) { - this.keepSelection = keepSelection; - } - - /** - * The popup to use and keep track of (can be NULL). - * - * @return the current popup - */ - public JPopupMenu getPopup() { - return popup; - } - - /** - * The popup to use and keep track of (can be NULL). - * - * @param popup - * the new popup - */ - public void setPopup(JPopupMenu popup) { - this.popup = popup; - } - - /** - * You can use a {@link TooltipCreator} if you want the list to display - * tooltips on mouse hover (can be NULL). - * - * @return the current {@link TooltipCreator} - */ - public TooltipCreator getTooltipCreator() { - return tooltipCreator; - } - - /** - * You can use a {@link TooltipCreator} if you want the list to display - * tooltips on mouse hover (can be NULL). - * - * @param tooltipCreator - * the new {@link TooltipCreator} - */ - public void setTooltipCreator(TooltipCreator tooltipCreator) { - this.tooltipCreator = tooltipCreator; - } - - /** - * Check if this element is currently under the mouse. - * - * @param element - * the element to check - * - * @return TRUE if it is - */ - public boolean isHovered(T element) { - return indexOf(element) == hoveredIndex; - } - - /** - * Check if this element is currently under the mouse. - * - * @param index - * the index of the element to check - * - * @return TRUE if it is - */ - public boolean isHovered(int index) { - return index == hoveredIndex; - } - - /** - * Add an item to the model. - * - * @param item - * the new item to add - */ - public void addItem(T item) { - items.add(item); - } - - /** - * Add items to the model. - * - * @param items - * the new items to add - */ - public void addAllItems(Collection items) { - this.items.addAll(items); - } - - /** - * Removes the first occurrence of the specified element from this list, if - * it is present (optional operation). - * - * @param item - * the item to remove if possible (can be NULL) - * - * @return TRUE if one element was removed, FALSE if not found - */ - public boolean removeItem(T item) { - return items.remove(item); - } - - /** - * Remove the items that pass the given filter (or all items if the filter - * is NULL). - * - * @param filter - * the filter (if the filter returns TRUE, the item will be - * removed) - * - * @return TRUE if at least one item was removed - */ - public boolean removeItemIf(Predicate filter) { - boolean changed = false; - if (filter == null) { - changed = !items.isEmpty(); - clearItems(); - } else { - for (int i = 0; i < items.size(); i++) { - if (filter.test(items.get(i))) { - items.remove(i--); - changed = true; - } - } - } - - return changed; - } - - /** - * Removes all the items from this model. - */ - public void clearItems() { - items.clear(); - } - - /** - * Filter the current elements. - *

- * This method will clear all the elements then look into all the items: - * those that pass the given filter will be copied as elements. - * - * @param filter - * the filter to select which elements to keep; an item that pass - * the filter will be copied as an element (can be NULL, in that - * case all items will be copied as elements) - */ - @SuppressWarnings("unchecked") // JList not compatible Java 1.6 - public void filter(Predicate filter) { - ListSnapshot snapshot = null; - - if (keepSelection) - snapshot = new ListSnapshot(list); - - clear(); - for (T item : items) { - if (filter == null || filter.test(item)) { - addElement(item); - } - } - - if (keepSelection) - snapshot.apply(); - - list.repaint(); - } - - /** - * Return the currently selected elements. - * - * @return the selected elements - */ - public List getSelectedElements() { - List selected = new ArrayList(); - for (int index : list.getSelectedIndices()) { - selected.add(get(index)); - } - - return selected; - } - - /** - * Return the selected element if one and only one element is - * selected. I.E., if zero, two or more elements are selected, NULL will be - * returned. - * - * @return the element if it is the only selected element, NULL otherwise - */ - public T getUniqueSelectedElement() { - List selected = getSelectedElements(); - if (selected.size() == 1) { - return selected.get(0); - } - - return null; - } - - /** - * Notify that this element has been changed. - * - * @param index - * the index of the element - */ - public void fireElementChanged(int index) { - if (index >= 0) { - fireContentsChanged(this, index, index); - } - } - - /** - * Notify that this element has been changed. - * - * @param element - * the element - */ - public void fireElementChanged(T element) { - int index = indexOf(element); - if (index >= 0) { - fireContentsChanged(this, index, index); - } - } - - @SuppressWarnings("unchecked") // JList not compatible Java 1.6 - @Override - public T get(int index) { - return (T) super.get(index); - } - - private Window newTooltip(final int index, final MouseEvent me) { - final T value = ListModel.this.get(index); - final Window newTooltip = tooltipCreator.generateTooltip(value, true); - if (newTooltip != null) { - newTooltip.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - Window promotedTooltip = tooltipCreator - .generateTooltip(value, false); - if (promotedTooltip != null) { - promotedTooltip.setLocation(me.getXOnScreen(), - me.getYOnScreen()); - promotedTooltip.setVisible(true); - } - - newTooltip.setVisible(false); - } - }); - - newTooltip.setLocation(me.getXOnScreen(), me.getYOnScreen()); - showTooltip(newTooltip); - } - - return newTooltip; - } - - private void showTooltip(Window tooltip) { - synchronized (tooltipWatcher) { - if (this.tooltip != null) { - this.tooltip.setVisible(false); - this.tooltip.dispose(); - } - - this.tooltip = tooltip; - - if (tooltip != null) { - tooltip.setVisible(true); - } - } - } - - /** - * Generate a {@link ListCellRenderer} that supports {@link Hoverable} - * elements. - * - * @param - * the type of elements and items (the same type), which should - * implement {@link Hoverable} (it will not cause issues if not, - * but then, it will be a default renderer) - * @param model - * the model to use - * - * @return a suitable, {@link Hoverable} compatible renderer - */ - static public ListCellRenderer6 generateRenderer( - final ListModel model) { - return new ListCellRenderer6() { - @Override - public Component getListCellRendererComponent(JList6 list, - T item, int index, boolean isSelected, - boolean cellHasFocus) { - if (item instanceof Hoverable) { - Hoverable hoverable = (Hoverable) item; - hoverable.setSelected(isSelected); - hoverable.setHovered(model.isHovered(index)); - } - - return item; - } - }; - } -} diff --git a/src/be/nikiroo/utils/ui/ListSnapshot.java b/src/be/nikiroo/utils/ui/ListSnapshot.java deleted file mode 100644 index d2e89c8..0000000 --- a/src/be/nikiroo/utils/ui/ListSnapshot.java +++ /dev/null @@ -1,62 +0,0 @@ -package be.nikiroo.utils.ui; - -import java.util.ArrayList; -import java.util.List; - -import javax.swing.JList; - -public class ListSnapshot { - private JList list; - private List elements = new ArrayList(); - - public ListSnapshot(JList list) { - this.list = list; - - for (int index : list.getSelectedIndices()) { - elements.add(list.getModel().getElementAt(index)); - } - } - - public void apply() { - applyTo(list); - } - - public void applyTo(JList list) { - List indices = new ArrayList(); - for (int i = 0; i < list.getModel().getSize(); i++) { - Object newObject = list.getModel().getElementAt(i); - for (Object oldObject : elements) { - if (isSameElement(oldObject, newObject)) { - indices.add(i); - break; - } - } - } - - int a[] = new int[indices.size()]; - for (int i = 0; i < indices.size(); i++) { - a[i] = indices.get(i); - } - list.setSelectedIndices(a); - } - - // You can override this - protected boolean isSameElement(Object oldElement, Object newElement) { - if (oldElement == null || newElement == null) - return oldElement == null && newElement == null; - - return oldElement.toString().equals(newElement.toString()); - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("List Snapshot of: ").append(list).append("\n"); - builder.append("Selected elements:\n"); - for (Object element : elements) { - builder.append("\t").append(element).append("\n"); - } - - return builder.toString(); - } -} diff --git a/src/be/nikiroo/utils/ui/ListenerItem.java b/src/be/nikiroo/utils/ui/ListenerItem.java deleted file mode 100644 index 3fa41c8..0000000 --- a/src/be/nikiroo/utils/ui/ListenerItem.java +++ /dev/null @@ -1,53 +0,0 @@ -package be.nikiroo.utils.ui; - -import java.awt.event.ActionListener; - -/** - * The default {@link ActionListener} add/remove/fire methods. - * - * @author niki - */ -public interface ListenerItem { - /** - * Check that this {@link ListenerItem} currently has - * {@link ActionListener}s that listen on it. - * - * @return TRUE if it has - */ - public boolean hasListeners(); - - /** - * Check how many events are currently waiting for an - * {@link ActionListener}. - * - * @return the number of waiting events (can be 0) - */ - public int getWaitingEventCount(); - - /** - * Adds the specified action listener to receive action events from this - * {@link ListenerItem}. - * - * @param listener - * the action listener to be added - */ - public void addActionListener(ActionListener listener); - - /** - * Removes the specified action listener so that it no longer receives - * action events from this {@link ListenerItem}. - * - * @param listener - * the action listener to be removed - */ - public void removeActionListener(ActionListener listener); - - /** - * Notify the listeners of an action. - * - * @param listenerCommand - * A string that may specify a command (possibly one of several) - * associated with the event - */ - public void fireActionPerformed(String listenerCommand); -} diff --git a/src/be/nikiroo/utils/ui/ListenerPanel.java b/src/be/nikiroo/utils/ui/ListenerPanel.java deleted file mode 100644 index ada0796..0000000 --- a/src/be/nikiroo/utils/ui/ListenerPanel.java +++ /dev/null @@ -1,73 +0,0 @@ -package be.nikiroo.utils.ui; - -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.util.LinkedList; -import java.util.Queue; - -import javax.swing.JPanel; - -/** - * A {@link JPanel} with the default {@link ActionListener} add/remove/fire - * methods. - *

- * Note that it will queue all events until at least one listener comes (or - * comes back!); this first (or at least currently unique) listener will drain - * the queue. - * - * @author niki - */ -public class ListenerPanel extends JPanel implements ListenerItem { - private static final long serialVersionUID = 1L; - - /** Waiting queue until at least one listener is here to get the events. */ - private final Queue waitingQueue; - - /** - * Create a new {@link ListenerPanel}. - */ - public ListenerPanel() { - waitingQueue = new LinkedList(); - } - - @Override - public synchronized boolean hasListeners() { - return listenerList.getListenerList().length > 1; - } - - @Override - public synchronized int getWaitingEventCount() { - return waitingQueue.size(); - } - - @Override - public synchronized void addActionListener(ActionListener listener) { - if (!hasListeners()) { - while (!waitingQueue.isEmpty()) { - listener.actionPerformed(waitingQueue.remove()); - } - } - - listenerList.add(ActionListener.class, listener); - } - - @Override - public synchronized void removeActionListener(ActionListener listener) { - listenerList.remove(ActionListener.class, listener); - } - - @Override - public synchronized void fireActionPerformed(String listenerCommand) { - ActionEvent e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, - listenerCommand); - - ActionListener[] listeners = getListeners(ActionListener.class); - if (listeners.length > 0) { - for (ActionListener action : listeners) { - action.actionPerformed(e); - } - } else { - waitingQueue.add(e); - } - } -} diff --git a/src/be/nikiroo/utils/ui/NavBar.java b/src/be/nikiroo/utils/ui/NavBar.java deleted file mode 100644 index 607b2cf..0000000 --- a/src/be/nikiroo/utils/ui/NavBar.java +++ /dev/null @@ -1,414 +0,0 @@ -package be.nikiroo.utils.ui; - -import java.awt.Dimension; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; - -import javax.swing.BorderFactory; -import javax.swing.BoxLayout; -import javax.swing.Icon; -import javax.swing.JButton; -import javax.swing.JLabel; -import javax.swing.JTextField; - -/** - * A Swing-based navigation bar, that displays first/previous/next/last page - * buttons. - * - * @author niki - */ -public class NavBar extends ListenerPanel { - private static final long serialVersionUID = 1L; - - /** The event that is fired on page change. */ - public static final String PAGE_CHANGED = "page changed"; - - private JTextField page; - private JLabel pageLabel; - private JLabel maxPage; - private JLabel label; - - private int index = 0; - private int min = 0; - private int max = 0; - private String extraLabel = null; - - private boolean vertical; - - private JButton first; - private JButton previous; - private JButton next; - private JButton last; - - /** - * Create a new navigation bar. - *

- * The minimum must be lower or equal to the maximum, but a max of "-1" - * means "infinite". - *

- * A {@link NavBar#PAGE_CHANGED} event will be fired on startup. - * - * @param min - * the minimum page number (cannot be negative) - * @param max - * the maximum page number (cannot be lower than min, except if - * -1 (infinite)) - * - * @throws IndexOutOfBoundsException - * if min > max and max is not "-1" - */ - public NavBar(int min, int max) { - if (min > max && max != -1) { - throw new IndexOutOfBoundsException( - String.format("min (%d) > max (%d)", min, max)); - } - - // Page navigation - first = new JButton(); - first.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - first(); - } - }); - - previous = new JButton(); - previous.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - previous(); - } - }); - - final int defaultHeight = new JButton("dummy") - .getPreferredSize().height; - final int width4 = new JButton("1234").getPreferredSize().width; - page = new JTextField(Integer.toString(min)); - page.setPreferredSize(new Dimension(width4, defaultHeight)); - page.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - try { - int pageNb = Integer.parseInt(page.getText()); - if (pageNb < NavBar.this.min || pageNb > NavBar.this.max) { - throw new NumberFormatException("invalid"); - } - - if (setIndex(pageNb)) - fireActionPerformed(PAGE_CHANGED); - } catch (NumberFormatException nfe) { - page.setText(Integer.toString(index)); - } - } - }); - - pageLabel = new JLabel(Integer.toString(min)); - pageLabel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10)); - - maxPage = new JLabel("of " + max); - maxPage.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10)); - - next = new JButton(); - next.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - next(); - } - }); - - last = new JButton(); - last.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - last(); - } - }); - - label = new JLabel(""); - - // Set the << < > >> "icons" - setIcons(null, null, null, null); - - this.min = min; - this.max = max; - this.index = min; - - updateEnabled(); - updateLabel(); - setOrientation(vertical); - - fireActionPerformed(PAGE_CHANGED); - } - - /** - * The current index, must be between {@link NavBar#min} and - * {@link NavBar#max}, both inclusive. - * - * @return the index - */ - public int getIndex() { - return index; - } - - /** - * The current index, should be between {@link NavBar#min} and - * {@link NavBar#max}, both inclusive. - * - * @param index - * the new index - * - * @return TRUE if the index changed, FALSE if not (either it was already at - * that value, or it is outside of the bounds set by - * {@link NavBar#min} and {@link NavBar#max}) - */ - public synchronized boolean setIndex(int index) { - if (index != this.index) { - if (index < min || (index > max && max != -1)) { - return false; - } - - this.index = index; - updateLabel(); - updateEnabled(); - - return true; - } - - return false; - } - - /** - * The minimun page number. Cannot be negative. - * - * @return the min - */ - public int getMin() { - return min; - } - - /** - * The minimum page number. Cannot be negative. - *

- * May update the index if needed (if the index is < the new min). - *

- * Will also (always) update the label and enable/disable the required - * buttons. - * - * @param min - * the new min - */ - public synchronized void setMin(int min) { - this.min = min; - if (index < min) { - index = min; - } - - updateEnabled(); - updateLabel(); - } - - /** - * The maximum page number. Cannot be lower than min, except if -1 - * (infinite). - * - * @return the max - */ - public int getMax() { - return max; - } - - /** - * The maximum page number. Cannot be lower than min, except if -1 - * (infinite). - *

- * May update the index if needed (if the index is > the new max). - *

- * Will also (always) update the label and enable/disable the required - * buttons. - * - * @param max - * the new max - */ - public synchronized void setMax(int max) { - this.max = max; - if (index > max && max != -1) { - index = max; - } - - maxPage.setText("of " + max); - updateEnabled(); - updateLabel(); - } - - /** - * The current extra label to display. - * - * @return the current label - */ - public String getExtraLabel() { - return extraLabel; - } - - /** - * The current extra label to display. - * - * @param currentLabel - * the new current label - */ - public void setExtraLabel(String currentLabel) { - this.extraLabel = currentLabel; - updateLabel(); - } - - /** - * Change the page to the next one. - * - * @return TRUE if it changed - */ - public synchronized boolean next() { - if (setIndex(index + 1)) { - fireActionPerformed(PAGE_CHANGED); - return true; - } - - return false; - } - - /** - * Change the page to the previous one. - * - * @return TRUE if it changed - */ - public synchronized boolean previous() { - if (setIndex(index - 1)) { - fireActionPerformed(PAGE_CHANGED); - return true; - } - - return false; - } - - /** - * Change the page to the first one. - * - * @return TRUE if it changed - */ - public synchronized boolean first() { - if (setIndex(min)) { - fireActionPerformed(PAGE_CHANGED); - return true; - } - - return false; - } - - /** - * Change the page to the last one. - * - * @return TRUE if it changed - */ - public synchronized boolean last() { - if (setIndex(max)) { - fireActionPerformed(PAGE_CHANGED); - return true; - } - - return false; - } - - /** - * Set icons for the buttons instead of square brackets. - *

- * Any NULL value will make the button use square brackets again. - * - * @param first - * the icon of the button "go to first page" - * @param previous - * the icon of the button "go to previous page" - * @param next - * the icon of the button "go to next page" - * @param last - * the icon of the button "go to last page" - */ - public void setIcons(Icon first, Icon previous, Icon next, Icon last) { - this.first.setIcon(first); - this.first.setText(first == null ? "<<" : ""); - this.previous.setIcon(previous); - this.previous.setText(previous == null ? "<" : ""); - this.next.setIcon(next); - this.next.setText(next == null ? ">" : ""); - this.last.setIcon(last); - this.last.setText(last == null ? ">>" : ""); - } - - /** - * The general orientation of the component. - * - * @return TRUE for vertical orientation, FALSE for horisontal orientation - */ - public boolean getOrientation() { - return vertical; - } - - /** - * Update the general orientation of the component. - * - * @param vertical - * TRUE for vertical orientation, FALSE for horisontal - * orientation - * - * @return TRUE if it changed something - */ - public boolean setOrientation(boolean vertical) { - if (getWidth() == 0 || this.vertical != vertical) { - this.vertical = vertical; - - BoxLayout layout = new BoxLayout(this, - vertical ? BoxLayout.Y_AXIS : BoxLayout.X_AXIS); - this.removeAll(); - setLayout(layout); - - this.add(first); - this.add(previous); - if (vertical) { - this.add(pageLabel); - } else { - this.add(page); - } - this.add(maxPage); - this.add(next); - this.add(last); - - if (!vertical) { - this.add(label); - } - - this.revalidate(); - this.repaint(); - - return true; - } - - return false; - } - - /** - * Update the label displayed in the UI. - */ - private void updateLabel() { - label.setText(getExtraLabel()); - pageLabel.setText(Integer.toString(index)); - page.setText(Integer.toString(index)); - } - - /** - * Update the navigation buttons "enabled" state according to the current - * index value. - */ - private synchronized void updateEnabled() { - first.setEnabled(index > min); - previous.setEnabled(index > min); - next.setEnabled(index < max || max == -1); - last.setEnabled(index < max || max == -1); - } -} diff --git a/src/be/nikiroo/utils/ui/ProgressBar.java b/src/be/nikiroo/utils/ui/ProgressBar.java deleted file mode 100644 index 11e1e6c..0000000 --- a/src/be/nikiroo/utils/ui/ProgressBar.java +++ /dev/null @@ -1,188 +0,0 @@ -package be.nikiroo.utils.ui; - -import java.awt.GridLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.swing.JPanel; -import javax.swing.JProgressBar; -import javax.swing.SwingUtilities; - -import be.nikiroo.utils.Progress; - -/** - * A graphical control to show the progress of a {@link Progress}. - *

- * This control is NOT thread-safe. - * - * @author niki - */ -public class ProgressBar extends JPanel { - private static final long serialVersionUID = 1L; - - private Map bars; - private List actionListeners; - private List updateListeners; - private Progress pg; - private Object lock = new Object(); - - public ProgressBar() { - bars = new HashMap(); - actionListeners = new ArrayList(); - updateListeners = new ArrayList(); - } - - public ProgressBar(Progress pg) { - this(); - setProgress(pg); - } - - public void setProgress(final Progress pg) { - this.pg = pg; - - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - if (pg != null) { - final JProgressBar bar = new JProgressBar(); - bar.setStringPainted(true); - - bars.clear(); - bars.put(pg, bar); - - bar.setMinimum(pg.getMin()); - bar.setMaximum(pg.getMax()); - bar.setValue(pg.getProgress()); - bar.setString(pg.getName()); - - pg.addProgressListener(new Progress.ProgressListener() { - @Override - public void progress(Progress progress, String name) { - final Progress.ProgressListener l = this; - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - Map newBars = new HashMap(); - newBars.put(pg, bar); - - bar.setMinimum(pg.getMin()); - bar.setMaximum(pg.getMax()); - bar.setValue(pg.getProgress()); - bar.setString(pg.getName()); - - synchronized (lock) { - for (Progress pgChild : getChildrenAsOrderedList(pg)) { - JProgressBar barChild = bars - .get(pgChild); - if (barChild == null) { - barChild = new JProgressBar(); - barChild.setStringPainted(true); - } - - newBars.put(pgChild, barChild); - - barChild.setMinimum(pgChild.getMin()); - barChild.setMaximum(pgChild.getMax()); - barChild.setValue(pgChild.getProgress()); - barChild.setString(pgChild.getName()); - } - - if (ProgressBar.this.pg == null) { - bars.clear(); - } else { - bars = newBars; - } - } - - if (ProgressBar.this.pg != null) { - if (pg.isDone()) { - pg.removeProgressListener(l); - for (ActionListener listener : actionListeners) { - listener.actionPerformed(new ActionEvent( - ProgressBar.this, 0, - "done")); - } - } - - update(); - } - } - }); - } - }); - } - - update(); - } - }); - } - - public void addActionListener(ActionListener l) { - actionListeners.add(l); - } - - public void clearActionListeners() { - actionListeners.clear(); - } - - public void addUpdateListener(ActionListener l) { - updateListeners.add(l); - } - - public void clearUpdateListeners() { - updateListeners.clear(); - } - - public int getProgress() { - if (pg == null) { - return 0; - } - - return pg.getProgress(); - } - - // only named ones - private List getChildrenAsOrderedList(Progress pg) { - List children = new ArrayList(); - - synchronized (lock) { - for (Progress child : pg.getChildren()) { - if (child.getName() != null && !child.getName().isEmpty()) { - children.add(child); - } - children.addAll(getChildrenAsOrderedList(child)); - } - } - - return children; - } - - private void update() { - synchronized (lock) { - invalidate(); - removeAll(); - - if (pg != null) { - setLayout(new GridLayout(bars.size(), 1)); - add(bars.get(pg), 0); - for (Progress child : getChildrenAsOrderedList(pg)) { - JProgressBar jbar = bars.get(child); - if (jbar != null) { - add(jbar); - } - } - } - - validate(); - repaint(); - } - - for (ActionListener listener : updateListeners) { - listener.actionPerformed(new ActionEvent(this, 0, "update")); - } - } -} diff --git a/src/be/nikiroo/utils/ui/SearchBar.java b/src/be/nikiroo/utils/ui/SearchBar.java deleted file mode 100644 index ffbc78b..0000000 --- a/src/be/nikiroo/utils/ui/SearchBar.java +++ /dev/null @@ -1,148 +0,0 @@ -package be.nikiroo.utils.ui; - -import java.awt.BorderLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.io.IOException; -import java.io.InputStream; - -import javax.swing.ImageIcon; -import javax.swing.JButton; -import javax.swing.JTextField; -import javax.swing.SwingUtilities; - -import be.nikiroo.utils.IOUtils; - -/** - * A generic search/filter bar. - * - * @author niki - */ -public class SearchBar extends ListenerPanel { - static private final long serialVersionUID = 1L; - static private ImageIcon searchIcon; - static private ImageIcon clearIcon; - - private JButton search; - private JTextField text; - private JButton clear; - - private boolean realTime; - - /** - * Create a new {@link SearchBar}. - */ - public SearchBar() { - setLayout(new BorderLayout()); - - // TODO: make an option to change the default setting here: - // (can already be manually toggled by the user) - realTime = true; - - if (searchIcon == null) - searchIcon = getIcon("search-16x16.png"); - if (clearIcon == null) - clearIcon = getIcon("clear-16x16.png"); - - search = new JButton(searchIcon); - if (searchIcon == null) { - search.setText("[s]"); - } - UIUtils.setButtonPressed(search, realTime); - search.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - realTime = !realTime; - UIUtils.setButtonPressed(search, realTime); - text.requestFocus(); - - if (realTime) { - fireActionPerformed(getText()); - } - } - }); - - text = new JTextField(); - text.addKeyListener(new KeyAdapter() { - @Override - public void keyTyped(final KeyEvent e) { - super.keyTyped(e); - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - boolean empty = (text.getText().isEmpty()); - clear.setVisible(!empty); - - if (realTime) { - fireActionPerformed(getText()); - } - } - }); - } - }); - text.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - if (!realTime) { - fireActionPerformed(getText()); - } - } - }); - - clear = new JButton(clearIcon); - if (clearIcon == null) { - clear.setText("(c)"); - } - clear.setBackground(text.getBackground()); - clear.setVisible(false); - clear.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - text.setText(""); - clear.setVisible(false); - text.requestFocus(); - - fireActionPerformed(getText()); - } - }); - - add(search, BorderLayout.WEST); - add(text, BorderLayout.CENTER); - add(clear, BorderLayout.EAST); - } - - /** - * Return the current text displayed by this {@link SearchBar}, or an empty - * {@link String} if none. - * - * @return the text, cannot be NULL - */ - public String getText() { - // Should usually not be NULL, but not impossible - String text = this.text.getText(); - return text == null ? "" : text; - } - - @Override - public void setEnabled(boolean enabled) { - search.setEnabled(enabled); - clear.setEnabled(enabled); - text.setEnabled(enabled); - super.setEnabled(enabled); - } - - private ImageIcon getIcon(String name) { - InputStream in = IOUtils.openResource(SearchBar.class, name); - if (in != null) { - try { - return new ImageIcon(IOUtils.toByteArray(in)); - } catch (IOException e) { - e.printStackTrace(); - } - } - - return null; - } -} diff --git a/src/be/nikiroo/utils/ui/TreeCellSpanner.java b/src/be/nikiroo/utils/ui/TreeCellSpanner.java deleted file mode 100644 index 703bfa1..0000000 --- a/src/be/nikiroo/utils/ui/TreeCellSpanner.java +++ /dev/null @@ -1,169 +0,0 @@ -/* - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ -// Can be found at: https://code.google.com/archive/p/aephyr/source/default/source -// package aephyr.swing; -package be.nikiroo.utils.ui; - -import java.awt.*; -import java.awt.event.*; - -import javax.swing.*; -import javax.swing.tree.*; - -import java.util.*; - -public class TreeCellSpanner extends Container implements TreeCellRenderer, ComponentListener { - - public TreeCellSpanner(JTree tree, TreeCellRenderer renderer) { - if (tree == null || renderer == null) - throw new NullPointerException(); - this.tree = tree; - this.renderer = renderer; - treeParent = tree.getParent(); - if (treeParent != null && treeParent instanceof JViewport) { - treeParent.addComponentListener(this); - } else { - treeParent = null; - tree.addComponentListener(this); - } - } - - protected final JTree tree; - - private TreeCellRenderer renderer; - - private Component rendererComponent; - - private Container treeParent; - - private Map offsets = new HashMap(); - - private TreePath path; - - public TreeCellRenderer getRenderer() { - return renderer; - } - - @Override - public Component getTreeCellRendererComponent(JTree tree, Object value, - boolean selected, boolean expanded, boolean leaf, int row, - boolean hasFocus) { - path = tree.getPathForRow(row); - if (path != null && path.getLastPathComponent() != value) - path = null; - rendererComponent = renderer.getTreeCellRendererComponent( - tree, value, selected, expanded, leaf, row, hasFocus); - if (getComponentCount() < 1 || getComponent(0) != rendererComponent) { - removeAll(); - add(rendererComponent); - } - return this; - } - - @Override - public void doLayout() { - int x = getX(); - if (x < 0) - return; - if (path != null) { - Integer offset = offsets.get(path); - if (offset == null || offset.intValue() != x) { - offsets.put(path, x); - fireTreePathChanged(path); - } - } - rendererComponent.setBounds(getX(), getY(), getWidth(), getHeight()); - } - - @Override - public void paint(Graphics g) { - if (rendererComponent != null) - rendererComponent.paint(g); - } - - @Override - public Dimension getPreferredSize() { - Dimension s = rendererComponent.getPreferredSize(); - // check if path count is greater than 1 to exclude the root - if (path != null && path.getPathCount() > 1) { - Integer offset = offsets.get(path); - if (offset != null) { - int width; - if (tree.getParent() == treeParent) { - width = treeParent.getWidth(); - } else { - if (treeParent != null) { - treeParent.removeComponentListener(this); - tree.addComponentListener(this); - treeParent = null; - } - if (tree.getParent() instanceof JViewport) { - treeParent = tree.getParent(); - tree.removeComponentListener(this); - treeParent.addComponentListener(this); - width = treeParent.getWidth(); - } else { - width = tree.getWidth(); - } - } - s.width = width - offset; - } - } - return s; - } - - - protected void fireTreePathChanged(TreePath path) { - if (path.getPathCount() > 1) { - // this cannot be used for the root node or else - // the entire tree will keep being revalidated ad infinitum - TreeModel model = tree.getModel(); - Object node = path.getLastPathComponent(); - if (node instanceof TreeNode && (model instanceof DefaultTreeModel - || (model instanceof TreeModelTransformer && - (model=((TreeModelTransformer)model).getModel()) instanceof DefaultTreeModel))) { - ((DefaultTreeModel)model).nodeChanged((TreeNode)node); - } else { - model.valueForPathChanged(path, node.toString()); - } - } else { - // root! - - } - } - - - private int lastWidth; - - @Override - public void componentHidden(ComponentEvent e) {} - - @Override - public void componentMoved(ComponentEvent e) {} - - @Override - public void componentResized(ComponentEvent e) { - if (e.getComponent().getWidth() != lastWidth) { - lastWidth = e.getComponent().getWidth(); - for (int row=tree.getRowCount(); --row>=0;) { - fireTreePathChanged(tree.getPathForRow(row)); - } - } - } - - @Override - public void componentShown(ComponentEvent e) {} - -} diff --git a/src/be/nikiroo/utils/ui/TreeModelTransformer.java b/src/be/nikiroo/utils/ui/TreeModelTransformer.java deleted file mode 100644 index 9568f17..0000000 --- a/src/be/nikiroo/utils/ui/TreeModelTransformer.java +++ /dev/null @@ -1,1217 +0,0 @@ -/* - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ -// Can be found at: https://code.google.com/archive/p/aephyr/source/default/source -// package aephyr.swing; -package be.nikiroo.utils.ui; - -import java.text.Collator; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.Enumeration; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.HashMap; -import java.util.Iterator; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.swing.JTree; -import javax.swing.SortOrder; -import javax.swing.event.EventListenerList; -import javax.swing.event.TreeExpansionEvent; -import javax.swing.event.TreeExpansionListener; -import javax.swing.event.TreeModelEvent; -import javax.swing.event.TreeModelListener; -import javax.swing.event.TreeWillExpandListener; -import javax.swing.tree.ExpandVetoException; -import javax.swing.tree.TreeModel; -import javax.swing.tree.TreePath; - - -public class TreeModelTransformer implements TreeModel { - - public TreeModelTransformer(JTree tree, TreeModel model) { - if (tree == null) - throw new IllegalArgumentException(); - if (model == null) - throw new IllegalArgumentException(); - this.tree = tree; - this.model = model; - handler = createHandler(); - addListeners(); - } - - private JTree tree; - - private TreeModel model; - - private Handler handler; - - private Filter filter; - - private TreePath filterStartPath; - - private int filterDepthLimit; - - private SortOrder sortOrder = SortOrder.UNSORTED; - - private Map converters; - - protected EventListenerList listenerList = new EventListenerList(); - - protected Handler createHandler() { - return new Handler(); - } - - protected void addListeners() { - tree.addTreeExpansionListener(handler); - model.addTreeModelListener(handler); - } - - protected void removeListeners() { - tree.removeTreeExpansionListener(handler); - model.removeTreeModelListener(handler); - } - - public void dispose() { - removeListeners(); - } - - public TreeModel getModel() { - return model; - } - - private Converter getConverter(Object node) { - return converters == null ? null : converters.get(node); - } - - int convertRowIndexToView(Object parent, int index) { - Converter converter = getConverter(parent); - if (converter != null) - return converter.convertRowIndexToView(index); - return index; - } - - int convertRowIndexToModel(Object parent, int index) { - Converter converter = getConverter(parent); - if (converter != null) - return converter.convertRowIndexToModel(index); - return index; - } - - @Override - public Object getChild(Object parent, int index) { - return model.getChild(parent, convertRowIndexToModel(parent, index)); - } - - @Override - public int getChildCount(Object parent) { - Converter converter = getConverter(parent); - if (converter != null) - return converter.getChildCount(); - return model.getChildCount(parent); - } - - @Override - public int getIndexOfChild(Object parent, Object child) { - int index = model.getIndexOfChild(parent, child); - if (index < 0) - return -1; - return convertRowIndexToView(parent, index); - } - - @Override - public Object getRoot() { - return model.getRoot(); - } - - @Override - public boolean isLeaf(Object node) { - return model.isLeaf(node); - } - - @Override - public void valueForPathChanged(TreePath path, Object newValue) { - model.valueForPathChanged(path, newValue); - } - - @Override - public void addTreeModelListener(TreeModelListener l) { - listenerList.add(TreeModelListener.class, l); - } - - @Override - public void removeTreeModelListener(TreeModelListener l) { - listenerList.remove(TreeModelListener.class, l); - } - - /** - * Set the comparator that compares nodes in sorting. - * @param comparator - * @see #getComparator() - */ - public void setComparator(Comparator comparator) { - handler.setComparator(comparator); - } - - /** - * @return comparator that compares nodes - * @see #setComparator(Comparator) - */ - public Comparator getComparator() { - return handler.getComparator(); - } - - public void setSortOrder(SortOrder newOrder) { - SortOrder oldOrder = sortOrder; - if (oldOrder == newOrder) - return; - sortOrder = newOrder; - ArrayList paths = null; - switch (newOrder) { - case ASCENDING: - if (oldOrder == SortOrder.DESCENDING) { - flip(); - } else { - paths = sort(); - } - break; - case DESCENDING: - if (oldOrder == SortOrder.ASCENDING) { - flip(); - } else { - paths = sort(); - } - break; - case UNSORTED: - unsort(); - break; - } - fireTreeStructureChangedAndExpand(new TreePath(getRoot()), paths, true); - } - - public SortOrder getSortOrder() { - return sortOrder; - } - - public void toggleSortOrder() { - setSortOrder(sortOrder == SortOrder.ASCENDING ? - SortOrder.DESCENDING : SortOrder.ASCENDING); - } - - - /** - * Flip all sorted paths. - */ - private void flip() { - for (Converter c : converters.values()) { - flip(c.viewToModel); - } - } - - /** - * Flip array. - * @param array - */ - private static void flip(int[] array) { - for (int left=0, right=array.length-1; - left cons = converters.values().iterator(); - while (cons.hasNext()) { - Converter converter = cons.next(); - if (!converter.isFiltered()) { - cons.remove(); - } else { - Arrays.sort(converter.viewToModel); - } - } - } - } - - /** - * Sort root and expanded descendants. - * @return list of paths that were sorted - */ - private ArrayList sort() { - if (converters == null) - converters = createConvertersMap(); //new IdentityHashMap(); - return sortHierarchy(new TreePath(model.getRoot())); - } - - /** - * Sort path and expanded descendants. - * @param path - * @return list of paths that were sorted - */ - private ArrayList sortHierarchy(TreePath path) { - ValueIndexPair[] pairs = createValueIndexPairArray(20); - ArrayList list = new ArrayList(); - pairs = sort(path.getLastPathComponent(), pairs); - list.add(path); - Enumeration paths = tree.getExpandedDescendants(path); - if (paths != null) - while (paths.hasMoreElements()) { - path = paths.nextElement(); - pairs = sort(path.getLastPathComponent(), pairs); - list.add(path); - } - return list; - } - - private ValueIndexPair[] sort(Object node, ValueIndexPair[] pairs) { - Converter converter = getConverter(node); - TreeModel mdl = model; - int[] vtm; - if (converter != null) { - vtm = converter.viewToModel; - if (pairs.length < vtm.length) - pairs = createValueIndexPairArray(vtm.length); - for (int i=vtm.length; --i>=0;) { - int idx = vtm[i]; - pairs[i].index = idx; - pairs[i].value = (N)mdl.getChild(node, idx); - } - } else { - int count = mdl.getChildCount(node); - if (count <= 0) - return pairs; - if (pairs.length < count) - pairs = createValueIndexPairArray(count); - for (int i=count; --i>=0;) { - pairs[i].index = i; - pairs[i].value = (N)mdl.getChild(node, i); - } - vtm = new int[count]; - } - Arrays.sort(pairs, 0, vtm.length, handler); - for (int i=vtm.length; --i>=0;) - vtm[i] = pairs[i].index; - if (converter == null) { - converters.put(node, new Converter(vtm, false)); - } - if (sortOrder == SortOrder.DESCENDING) - flip(vtm); - return pairs; - } - - private ValueIndexPair[] createValueIndexPairArray(int len) { - ValueIndexPair[] pairs = new ValueIndexPair[len]; - for (int i=len; --i>=0;) - pairs[i] = new ValueIndexPair(); - return pairs; - } - - public void setFilter(Filter filter) { - setFilter(filter, null); - } - - public void setFilter(Filter filter, TreePath startingPath) { - setFilter(filter, null, -1); - } - - public void setFilter(Filter filter, TreePath startingPath, int depthLimit) { - if (filter == null && startingPath != null) - throw new IllegalArgumentException(); - if (startingPath != null && startingPath.getPathCount() == 1) - startingPath = null; - Filter oldFilter = this.filter; - TreePath oldStartPath = filterStartPath; - this.filter = filter; - filterStartPath = startingPath; - filterDepthLimit = depthLimit; - applyFilter(oldFilter, oldStartPath, null, true); - } - - public Filter getFilter() { - return filter; - } - - public TreePath getFilterStartPath() { - return filterStartPath; - } - - private void applyFilter(Filter oldFilter, TreePath oldStartPath, Collection expanded, boolean sort) { - TreePath startingPath = filterStartPath; - ArrayList expand = null; - if (filter == null) { - converters = null; - } else { - if (converters == null || startingPath == null) { - converters = createConvertersMap(); - } else if (oldFilter != null) { - // unfilter the oldStartPath if oldStartPath isn't descendant of startingPath - if (oldStartPath == null) { - converters = createConvertersMap(); - fireTreeStructureChangedAndExpand(new TreePath(getRoot()), null, true); - } else if (!startingPath.isDescendant(oldStartPath)) { - Object node = oldStartPath.getLastPathComponent(); - handler.removeConverter(getConverter(node), node); - fireTreeStructureChangedAndExpand(oldStartPath, null, true); - } - } - expand = new ArrayList(); - TreePath path = startingPath != null ? startingPath : new TreePath(getRoot()); - if (!applyFilter(filter, path, expand, filterDepthLimit)) { - converters.put(path.getLastPathComponent(), new Converter(Converter.EMPTY, true)); - } - } - if (startingPath == null) - startingPath = new TreePath(getRoot()); - fireTreeStructureChanged(startingPath); - if (expanded != null) - expand.retainAll(expanded); - expandPaths(expand); - if (sort && sortOrder != SortOrder.UNSORTED) { - if (filter == null) - converters = createConvertersMap(); - if (startingPath.getPathCount() > 1 && oldFilter != null) { - // upgrade startingPath or sort oldStartPath - if (oldStartPath == null) { - startingPath = new TreePath(getRoot()); - } else if (oldStartPath.isDescendant(startingPath)) { - startingPath = oldStartPath; - } else if (!startingPath.isDescendant(oldStartPath)) { - expand = sortHierarchy(oldStartPath); - fireTreeStructureChanged(oldStartPath); - expandPaths(expand); - } - } - expand = sortHierarchy(startingPath); - fireTreeStructureChanged(startingPath); - expandPaths(expand); - } - - } - - private boolean applyFilter(Filter filter, TreePath path, ArrayList expand) { - int depthLimit = filterDepthLimit; - if (depthLimit >= 0) { - depthLimit -= filterStartPath.getPathCount() - path.getPathCount(); - if (depthLimit <= 0) - return false; - } - return applyFilter(filter, path, expand, depthLimit); - } - - private boolean applyFilter(Filter filter, TreePath path, ArrayList expand, int depthLimit) { - Object node = path.getLastPathComponent(); - int count = model.getChildCount(node); - int[] viewToModel = null; - int viewIndex = 0; - boolean needsExpand = false; - boolean isExpanded = false; - if (depthLimit > 0) - depthLimit--; - for (int i=0; i 1) { - expand.add(path); - } - if (viewToModel != null) { - if (viewIndex < viewToModel.length) - viewToModel = Arrays.copyOf(viewToModel, viewIndex); - // a node must have a converter to signify that tree modifications - // need to query the filter, so have to put in converter - // even if viewIndex == viewToModel.length - converters.put(node, new Converter(viewToModel, true)); - return true; - } - return false; - } - - - private void expandPaths(ArrayList paths) { - if (paths == null || paths.isEmpty()) - return; - JTree tre = tree; - for (TreePath path : paths) - tre.expandPath(path); - } - - - private void fireTreeStructureChangedAndExpand(TreePath path, ArrayList list, boolean retainSelection) { - Enumeration paths = list != null ? - Collections.enumeration(list) : tree.getExpandedDescendants(path); - TreePath[] sel = retainSelection ? tree.getSelectionPaths() : null; - fireTreeStructureChanged(path); - if (paths != null) - while (paths.hasMoreElements()) - tree.expandPath(paths.nextElement()); - if (sel != null) - tree.setSelectionPaths(sel); - } - - - - protected void fireTreeStructureChanged(TreePath path) { - Object[] listeners = listenerList.getListenerList(); - TreeModelEvent e = null; - for (int i = listeners.length-2; i>=0; i-=2) { - if (listeners[i]==TreeModelListener.class) { - if (e == null) - e = new TreeModelEvent(this, path, null, null); - ((TreeModelListener)listeners[i+1]).treeStructureChanged(e); - } - } - } - - protected void fireTreeNodesChanged(TreePath path, int[] childIndices, Object[] childNodes) { - Object[] listeners = listenerList.getListenerList(); - TreeModelEvent e = null; - for (int i = listeners.length-2; i>=0; i-=2) { - if (listeners[i]==TreeModelListener.class) { - if (e == null) - e = new TreeModelEvent(this, path, childIndices, childNodes); - ((TreeModelListener)listeners[i+1]).treeNodesChanged(e); - } - } - } - - protected void fireTreeNodesInserted(TreePath path, int[] childIndices, Object[] childNodes) { - Object[] listeners = listenerList.getListenerList(); - TreeModelEvent e = null; - for (int i = listeners.length-2; i>=0; i-=2) { - if (listeners[i]==TreeModelListener.class) { - if (e == null) - e = new TreeModelEvent(this, path, childIndices, childNodes); - ((TreeModelListener)listeners[i+1]).treeNodesInserted(e); - } - } - } - - protected void fireTreeNodesRemoved(TreePath path, int[] childIndices, Object[] childNodes) { - Object[] listeners = listenerList.getListenerList(); - TreeModelEvent e = null; - for (int i = listeners.length-2; i>=0; i-=2) { - if (listeners[i]==TreeModelListener.class) { - if (e == null) - e = new TreeModelEvent(this, path, childIndices, childNodes); - ((TreeModelListener)listeners[i+1]).treeNodesRemoved(e); - } - } - } - - - protected class Handler implements Comparator>, - TreeModelListener, TreeExpansionListener { - - private Comparator comparator; - - private Collator collator = Collator.getInstance(); - - void setComparator(Comparator cmp) { - comparator = cmp; - collator = cmp == null ? Collator.getInstance() : null; - } - - Comparator getComparator() { - return comparator; - } - - // TODO, maybe switch to TreeWillExpandListener? - // TreeExpansionListener was used in case an expanded node - // had children that would also be expanded, but it is impossible - // for hidden nodes' expansion state to survive a SortOrder change - // since they are all erased when the tree structure change event - // is fired after changing the SortOrder. - - @Override - public void treeCollapsed(TreeExpansionEvent e) {} - - @Override - public void treeExpanded(TreeExpansionEvent e) { - if (sortOrder != SortOrder.UNSORTED) { - TreePath path = e.getPath(); - Converter converter = getConverter(path.getLastPathComponent()); - if (converter == null) { - ArrayList paths = sortHierarchy(path); - fireTreeStructureChangedAndExpand(path, paths, false); - } - } - } - - private boolean isFiltered(Object node) { - Converter c = getConverter(node); - return c == null ? false : c.isFiltered(); - } - - private boolean acceptable(TreePath path, Object[] childNodes, int index, ArrayList expand) { - return acceptable(path, childNodes, index) || - applyFilter(filter, path.pathByAddingChild(childNodes[index]), expand); - } - - @Override - public void treeNodesChanged(TreeModelEvent e) { - treeNodesChanged(e.getTreePath(), e.getChildIndices(), e.getChildren()); - } - - protected void treeNodesChanged(TreePath path, int[] childIndices, Object[] childNodes) { - if (childIndices == null) { - // path should be root path - // reapply filter - if (filter != null) - applyFilter(null, null, null, true); - return; - } - Converter converter = getConverter(path.getLastPathComponent()); - ArrayList expand = null; - if (converter != null) { - expand = new ArrayList(); - int childIndex = 0; - for (int i=0; i= 0) { - // see if the filter dislikes the nodes new state - if (converter.isFiltered() && - !isFiltered(childNodes[i]) && - !acceptable(path, childNodes, i)) { - // maybe it likes a child nodes state - if (!applyFilter(filter, path.pathByAddingChild(childNodes[i]), expand)) - remove(path, childNodes[i]); - continue; - } - childNodes[childIndex] = childNodes[i]; - childIndices[childIndex++] = idx; - } else if (acceptable(path, childNodes, i, expand)) { - int viewIndex = insert(path.getLastPathComponent(), - childNodes[i], childIndices[i], converter); - fireTreeNodesInserted(path, indices(viewIndex), nodes(childNodes[i])); - } - } - if (childIndex == 0) { - maybeFireStructureChange(path, expand); - return; - } - if (sortOrder != SortOrder.UNSORTED && converter.getChildCount() > 1) { - sort(path.getLastPathComponent(), createValueIndexPairArray(converter.getChildCount())); - fireTreeStructureChangedAndExpand(path, null, true); - expandPaths(expand); - return; - } - if (childIndex != childIndices.length) { - childIndices = Arrays.copyOf(childIndices, childIndex); - childNodes = Arrays.copyOf(childNodes, childIndex); - } - } else if (filter != null && isFilteredOut(path)) { - // see if the filter likes the nodes new states - expand = new ArrayList(); - int[] vtm = null; - int idx = 0; - for (int i=0; i expand) { - if (expand != null && !expand.isEmpty()) { - Enumeration expanded = tree.getExpandedDescendants(path); - fireTreeStructureChanged(path); - if (expanded != null) - while (expanded.hasMoreElements()) - tree.expandPath(expanded.nextElement()); - expandPaths(expand); - } - } - - @Override - public void treeNodesInserted(TreeModelEvent e) { - treeNodesInserted(e.getTreePath(), e.getChildIndices(), e.getChildren()); - } - - protected void treeNodesInserted(TreePath path, int[] childIndices, Object[] childNodes) { - Object parent = path.getLastPathComponent(); - Converter converter = getConverter(parent); - ArrayList expand = null; - if (converter != null) { -// if (childIndices.length > 3 && !converter.isFiltered() -// && childIndices.length > converter.getChildCount()/10) { -// TreePath expand = sortHierarchy(path); -// fireTreeStructureChangedAndExpand(expand); -// return; -// } - int childIndex = 0; - for (int i=0; i(); - if (!applyFilter(filter, path.pathByAddingChild(childNodes[i]), expand)) - continue; - } - // shift the appropriate cached modelIndices - int[] vtm = converter.viewToModel; - int modelIndex = childIndices[i]; - for (int j=vtm.length; --j>=0;) { - if (vtm[j] >= modelIndex) - vtm[j] += 1; - } - // insert modelIndex to converter - int viewIndex = insert(parent, childNodes[i], modelIndex, converter); - childNodes[childIndex] = childNodes[i]; - childIndices[childIndex++] = viewIndex; - } - if (childIndex == 0) - return; - if (childIndex != childIndices.length) { - childIndices = Arrays.copyOf(childIndices, childIndex); - childNodes = Arrays.copyOf(childNodes, childIndex); - } - if (childIndex > 1 && sortOrder != SortOrder.UNSORTED) { - sort(childIndices, childNodes); - } - } else if (filter != null && isFilteredOut(path)) { - // apply filter to inserted nodes - int[] vtm = null; - int idx = 0; - expand = new ArrayList(); - for (int i=0; i= 0) { - childNodes[len] = childNodes[i]; - childIndices[len++] = viewIndex; - } - } - if (len == 0) - return; - if (converter.isFiltered() && converter.getChildCount() == len) { - ArrayList expand = new ArrayList(); - if (applyFilter(filter, path, expand)) { - expand.retainAll(getExpandedPaths(path)); - if (sortOrder != SortOrder.UNSORTED) - sortHierarchy(path); - fireTreeStructureChangedAndExpand(path, expand, true); - } else if (isFilterStartPath(path)) { - converters.put(parent, new Converter(Converter.EMPTY, true)); - fireTreeStructureChanged(path); - } else { - remove(path.getParentPath(), parent); - } - return; - } - if (len != childIndices.length) { - childIndices = Arrays.copyOf(childIndices, len); - childNodes = Arrays.copyOf(childNodes, len); - } - if (len > 1 && sortOrder != SortOrder.UNSORTED) { - sort(childIndices, childNodes); - } - if (childIndices.length == 1) { - converter.remove(converter.convertRowIndexToModel(childIndices[0])); - } else { - converter.remove(childIndices); - } - } else if (filter != null && isFilteredOut(path)) { - return; - } - fireTreeNodesRemoved(path, childIndices, childNodes); - } - - private Collection getExpandedPaths(TreePath path) { - Enumeration en = tree.getExpandedDescendants(path); - if (en == null) - return Collections.emptySet(); - HashSet expanded = new HashSet(); - while (en.hasMoreElements()) - expanded.add(en.nextElement()); - return expanded; - } - - @Override - public void treeStructureChanged(TreeModelEvent e) { - if (converters != null) { - // not enough information to properly clean up - // reapply filter/sort - converters = createConvertersMap(); - TreePath[] sel = tree.getSelectionPaths(); - if (filter != null) { - applyFilter(null, null, getExpandedPaths(new TreePath(getRoot())), false); - } - if (sortOrder != SortOrder.UNSORTED) { - TreePath path = new TreePath(getRoot()); - ArrayList expand = sortHierarchy(path); - fireTreeStructureChangedAndExpand(path, expand, false); - } - if (sel != null) { - tree.clearSelection(); - TreePath changedPath = e.getTreePath(); - for (TreePath path : sel) { - if (!changedPath.isDescendant(path)) - tree.addSelectionPath(path); - } - } - } else { - fireTreeStructureChanged(e.getTreePath()); - } - } - - - @Override - public final int compare(ValueIndexPair a, ValueIndexPair b) { - return compareNodes(a.value, b.value); - } - - - protected int compareNodes(N a, N b) { - if (comparator != null) - return comparator.compare(a, b); - return collator.compare(a.toString(), b.toString()); - } - - private void removeConverter(Object node) { - Converter c = getConverter(node); - if (c != null) - removeConverter(c, node); - } - - private void removeConverter(Converter converter, Object node) { - for (int i=converter.getChildCount(); --i>=0;) { - int index = converter.convertRowIndexToModel(i); - Object child = model.getChild(node, index); - Converter c = getConverter(child); - if (c != null) - removeConverter(c, child); - } - converters.remove(node); - } - - private boolean isFilteredOut(TreePath path) { - if (filterStartPath != null && !filterStartPath.isDescendant(path)) - return false; - TreePath parent = path.getParentPath(); - // root should always have a converter if filter is non-null, - // so if parent is ever null, there is a bug somewhere else - Converter c = getConverter(parent.getLastPathComponent()); - if (c != null) { - return getIndexOfChild( - parent.getLastPathComponent(), - path.getLastPathComponent()) < 0; - } - return isFilteredOut(parent); - } - - private void filterIn(int[] vtm, int vtmLength, TreePath path, ArrayList expand) { - Object node = path.getLastPathComponent(); - if (vtmLength != vtm.length) - vtm = Arrays.copyOf(vtm, vtmLength); - Converter converter = new Converter(vtm, true); - converters.put(node, converter); - insert(path.getParentPath(), node); - tree.expandPath(path); - expandPaths(expand); - } - - private boolean acceptable(TreePath path, Object[] nodes, int index) { - Object node = nodes[index]; - return filter.acceptNode(path, (N)node, model.isLeaf(node)); - } - - private int ascInsertionIndex(int[] vtm, Object parent, N node, int idx) { - for (int i=vtm.length; --i>=0;) { - int cmp = compareNodes(node, (N)model.getChild(parent, vtm[i])); - if (cmp > 0 || (cmp == 0 && idx > vtm[i])) { - return i+1; - } - } - return 0; - } - - - private int dscInsertionIndex(int[] vtm, Object parent, N node, int idx) { - for (int i=vtm.length; --i>=0;) { - int cmp = compareNodes(node, (N)model.getChild(parent, vtm[i])); - if (cmp < 0) { - return i+1; - } else if (cmp == 0 && idx < vtm[i]) { - return i; - } - } - return 0; - } - - - /** - * Inserts the specified path and node and any parent paths as necessary. - *

- * Fires appropriate event. - * @param path - * @param node - */ - private void insert(TreePath path, Object node) { - Object parent = path.getLastPathComponent(); - Converter converter = converters.get(parent); - int modelIndex = model.getIndexOfChild(parent, node); - if (converter == null) { - converter = new Converter(indices(modelIndex), true); - converters.put(parent, converter); - insert(path.getParentPath(), parent); - } else { - int viewIndex = insert(parent, node, modelIndex, converter); - fireTreeNodesInserted(path, indices(viewIndex), nodes(node)); - } - } - - /** - * Inserts node into parent in correct sort order. - *

- * Responsibility of caller to fire appropriate event with the returned viewIndex. - * @param path - * @param node - * @param modelIndex - * @param converter - * @return viewIndex - */ - private int insert(Object parent, Object node, int modelIndex, Converter converter) { - int[] vtm = converter.viewToModel; - int viewIndex; - switch (sortOrder) { - case ASCENDING: - viewIndex = ascInsertionIndex(vtm, parent, (N)node, modelIndex); - break; - case DESCENDING: - viewIndex = dscInsertionIndex(vtm, parent, (N)node, modelIndex); - break; - default: case UNSORTED: - viewIndex = unsortedInsertionIndex(vtm, modelIndex); - break; - } - int[] a = new int[vtm.length+1]; - System.arraycopy(vtm, 0, a, 0, viewIndex); - System.arraycopy(vtm, viewIndex, a, viewIndex+1, vtm.length-viewIndex); - a[viewIndex] = modelIndex; - converter.viewToModel = a; - return viewIndex; - } - - private void remove(TreePath path, Object node) { - Object parent = path.getLastPathComponent(); - if (path.getPathCount() == 1 || (filterStartPath != null && filterStartPath.equals(path))) { - removeConverter(node); - converters.put(parent, new Converter(Converter.EMPTY, true)); - fireTreeNodesRemoved(path, indices(0), nodes(node)); - return; - } - Converter converter = converters.get(parent); - int modelIndex = model.getIndexOfChild(parent, node); - int viewIndex = converter.remove(modelIndex); - switch (viewIndex) { - default: - removeConverter(node); - fireTreeNodesRemoved(path, indices(viewIndex), nodes(node)); - break; - case Converter.ONLY_INDEX: -// if (path.getParentPath() == null) { -// // reached filter root -// removeConverter(node); -// converters.put(parent, new Converter(Converter.EMPTY, true)); -// fireTreeNodesRemoved(path, indices(0), nodes(node)); -// return; -// } - remove(path.getParentPath(), parent); - break; - case Converter.INDEX_NOT_FOUND: - removeConverter(node); - } - } - - - - } - - - - private static int unsortedInsertionIndex(int[] vtm, int idx) { - for (int i=vtm.length; --i>=0;) - if (vtm[i] < idx) - return i+1; - return 0; - } - - private static void sort(int[] childIndices, Object[] childNodes) { - int len = childIndices.length; - ValueIndexPair[] pairs = new ValueIndexPair[len]; - for (int i=len; --i>=0;) - pairs[i] = new ValueIndexPair(childIndices[i], childNodes[i]); - Arrays.sort(pairs); - for (int i=len; --i>=0;) { - childIndices[i] = pairs[i].index; - childNodes[i] = pairs[i].value; - } - } - - private static int[] indices(int...indices) { - return indices; - } - - private static Object[] nodes(Object...nodes) { - return nodes; - } - - - - - /** - * This class has a dual purpose, both related to comparing/sorting. - *

- * The Handler class sorts an array of ValueIndexPair based on the value. - * Used for sorting the view. - *

- * ValueIndexPair sorts itself based on the index. - * Used for sorting childIndices for fire* methods. - */ - private static class ValueIndexPair implements Comparable> { - ValueIndexPair() {} - - ValueIndexPair(int idx, N val) { - index = idx; - value = val; - } - - N value; - - int index; - - public int compareTo(ValueIndexPair o) { - return index - o.index; - } - } - - private static class Converter { - - static final int[] EMPTY = new int[0]; - - static final int ONLY_INDEX = -2; - - static final int INDEX_NOT_FOUND = -1; - - Converter(int[] vtm, boolean filtered) { - viewToModel = vtm; - isFiltered = filtered; - } - - private int[] viewToModel; - - private boolean isFiltered; - -// public boolean equals(Converter conv) { -// if (conv == null) -// return false; -// if (isFiltered != conv.isFiltered) -// return false; -// return Arrays.equals(viewToModel, conv.viewToModel); -// } - - boolean isFiltered() { - return isFiltered; - } - - void remove(int viewIndices[]) { - int len = viewToModel.length - viewIndices.length; - if (len == 0) { - viewToModel = EMPTY; - } else { - int[] oldVTM = viewToModel; - int[] newVTM = new int[len]; - for (int oldIndex=0, newIndex=0, removeIndex=0; - newIndex=0;) - if (newVTM[i] > idx) - newVTM[i]--; - for (int i=oldIndex; i idx) - oldVTM[i]--; - } - newVTM[newIndex] = oldVTM[oldIndex]; - } - viewToModel = newVTM; - } - } - - /** - * @param modelIndex - * @return viewIndex that was removed
- * or ONLY_INDEX if the modelIndex is the only one in the view
- * or INDEX_NOT_FOUND if the modelIndex is not in the view - */ - int remove(int modelIndex) { - int[] vtm = viewToModel; - for (int i=vtm.length; --i>=0;) { - if (vtm[i] > modelIndex) { - vtm[i] -= 1; - } else if (vtm[i] == modelIndex) { - if (vtm.length == 1) { - viewToModel = EMPTY; - return ONLY_INDEX; - } - int viewIndex = i; - while (--i>=0) { - if (vtm[i] > modelIndex) - vtm[i] -= 1; - } - int[] a = new int[vtm.length-1]; - if (viewIndex > 0) - System.arraycopy(vtm, 0, a, 0, viewIndex); - int len = a.length-viewIndex; - if (len > 0) - System.arraycopy(vtm, viewIndex+1, a, viewIndex, len); - viewToModel = a; - return viewIndex; - } - } - return INDEX_NOT_FOUND; - } - - - int getChildCount() { - return viewToModel.length; - } - - /** - * @param modelIndex - * @return viewIndex corresponding to modelIndex
- * or INDEX_NOT_FOUND if the modelIndex is not in the view - */ - int convertRowIndexToView(int modelIndex) { - int[] vtm = viewToModel; - for (int i=vtm.length; --i>=0;) { - if (vtm[i] == modelIndex) - return i; - } - return INDEX_NOT_FOUND; - } - - int convertRowIndexToModel(int viewIndex) { - return viewToModel[viewIndex]; - } - } - - public interface Filter { - boolean acceptNode(TreePath parent, N node, boolean leaf); - } - - public static class RegexFilter implements Filter { - - public RegexFilter(Pattern pattern, boolean leaf) { - matcher = pattern.matcher(""); - leafOnly = leaf; - } - - private Matcher matcher; - - private boolean leafOnly; - - public boolean acceptNode(TreePath parent, N node, boolean leaf) { - if (leafOnly && !leaf) - return false; - matcher.reset(getStringValue(node)); - return matcher.find(); - } - - protected String getStringValue(N node) { - return node.toString(); - } - } - - - private static Map createConvertersMap() { - return new HashMap(); - } -} diff --git a/src/be/nikiroo/utils/ui/TreeSnapshot.java b/src/be/nikiroo/utils/ui/TreeSnapshot.java deleted file mode 100644 index ef9a6fb..0000000 --- a/src/be/nikiroo/utils/ui/TreeSnapshot.java +++ /dev/null @@ -1,127 +0,0 @@ -package be.nikiroo.utils.ui; - -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; - -import javax.swing.JTree; -import javax.swing.tree.TreeModel; -import javax.swing.tree.TreeNode; -import javax.swing.tree.TreePath; - -public class TreeSnapshot { - private interface NodeAction { - public void run(TreeNode node); - } - - private JTree tree; - private TreePath[] selectionPaths; - private List expanded; - - public TreeSnapshot(JTree tree) { - this.tree = tree; - - selectionPaths = tree.getSelectionPaths(); - if (selectionPaths == null) { - selectionPaths = new TreePath[0]; - } - - expanded = new ArrayList(); - forEach(tree, new NodeAction() { - @Override - public void run(TreeNode node) { - TreePath path = nodeToPath(node); - if (path != null) { - if (TreeSnapshot.this.tree.isExpanded(path)) { - expanded.add(path); - } - } - } - }); - } - - public void apply() { - applyTo(tree); - } - - public void applyTo(JTree tree) { - final List newExpanded = new ArrayList(); - final List newSlectionPaths = new ArrayList(); - - forEach(tree, new NodeAction() { - @Override - public void run(TreeNode newNode) { - TreePath newPath = nodeToPath(newNode); - if (newPath != null) { - for (TreePath path : selectionPaths) { - if (isSamePath(path, newPath)) { - newSlectionPaths.add(newPath); - if (expanded.contains(path)) { - newExpanded.add(newPath); - } - } - } - } - } - }); - - for (TreePath newPath : newExpanded) { - tree.expandPath(newPath); - } - - tree.setSelectionPaths(newSlectionPaths.toArray(new TreePath[0])); - } - - // You can override this - protected boolean isSamePath(TreePath oldPath, TreePath newPath) { - return newPath.toString().equals(oldPath.toString()); - } - - private void forEach(JTree tree, NodeAction action) { - forEach(tree.getModel(), tree.getModel().getRoot(), action); - } - - private void forEach(TreeModel model, Object parent, NodeAction action) { - if (!(parent instanceof TreeNode)) - return; - - TreeNode node = (TreeNode) parent; - - action.run(node); - int count = model.getChildCount(node); - for (int i = 0; i < count; i++) { - Object child = model.getChild(node, i); - forEach(model, child, action); - } - } - - private static TreePath nodeToPath(TreeNode node) { - List nodes = new LinkedList(); - if (node != null) { - nodes.add(node); - node = node.getParent(); - while (node != null) { - nodes.add(0, node); - node = node.getParent(); - } - } - - return nodes.isEmpty() ? null : new TreePath(nodes.toArray()); - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("Tree Snapshot of: ").append(tree).append("\n"); - builder.append("Selected paths:\n"); - for (TreePath path : selectionPaths) { - builder.append("\t").append(path).append("\n"); - } - builder.append("Expanded paths:\n"); - for (TreePath epath : expanded) { - builder.append("\t").append(epath).append("\n"); - } - - return builder.toString(); - } -} diff --git a/src/be/nikiroo/utils/ui/UIUtils.java b/src/be/nikiroo/utils/ui/UIUtils.java deleted file mode 100644 index ce7bcc1..0000000 --- a/src/be/nikiroo/utils/ui/UIUtils.java +++ /dev/null @@ -1,371 +0,0 @@ -package be.nikiroo.utils.ui; - -import java.awt.Color; -import java.awt.Component; -import java.awt.Desktop; -import java.awt.GradientPaint; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Paint; -import java.awt.RadialGradientPaint; -import java.awt.RenderingHints; -import java.io.IOException; -import java.net.URISyntaxException; - -import javax.swing.JButton; -import javax.swing.JComponent; -import javax.swing.JEditorPane; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JScrollPane; -import javax.swing.UIManager; -import javax.swing.UnsupportedLookAndFeelException; -import javax.swing.event.HyperlinkEvent; -import javax.swing.event.HyperlinkListener; - -import be.nikiroo.utils.Version; -import be.nikiroo.utils.VersionCheck; - -/** - * Some Java Swing utilities. - * - * @author niki - */ -public class UIUtils { - static private Color buttonNormal; - static private Color buttonPressed; - - /** - * Set a fake "native Look & Feel" for the application if possible - * (check for the one currently in use, then try GTK). - *

- * Must be called prior to any GUI work. - * - * @return TRUE if it succeeded - */ - static public boolean setLookAndFeel() { - // native look & feel - String noLF = "javax.swing.plaf.metal.MetalLookAndFeel"; - String lf = UIManager.getSystemLookAndFeelClassName(); - if (lf.equals(noLF)) - lf = "com.sun.java.swing.plaf.gtk.GTKLookAndFeel"; - - return setLookAndFeel(lf); - } - - /** - * Switch to the given Look & Feel for the application if possible - * (check for the one currently in use, then try GTK). - *

- * Must be called prior to any GUI work. - * - * @param laf - * the Look & Feel to use - * - * @return TRUE if it succeeded - */ - static public boolean setLookAndFeel(String laf) { - try { - UIManager.setLookAndFeel(laf); - return true; - } catch (InstantiationException e) { - } catch (ClassNotFoundException e) { - } catch (UnsupportedLookAndFeelException e) { - } catch (IllegalAccessException e) { - } - - return false; - } - - /** - * Draw a 3D-looking ellipse at the given location, if the given - * {@link Graphics} object is compatible (with {@link Graphics2D}); draw a - * simple ellipse if not. - * - * @param g - * the {@link Graphics} to draw on - * @param color - * the base colour - * @param x - * the X coordinate - * @param y - * the Y coordinate - * @param width - * the width radius - * @param height - * the height radius - */ - static public void drawEllipse3D(Graphics g, Color color, int x, int y, - int width, int height) { - drawEllipse3D(g, color, x, y, width, height, true); - } - - /** - * Draw a 3D-looking ellipse at the given location, if the given - * {@link Graphics} object is compatible (with {@link Graphics2D}); draw a - * simple ellipse if not. - * - * @param g - * the {@link Graphics} to draw on - * @param color - * the base colour - * @param x - * the X coordinate of the upper left corner - * @param y - * the Y coordinate of the upper left corner - * @param width - * the width radius - * @param height - * the height radius - * @param fill - * fill the content of the ellipse - */ - static public void drawEllipse3D(Graphics g, Color color, int x, int y, - int width, int height, boolean fill) { - if (g instanceof Graphics2D) { - Graphics2D g2 = (Graphics2D) g; - g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON); - - // Retains the previous state - Paint oldPaint = g2.getPaint(); - - // Base shape - g2.setColor(color); - if (fill) { - g2.fillOval(x, y, width, height); - } else { - g2.drawOval(x, y, width, height); - } - - // Compute dark/bright colours - Paint p = null; - Color dark = color.darker().darker(); - Color bright = color.brighter().brighter(); - Color darkEnd = new Color(dark.getRed(), dark.getGreen(), - dark.getBlue(), 0); - Color darkPartial = new Color(dark.getRed(), dark.getGreen(), - dark.getBlue(), 64); - Color brightEnd = new Color(bright.getRed(), bright.getGreen(), - bright.getBlue(), 0); - - // Adds shadows at the bottom left - p = new GradientPaint(0, height, dark, width, 0, darkEnd); - g2.setPaint(p); - if (fill) { - g2.fillOval(x, y, width, height); - } else { - g2.drawOval(x, y, width, height); - } - // Adds highlights at the top right - p = new GradientPaint(width, 0, bright, 0, height, brightEnd); - g2.setPaint(p); - if (fill) { - g2.fillOval(x, y, width, height); - } else { - g2.drawOval(x, y, width, height); - } - - // Darken the edges - p = new RadialGradientPaint(x + width / 2f, y + height / 2f, - Math.min(width / 2f, height / 2f), new float[] { 0f, 1f }, - new Color[] { darkEnd, darkPartial }, - RadialGradientPaint.CycleMethod.NO_CYCLE); - g2.setPaint(p); - if (fill) { - g2.fillOval(x, y, width, height); - } else { - g2.drawOval(x, y, width, height); - } - - // Adds inner highlight at the top right - p = new RadialGradientPaint(x + 3f * width / 4f, y + height / 4f, - Math.min(width / 4f, height / 4f), - new float[] { 0.0f, 0.8f }, - new Color[] { bright, brightEnd }, - RadialGradientPaint.CycleMethod.NO_CYCLE); - g2.setPaint(p); - if (fill) { - g2.fillOval(x * 2, y, width, height); - } else { - g2.drawOval(x * 2, y, width, height); - } - - // Reset original paint - g2.setPaint(oldPaint); - } else { - g.setColor(color); - if (fill) { - g.fillOval(x, y, width, height); - } else { - g.drawOval(x, y, width, height); - } - } - } - - /** - * Add a {@link JScrollPane} around the given panel and use a sensible (for - * me) increment for the mouse wheel. - * - * @param pane - * the panel to wrap in a {@link JScrollPane} - * @param allowHorizontal - * allow horizontal scrolling (not always desired) - * - * @return the {@link JScrollPane} - */ - static public JScrollPane scroll(JComponent pane, boolean allowHorizontal) { - return scroll(pane, allowHorizontal, true); - } - - /** - * Add a {@link JScrollPane} around the given panel and use a sensible (for - * me) increment for the mouse wheel. - * - * @param pane - * the panel to wrap in a {@link JScrollPane} - * @param allowHorizontal - * allow horizontal scrolling (not always desired) - * @param allowVertical - * allow vertical scrolling (usually yes, but sometimes you only - * want horizontal) - * - * @return the {@link JScrollPane} - */ - static public JScrollPane scroll(JComponent pane, boolean allowHorizontal, - boolean allowVertical) { - JScrollPane scroll = new JScrollPane(pane); - - scroll.getVerticalScrollBar().setUnitIncrement(16); - scroll.getHorizontalScrollBar().setUnitIncrement(16); - - if (!allowHorizontal) { - scroll.setHorizontalScrollBarPolicy( - JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); - } - if (!allowVertical) { - scroll.setVerticalScrollBarPolicy( - JScrollPane.VERTICAL_SCROLLBAR_NEVER); - } - - return scroll; - } - - /** - * Show a confirmation message to the user to show him the changes since - * last version. - *

- * HTML 3.2 supported, links included (the user browser will be launched if - * possible). - *

- * If this is already the latest version, a message will still be displayed. - * - * @param parentComponent - * determines the {@link java.awt.Frame} in which the dialog is - * displayed; if null, or if the - * parentComponent has no {@link java.awt.Frame}, a - * default {@link java.awt.Frame} is used - * @param updates - * the new version - * @param introText - * an introduction text before the list of changes - * @param title - * the title of the dialog - * - * @return TRUE if the user clicked on OK, false if the dialog was dismissed - */ - static public boolean showUpdatedDialog(Component parentComponent, - VersionCheck updates, String introText, String title) { - - StringBuilder builder = new StringBuilder(); - final JEditorPane updateMessage = new JEditorPane("text/html", ""); - if (introText != null && !introText.isEmpty()) { - builder.append(introText); - builder.append("
"); - builder.append("
"); - } - for (Version v : updates.getNewer()) { - builder.append("\t" // - + "Version " + v.toString() // - + ""); - builder.append("
"); - builder.append("

    "); - for (String item : updates.getChanges().get(v)) { - builder.append("
  • " + item + "
  • "); - } - builder.append("
"); - } - - // html content - updateMessage.setText("" // - + builder// - + ""); - - // handle link events - updateMessage.addHyperlinkListener(new HyperlinkListener() { - @Override - public void hyperlinkUpdate(HyperlinkEvent e) { - if (e.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) - try { - Desktop.getDesktop().browse(e.getURL().toURI()); - } catch (IOException ee) { - ee.printStackTrace(); - } catch (URISyntaxException ee) { - ee.printStackTrace(); - } - } - }); - updateMessage.setEditable(false); - updateMessage.setBackground(new JLabel().getBackground()); - updateMessage.addHyperlinkListener(new HyperlinkListener() { - @Override - public void hyperlinkUpdate(HyperlinkEvent evn) { - if (evn.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { - if (Desktop.isDesktopSupported()) { - try { - Desktop.getDesktop().browse(evn.getURL().toURI()); - } catch (IOException e) { - } catch (URISyntaxException e) { - } - } - } - } - }); - - return JOptionPane.showConfirmDialog(parentComponent, updateMessage, - title, JOptionPane.DEFAULT_OPTION) == JOptionPane.OK_OPTION; - } - - /** - * Set the given {@link JButton} as "pressed" (selected, but with more UI - * visibility). - *

- * The {@link JButton} will answer {@link JButton#isSelected()} if it is - * pressed. - * - * @param button - * the button to select/press - * @param pressed - * the new "pressed" state - */ - static public void setButtonPressed(JButton button, boolean pressed) { - if (buttonNormal == null) { - JButton defButton = new JButton(" "); - buttonNormal = defButton.getBackground(); - if (buttonNormal.getBlue() >= 128) { - buttonPressed = new Color( // - Math.max(buttonNormal.getRed() - 100, 0), // - Math.max(buttonNormal.getGreen() - 100, 0), // - Math.max(buttonNormal.getBlue() - 100, 0)); - } else { - buttonPressed = new Color( // - Math.min(buttonNormal.getRed() + 100, 255), // - Math.min(buttonNormal.getGreen() + 100, 255), // - Math.min(buttonNormal.getBlue() + 100, 255)); - } - } - - button.setSelected(pressed); - button.setBackground(pressed ? buttonPressed : buttonNormal); - } -} diff --git a/src/be/nikiroo/utils/ui/WaitingDialog.java b/src/be/nikiroo/utils/ui/WaitingDialog.java deleted file mode 100644 index 0fd4574..0000000 --- a/src/be/nikiroo/utils/ui/WaitingDialog.java +++ /dev/null @@ -1,176 +0,0 @@ -package be.nikiroo.utils.ui; - -import java.awt.Window; - -import javax.swing.JDialog; -import javax.swing.JLabel; -import javax.swing.SwingWorker; -import javax.swing.border.EmptyBorder; - -import be.nikiroo.utils.Progress; -import be.nikiroo.utils.Progress.ProgressListener; - -/** - * A small waiting dialog that will show only if more than X milliseconds passed - * before we dismiss it. - * - * @author niki - */ -public class WaitingDialog extends JDialog { - private static final long serialVersionUID = 1L; - - private boolean waitScreen; - private Object waitLock = new Object(); - - private Progress pg; - private ProgressListener pgl; - - /** - * Create a new {@link WaitingDialog}. - * - * @param parent - * the parent/owner of this {@link WaitingDialog} - * @param delayMs - * the delay after which to show the dialog if it is still not - * dismiss (see {@link WaitingDialog#dismiss()}) - */ - public WaitingDialog(Window parent, long delayMs) { - this(parent, delayMs, null, null); - } - - /** - * Create a new {@link WaitingDialog}. - * - * @param parent - * the parent/owner of this {@link WaitingDialog} - * @param delayMs - * the delay after which to show the dialog if it is still not - * dismiss (see {@link WaitingDialog#dismiss()}) - * @param pg - * the {@link Progress} to listen on -- when it is - * {@link Progress#done()}, this {@link WaitingDialog} will - * automatically be dismissed as if - * {@link WaitingDialog#dismiss()} was called - */ - public WaitingDialog(Window parent, long delayMs, Progress pg) { - this(parent, delayMs, pg, null); - } - - /** - * Create a new {@link WaitingDialog}. - * - * @param parent - * the parent/owner of this {@link WaitingDialog} - * @param delayMs - * the delay after which to show the dialog if it is still not - * dismiss (see {@link WaitingDialog#dismiss()}) - * @param waitingText - * a waiting text to display (note: you may want to subclass it - * for nicer UI) - */ - public WaitingDialog(Window parent, long delayMs, String waitingText) { - this(parent, delayMs, null, waitingText); - } - - /** - * Create a new {@link WaitingDialog}. - * - * @param parent - * the parent/owner of this {@link WaitingDialog} - * @param delayMs - * the delay after which to show the dialog if it is still not - * dismiss (see {@link WaitingDialog#dismiss()}) - * @param pg - * the {@link Progress} to listen on -- when it is - * {@link Progress#done()}, this {@link WaitingDialog} will - * automatically be dismissed as if - * {@link WaitingDialog#dismiss()} was called - * @param waitingText - * a waiting text to display (note: you may want to subclass it - * for nicer UI) - */ - public WaitingDialog(Window parent, long delayMs, Progress pg, - String waitingText) { - super(parent); - - this.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); - - this.pg = pg; - - if (waitingText != null) { - JLabel waitingTextLabel = new JLabel(waitingText); - this.add(waitingTextLabel); - waitingTextLabel.setBorder(new EmptyBorder(10, 10, 10, 10)); - this.pack(); - } - - if (pg != null) { - pgl = new ProgressListener() { - @Override - public void progress(Progress progress, String name) { - if (WaitingDialog.this.pg.isDone()) { - // Must be done out of this thread (cannot remove a pgl - // from a running pgl) - new SwingWorker() { - @Override - protected Void doInBackground() throws Exception { - return null; - } - - @Override - public void done() { - dismiss(); - } - }.execute(); - } - } - }; - - pg.addProgressListener(pgl); - - if (pg.isDone()) { - dismiss(); - return; - } - } - - final long delay = delayMs; - new Thread(new Runnable() { - @Override - public void run() { - try { - Thread.sleep(delay); - } catch (InterruptedException e) { - } - - synchronized (waitLock) { - if (!waitScreen) { - waitScreen = true; - setVisible(true); - } - } - } - }).start(); - } - - /** - * Notify this {@link WaitingDialog} that the job is done, and dismiss it if - * it was already showing on screen (or never show it if it was not). - *

- * Will also dispose the {@link WaitingDialog}. - */ - public void dismiss() { - synchronized (waitLock) { - if (waitScreen) { - setVisible(false); - } - waitScreen = true; - } - - if (pg != null) { - pg.removeProgressListener(pgl); - } - - dispose(); - } -} diff --git a/src/be/nikiroo/utils/ui/WrapLayout.java b/src/be/nikiroo/utils/ui/WrapLayout.java deleted file mode 100644 index 7f34d79..0000000 --- a/src/be/nikiroo/utils/ui/WrapLayout.java +++ /dev/null @@ -1,205 +0,0 @@ -package be.nikiroo.utils.ui; - -import java.awt.Component; -import java.awt.Container; -import java.awt.Dimension; -import java.awt.FlowLayout; -import java.awt.Insets; - -import javax.swing.JScrollPane; -import javax.swing.SwingUtilities; - -/** - * FlowLayout subclass that fully supports wrapping of components. - * - * @author https://tips4java.wordpress.com/2008/11/06/wrap-layout/ - */ -public class WrapLayout extends FlowLayout { - private static final long serialVersionUID = 1L; - - /** - * Constructs a new WrapLayout with a left alignment and a - * default 5-unit horizontal and vertical gap. - */ - public WrapLayout() { - super(); - } - - /** - * Constructs a new FlowLayout with the specified alignment and - * a default 5-unit horizontal and vertical gap. The value of the alignment - * argument must be one of WrapLayout, WrapLayout, - * or WrapLayout. - * - * @param align - * the alignment value - */ - public WrapLayout(int align) { - super(align); - } - - /** - * Creates a new flow layout manager with the indicated alignment and the - * indicated horizontal and vertical gaps. - *

- * The value of the alignment argument must be one of - * WrapLayout, WrapLayout, or - * WrapLayout. - * - * @param align - * the alignment value - * @param hgap - * the horizontal gap between components - * @param vgap - * the vertical gap between components - */ - public WrapLayout(int align, int hgap, int vgap) { - super(align, hgap, vgap); - } - - /** - * Returns the preferred dimensions for this layout given the visible - * components in the specified target container. - * - * @param target - * the component which needs to be laid out - * @return the preferred dimensions to lay out the subcomponents of the - * specified container - */ - @Override - public Dimension preferredLayoutSize(Container target) { - return layoutSize(target, true); - } - - /** - * Returns the minimum dimensions needed to layout the visible - * components contained in the specified target container. - * - * @param target - * the component which needs to be laid out - * @return the minimum dimensions to lay out the subcomponents of the - * specified container - */ - @Override - public Dimension minimumLayoutSize(Container target) { - Dimension minimum = layoutSize(target, false); - minimum.width -= (getHgap() + 1); - return minimum; - } - - /** - * Returns the minimum or preferred dimension needed to layout the target - * container. - * - * @param target - * target to get layout size for - * @param preferred - * should preferred size be calculated - * @return the dimension to layout the target container - */ - private Dimension layoutSize(Container target, boolean preferred) { - synchronized (target.getTreeLock()) { - // Each row must fit with the width allocated to the containter. - // When the container width = 0, the preferred width of the - // container - // has not yet been calculated so lets ask for the maximum. - - int targetWidth = target.getSize().width; - Container container = target; - - while (container.getSize().width == 0 - && container.getParent() != null) { - container = container.getParent(); - } - - targetWidth = container.getSize().width; - - if (targetWidth == 0) - targetWidth = Integer.MAX_VALUE; - - int hgap = getHgap(); - int vgap = getVgap(); - Insets insets = target.getInsets(); - int horizontalInsetsAndGap = insets.left + insets.right - + (hgap * 2); - int maxWidth = targetWidth - horizontalInsetsAndGap; - - // Fit components into the allowed width - - Dimension dim = new Dimension(0, 0); - int rowWidth = 0; - int rowHeight = 0; - - int nmembers = target.getComponentCount(); - - for (int i = 0; i < nmembers; i++) { - Component m = target.getComponent(i); - - if (m.isVisible()) { - Dimension d = preferred ? m.getPreferredSize() : m - .getMinimumSize(); - - // Can't add the component to current row. Start a new - // row. - - if (rowWidth + d.width > maxWidth) { - addRow(dim, rowWidth, rowHeight); - rowWidth = 0; - rowHeight = 0; - } - - // Add a horizontal gap for all components after the - // first - - if (rowWidth != 0) { - rowWidth += hgap; - } - - rowWidth += d.width; - rowHeight = Math.max(rowHeight, d.height); - } - } - - addRow(dim, rowWidth, rowHeight); - - dim.width += horizontalInsetsAndGap; - dim.height += insets.top + insets.bottom + vgap * 2; - - // When using a scroll pane or the DecoratedLookAndFeel we need - // to - // make sure the preferred size is less than the size of the - // target containter so shrinking the container size works - // correctly. Removing the horizontal gap is an easy way to do - // this. - - Container scrollPane = SwingUtilities.getAncestorOfClass( - JScrollPane.class, target); - - if (scrollPane != null && target.isValid()) { - dim.width -= (hgap + 1); - } - - return dim; - } - } - - /* - * A new row has been completed. Use the dimensions of this row to update - * the preferred size for the container. - * - * @param dim update the width and height when appropriate - * - * @param rowWidth the width of the row to add - * - * @param rowHeight the height of the row to add - */ - private void addRow(Dimension dim, int rowWidth, int rowHeight) { - dim.width = Math.max(dim.width, rowWidth); - - if (dim.height > 0) { - dim.height += getVgap(); - } - - dim.height += rowHeight; - } -} \ No newline at end of file diff --git a/src/be/nikiroo/utils/ui/ZoomBox.java b/src/be/nikiroo/utils/ui/ZoomBox.java deleted file mode 100644 index a8f9609..0000000 --- a/src/be/nikiroo/utils/ui/ZoomBox.java +++ /dev/null @@ -1,477 +0,0 @@ -package be.nikiroo.utils.ui; - -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; - -import javax.swing.BorderFactory; -import javax.swing.BoxLayout; -import javax.swing.DefaultComboBoxModel; -import javax.swing.Icon; -import javax.swing.JButton; -import javax.swing.JComboBox; -import javax.swing.JLabel; - -/** - * A small panel that let you choose a zoom level or an actual zoom value (when - * there is enough space to allow that). - * - * @author niki - */ -public class ZoomBox extends ListenerPanel { - private static final long serialVersionUID = 1L; - - /** The event that is fired on zoom change. */ - public static final String ZOOM_CHANGED = "zoom_changed"; - - private enum ZoomLevel { - FIT_TO_WIDTH(0, true), // - FIT_TO_HEIGHT(0, false), // - ACTUAL_SIZE(1, null), // - HALF_SIZE(0.5, null), // - DOUBLE_SIZE(2, null),// - ; - - private final double zoom; - private final Boolean snapMode; - - private ZoomLevel(double zoom, Boolean snapMode) { - this.zoom = zoom; - this.snapMode = snapMode; - } - - public double getZoom() { - return zoom; - } - - public Boolean getSnapToWidth() { - return snapMode; - } - - /** - * Use default values that can be understood by a human. - */ - @Override - public String toString() { - switch (this) { - case FIT_TO_WIDTH: - return "Fit to width"; - case FIT_TO_HEIGHT: - return "Fit to height"; - case ACTUAL_SIZE: - return "Actual size"; - case HALF_SIZE: - return "Half size"; - case DOUBLE_SIZE: - return "Double size"; - } - return super.toString(); - } - - static ZoomLevel[] values(boolean orderedSelection) { - if (orderedSelection) { - return new ZoomLevel[] { // - FIT_TO_WIDTH, // - FIT_TO_HEIGHT, // - ACTUAL_SIZE, // - HALF_SIZE, // - DOUBLE_SIZE,// - }; - } - - return values(); - } - } - - private boolean vertical; - private boolean small; - - private JButton zoomIn; - private JButton zoomOut; - private JButton snapWidth; - private JButton snapHeight; - private JLabel zoomLabel; - - @SuppressWarnings("rawtypes") // JComboBox is not java 1.6 compatible - private JComboBox zoombox; - - private double zoom = 1; - private Boolean snapMode = true; - - @SuppressWarnings("rawtypes") // JComboBox not compatible java 1.6 - private DefaultComboBoxModel zoomBoxModel; - - /** - * Create a new {@link ZoomBox}. - */ - @SuppressWarnings({ "unchecked", "rawtypes" }) // JComboBox not - // compatible java 1.6 - public ZoomBox() { - zoomIn = new JButton(); - zoomIn.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - zoomIn(1); - } - }); - - zoomBoxModel = new DefaultComboBoxModel(ZoomLevel.values(true)); - zoombox = new JComboBox(zoomBoxModel); - zoombox.setEditable(true); - zoombox.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - Object selected = zoomBoxModel.getSelectedItem(); - - if (selected == null) { - return; - } - - if (selected instanceof ZoomLevel) { - ZoomLevel selectedZoomLevel = (ZoomLevel) selected; - setZoomSnapMode(selectedZoomLevel.getZoom(), - selectedZoomLevel.getSnapToWidth()); - } else { - String selectedString = selected.toString(); - selectedString = selectedString.trim(); - if (selectedString.endsWith("%")) { - selectedString = selectedString - .substring(0, selectedString.length() - 1) - .trim(); - } - - try { - int pc = Integer.parseInt(selectedString); - if (pc <= 0) { - throw new NumberFormatException("invalid"); - } - - setZoomSnapMode(pc / 100.0, null); - } catch (NumberFormatException nfe) { - } - } - - fireActionPerformed(ZOOM_CHANGED); - } - }); - - zoomOut = new JButton(); - zoomOut.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - zoomOut(1); - } - }); - - snapWidth = new JButton(); - snapWidth.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - setSnapMode(true); - } - }); - - snapHeight = new JButton(); - snapHeight.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - setSnapMode(false); - } - }); - - zoomLabel = new JLabel(); - zoomLabel.setBorder(BorderFactory.createEmptyBorder(10, 10, 0, 0)); - - setIcons(null, null, null, null); - setOrientation(vertical); - } - - /** - * The zoom level. - *

- * It usually returns 1 (default value), the value you passed yourself or 1 - * (a snap to width or snap to height was asked by the user). - *

- * Will cause a fire event if needed. - * - * @param zoom - * the zoom level - */ - public void setZoom(double zoom) { - if (this.zoom != zoom) { - doSetZoom(zoom); - fireActionPerformed(ZOOM_CHANGED); - } - } - - /** - * The snap mode (NULL means no snap mode, TRUE for snap to width, FALSE for - * snap to height). - *

- * Will cause a fire event if needed. - * - * @param snapToWidth - * the snap mode - */ - public void setSnapMode(Boolean snapToWidth) { - if (this.snapMode != snapToWidth) { - doSetSnapMode(snapToWidth); - fireActionPerformed(ZOOM_CHANGED); - } - } - - /** - * Set both {@link ZoomBox#setZoom(double)} and - * {@link ZoomBox#setSnapMode(Boolean)} but fire only one change event. - *

- * Will cause a fire event if needed. - * - * @param zoom - * the zoom level - * @param snapMode - * the snap mode - */ - public void setZoomSnapMode(double zoom, Boolean snapMode) { - if (this.zoom != zoom || this.snapMode != snapMode) { - doSetZoom(zoom); - doSetSnapMode(snapMode); - fireActionPerformed(ZOOM_CHANGED); - } - } - - /** - * The zoom level. - *

- * It usually returns 1 (default value), the value you passed yourself or 0 - * (a snap to width or snap to height was asked by the user). - * - * @return the zoom level - */ - public double getZoom() { - return zoom; - } - - /** - * The snap mode (NULL means no snap mode, TRUE for snap to width, FALSE for - * snap to height). - * - * @return the snap mode - */ - public Boolean getSnapMode() { - return snapMode; - } - - /** - * Zoom in, by a certain amount in "steps". - *

- * Note that zoomIn(-1) is the same as zoomOut(1). - * - * @param steps - * the number of zoom steps to make, can be negative - */ - public void zoomIn(int steps) { - // TODO: redo zoomIn/zoomOut correctly - if (steps < 0) { - zoomOut(-steps); - return; - } - - double newZoom = zoom; - for (int i = 0; i < steps; i++) { - newZoom = newZoom + (newZoom < 0.1 ? 0.01 : 0.1); - if (newZoom > 0.1) { - newZoom = Math.round(newZoom * 10.0) / 10.0; // snap to 10% - } else { - newZoom = Math.round(newZoom * 100.0) / 100.0; // snap to 1% - } - } - - setZoomSnapMode(newZoom, null); - fireActionPerformed(ZOOM_CHANGED); - } - - /** - * Zoom out, by a certain amount in "steps". - *

- * Note that zoomOut(-1) is the same as zoomIn(1). - * - * @param steps - * the number of zoom steps to make, can be negative - */ - public void zoomOut(int steps) { - if (steps < 0) { - zoomIn(-steps); - return; - } - - double newZoom = zoom; - for (int i = 0; i < steps; i++) { - newZoom = newZoom - (newZoom > 0.19 ? 0.1 : 0.01); - if (newZoom < 0.01) { - newZoom = 0.01; - break; - } - - if (newZoom > 0.1) { - newZoom = Math.round(newZoom * 10.0) / 10.0; // snap to 10% - } else { - newZoom = Math.round(newZoom * 100.0) / 100.0; // snap to 1% - } - } - - setZoomSnapMode(newZoom, null); - fireActionPerformed(ZOOM_CHANGED); - } - - /** - * Set icons for the buttons instead of square brackets. - *

- * Any NULL value will make the button use square brackets again. - * - * @param zoomIn - * the icon of the button "go to first page" - * @param zoomOut - * the icon of the button "go to previous page" - * @param snapWidth - * the icon of the button "go to next page" - * @param snapHeight - * the icon of the button "go to last page" - */ - public void setIcons(Icon zoomIn, Icon zoomOut, Icon snapWidth, - Icon snapHeight) { - this.zoomIn.setIcon(zoomIn); - this.zoomIn.setText(zoomIn == null ? "+" : ""); - this.zoomOut.setIcon(zoomOut); - this.zoomOut.setText(zoomOut == null ? "-" : ""); - this.snapWidth.setIcon(snapWidth); - this.snapWidth.setText(snapWidth == null ? "W" : ""); - this.snapHeight.setIcon(snapHeight); - this.snapHeight.setText(snapHeight == null ? "H" : ""); - } - - /** - * A smaller {@link ZoomBox} that uses buttons instead of a big combo box - * for the zoom modes. - *

- * Always small in vertical orientation. - * - * @return TRUE if it is small - */ - public boolean getSmall() { - return small; - } - - /** - * A smaller {@link ZoomBox} that uses buttons instead of a big combo box - * for the zoom modes. - *

- * Always small in vertical orientation. - * - * @param small - * TRUE to set it small - * - * @return TRUE if it changed something - */ - public boolean setSmall(boolean small) { - return setUi(small, vertical); - } - - /** - * The general orientation of the component. - * - * @return TRUE for vertical orientation, FALSE for horisontal orientation - */ - public boolean getOrientation() { - return vertical; - } - - /** - * The general orientation of the component. - * - * @param vertical - * TRUE for vertical orientation, FALSE for horisontal - * orientation - * - * @return TRUE if it changed something - */ - public boolean setOrientation(boolean vertical) { - return setUi(small, vertical); - } - - /** - * Set the zoom level, no fire event. - *

- * It usually returns 1 (default value), the value you passed yourself or 0 - * (a snap to width or snap to height was asked by the user). - * - * @param zoom - * the zoom level - */ - private void doSetZoom(double zoom) { - if (zoom > 0) { - String zoomStr = Integer.toString((int) Math.round(zoom * 100)) - + " %"; - zoomLabel.setText(zoomStr); - if (snapMode == null) { - zoomBoxModel.setSelectedItem(zoomStr); - } - } - - this.zoom = zoom; - } - - /** - * Set the snap mode, no fire event. - * - * @param snapToWidth - * the snap mode - */ - private void doSetSnapMode(Boolean snapToWidth) { - if (snapToWidth == null) { - String zoomStr = Integer.toString((int) Math.round(zoom * 100)) - + " %"; - if (zoom > 0) { - zoomBoxModel.setSelectedItem(zoomStr); - } - } else { - for (ZoomLevel level : ZoomLevel.values()) { - if (level.getSnapToWidth() == snapToWidth) { - zoomBoxModel.setSelectedItem(level); - } - } - } - - this.snapMode = snapToWidth; - } - - private boolean setUi(boolean small, boolean vertical) { - if (getWidth() == 0 || this.small != small - || this.vertical != vertical) { - this.small = small; - this.vertical = vertical; - - BoxLayout layout = new BoxLayout(this, - vertical ? BoxLayout.Y_AXIS : BoxLayout.X_AXIS); - this.removeAll(); - setLayout(layout); - - if (vertical || small) { - this.add(zoomIn); - this.add(snapWidth); - this.add(snapHeight); - this.add(zoomOut); - this.add(zoomLabel); - } else { - this.add(zoomIn); - this.add(zoombox); - this.add(zoomOut); - } - - this.revalidate(); - this.repaint(); - - return true; - } - - return false; - } -} diff --git a/src/be/nikiroo/utils/ui/clear-16x16.png b/src/be/nikiroo/utils/ui/clear-16x16.png deleted file mode 100644 index 3ea2622aa1ef1f9ad0b85322e5388be709d5c678..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1232 zcmZuxc~H|w6y9(I0Vz0A4N){{?u3wEViF`Gas?743|Anc&Ts{dSfMl^n5t0_E9z+R z06FYbtL@kpYAXy-og%1XQQ}k-D4?J~O+f|$k*nr)?SGx=?!5Qy`}TV?Z+G7A(deiM zHy5f4LdZ?63X8?)@!l5MW6u@3$6;{T9;^vQ=-Nrwzf&A>OiNS6Y7pAXM#x-*&^)Hh zR)lg1ghrAPQj{aKJiDqXCIBJ3_0f^>N-Pj90Pz+Ao1btd7OnvJyaD-Z5R8Fn6nvlA zgoEJo6a+s#<39rW{*(MZ;64Fg<(QxkZ~^S!AR8d(&1C+_uox%yTLT~U1a%Dr^$bZ~ zO$z`dQ#Mi8b3w}k&ds}mC;j3{Ag00pwk5jmhIn{t1pqM%#Dgw+^*7=f-~x!O^P%-E z8|#}z{jWviAZ@xwKXKltd&C34cX%fBMtem0CHb`$_UTK${nIf3900*zAZr`sAFmcZ zot6Ov%me>9D6ahC6PqMx?H2*?pMrl$6-%4KYwD2#@H#+vvDr(jXMF!~-M=B{`T%Fg zK1R~6rCI~Kaaaw&zH9raqLool#=bMd{&kX?SLT_R#rU?Hbr%@5!z)uwtojj7CvaW> zvtfMsC#R`}SJ`dAY6V8)l=u0z)fX)EFCVh%tn7!tZU=VD4DDjas_OPtl@I7OLs-VT zWfS(;v;ca1djLD$z1sq?4Pe87jd{zKbg*!D(P85kr+KM*Dk1ie%Cq+!C_ZR56(3on zH*lB?Dw~sbwkkg_*I>*GNH@sD1V<{@9V$snO-)SNqK?xB1_p}#*Trp4(nLmvhJ?ta z(r}e3C@9F!&(DX?_lrsN_mfKqk$}sk(`b}cG`>dX!{hQ~{>$i0SLa0zWb#UtZY7oK z&0spZEx}D<@8Y&Je6u@^vxFriujV-{^LC=~yq-G^;k_fq*r@l-`2Qozynnwrfh^l+ zK5#H2-8!zfTWcBAk+d&Iw+2|Ov7`j+tASVBNEjy!cBF){ITLxqz+A4wh7K$Z?VNo( zx|I~(^R{Tu+$2>bqblZPIo=GsmWxVro3@mY+9Hqa{x!ghBz@c4@G@?fTX_td!` zCY+C|d+WzlQ7OqPO>bl7p1t8233-+aHy3iID3h)($4WMBR4VVc3HaY#&7|xc9T{6e zy+fhgW>r*dN$Mp6Tv6?#h~RMfsbYL)mQkrQrl%P7inJU(1|%UQgpiO43BPy=p^(WG zghW6{6a-OZFi8KC@NrhUA)^2%h_PBMB&6|Tf2>nTWCB8r7Zb{S{1qXjRz`)@gd`vO E2T8{XCIA2c diff --git a/src/be/nikiroo/utils/ui/compat/DefaultListModel6.java b/src/be/nikiroo/utils/ui/compat/DefaultListModel6.java deleted file mode 100644 index 3f7552f..0000000 --- a/src/be/nikiroo/utils/ui/compat/DefaultListModel6.java +++ /dev/null @@ -1,22 +0,0 @@ -package be.nikiroo.utils.ui.compat; - -import javax.swing.DefaultListModel; -import javax.swing.JList; - -/** - * Compatibility layer so I can at least get rid of the warnings of using - * {@link JList} without a parameter (and still staying Java 1.6 compatible). - *

- * This class is merely a {@link DefaultListModel} that you can parametrise also - * in Java 1.6. - * - * @author niki - * - * @param - * the type to use - */ -@SuppressWarnings("rawtypes") // not compatible Java 1.6 -public class DefaultListModel6 extends DefaultListModel - implements ListModel6 { - private static final long serialVersionUID = 1L; -} diff --git a/src/be/nikiroo/utils/ui/compat/JList6.java b/src/be/nikiroo/utils/ui/compat/JList6.java deleted file mode 100644 index a504abb..0000000 --- a/src/be/nikiroo/utils/ui/compat/JList6.java +++ /dev/null @@ -1,84 +0,0 @@ -package be.nikiroo.utils.ui.compat; - -import javax.swing.JList; -import javax.swing.ListCellRenderer; -import javax.swing.ListModel; - -/** - * Compatibility layer so I can at least get rid of the warnings of using - * {@link JList} without a parameter (and still staying Java 1.6 compatible). - *

- * This class is merely a {@link JList} that you can parametrise also in Java - * 1.6. - * - * @author niki - * - * @param - * the type to use - */ -@SuppressWarnings({ "unchecked", "rawtypes" }) // not compatible Java 1.6 -public class JList6 extends JList { - private static final long serialVersionUID = 1L; - - @Override - @Deprecated - /** - * @deprecated please use {@link JList6#setCellRenderer(ListCellRenderer6)} - * instead - */ - public void setCellRenderer(ListCellRenderer cellRenderer) { - super.setCellRenderer(cellRenderer); - } - - /** - * Sets the delegate that is used to paint each cell in the list. The job of - * a cell renderer is discussed in detail in the class - * level documentation. - *

- * If the {@code prototypeCellValue} property is {@code non-null}, setting - * the cell renderer also causes the {@code fixedCellWidth} and - * {@code fixedCellHeight} properties to be re-calculated. Only one - * PropertyChangeEvent is generated however - for the - * cellRenderer property. - *

- * The default value of this property is provided by the {@code ListUI} - * delegate, i.e. by the look and feel implementation. - *

- * This is a JavaBeans bound property. - * - * @param cellRenderer - * the ListCellRenderer that paints list cells - * @see #getCellRenderer - * @beaninfo bound: true attribute: visualUpdate true description: The - * component used to draw the cells. - */ - public void setCellRenderer(ListCellRenderer6 cellRenderer) { - super.setCellRenderer(cellRenderer); - } - - @Override - @Deprecated - public void setModel(ListModel model) { - super.setModel(model); - } - - /** - * Sets the model that represents the contents or "value" of the list, - * notifies property change listeners, and then clears the list's selection. - *

- * This is a JavaBeans bound property. - * - * @param model - * the ListModel that provides the list of items for - * display - * @exception IllegalArgumentException - * if model is null - * @see #getModel - * @see #clearSelection - * @beaninfo bound: true attribute: visualUpdate true description: The - * object that contains the data to be drawn by this JList. - */ - public void setModel(ListModel6 model) { - super.setModel(model); - } -} diff --git a/src/be/nikiroo/utils/ui/compat/ListCellRenderer6.java b/src/be/nikiroo/utils/ui/compat/ListCellRenderer6.java deleted file mode 100644 index bc76e80..0000000 --- a/src/be/nikiroo/utils/ui/compat/ListCellRenderer6.java +++ /dev/null @@ -1,65 +0,0 @@ -package be.nikiroo.utils.ui.compat; - -import java.awt.Component; - -import javax.swing.JList; -import javax.swing.ListCellRenderer; -import javax.swing.ListModel; -import javax.swing.ListSelectionModel; - -/** - * Compatibility layer so I can at least get rid of the warnings of using - * {@link JList} without a parameter (and still staying Java 1.6 compatible). - *

- * This class is merely a {@link ListCellRenderer} that you can parametrise also - * in Java 1.6. - * - * @author niki - * - * @param - * the type to use - */ -@SuppressWarnings({ "unchecked", "rawtypes" }) // not compatible Java 1.6 -public abstract class ListCellRenderer6 implements ListCellRenderer { - @Override - @Deprecated - /** - * @deprecated please use@deprecated please use - * {@link ListCellRenderer6#getListCellRendererComponent(JList6, Object, int, boolean, boolean)} - * instead - * {@link ListCellRenderer6#getListCellRendererComponent(JList6, Object, int, boolean, boolean)} - * instead - */ - public Component getListCellRendererComponent(JList list, Object value, - int index, boolean isSelected, boolean cellHasFocus) { - return getListCellRendererComponent((JList6) list, (E) value, index, - isSelected, cellHasFocus); - } - - /** - * Return a component that has been configured to display the specified - * value. That component's paint method is then called to - * "render" the cell. If it is necessary to compute the dimensions of a list - * because the list cells do not have a fixed size, this method is called to - * generate a component on which getPreferredSize can be - * invoked. - * - * @param list - * The JList we're painting. - * @param value - * The value returned by list.getModel().getElementAt(index). - * @param index - * The cells index. - * @param isSelected - * True if the specified cell was selected. - * @param cellHasFocus - * True if the specified cell has the focus. - * @return A component whose paint() method will render the specified value. - * - * @see JList - * @see ListSelectionModel - * @see ListModel - */ - public abstract Component getListCellRendererComponent(JList6 list, - E value, int index, boolean isSelected, boolean cellHasFocus); -} diff --git a/src/be/nikiroo/utils/ui/compat/ListModel6.java b/src/be/nikiroo/utils/ui/compat/ListModel6.java deleted file mode 100644 index 938da14..0000000 --- a/src/be/nikiroo/utils/ui/compat/ListModel6.java +++ /dev/null @@ -1,19 +0,0 @@ -package be.nikiroo.utils.ui.compat; - -import javax.swing.JList; - -/** - * Compatibility layer so I can at least get rid of the warnings of using - * {@link JList} without a parameter (and still staying Java 1.6 compatible). - *

- * This class is merely a {@link javax.swing.ListModel} that you can parametrise - * also in Java 1.6. - * - * @author niki - * - * @param - * the type to use - */ -@SuppressWarnings("rawtypes") // not compatible Java 1.6 -public interface ListModel6 extends javax.swing.ListModel { -} diff --git a/src/be/nikiroo/utils/ui/search-16x16.png b/src/be/nikiroo/utils/ui/search-16x16.png deleted file mode 100644 index 0164b31df14111b448b3b008d87a9cff5e708808..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1274 zcmah}YfO`86n?QVKp--}LF<583Y1~BZ9qcfM6kqm0x3{LXc?E5ixRnLnV>OthRe7J z8a8BHB0(wPUYRl&&;b<~*CKalX`x&?AY2TIn{F99-G1%Y-sJn<_d7Y~d7kr}H(xG0 z;F8%<+);#(8H>pXg56yA7-8T|A|DHeO+Vh--y5M9MJ9hn8^AZ7&kXWM=#CRYqD+K7 zKuGi}LTMC)wxSTCmm*}HQt?yZIfRb*vHdu{kkH%P+uGWyR4QdM*%BW22*x55dqB5jQOd;*jZh`V5hu$%ste z+1UY1*CL5WQr7nN_Tu8=S+R_D|1so%c(;t~fXCG~Km#ZLZE$dK&{0@!msXvTki+ij_FzsZh^(xwZ|{-{ zMOiU%3E*B#L2*dhJ?{17mX;Qf4d#H`;<~!0&z?&p66kAaXlQI~Y;tn)@bEDAVYcpq zW(k7=ba(TAmw}_LxxPi4vZLe<&(rQ>J-2RfDVhe<-kw3X6{{PI|{@H?|x%Ii_b~ zg*m16fB_)An9G+Kvb=;sANVj%VTRp8$i!0j=%KO-8weT-S^mC;YCU}`gw=e&zJVG8 z7Q>rkGsTaB#R&`vo{NeRVdDJBH*6z5zlXW#!2d)d%DtAzy_@rWM^?>oSEgueQT#(g z=d0Qu<+Fvh#GdHA39o3)^Pyldot}E+J#YMU^ORU#>Y4cNQ;*w8Rj4ROtIRmCKE->P zUiif03|k~0ygfAeex#(O^wp%?UO>8kVYz-LakiQ3mG~7~g4h?>B%)9@;9stww`FFT zUB37pq;{Shx0Dh`36W;kmq^&j#67z6=Zti3sL8%d6PxDx@@Z&W#<{HG z=Ici3DD=H56hsSS>HM3qupt_SMj=z&$rKL`jY4