Compare commits

...
85 Commits
Author SHA1 Message Date
hajdam 6f5d152cbd Release 0.2.7 2025-06-19 17:58:33 +02:00
hajdam da66be3d57 Binary search - 3 2025-06-18 23:15:14 +02:00
hajdam 15940b4357 Library update 2025-06-18 00:09:19 +02:00
hajdam 1e9fdcae07 Added ru and es translations 2025-06-17 19:42:07 +02:00
hajdam 0a34b72a26 Binary search - 2 2025-06-16 23:44:07 +02:00
hajdam 24a19e3e2f Lazy day 2025-06-14 23:13:25 +02:00
hajdam 9362ce762c Binary search - 1 2025-06-11 23:44:06 +02:00
hajdam bfd2b19190 Added more download buttons - 5 2025-06-10 23:28:42 +02:00
hajdam c78381a20a Added more download buttons - 4 2025-06-10 23:21:31 +02:00
hajdam 06e16367f1 Added more download buttons - 3 2025-06-10 23:10:50 +02:00
hajdam 72565964f2 Added more download buttons - 2 2025-06-10 22:59:25 +02:00
hajdam 03edbba8c4 Added more download buttons 2025-06-10 22:57:09 +02:00
hajdam 908372c235 Minor fix 2025-06-10 21:28:11 +02:00
hajdam 4e3872519a Added contributor 2025-06-08 13:09:58 +02:00
hajdam 3f68527616 Added turkish language entry 2025-06-08 12:00:39 +02:00
Miroslav HajdaandGitHub c0487ce0c3 Merge pull request #23 from turkce-ceviriler/master
add turkish translation
2025-06-08 11:40:22 +02:00
Baturax 4dc8540461 add turkish 2025-06-08 11:57:53 +03:00
hajdam 9d40b98a4c Minor fix 2025-06-04 13:57:30 +02:00
hajdam 5c01dad173 Added Portuguese translation - 2 2025-05-30 19:20:46 +02:00
hajdam a5328702af Added Portuguese translation 2025-05-30 00:34:13 +02:00
hajdam fc17fe853e Fastlane update 2025-05-29 14:43:47 +02:00
hajdam 80fbed262d Release 0.2.6 2025-05-29 13:36:11 +02:00
hajdam a43e45f223 Added experimental support for tbl files - 3 2025-05-27 21:54:34 +02:00
hajdam e21069fd11 Added experimental support for tbl files - 2 2025-05-26 22:17:25 +02:00
hajdam 4d8552ac66 Added experimental support for tbl files 2025-05-25 20:13:33 +02:00
hajdam 2bcf3c6421 Library update 2025-05-25 14:42:35 +02:00
hajdam 9331dccb8b Library update 2025-05-22 18:16:21 +02:00
hajdam 424e9ce897 Libraries updated - 2 2025-05-13 00:22:54 +02:00
hajdam 5679c26e99 Libraries updated 2025-05-13 00:00:02 +02:00
hajdam d732892673 Attempt to mitigate PlayStore rejection - DPad issue 2025-05-09 19:50:52 +02:00
hajdam df32e610b1 Minor fix 2025-05-09 16:10:21 +02:00
hajdam 15a6e5b222 Added Chinese translation contributor 2025-05-09 14:15:37 +02:00
hajdam 89e0bb7cdc Language switching compatiblity fix 2025-05-08 18:32:48 +02:00
Miroslav HajdaandGitHub 69ab704151 Merge pull request #17 from WeiguangTWK/master
Correction in Language options
2025-05-08 17:54:41 +02:00
WeiguangTWK e11ce37438 Correct translation 2025-05-08 21:46:20 +08:00
hajdam db9c93bdf5 Version raised 2025-05-08 12:49:02 +02:00
Miroslav HajdaandGitHub ecb061f65c Merge pull request #16 from WeiguangTWK/master
Add Chinese-Simplified Translation
2025-05-08 11:59:20 +02:00
WeiguangTWK 21fe741eb8 Add Chinese-Simplified Translation in arrays.xml 2025-05-08 13:56:54 +08:00
WeiguangTWK 5167e4c9b5 Add Chinese-Simplified Translation in strings.xml 2025-05-08 13:56:37 +08:00
hajdam 44d6ee0df6 Release 0.2.5 2025-05-07 16:59:06 +02:00
hajdam ae3d07ebdd Double backpress exists app 2025-05-07 14:05:23 +02:00
hajdam a90fedbc4c Search threading 2025-05-07 03:13:10 +02:00
hajdam 97c6694fd2 Minor fixes / updated dependencies 2025-05-06 23:54:34 +02:00
hajdam b863035f09 Replaced app icon for playstore issue 2025-04-07 23:10:00 +02:00
hajdam 4e7279c766 Release 0.2.4 - target sdk fix 2025-04-06 16:25:07 +02:00
hajdam 16f8793308 Release 0.2.4 - fastlane update 2025-04-06 14:36:04 +02:00
hajdam 6f8388a0b5 Release 0.2.4 2025-04-06 14:30:43 +02:00
hajdam c02666d357 Extended compatibility to Android 4.0 / sdk14 - 3 2025-04-05 23:28:07 +02:00
hajdam 1df4d47ca6 Extended compatibility to Android 4.0 / sdk14 - 2 2025-04-05 00:25:12 +02:00
hajdam 0f9b9380cf Extended compatibility to Android 4.0 / sdk14 2025-04-04 23:56:39 +02:00
hajdam a6b38be5a8 Android gradle plugin update 2025-04-04 00:48:20 +02:00
hajdam 4775da61fc Gradle version raised 2025-02-22 11:08:04 +01:00
hajdam 2e306e87ff Minor refactoring 2024-12-13 23:44:17 +01:00
hajdam 8a71c42398 Binary data byte buffer - 6 2024-12-12 23:46:11 +01:00
hajdam 0c5e8ec482 DefaultCodeAreaCharAssessor update - 3 2024-12-06 19:36:52 +01:00
hajdam f082e7209c DefaultCodeAreaCharAssessor update - 2 2024-12-03 14:23:33 +01:00
hajdam 0e9412af7f DefaultCodeAreaCharAssessor update 2024-12-01 22:57:53 +01:00
hajdam 38e92e0fc3 Enabled some tests 2024-11-29 23:53:52 +01:00
hajdam 3e146185df Icon tint selector fix 2024-11-29 21:26:40 +01:00
hajdam 16c44bc7cd Rounded icon with less padding 2024-11-21 20:19:36 +01:00
hajdam 5ac5351dbe Version raised 2024-11-14 13:34:48 +01:00
hajdam dbcfa995fb Release 0.2.3 2024-11-13 23:14:27 +01:00
hajdam 16bc17921c Basic data inspector - 6 2024-11-13 18:05:27 +01:00
hajdam eb3b9f1e4a Basic data inspector - 5 2024-11-12 23:55:50 +01:00
hajdam f1f274f5af Basic data inspector - 4 2024-11-11 22:33:24 +01:00
hajdam 1b0d715004 Basic data inspector - 3 2024-11-10 21:54:30 +01:00
hajdam 98cceb8184 Reenabling minify 2024-11-07 14:38:37 +01:00
hajdam 7012192995 Basic data inspector - 2 2024-10-30 22:54:48 +01:00
hajdam 8d9e91693a Basic data inspector - 1 2024-10-29 22:50:15 +01:00
hajdam 604f967634 Lazy day 2024-10-28 23:43:31 +01:00
hajdam b7f5bf70bf Reworking action update - 26 2024-10-26 20:11:08 +02:00
hajdam dda92b2656 Options for button size in keys panel 2024-10-25 23:19:42 +02:00
Miroslav HajdaandGitHub 98f55f3554 Merge pull request #13 from DarioTD/patch-1
Fixed buttonAction9
2024-10-24 08:41:25 +02:00
Dario TabaresandGitHub 29f2ec28d6 Fixed buttonAction9
Now maps to 9 instead of 1
2024-10-23 23:59:27 -05:00
hajdam cf6ddbfebb Rework file picker - 2 2024-10-21 22:03:06 +02:00
hajdam 608e788673 Translation fix 2024-10-20 23:10:12 +02:00
hajdam 1f85b5941a Support for partial file opening - 3 2024-10-20 22:47:10 +02:00
hajdam beff277f6c Support for partial file opening - 2 2024-10-20 21:10:32 +02:00
hajdam 4d0e36932c Support for partial file opening 2024-10-18 22:31:52 +02:00
hajdam 75ee4dc363 Play store has issues with slash in app name 2024-10-17 23:34:17 +02:00
hajdam 490eee7146 Rework file picker - 1 2024-10-16 23:54:46 +02:00
hajdam 954f91410b Readme updated with play store download button 2024-10-16 14:03:53 +02:00
hajdam 7051207876 Version raised 2024-10-15 15:18:30 +02:00
Miroslav HajdaandGitHub 39475b6343 Merge pull request #12 from Poussinou/patch-1
Update README.md
2024-10-14 21:02:47 +02:00
PoussinouandGitHub 57044959b1 Update README.md 2024-10-14 14:36:58 -04:00
541 changed files with 14644 additions and 2022 deletions
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" />
<bytecodeTargetLevel target="21" />
</component>
</project>
+1
View File
@@ -11,6 +11,7 @@
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/file_picker" />
</set>
</option>
<option name="resolveExternalAnnotations" value="false" />
+1 -1
View File
@@ -56,7 +56,7 @@
</value>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
+4
View File
@@ -5,8 +5,12 @@
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
+21 -7
View File
@@ -1,10 +1,14 @@
BinEd - Binary/Hex Editor
=========================
BinEd - Hex Editor
==================
Editor for binary data (hex viewer/editor) written in Java.
Homepage: https://bined.exbin.org/android
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" alt="Get it on F-Droid" height="80">](https://f-droid.org/packages/org.exbin.bined.editor.android/)[<img src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png" alt="Get it on Google Play" height="80">](https://play.google.com/store/apps/details?id=org.exbin.bined.editor.android)[<img src="https://github.com/user-attachments/assets/713d71c5-3dec-4ec4-a3f2-8d28d025a9c6" alt="Get it on Obtainium" height="80">](http://apps.obtainium.imranr.dev/redirect.html?r=obtainium://app/%7B%22id%22%3A%22org.exbin.bined.editor.android%22%2C%22url%22%3A%22https%3A%2F%2Fgithub.com%2Fexbin%2Fbined-android%22%2C%22author%22%3A%22ExBin%20Project%22%2C%22name%22%3A%22BinEd%22%2C%22additionalSettings%22%3A%22%7B%5C%22includePrereleases%5C%22%3Afalse%7D%22%7D)[<img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" alt="Get it on IzzyDroid" height="80">](https://android.izzysoft.de/repo/apk/org.exbin.bined.editor.android)
Or download the latest APK from the [Releases Section](https://github.com/exbin/bined-android/releases/latest).
Screenshot
----------
@@ -13,22 +17,32 @@ Screenshot
Features
--------
UNDER DEVELOPMENT
* Visualize data as numerical (hexadecimal) codes and text representation
* Codes can be also binary, octal or decimal
* Support for Unicode, UTF-8 and other charsets
* Insert and overwrite edit modes
* TODO: Searching for text / hexadecimal code with matching highlighting
* Searching for text / hexadecimal code with matches highlighting
* Support for undo/redo
* TODO: Support for files with size up to exabytes
* Support for files with size up to exabytes (in partial file mode)
Compiling
---------
To build this project, Android Studio is recommended.
To compile project and build apk you need installed Java JDK 17 or later and run:
./gradlew assembleRelease
or on Windows:
gradlew.bat assembleRelease
To modify this project, Android Studio is recommended.
https://developer.android.com/studio/
When using Android Studio modify following line in build.gradle file to avoid legacy errors:
def playStore = true
License
-------
+44 -14
View File
@@ -2,49 +2,79 @@ plugins {
id 'com.android.application'
}
def playStore = false
def materialVersion = '1.6.1'
android {
compileSdk 34
defaultConfig {
applicationId "org.exbin.bined.editor.android"
minSdk 24
if (playStore) {
minSdk 24
materialVersion = '1.12.0'
} else {
minSdk 14
multiDexEnabled true
}
targetSdk 34
versionCode 8
versionName "0.2.2"
versionCode 16
versionName "0.2.7"
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
vectorDrawables.generatedDensities = []
}
buildTypes {
release {
minifyEnabled false
minifyEnabled true
// shrinkResources false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
sourceSets {
main.java.srcDirs += 'libsrc'
}
compileOptions {
// Flag to enable support for the new language APIs
// coreLibraryDesugaringEnabled true
// sourceCompatibility JavaVersion.VERSION_1_8
// targetCompatibility JavaVersion.VERSION_1_8
}
namespace 'org.exbin.bined.editor.android'
lint {
checkReleaseBuilds false
}
}
tasks.withType(JavaCompile) {
tasks.withType(JavaCompile).configureEach {
options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'androidx.appcompat:appcompat:1.7.0'
// implementation 'androidx.appcompat:appcompat:1.7.0'
// implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'com.google.android.flexbox:flexbox:3.0.0'
compileOnly group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2'
implementation 'com.github.rustamg:file-dialogs:1.0'
implementation 'com.google.android.material:material:1.12.0'
implementation "com.google.android.material:material:${materialVersion}"
// coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
implementation 'androidx.preference:preference:1.2.1'
implementation 'net.java.dev.jna:jna:5.17.0@aar'
implementation project(':file_picker')
// implementation 'org.exbin.bined:bined-core:0.3.0-SNAPSHOT'
// implementation 'org.exbin.bined:bined-operation:0.3.0-SNAPSHOT'
// implementation 'org.exbin.auxiliary:binary_data:0.2.1'
// implementation 'org.exbin.auxiliary:binary_data-array:0.2.1'
// implementation 'org.exbin.auxiliary:binary_data-delta:0.2.1'
testCompileOnly group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2'
testImplementation 'junit:junit:4.13.2'
testImplementation fileTree(include: ['*.jar'], dir: 'libs')
testImplementation 'androidx.test:core:1.6.1'
// testImplementation 'androidx.test.ext:junit:1.2.1'
testCompileOnly group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2'
testImplementation 'androidx.test.ext:junit:1.2.1'
testImplementation "androidx.test.espresso:espresso-core:3.6.1";
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation "androidx.test:runner:1.6.2";
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
// implementation 'org.exbin.bined:bined-core:0.3.0-SNAPSHOT'
// implementation 'org.exbin.bined:bined-operation:0.3.0-SNAPSHOT'
implementation 'org.exbin.auxiliary:binary_data:0.2.1'
// implementation 'org.exbin.auxiliary:binary_data-paged:0.2.1'
}
@@ -0,0 +1,104 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
/**
* Interface for binary data - readable sequence of bytes.
* <p>
* Provides methods to read whole or part of the data to array or stream.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public interface BinaryData {
/**
* Returns true if data are empty.
*
* @return true if data empty
*/
boolean isEmpty();
/**
* Returns size of data or -1 if size is not available.
*
* @return size of data in bytes
*/
long getDataSize();
/**
* Returns particular byte from data.
*
* @param position position
* @return byte on requested position
*/
byte getByte(long position);
/**
* Creates copy of all data.
*
* @return copy of data
*/
@Nonnull
BinaryData copy();
/**
* Creates copy of given area.
*
* @param startFrom position to start copy from
* @param length length of area
* @return copy of data
*/
@Nonnull
BinaryData copy(long startFrom, long length);
/**
* Creates copy of given area into array of bytes.
*
* @param startFrom position to start copy from
* @param target target byte array
* @param offset offset position in target
* @param length length of area to copy
*/
void copyToArray(long startFrom, byte[] target, int offset, int length);
/**
* Saves/copies all data to given stream.
*
* @param outputStream output stream
* @throws java.io.IOException if input/output error
*/
void saveToStream(OutputStream outputStream) throws IOException;
/**
* Provides handler for input stream generation.
*
* @return new instance of input stream
*/
@Nonnull
InputStream getDataInputStream();
/**
* Disposes all allocated data if possible.
*/
void dispose();
}
@@ -0,0 +1,126 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data;
import java.io.IOException;
import java.io.InputStream;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
/**
* Input stream for binary data.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public class BinaryDataInputStream extends InputStream implements SeekableStream, FinishableStream {
@Nonnull
protected final BinaryData data;
protected long position = 0;
protected long mark = 0;
public BinaryDataInputStream(BinaryData data) {
this.data = data;
}
@Override
public int read() throws IOException {
try {
return data.getByte(position++) & 0xFF;
} catch (ArrayIndexOutOfBoundsException ex) {
return -1;
}
}
@Override
public int read(byte[] output, int off, int len) throws IOException {
if (output.length == 0 || len == 0) {
return 0;
}
long dataSize = data.getDataSize();
if (position > dataSize - len) {
if (position >= dataSize) {
return -1;
}
len = (int) (dataSize - position);
}
data.copyToArray(position, output, off, len);
position += len;
return len;
}
@Override
public void close() throws IOException {
finish();
}
@Override
public int available() throws IOException {
return (int) (data.getDataSize() - position);
}
@Override
public void seek(long position) throws IOException {
this.position = position;
}
@Override
public long finish() throws IOException {
position = data.getDataSize();
return position;
}
@Override
public long getProcessedSize() {
return position;
}
@Override
public long getStreamSize() {
return data.getDataSize();
}
@Override
public boolean markSupported() {
return true;
}
@Override
public synchronized void reset() throws IOException {
position = mark;
}
@Override
public synchronized void mark(int readlimit) {
mark = position;
}
@Override
public long skip(long n) throws IOException {
long dataSize = data.getDataSize();
if (position + n < dataSize) {
position += n;
return n;
}
long skipped = dataSize - position;
position = dataSize;
return skipped;
}
}
@@ -0,0 +1,89 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data;
import java.io.IOException;
import java.io.OutputStream;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
/**
* Output stream for binary data.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public class BinaryDataOutputStream extends OutputStream implements SeekableStream, FinishableStream {
@Nonnull
protected final EditableBinaryData data;
protected long position = 0;
public BinaryDataOutputStream(EditableBinaryData data) {
this.data = data;
}
@Override
public void write(int value) throws IOException {
long dataSize = data.getDataSize();
if (position == dataSize) {
dataSize++;
data.setDataSize(dataSize);
}
data.setByte(position++, (byte) value);
}
@Override
public void write(byte[] input, int off, int len) throws IOException {
if (len == 0) {
return;
}
long dataSize = data.getDataSize();
if (position + len > dataSize) {
data.setDataSize(position + len);
}
data.replace(position, input, off, len);
position += len;
}
@Override
public void seek(long position) throws IOException {
this.position = position;
}
@Override
public long getStreamSize() {
return data.getDataSize();
}
@Override
public long getProcessedSize() {
return position;
}
@Override
public void close() throws IOException {
finish();
}
@Override
public long finish() throws IOException {
position = data.getDataSize();
return position;
}
}
@@ -0,0 +1,72 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data;
import java.util.Optional;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Exception for data reading or writing when range of data is not present.
* <p>
* Exception can optionally provide information about range of data which are
* not present, but it doesn't have to be full segment.
*
* @author ExBin Project (https://exbin.org)
*/
public class DataNotPresentException extends RuntimeException {
@Nullable
protected final DataRange dataRange;
public DataNotPresentException() {
dataRange = null;
}
public DataNotPresentException(@Nullable DataRange dataRange) {
this.dataRange = dataRange;
}
public DataNotPresentException(@Nullable String message) {
super(message);
dataRange = null;
}
public DataNotPresentException(@Nullable String message, @Nullable DataRange dataRange) {
super(message);
this.dataRange = dataRange;
}
public DataNotPresentException(@Nullable String message, @Nullable Throwable cause) {
super(message, cause);
dataRange = null;
}
public DataNotPresentException(@Nullable Throwable cause) {
super(cause);
dataRange = null;
}
public DataNotPresentException(@Nullable String message, @Nullable Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
dataRange = null;
}
@Nonnull
public Optional<DataRange> getDataRange() {
return Optional.ofNullable(dataRange);
}
}
@@ -0,0 +1,46 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data;
import javax.annotation.Nullable;
/**
* Exception for overflow situation where more data is inserted/added than it is
* allowed to handle.
*
* @author ExBin Project (https://exbin.org)
*/
public class DataOverflowException extends RuntimeException {
public DataOverflowException() {
}
public DataOverflowException(@Nullable String message) {
super(message);
}
public DataOverflowException(@Nullable String message, @Nullable Throwable cause) {
super(message, cause);
}
public DataOverflowException(@Nullable Throwable cause) {
super(cause);
}
public DataOverflowException(@Nullable String message, @Nullable Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
@@ -0,0 +1,51 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data;
import javax.annotation.concurrent.Immutable;
/**
* Simple representation of data range.
*
* @author ExBin Project (https://exbin.org)
*/
@Immutable
public class DataRange {
protected final long startPosition;
protected final long endPosition;
public DataRange(long startPosition, long endPosition) {
if (endPosition > startPosition) {
throw new IllegalStateException("Invalid data range");
}
this.startPosition = startPosition;
this.endPosition = endPosition;
}
public long getStartPosition() {
return startPosition;
}
public long getEndPosition() {
return endPosition;
}
public long getLength() {
return endPosition - startPosition + 1;
}
}
@@ -0,0 +1,215 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
/**
* Interface for editable binary data - sequence of bytes.
* <p>
* Provides methods to read/write whole or part of the data to array or stream.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public interface EditableBinaryData extends BinaryData {
/**
* Sets data size.
* <p>
* If size is bigger than current size, it will fill it with zeros,
* otherwise it will shrink current data.
*
* @param size target size
*/
void setDataSize(long size);
/**
* Sets byte to given position.
*
* @param position position
* @param value byte value to be set
*/
void setByte(long position, byte value);
/**
* Inserts data space of given length to given position without setting any
* data to it.
*
* @param startFrom position to insert to
* @param length length of data
*/
void insertUninitialized(long startFrom, long length);
/**
* Inserts empty data of given length to given position.
*
* @param startFrom position to insert to
* @param length length of data
*/
void insert(long startFrom, long length);
/**
* Inserts given data to given position.
*
* @param startFrom position to insert to
* @param insertedData data to insert
*/
void insert(long startFrom, byte[] insertedData);
/**
* Inserts given data to given position.
*
* @param startFrom position to insert to
* @param insertedData data to insert
* @param insertedDataOffset inserted data offset
* @param insertedDataLength inserted data length
*/
void insert(long startFrom, byte[] insertedData, int insertedDataOffset, int insertedDataLength);
/**
* Inserts given data to given position.
*
* @param startFrom position to insert to
* @param insertedData data to insert
*/
void insert(long startFrom, BinaryData insertedData);
/**
* Inserts given data to given position.
*
* @param startFrom position to insert to
* @param insertedData data to insert
* @param insertedDataOffset inserted data offset
* @param insertedDataLength inserted data length
*/
void insert(long startFrom, BinaryData insertedData, long insertedDataOffset, long insertedDataLength);
/**
* Loads data from given stream expecting provided size.
* <p>
* Preserves original data outside loaded range. Extends data if needed.
*
* @param startFrom start position to insert data
* @param inputStream input stream
* @param maximumDataSize size of data to load or -1 for all data
* @return length of loaded data
* @throws java.io.IOException if input/output error
*/
long insert(long startFrom, InputStream inputStream, long maximumDataSize) throws IOException;
/**
* Replaces data in given area with provided data.
* <p>
* If sourceData are the same instance, data are replaced as it would be
* copied to buffer first and replaced then.
*
* @param targetPosition target position to write to
* @param replacingData data to read from
*/
void replace(long targetPosition, BinaryData replacingData);
/**
* Replaces data in given area with provided data.
* <p>
* If sourceData are the same instance, data are replaced as it would be
* copied to buffer first and replaced then.
*
* @param targetPosition target position to write to
* @param replacingData data to read from
* @param startFrom position to start copy from
* @param length length of data to copy
*/
void replace(long targetPosition, BinaryData replacingData, long startFrom, long length);
/**
* Replaces data in given area with provided data.
* <p>
* If sourceData are the same instance, data are replaced as it would be
* copied to buffer first and replaced then.
*
* @param targetPosition target position to write to
* @param replacingData data to read from
*/
void replace(long targetPosition, byte[] replacingData);
/**
* Replaces data in given area with provided data.
* <p>
* If sourceData are the same instance, data are replaced as it would be
* copied to buffer first and replaced then.
*
* @param targetPosition target position to write to
* @param replacingData data to read from
* @param replacingDataOffset position to start copy from
* @param length length of data to copy
*/
void replace(long targetPosition, byte[] replacingData, int replacingDataOffset, int length);
/**
* Fills given area with empty data.
*
* @param startFrom position to fill data to
* @param length length of area
*/
void fillData(long startFrom, long length);
/**
* Fills given area with bytes of given value.
*
* @param startFrom position to fill data to
* @param length length of area
* @param fill value to fill with
*/
void fillData(long startFrom, long length, byte fill);
/**
* Removes area of data.
*
* @param startFrom position to start removal from
* @param length length of area
*/
void remove(long startFrom, long length);
/**
* Removes all existing data.
* <p>
* Simply releases all references to data pages.
*/
void clear();
/**
* Replaces all data by data red from given stream.
*
* @param inputStream input stream
* @throws java.io.IOException if input/output error
*/
void loadFromStream(InputStream inputStream) throws IOException;
/**
* Provides handler for output stream generation.
* <p>
* Received data are appended to existing data.
*
* @return new instance of output stream
*/
@Nonnull
OutputStream getDataOutputStream();
}
@@ -0,0 +1,102 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* Empty read-only binary data.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
@Immutable
public class EmptyBinaryData implements BinaryData {
private static final class Singleton {
private static final EmptyBinaryData INSTANCE = new EmptyBinaryData();
}
@Nonnull
public static EmptyBinaryData getInstance() {
return Singleton.INSTANCE;
}
@Override
public boolean isEmpty() {
return true;
}
@Override
public long getDataSize() {
return 0;
}
@Override
public byte getByte(long position) {
throw new OutOfBoundsException();
}
@Nonnull
@Override
public BinaryData copy() {
return this;
}
@Nonnull
@Override
public BinaryData copy(long startFrom, long length) {
if (startFrom == 0 && length == 0) {
return this;
}
throw new OutOfBoundsException();
}
@Override
public void copyToArray(long startFrom, byte[] target, int offset, int length) {
if (startFrom == 0 && length == 0) {
return;
}
throw new OutOfBoundsException();
}
@Override
public void saveToStream(OutputStream outputStream) throws IOException {
}
@Nonnull
@Override
public InputStream getDataInputStream() {
return new InputStream() {
@Override
public int read() throws IOException {
return -1;
}
};
}
@Override
public void dispose() {
}
}
@@ -0,0 +1,43 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data;
import java.io.IOException;
/**
* Interface for finish-able stream - allows to skip/process data to identify
* size of remaining data.
*
* @author ExBin Project (https://exbin.org)
*/
public interface FinishableStream {
/**
* Reads remaining data and returns size of all data processed by this
* stream.
*
* @return size of data in bytes
* @throws IOException if input/output error occurs
*/
long finish() throws IOException;
/**
* Returns size of data processed so far.
*
* @return size of data in bytes
*/
long getProcessedSize();
}
@@ -0,0 +1,45 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data;
import javax.annotation.Nullable;
/**
* Exception for out of bounds issues with binary data manipulation operations.
*
* @author ExBin Project (https://exbin.org)
*/
public class OutOfBoundsException extends RuntimeException {
public OutOfBoundsException() {
}
public OutOfBoundsException(@Nullable String message) {
super(message);
}
public OutOfBoundsException(@Nullable String message, @Nullable Throwable cause) {
super(message, cause);
}
public OutOfBoundsException(@Nullable Throwable cause) {
super(cause);
}
public OutOfBoundsException(@Nullable String message, @Nullable Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
@@ -0,0 +1,42 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data;
import java.io.IOException;
/**
* Interface for seekable stream - allows to change current position.
*
* @author ExBin Project (https://exbin.org)
*/
public interface SeekableStream {
/**
* Moves position in the stream to given position from the start of the
* stream.
*
* @param position target position
* @throws IOException if input/output error
*/
void seek(long position) throws IOException;
/**
* Returns length of the stream.
*
* @return length of the stream in bytes, -1 if unable to determine
*/
long getStreamSize();
}
@@ -0,0 +1,60 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data;
/**
* Interface for binary data with missing segments / segments where no data are
* present.
* <p>
* Expected use is to either check presence per position or segments or to read
* data and handle presence only when {@link DataNotPresentException} is thrown.
*
* @author ExBin Project (https://exbin.org)
*/
public interface SparseBinaryData {
/**
* Returns true if data are present on given position.
*
* @param position position
* @return true if present
*/
boolean isDataPresent(long position);
/**
* Returns closest next subsequent data position where data presence changes
* or any position closer, but greater than given position.
* <p>
* Basic implementation can return position + 1 which forces reading and
* handling of {@link DataNotPresentException}.
*
* @param position position to check from
* @return next position to check for presence
*/
long closestNextPossiblePresenceChange(long position);
/**
* Returns closest previous preceding data position where data presence
* changes or any position closer, but lesser than given position.
* <p>
* Basic implementation can return position - 1 which forces reading and
* handling of {@link DataNotPresentException}.
*
* @param position position to check from
* @return previous position to check for presence
*/
long closestPreviousPossiblePresenceChange(long position);
}
@@ -0,0 +1,241 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data.buffer;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import org.exbin.auxiliary.binary_data.BinaryData;
import org.exbin.auxiliary.binary_data.BinaryDataInputStream;
import org.exbin.auxiliary.binary_data.OutOfBoundsException;
/**
* Implementation of binary data interface using byte buffer.
* <p>
* To allow parallel reading, read operations must be synchronized due to split
* nature of the ByteBuffer position and depending operation.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public class BufferData implements BinaryData {
protected static final int BUFFER_SIZE = 4096;
@Nonnull
protected ByteBuffer data;
public BufferData() {
this((ByteBuffer) null);
}
/**
* Creates instance directly wrapping provided byte buffer.
*
* @param data byte buffer
*/
public BufferData(@Nullable ByteBuffer data) {
this.data = data != null ? data : BufferData.this.allocateBuffer(0);
}
/**
* Creates instance setting value to provided byte array.
*
* @param data byte array
*/
public BufferData(@Nullable byte[] data) {
if (data == null) {
this.data = BufferData.this.allocateBuffer(0);
} else {
this.data = BufferData.this.allocateBuffer(data.length);
this.data.put(data);
this.data.clear();
}
}
/**
* Creates instance with specified size.
*
* @param dataSize data size
*/
public BufferData(int dataSize) {
this.data = BufferData.this.allocateBuffer(dataSize);
}
/**
* Returns internal data.
*
* @return byte array
*/
@Nonnull
public ByteBuffer getData() {
return data;
}
@Override
public boolean isEmpty() {
return data.capacity() == 0;
}
@Override
public long getDataSize() {
return data.capacity();
}
@Override
public synchronized byte getByte(long position) {
try {
return data.get((int) position);
} catch (IndexOutOfBoundsException ex) {
throw new OutOfBoundsException(ex);
}
}
@Nonnull
@Override
public BufferData copy() {
ByteBuffer copy = allocateBuffer(data.capacity());
synchronized (this) {
data.rewind();
copy.put(data);
}
return new BufferData(copy);
}
@Nonnull
@Override
public BufferData copy(long startFrom, long length) {
if (length > Integer.MAX_VALUE) {
throw new OutOfBoundsException("Buffer data is limited by integer length");
}
if (startFrom + length > data.capacity()) {
throw new OutOfBoundsException("Attempt to copy outside of data");
}
ByteBuffer copy = allocateBuffer((int) length);
synchronized (this) {
data.position((int) startFrom);
data.limit((int) (startFrom + length));
copy.put(data);
data.clear();
return new BufferData(copy);
}
}
@Override
public synchronized void copyToArray(long startFrom, byte[] target, int offset, int length) {
try {
data.position((int) startFrom);
data.get(target, offset, length);
} catch (IndexOutOfBoundsException | BufferUnderflowException | IllegalArgumentException ex) {
throw new OutOfBoundsException(ex);
}
}
@Override
public void saveToStream(OutputStream outputStream) throws IOException {
byte[] buffer = new byte[BUFFER_SIZE];
int remaining = data.capacity();
int position = 0;
while (remaining > 0) {
int length = remaining > BUFFER_SIZE ? BUFFER_SIZE : remaining;
synchronized (this) {
data.position(position);
data.get(buffer, 0, length);
}
outputStream.write(buffer, 0, length);
position += length;
remaining -= length;
}
}
@Nonnull
@Override
public InputStream getDataInputStream() {
return new BinaryDataInputStream(this);
}
@Override
public synchronized int hashCode() {
return data.hashCode();
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
if (obj instanceof BinaryData) {
BinaryData other = (BinaryData) obj;
return compareTo(other);
}
return false;
}
final BufferData other = (BufferData) obj;
synchronized (this) {
other.data.rewind();
data.rewind();
return other.data.compareTo(data) == 0;
}
}
public boolean compareTo(BinaryData other) {
long dataSize = getDataSize();
if (other.getDataSize() != dataSize) {
return false;
}
int bufferSize = dataSize > BUFFER_SIZE ? BUFFER_SIZE : (int) dataSize;
byte[] buffer = new byte[bufferSize];
int offset = 0;
int remain = (int) dataSize;
while (remain > 0) {
int length = remain > bufferSize ? bufferSize : remain;
other.copyToArray(offset, buffer, 0, length);
for (int i = 0; i < length; i++) {
if (data.get(offset + i) != buffer[i]) {
return false;
}
}
offset += length;
remain -= length;
}
return true;
}
@Override
public void dispose() {
}
@Nonnull
protected ByteBuffer allocateBuffer(int capacity) {
return ByteBuffer.allocate(capacity);
}
}
@@ -0,0 +1,395 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data.buffer;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.security.InvalidParameterException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import org.exbin.auxiliary.binary_data.BinaryData;
import org.exbin.auxiliary.binary_data.BinaryDataOutputStream;
import org.exbin.auxiliary.binary_data.DataOverflowException;
import org.exbin.auxiliary.binary_data.EditableBinaryData;
import org.exbin.auxiliary.binary_data.OutOfBoundsException;
/**
* Implementation of editable binary data interface using byte buffer.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public class BufferEditableData extends BufferData implements EditableBinaryData {
public static final int MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 5;
private static final String WRONG_INSERTION_POSITION_ERROR = "Data can be inserted only inside or at the end";
private static final String WRONG_REPLACE_POSITION_ERROR = "Data can be replaced only inside or at the end";
private static final String ARRAY_OVERFLOW_ERROR = "Maximum array size overflow";
public BufferEditableData() {
this((ByteBuffer) null);
}
public BufferEditableData(int dataSize) {
super(dataSize);
}
public BufferEditableData(@Nullable ByteBuffer data) {
super(data);
}
public BufferEditableData(@Nullable byte[] data) {
super(data);
}
@Override
public void setDataSize(long size) {
if (size < 0) {
throw new InvalidParameterException("Size cannot be negative");
}
int oldSize = data.capacity();
if (oldSize != size) {
ByteBuffer newData = allocateBuffer((int) size);
if (size < oldSize) {
if (size > 0) {
data.rewind();
data.limit((int) size);
newData.put(data);
data.limit(oldSize);
}
data = newData;
} else {
if (oldSize > 0) {
data.rewind();
newData.put(data);
}
data = newData;
}
}
}
@Override
public void setByte(long position, byte value) {
try {
data.put((int) position, value);
} catch (IndexOutOfBoundsException ex) {
throw new OutOfBoundsException(ex);
}
}
@Override
public void insertUninitialized(long startFrom, long length) {
if (startFrom > data.capacity()) {
throw new OutOfBoundsException(WRONG_INSERTION_POSITION_ERROR);
}
if (length > MAX_ARRAY_LENGTH - data.capacity()) {
throw new DataOverflowException(ARRAY_OVERFLOW_ERROR);
}
if (length > 0) {
ByteBuffer newData = allocateBuffer((int) (data.capacity() + length));
BufferEditableData.put(newData, 0, data, 0, (int) startFrom);
BufferEditableData.put(newData, (int) (startFrom + length), data, (int) startFrom, (int) (data.capacity() - startFrom));
data = newData;
}
}
@Override
public void insert(long startFrom, long length) {
insertUninitialized(startFrom, length);
fillData(startFrom, length, (byte) 0);
}
@Override
public void insert(long startFrom, byte[] insertedData) {
if (startFrom > data.capacity()) {
throw new OutOfBoundsException(WRONG_INSERTION_POSITION_ERROR);
}
if (insertedData.length > MAX_ARRAY_LENGTH - data.capacity()) {
throw new DataOverflowException(ARRAY_OVERFLOW_ERROR);
}
int length = insertedData.length;
if (length > 0) {
ByteBuffer newData = allocateBuffer((int) (data.capacity() + length));
BufferEditableData.put(newData, 0, data, 0, (int) startFrom);
try {
newData.position((int) startFrom);
newData.put(insertedData);
BufferEditableData.put(newData, (int) (startFrom + length), data, (int) startFrom, (int) (data.capacity() - startFrom));
} catch (IndexOutOfBoundsException ex) {
throw new OutOfBoundsException(ex);
}
data = newData;
}
}
@Override
public void insert(long startFrom, byte[] insertedData, int insertedDataOffset, int length) {
if (startFrom > data.capacity()) {
throw new OutOfBoundsException(WRONG_INSERTION_POSITION_ERROR);
}
if (length > MAX_ARRAY_LENGTH - data.capacity()) {
throw new DataOverflowException(ARRAY_OVERFLOW_ERROR);
}
if (length > 0) {
ByteBuffer newData = allocateBuffer((int) (data.capacity() + length));
BufferEditableData.put(newData, 0, data, 0, (int) startFrom);
try {
newData.position((int) startFrom);
newData.put(insertedData, insertedDataOffset, length);
BufferEditableData.put(newData, (int) (startFrom + length), data, (int) startFrom, (int) (data.capacity() - startFrom));
} catch (IndexOutOfBoundsException ex) {
throw new OutOfBoundsException(ex);
}
data = newData;
}
}
@Override
public void insert(long startFrom, BinaryData insertedData) {
if (startFrom > data.capacity()) {
throw new OutOfBoundsException(WRONG_INSERTION_POSITION_ERROR);
}
if (insertedData.getDataSize() > MAX_ARRAY_LENGTH - data.capacity()) {
throw new DataOverflowException(ARRAY_OVERFLOW_ERROR);
}
insert(startFrom, insertedData, 0, insertedData.getDataSize());
}
@Override
public void insert(long startFrom, BinaryData insertedData, long insertedDataOffset, long insertedDataLength) {
if (startFrom > data.capacity()) {
throw new OutOfBoundsException(WRONG_INSERTION_POSITION_ERROR);
}
if (insertedDataLength > MAX_ARRAY_LENGTH - data.capacity()) {
throw new DataOverflowException(ARRAY_OVERFLOW_ERROR);
}
long length = insertedDataLength;
if (length > 0) {
ByteBuffer newData = allocateBuffer((int) (data.capacity() + length));
BufferEditableData.put(newData, 0, data, 0, (int) startFrom);
for (int i = 0; i < length; i++) {
newData.position((int) startFrom + i);
newData.put(insertedData.getByte(insertedDataOffset + i));
}
BufferEditableData.put(newData, (int) (startFrom + length), data, (int) startFrom, (int) (data.capacity() - startFrom));
data = newData;
}
}
@Override
public long insert(long startFrom, InputStream inputStream, long maximumDataSize) throws IOException {
if (maximumDataSize > MAX_ARRAY_LENGTH - data.capacity()) {
throw new DataOverflowException(ARRAY_OVERFLOW_ERROR);
}
try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
byte[] buffer = new byte[BUFFER_SIZE];
while (maximumDataSize == -1 || maximumDataSize > 0) {
int toRead = buffer.length;
if (maximumDataSize >= 0 && toRead > maximumDataSize) {
toRead = (int) maximumDataSize;
}
int read = inputStream.read(buffer, 0, toRead);
if (read == -1) {
break;
}
if (read > 0) {
output.write(buffer, 0, read);
if (maximumDataSize >= 0) {
maximumDataSize -= read;
}
}
}
byte[] newData = output.toByteArray();
if (startFrom + newData.length > getDataSize()) {
setDataSize(startFrom + newData.length);
}
replace(startFrom, newData);
return newData.length;
}
}
@Override
public void fillData(long startFrom, long length) {
fillData(startFrom, length, (byte) 0);
}
@Override
public void fillData(long startFrom, long length, byte fill) {
if (length > 0) {
try {
for (int i = (int) startFrom; i < startFrom + length; i++) {
data.put(i, fill);
}
} catch (IndexOutOfBoundsException ex) {
throw new OutOfBoundsException(ex);
}
}
}
@Override
public void replace(long targetPosition, BinaryData sourceData) {
replace(targetPosition, sourceData, 0, sourceData.getDataSize());
}
@Override
public void replace(long targetPosition, BinaryData replacingData, long startFrom, long replacingLength) {
if (targetPosition + replacingLength > getDataSize()) {
throw new OutOfBoundsException(WRONG_REPLACE_POSITION_ERROR);
}
while (replacingLength > 0) {
setByte(targetPosition, replacingData.getByte(startFrom));
targetPosition++;
startFrom++;
replacingLength--;
}
}
@Override
public void replace(long targetPosition, byte[] replacingData) {
replace(targetPosition, replacingData, 0, replacingData.length);
}
@Override
public void replace(long targetPosition, byte[] replacingData, int replacingDataOffset, int length) {
if (targetPosition + length > getDataSize()) {
throw new OutOfBoundsException(WRONG_REPLACE_POSITION_ERROR);
}
try {
data.position((int) targetPosition);
data.put(replacingData, replacingDataOffset, length);
} catch (IndexOutOfBoundsException ex) {
throw new OutOfBoundsException(ex);
}
}
@Override
public void remove(long startFrom, long length) {
if (startFrom + length > data.capacity()) {
throw new OutOfBoundsException("Cannot remove from " + startFrom + " with length " + length);
}
if (length > 0) {
ByteBuffer newData = allocateBuffer((int) (data.capacity() - length));
BufferEditableData.put(newData, 0, data, 0, (int) startFrom);
BufferEditableData.put(newData, (int) startFrom, data, (int) (startFrom + length), (int) (data.capacity() - startFrom - length));
data = newData;
}
}
@Nonnull
@Override
public BufferEditableData copy() {
ByteBuffer copy = allocateBuffer(data.capacity());
synchronized (this) {
data.rewind();
copy.put(data);
}
return new BufferEditableData(copy);
}
@Nonnull
@Override
public BufferEditableData copy(long startFrom, long length) {
if (startFrom + length > data.capacity()) {
throw new OutOfBoundsException("Attempt to copy outside of data");
}
ByteBuffer copy = allocateBuffer((int) length);
synchronized (this) {
BufferEditableData.put(copy, 0, data, (int) startFrom, (int) length);
return new BufferEditableData(copy);
}
}
@Override
public void clear() {
data = allocateBuffer(0);
}
@Override
public void loadFromStream(InputStream inputStream) throws IOException {
try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
byte[] buffer = new byte[BUFFER_SIZE];
int read;
do {
read = inputStream.read(buffer);
if (read > 0) {
output.write(buffer, 0, read);
}
} while (read > 0);
byte[] byteArray = output.toByteArray();
data = allocateBuffer(byteArray.length);
data.put(byteArray);
}
}
@Nonnull
@Override
public OutputStream getDataOutputStream() {
return new BinaryDataOutputStream(this);
}
public static void put(ByteBuffer target, int position, ByteBuffer source, int offset, int length) throws IndexOutOfBoundsException {
if (target == source) {
byte[] buffer = new byte[BUFFER_SIZE];
if (position < offset) {
while (length > 0) {
int blockSize = length > BUFFER_SIZE ? BUFFER_SIZE : length;
source.position(offset);
source.get(buffer, 0, blockSize);
target.position(position);
target.put(buffer, 0, blockSize);
offset += blockSize;
position += blockSize;
length -= blockSize;
}
} else if (position > offset) {
while (length > 0) {
int blockSize = length > BUFFER_SIZE ? BUFFER_SIZE : length;
source.position(offset + length - blockSize);
source.get(buffer, 0, blockSize);
target.position(position + length - blockSize);
target.put(buffer, 0, blockSize);
length -= blockSize;
}
}
target.clear();
return;
}
source.position(offset);
source.limit(offset + length);
target.position(position);
target.put(source);
target.clear();
source.clear();
}
}
@@ -0,0 +1,84 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data.buffer;
import java.nio.ByteBuffer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
/**
* Implementation of binary data interface using direct byte buffer.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public class DirectBufferData extends BufferData {
public DirectBufferData() {
this((ByteBuffer) null);
}
/**
* Creates instance directly wrapping provided byte buffer.
*
* @param data byte buffer
*/
public DirectBufferData(@Nullable ByteBuffer data) {
super(data != null ? data : DirectBufferData.allocateBufferInt(0));
}
/**
* Creates instance setting value to provided byte array.
*
* @param data byte array
*/
public DirectBufferData(@Nullable byte[] data) {
super(DirectBufferData.allocateBufferInt(data));
}
/**
* Creates instance with specified size.
*
* @param dataSize data size
*/
public DirectBufferData(int dataSize) {
super(DirectBufferData.allocateBufferInt(dataSize));
}
@Nonnull
@Override
protected ByteBuffer allocateBuffer(int capacity) {
return DirectBufferData.allocateBufferInt(capacity);
}
@Nonnull
private static ByteBuffer allocateBufferInt(int capacity) {
return ByteBuffer.allocateDirect(capacity);
}
@Nonnull
private static ByteBuffer allocateBufferInt(@Nullable byte[] data) {
if (data == null) {
return DirectBufferData.allocateBufferInt(0);
} else {
ByteBuffer buffer = DirectBufferData.allocateBufferInt(data.length);
buffer.put(data);
buffer.clear();
return buffer;
}
}
}
@@ -0,0 +1,84 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data.buffer;
import java.nio.ByteBuffer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
/**
* Implementation of editable binary data interface using direct byte buffer.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public class DirectBufferEditableData extends BufferEditableData {
public DirectBufferEditableData() {
this((ByteBuffer) null);
}
/**
* Creates instance directly wrapping provided byte buffer.
*
* @param data byte buffer
*/
public DirectBufferEditableData(@Nullable ByteBuffer data) {
super(data != null ? data : DirectBufferEditableData.allocateBufferInt(0));
}
/**
* Creates instance setting value to provided byte array.
*
* @param data byte array
*/
public DirectBufferEditableData(@Nullable byte[] data) {
super(DirectBufferEditableData.allocateBufferInt(data));
}
/**
* Creates instance with specified size.
*
* @param dataSize data size
*/
public DirectBufferEditableData(int dataSize) {
super(DirectBufferEditableData.allocateBufferInt(dataSize));
}
@Nonnull
@Override
protected ByteBuffer allocateBuffer(int capacity) {
return DirectBufferEditableData.allocateBufferInt(capacity);
}
@Nonnull
private static ByteBuffer allocateBufferInt(int capacity) {
return ByteBuffer.allocateDirect(capacity);
}
@Nonnull
private static ByteBuffer allocateBufferInt(@Nullable byte[] data) {
if (data == null) {
return DirectBufferEditableData.allocateBufferInt(0);
} else {
ByteBuffer buffer = DirectBufferEditableData.allocateBufferInt(data.length);
buffer.put(data);
buffer.clear();
return buffer;
}
}
}
@@ -0,0 +1,733 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data.buffer.paged;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import org.exbin.auxiliary.binary_data.BinaryData;
import org.exbin.auxiliary.binary_data.BinaryDataInputStream;
import org.exbin.auxiliary.binary_data.BinaryDataOutputStream;
import org.exbin.auxiliary.binary_data.buffer.BufferData;
import org.exbin.auxiliary.binary_data.buffer.BufferEditableData;
import org.exbin.auxiliary.binary_data.DataOverflowException;
import org.exbin.auxiliary.binary_data.EditableBinaryData;
import org.exbin.auxiliary.binary_data.OutOfBoundsException;
import org.exbin.auxiliary.binary_data.paged.PagedData;
import org.exbin.auxiliary.binary_data.paged.DataPageCreator;
/**
* Paged data stored using byte buffer.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public class BufferPagedData implements PagedData {
public static final int DEFAULT_PAGE_SIZE = 4096;
public static final long MAX_DATA_SIZE = Long.MAX_VALUE;
protected int pageSize = DEFAULT_PAGE_SIZE;
@Nonnull
protected final List<BufferData> data = new ArrayList<>();
@Nullable
protected DataPageCreator dataPageCreator = null;
public BufferPagedData() {
}
public BufferPagedData(DataPageCreator dataPageCreator) {
this.dataPageCreator = dataPageCreator;
}
public BufferPagedData(int pageSize) {
if (pageSize < 1) {
throw new InvalidParameterException("Page size cannot be less than 1");
}
this.pageSize = pageSize;
}
@Override
public boolean isEmpty() {
return data.isEmpty();
}
@Override
public long getDataSize() {
return (data.size() > 1 ? (long) (data.size() - 1) * pageSize : 0) + (!data.isEmpty() ? data.get(data.size() - 1).getDataSize() : 0);
}
@Override
public void setDataSize(long size) {
if (size < 0) {
throw new InvalidParameterException("Size cannot be negative");
}
long dataSize = getDataSize();
if (size > dataSize) {
int lastPage = (int) (dataSize / pageSize);
int lastPageSize = (int) (dataSize % pageSize);
long remaining = size - dataSize;
// extend last page
if (lastPageSize > 0) {
BufferData page = getPage(lastPage);
int nextPageSize = remaining + lastPageSize > pageSize ? pageSize : (int) remaining + lastPageSize;
BufferData newPage = createNewPage(nextPageSize);
page.getData().rewind();
newPage.getData().put(page.getData());
setPage(lastPage, newPage);
remaining -= (nextPageSize - lastPageSize);
lastPage++;
}
while (remaining > 0) {
int nextPageSize = remaining > pageSize ? pageSize : (int) remaining;
data.add(createNewPage(nextPageSize));
remaining -= nextPageSize;
}
} else if (size < dataSize) {
int lastPage = (int) (size / pageSize);
int lastPageSize = (int) (size % pageSize);
// shrink last page
if (lastPageSize > 0) {
BufferData page = getPage(lastPage);
BufferData newPage = createNewPage(lastPageSize);
BufferPagedData.put(newPage.getData(), 0, page.getData(), 0, lastPageSize);
setPage(lastPage, newPage);
lastPage++;
}
for (int pageIndex = data.size() - 1; pageIndex >= lastPage; pageIndex--) {
data.remove(pageIndex);
}
}
}
@Override
public byte getByte(long position) {
BufferData page = getPage((int) (position / pageSize));
try {
return page.getByte(position % pageSize);
} catch (IndexOutOfBoundsException ex) {
throw new OutOfBoundsException(ex);
}
}
@Override
public void setByte(long position, byte value) {
BufferData page = getPage((int) (position / pageSize));
try {
page.getData().put((int) (position % pageSize), value);
} catch (IndexOutOfBoundsException ex) {
throw new OutOfBoundsException(ex);
}
}
@Override
public void insertUninitialized(long startFrom, long length) {
if (length < 0) {
throw new IllegalArgumentException("Length of inserted block must be non-negative");
}
if (startFrom < 0) {
throw new IllegalArgumentException("Position of inserted block must be non-negative");
}
long dataSize = getDataSize();
if (startFrom > dataSize) {
throw new OutOfBoundsException("Inserted block must be inside or directly after existing data");
}
if (length > MAX_DATA_SIZE - getDataSize()) {
throw new DataOverflowException("Maximum array size overflow");
}
if (length == 0) {
return;
}
long copyLength = dataSize - startFrom;
dataSize = dataSize + length;
setDataSize(dataSize);
long sourceEnd = dataSize - length;
long targetEnd = dataSize;
// Backward copy
while (copyLength > 0) {
BufferData sourcePage;
int sourceOffset = (int) (sourceEnd % pageSize);
if (sourceOffset == 0) {
sourcePage = getPage((int) ((sourceEnd - 1) / pageSize));
sourceOffset = (int) sourcePage.getDataSize();
} else {
sourcePage = getPage((int) (sourceEnd / pageSize));
}
BufferData targetPage;
int targetOffset = (int) (targetEnd % pageSize);
if (targetOffset == 0) {
targetPage = getPage((int) ((targetEnd - 1) / pageSize));
targetOffset = (int) targetPage.getDataSize();
} else {
targetPage = getPage((int) (targetEnd / pageSize));
}
int copySize = Math.min(sourceOffset, targetOffset);
if (copySize > copyLength) {
copySize = (int) copyLength;
}
BufferPagedData.put(targetPage.getData(), targetOffset - copySize, sourcePage.getData(), sourceOffset - copySize, copySize);
copyLength -= copySize;
sourceEnd -= copySize;
targetEnd -= copySize;
}
}
@Override
public void insert(long startFrom, long length) {
insertUninitialized(startFrom, length);
fillData(startFrom, length);
}
@Override
public void insert(long startFrom, BinaryData insertedData) {
long length = insertedData.getDataSize();
insertUninitialized(startFrom, length);
replace(startFrom, insertedData, 0, length);
}
@Override
public void insert(long startFrom, BinaryData insertedData, long insertedDataOffset, long insertedDataLength) {
insertUninitialized(startFrom, insertedDataLength);
replace(startFrom, insertedData, insertedDataOffset, insertedDataLength);
}
@Override
public void insert(long startFrom, byte[] insertedData) {
insert(startFrom, insertedData, 0, insertedData.length);
}
@Override
public void insert(long startFrom, byte[] insertedData, int insertedDataOffset, int insertedDataLength) {
if (insertedDataLength <= 0) {
return;
}
insertUninitialized(startFrom, insertedDataLength);
while (insertedDataLength > 0) {
BufferData targetPage = getPage((int) (startFrom / pageSize));
int targetOffset = (int) (startFrom % pageSize);
int blockLength = pageSize - targetOffset;
if (blockLength > insertedDataLength) {
blockLength = insertedDataLength;
}
try {
targetPage.getData().position(targetOffset);
targetPage.getData().put(insertedData, insertedDataOffset, blockLength);
} catch (IndexOutOfBoundsException ex) {
throw new OutOfBoundsException(ex);
}
insertedDataOffset += blockLength;
insertedDataLength -= blockLength;
startFrom += blockLength;
}
}
@Override
public long insert(long startFrom, InputStream inputStream, long maximumDataSize) throws IOException {
if (maximumDataSize > MAX_DATA_SIZE - getDataSize()) {
throw new DataOverflowException("Maximum array size overflow");
}
if (startFrom > getDataSize()) {
setDataSize(startFrom);
}
long loadedData = 0;
int pageOffset = (int) (startFrom % pageSize);
byte[] buffer = new byte[pageSize];
while (maximumDataSize == -1 || maximumDataSize > 0) {
int dataToRead = pageSize - pageOffset;
if (maximumDataSize >= 0 && maximumDataSize < dataToRead) {
dataToRead = (int) maximumDataSize;
}
if (pageOffset > 0 && dataToRead > pageOffset) {
// Align to data pages
dataToRead = pageOffset;
}
int readLength = 0;
while (dataToRead > 0) {
int read = inputStream.read(buffer, readLength, dataToRead);
if (read == -1) {
break;
}
readLength += read;
dataToRead -= read;
}
insert(startFrom, buffer, 0, readLength);
startFrom += readLength;
if (maximumDataSize >= 0) {
maximumDataSize -= readLength;
}
loadedData += readLength;
pageOffset = 0;
}
return loadedData;
}
@Override
public void fillData(long startFrom, long length) {
fillData(startFrom, length, (byte) 0);
}
@Override
public void fillData(long startFrom, long length, byte fill) {
if (length < 0) {
throw new IllegalArgumentException("Length of filled block must be non-negative");
}
if (startFrom < 0) {
throw new IllegalArgumentException("Position of filler block must be non-negative");
}
if (startFrom + length > getDataSize()) {
throw new OutOfBoundsException("Filled block must be inside existing data");
}
while (length > 0) {
BufferData page = getPage((int) (startFrom / pageSize));
int pageOffset = (int) (startFrom % pageSize);
int fillSize = (int) (page.getDataSize() - pageOffset);
if (fillSize > length) {
fillSize = (int) length;
}
ByteBuffer pageBuffer = page.getData();
for (int i = pageOffset; i < pageOffset + fillSize; i++) {
pageBuffer.put(i, fill);
}
length -= fillSize;
startFrom += fillSize;
}
}
@Nonnull
@Override
public BufferPagedData copy() {
BufferPagedData targetData = new BufferPagedData();
targetData.insert(0, this);
return targetData;
}
@Nonnull
@Override
public BufferPagedData copy(long startFrom, long length) {
BufferPagedData targetData = new BufferPagedData();
targetData.insertUninitialized(0, length);
targetData.replace(0, this, startFrom, length);
return targetData;
}
@Override
public void copyToArray(long startFrom, byte[] target, int offset, int length) {
while (length > 0) {
BufferData page = getPage((int) (startFrom / pageSize));
int pageOffset = (int) (startFrom % pageSize);
int copySize = pageSize - pageOffset;
if (copySize > length) {
copySize = length;
}
page.copyToArray(pageOffset, target, offset, copySize);
length -= copySize;
offset += copySize;
startFrom += copySize;
}
}
@Override
public void remove(long startFrom, long length) {
if (length < 0) {
throw new IllegalArgumentException("Length of removed block must be non-negative");
}
if (startFrom < 0) {
throw new IllegalArgumentException("Position of removed block must be non-negative");
}
if (startFrom + length > getDataSize()) {
throw new OutOfBoundsException("Removed block must be inside existing data");
}
if (length > 0) {
long dataSize = getDataSize();
replace(startFrom, this, startFrom + length, dataSize - startFrom - length);
setDataSize(dataSize - length);
}
}
@Override
public void clear() {
data.clear();
}
/**
* Returns number of pages currently used.
*
* @return count of pages
*/
@Override
public int getPagesCount() {
return data.size();
}
/**
* Returns currently used page size.
*
* @return page size in bytes
*/
@Override
public int getPageSize() {
return pageSize;
}
/**
* Gets data page allowing direct access to it.
*
* @param pageIndex page index
* @return data page
*/
@Nonnull
@Override
public BufferData getPage(int pageIndex) {
try {
return data.get(pageIndex);
} catch (IndexOutOfBoundsException ex) {
throw new OutOfBoundsException(ex);
}
}
/**
* Sets data page replacing existing page by reference.
*
* @param pageIndex page index
* @param dataPage data page
*/
@Override
public void setPage(int pageIndex, BinaryData dataPage) {
if (!(dataPage instanceof BufferData)) {
throw new UnsupportedOperationException("Unsupported data page type");
}
try {
data.set(pageIndex, (BufferData) dataPage);
} catch (IndexOutOfBoundsException ex) {
throw new OutOfBoundsException(ex);
}
}
@Override
public void replace(long targetPosition, BinaryData replacingData) {
replace(targetPosition, replacingData, 0, replacingData.getDataSize());
}
@Override
public void replace(long targetPosition, BinaryData replacingData, long startFrom, long length) {
if (targetPosition + length > getDataSize()) {
throw new OutOfBoundsException("Data can be replaced only inside or at the end");
}
if (replacingData instanceof BufferPagedData) {
if (replacingData != this || (startFrom > targetPosition) || (startFrom + length < targetPosition)) {
while (length > 0) {
BufferData page = getPage((int) (targetPosition / pageSize));
int offset = (int) (targetPosition % pageSize);
BufferData sourcePage = ((BufferPagedData) replacingData).getPage((int) (startFrom / ((BufferPagedData) replacingData).getPageSize()));
int sourceOffset = (int) (startFrom % ((BufferPagedData) replacingData).getPageSize());
int copySize = pageSize - offset;
if (copySize > ((BufferPagedData) replacingData).getPageSize() - sourceOffset) {
copySize = ((BufferPagedData) replacingData).getPageSize() - sourceOffset;
}
if (copySize > length) {
copySize = (int) length;
}
try {
BufferPagedData.put(page.getData(), offset, sourcePage.getData(), sourceOffset, copySize);
} catch (IndexOutOfBoundsException ex) {
throw new OutOfBoundsException(ex);
}
length -= copySize;
targetPosition += copySize;
startFrom += copySize;
}
} else {
targetPosition += length - 1;
startFrom += length - 1;
while (length > 0) {
BufferData page = getPage((int) (targetPosition / pageSize));
int upTo = (int) (targetPosition % pageSize) + 1;
BufferData sourcePage = ((BufferPagedData) replacingData).getPage((int) (startFrom / ((BufferPagedData) replacingData).getPageSize()));
int sourceUpTo = (int) (startFrom % ((BufferPagedData) replacingData).getPageSize()) + 1;
int copySize = upTo;
if (copySize > sourceUpTo) {
copySize = sourceUpTo;
}
if (copySize > length) {
copySize = (int) length;
}
int offset = upTo - copySize;
int sourceOffset = sourceUpTo - copySize;
BufferPagedData.put(page.getData(), offset, sourcePage.getData(), sourceOffset, copySize);
length -= copySize;
targetPosition -= copySize;
startFrom -= copySize;
}
}
} else {
while (length > 0) {
BufferData page = getPage((int) (targetPosition / pageSize));
int offset = (int) (targetPosition % pageSize);
int copySize = pageSize - offset;
if (copySize > length) {
copySize = (int) length;
}
byte[] buffer = new byte[copySize];
replacingData.copyToArray(startFrom, buffer, 0, copySize);
page.getData().position(offset);
page.getData().put(buffer);
length -= copySize;
targetPosition += copySize;
startFrom += copySize;
}
}
}
@Override
public void replace(long targetPosition, byte[] replacingData) {
replace(targetPosition, replacingData, 0, replacingData.length);
}
@Override
public void replace(long targetPosition, byte[] replacingData, int replacingDataOffset, int length) {
if (targetPosition + length > getDataSize()) {
throw new OutOfBoundsException("Data can be replaced only inside or at the end");
}
while (length > 0) {
BufferData page = getPage((int) (targetPosition / pageSize));
int offset = (int) (targetPosition % pageSize);
int copySize = pageSize - offset;
if (copySize > length) {
copySize = length;
}
try {
page.getData().position(offset);
page.getData().put(replacingData, replacingDataOffset, copySize);
} catch (IndexOutOfBoundsException ex) {
throw new OutOfBoundsException(ex);
}
length -= copySize;
targetPosition += copySize;
replacingDataOffset += copySize;
}
}
@Override
public void loadFromStream(InputStream inputStream) throws IOException {
data.clear();
byte[] buffer = new byte[pageSize];
int cnt;
int offset = 0;
while ((cnt = inputStream.read(buffer, offset, buffer.length - offset)) > 0) {
if (cnt + offset < pageSize) {
offset = offset + cnt;
} else {
data.add(createNewPage(buffer));
buffer = new byte[pageSize];
offset = 0;
}
}
if (offset > 0) {
byte[] tail = new byte[offset];
System.arraycopy(buffer, 0, tail, 0, offset);
data.add(createNewPage(tail));
}
}
@Override
public void saveToStream(OutputStream outputStream) throws IOException {
for (BufferData dataPage : data) {
dataPage.saveToStream(outputStream);
}
}
@Nonnull
@Override
public OutputStream getDataOutputStream() {
return new BinaryDataOutputStream(this);
}
@Nonnull
@Override
public InputStream getDataInputStream() {
return new BinaryDataInputStream(this);
}
@Nonnull
protected BufferData createNewPage(byte[] pageData) {
if (dataPageCreator != null) {
EditableBinaryData page = dataPageCreator.createPage(pageData.length);
page.replace(0, pageData);
return (BufferData) page;
}
return new BufferData(pageData);
}
@Nonnull
protected BufferData createNewPage(int pageDataSize) {
if (dataPageCreator != null) {
return (BufferData) dataPageCreator.createPage(pageDataSize);
}
return new BufferData(pageDataSize);
}
@Nullable
public DataPageCreator getDataPageCreator() {
return dataPageCreator;
}
public void setDataPageCreator(@Nullable DataPageCreator dataPageCreator) {
this.dataPageCreator = dataPageCreator;
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
if (obj instanceof BinaryData) {
BinaryData other = (BinaryData) obj;
long dataSize = getDataSize();
if (other.getDataSize() != dataSize) {
return false;
}
int pageIndex = 0;
int bufferSize = dataSize > pageSize ? pageSize : (int) dataSize;
byte[] buffer = new byte[bufferSize];
int offset = 0;
int remain = (int) dataSize;
while (remain > 0) {
int length = remain > bufferSize ? bufferSize : remain;
other.copyToArray(offset, buffer, 0, length);
BufferData pageData = data.get(pageIndex);
for (int i = 0; i < length; i++) {
if (pageData.getByte(i) != buffer[i]) {
return false;
}
}
offset += length;
remain -= length;
pageIndex++;
}
return true;
}
return false;
}
final BufferPagedData other = (BufferPagedData) obj;
long dataSize = getDataSize();
if (other.getDataSize() != dataSize) {
return false;
}
int pageIndex = 0;
int otherPageIndex = 0;
long offset = 0;
long remain = dataSize;
while (remain > 0) {
int pageOffset = (int) (offset % pageSize);
int otherPageOffset = (int) (offset % other.pageSize);
int length = remain > pageSize - pageOffset ? pageSize - pageOffset : (int) remain;
if (length > other.pageSize - otherPageOffset) {
length = other.pageSize - otherPageOffset;
}
BufferData pageData = data.get(pageIndex);
BufferData otherPageData = other.data.get(otherPageIndex);
int pageTestPos = pageOffset;
int otherPageTestPos = otherPageOffset;
for (int i = 0; i < length; i++) {
if (pageData.getByte(pageTestPos) != otherPageData.getByte(otherPageTestPos)) {
return false;
}
pageTestPos++;
otherPageTestPos++;
}
offset += length;
remain -= length;
pageIndex++;
}
return true;
}
@Override
public int hashCode() {
return Objects.hash(getDataSize());
}
@Override
public void dispose() {
}
private static void put(ByteBuffer target, int position, ByteBuffer source, int offset, int length) throws IndexOutOfBoundsException {
BufferEditableData.put(target, position, source, offset, length);
}
}
@@ -0,0 +1,82 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data.delta;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import org.exbin.auxiliary.binary_data.delta.list.DoublyLinkedItem;
/**
* Abstract data segment of delta data source.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public abstract class DataSegment implements DoublyLinkedItem<DataSegment> {
@Nullable
private DataSegment previous;
@Nullable
private DataSegment next;
public DataSegment() {
}
/**
* Returns start position.
*
* @return start position
*/
public abstract long getStartPosition();
/**
* Returns length of this segment in bytes.
*
* @return length of this segment
*/
public abstract long getLength();
/**
* Returns detached copy of this segment.
*
* @return copy of this segment
*/
@Nonnull
public abstract DataSegment copy();
@Nullable
@Override
public DataSegment getNext() {
return next;
}
@Override
public void setNext(@Nullable DataSegment next) {
this.next = next;
}
@Nullable
@Override
public DataSegment getPrev() {
return previous;
}
@Override
public void setPrev(@Nullable DataSegment previous) {
this.previous = previous;
}
}
@@ -0,0 +1,97 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data.delta;
import java.io.IOException;
import javax.annotation.ParametersAreNonnullByDefault;
/**
* Data source interface.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public interface DataSource {
/**
* Returns length of the data.
*
* @return length of the data
* @throws java.io.IOException input output exception
*/
long getDataLength() throws IOException;
/**
* Sets data length.
*
* @param dataLength data length
* @throws java.io.IOException input output exception
*/
void setDataLength(long dataLength) throws IOException;
/**
* Returns single byte of the data.
*
* @param position data position
* @return byte value
* @throws java.io.IOException input output exception
*/
byte getByte(long position) throws IOException;
/**
* Sets single byte of the data.
*
* @param position data position
* @param value byte value
* @throws java.io.IOException input output exception
*/
void setByte(long position, byte value) throws IOException;
/**
* Reads data to buffer.
*
* @param position data position
* @param buffer data buffer
* @param offset buffer offset
* @param length data length
* @return length of data red
* @throws java.io.IOException input output exception
*/
int read(long position, byte[] buffer, int offset, int length) throws IOException;
/**
* Writes data from buffer.
*
* @param position data position
* @param buffer data buffer
* @param offset buffer offset
* @param length data length
* @throws java.io.IOException input output exception
*/
void write(long position, byte[] buffer, int offset, int length) throws IOException;
/**
* Clears caches.
*/
void clearCache();
/**
* Close data source.
*
* @throws java.io.IOException input output exception
*/
void close() throws IOException;
}
@@ -0,0 +1,395 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data.delta;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import org.exbin.auxiliary.binary_data.BinaryData;
import org.exbin.auxiliary.binary_data.EditableBinaryData;
import org.exbin.auxiliary.binary_data.delta.list.DefaultDoublyLinkedList;
/**
* Delta document defined as a sequence of segments.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public class DeltaDocument implements EditableBinaryData {
private final SegmentsRepository repository;
private DataSource dataSource;
private final DefaultDoublyLinkedList<DataSegment> segments = new DefaultDoublyLinkedList<>();
private long dataLength = 0;
private final DeltaDocumentWindow pointerWindow;
private final List<DeltaDocumentChangedListener> changeListeners = new ArrayList<>();
private static final int BUFFER_SIZE = 4096;
public DeltaDocument(SegmentsRepository repository, DataSource dataSource) throws IOException {
this.repository = repository;
this.dataSource = dataSource;
dataLength = dataSource.getDataLength();
if (dataLength > 0) {
DataSegment fullFileSegment = repository.createSourceSegment(dataSource, 0, dataLength);
segments.add(fullFileSegment);
}
pointerWindow = new DeltaDocumentWindow(this);
pointerWindow.reset();
}
public DeltaDocument(SegmentsRepository repository) {
this.repository = repository;
dataLength = 0;
pointerWindow = new DeltaDocumentWindow(this);
pointerWindow.reset();
}
/**
* Method for accessing data pages.
* <p>
* Use only if you know what you are doing.
*
* @return segments
*/
@Nonnull
public DefaultDoublyLinkedList<DataSegment> getSegments() {
return segments;
}
/**
* Returns segment starting at or before given position and ending after it.
* <p>
* Returns null if position is at the end or after then end of the document.
*
* @param position requested position
* @return data segment or null
*/
@Nullable
public DataSegment getSegment(long position) {
return pointerWindow.getSegment(position);
}
@Override
public boolean isEmpty() {
return dataLength == 0;
}
@Override
public long getDataSize() {
return dataLength;
}
@Override
public byte getByte(long position) {
return pointerWindow.getByte(position);
}
@Override
public void setByte(long position, byte value) {
pointerWindow.setByte(position, value);
}
@Override
public void insertUninitialized(long startFrom, long length) {
pointerWindow.insertUninitialized(startFrom, length);
}
@Override
public void insert(long startFrom, long length) {
pointerWindow.insert(startFrom, length);
}
@Override
public void insert(long startFrom, byte[] insertedData) {
pointerWindow.insert(startFrom, insertedData);
}
@Override
public void insert(long startFrom, byte[] insertedData, int insertedDataOffset, int insertedDataLength) {
pointerWindow.insert(startFrom, insertedData, insertedDataOffset, insertedDataLength);
}
@Override
public void insert(long startFrom, BinaryData insertedData) {
pointerWindow.insert(startFrom, insertedData);
}
@Override
public void insert(long startFrom, BinaryData insertedData, long insertedDataOffset, long insertedDataLength) {
pointerWindow.insert(startFrom, insertedData, insertedDataOffset, insertedDataLength);
}
/**
* Directly inserts segment into given position.
*
* @param startFrom start position
* @param segment inserted segment
*/
public void insertSegment(long startFrom, DataSegment segment) {
pointerWindow.insertSegment(startFrom, segment);
}
@Override
public long insert(long startFrom, InputStream inputStream, long maximumDataSize) throws IOException {
// TODO optimization later
long processed = 0;
byte[] buffer = new byte[BUFFER_SIZE];
while (maximumDataSize == -1 || maximumDataSize > 0) {
int toRead = BUFFER_SIZE;
if (maximumDataSize >= 0 && maximumDataSize < toRead) {
toRead = (int) maximumDataSize;
}
int read = inputStream.read(buffer, 0, toRead);
if (read == -1) {
break;
}
pointerWindow.insert(startFrom, buffer, 0, read);
if (maximumDataSize >= 0) {
maximumDataSize -= read;
}
startFrom += read;
processed += read;
}
return processed;
}
@Override
public void replace(long targetPosition, BinaryData replacingData) {
remove(targetPosition, replacingData.getDataSize());
insert(targetPosition, replacingData);
}
@Override
public void replace(long targetPosition, BinaryData replacingData, long startFrom, long length) {
remove(targetPosition, length);
insert(targetPosition, replacingData, startFrom, length);
}
@Override
public void replace(long targetPosition, byte[] replacingData) {
remove(targetPosition, replacingData.length);
insert(targetPosition, replacingData);
}
@Override
public void replace(long targetPosition, byte[] replacingData, int replacingDataOffset, int length) {
remove(targetPosition, length);
insert(targetPosition, replacingData, replacingDataOffset, length);
}
/**
* Directly replaces segment into given position.
*
* @param targetPosition target position
* @param segment inserted segment
*/
public void replaceSegment(long targetPosition, DataSegment segment) {
remove(targetPosition, segment.getLength());
insertSegment(targetPosition, segment);
}
@Override
public void fillData(long startFrom, long length) {
fillData(startFrom, length, (byte) 0);
}
@Override
public void fillData(long startFrom, long length, byte fill) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void remove(long startFrom, long length) {
pointerWindow.remove(startFrom, length);
}
@Override
public void clear() {
dataLength = 0;
segments.clear();
pointerWindow.reset();
}
@Override
public void dispose() {
repository.dropDocument(this);
}
@Override
public void loadFromStream(InputStream stream) throws IOException {
clear();
DeltaDocumentWindow documentWindow = new DeltaDocumentWindow(this);
byte[] buffer = new byte[BUFFER_SIZE];
long position = 0;
int read;
do {
read = stream.read(buffer);
if (read > 0) {
documentWindow.insert(position, buffer, 0, read);
position += read;
}
} while (read >= 0);
}
@Override
public void saveToStream(OutputStream stream) throws IOException {
DeltaDocumentWindow documentWindow = new DeltaDocumentWindow(this);
byte[] buffer = new byte[BUFFER_SIZE];
long position = 0;
long dataSize = getDataSize();
while (position < dataSize) {
long remains = dataSize - position;
int toProcess = remains < BUFFER_SIZE ? (int) remains : BUFFER_SIZE;
documentWindow.copyToArray(position, buffer, 0, toProcess);
stream.write(buffer, 0, toProcess);
position += toProcess;
}
}
@Nonnull
@Override
public BinaryData copy() {
return pointerWindow.copy();
}
@Nonnull
@Override
public BinaryData copy(long startFrom, long length) {
return pointerWindow.copy(startFrom, length);
}
@Override
public void copyToArray(long startFrom, byte[] target, int offset, int length) {
// TODO optimization later
for (int i = 0; i < length; i++) {
target[offset + i] = getByte(startFrom + i);
}
}
@Nonnull
@Override
public OutputStream getDataOutputStream() {
return new DeltaDocumentOutputStream(this);
}
@Nonnull
@Override
public InputStream getDataInputStream() {
return new DeltaDocumentInputStream(this);
}
@Override
public void setDataSize(long dataSize) {
if (dataSize < dataLength) {
remove(dataSize, dataLength - dataSize);
} else if (dataSize > dataLength) {
insert(dataLength, dataSize - dataLength);
}
}
/**
* Performs save to source file.
*
* @throws java.io.IOException on input/output error
*/
public void save() throws IOException {
repository.saveDocument(this);
}
/**
* Resets cached state - needed after change.
*/
public void clearCache() {
pointerWindow.reset();
}
/* package */ void setDataLength(long dataSize) {
this.dataLength = dataSize;
}
/**
* Returns segment starting from given position or copy of part of the
* segment starting from given position up to the end of length.
*
* @param position position
* @param length length
* @return data segment
*/
@Nullable
public DataSegment getPartCopy(long position, long length) {
return pointerWindow.getPartCopy(position, length);
}
@Nullable
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
@Nonnull
public SegmentsRepository getRepository() {
return repository;
}
public void addChangeListener(DeltaDocumentChangedListener listener) {
changeListeners.add(listener);
}
public void removeChangeListener(DeltaDocumentChangedListener listener) {
changeListeners.remove(listener);
}
public void notifyChangeListeners(DeltaDocumentWindow window) {
for (DeltaDocumentChangedListener listener : changeListeners) {
listener.dataChanged(window);
}
}
public void validatePointerPosition() {
pointerWindow.validatePointerPosition();
}
public void validateDocumentSize() {
long segmentsSizeSum = 0;
DataSegment segment = segments.first();
while (segment != null) {
segmentsSizeSum += segment.getLength();
segment = segment.getNext();
}
if (segmentsSizeSum != getDataSize()) {
throw new IllegalStateException("Invalid size " + getDataSize() + " (expected " + segmentsSizeSum + ")");
}
}
public void validate() {
validatePointerPosition();
validateDocumentSize();
}
}
@@ -0,0 +1,34 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data.delta;
import javax.annotation.ParametersAreNonnullByDefault;
/**
* Listener for delta document data changes.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public interface DeltaDocumentChangedListener {
/**
* Change in data occurred.
*
* @param window updated window for reference
*/
void dataChanged(DeltaDocumentWindow window);
}
@@ -0,0 +1,136 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data.delta;
import java.io.IOException;
import java.io.InputStream;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
import org.exbin.auxiliary.binary_data.FinishableStream;
import org.exbin.auxiliary.binary_data.SeekableStream;
/**
* Delta document input stream.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public class DeltaDocumentInputStream extends InputStream implements SeekableStream, FinishableStream {
@Nonnull
private final DeltaDocumentWindow data;
private long position = 0;
private long mark = 0;
public DeltaDocumentInputStream(DeltaDocument document) {
this.data = new DeltaDocumentWindow(document);
}
@Override
public int read() throws IOException {
if (position >= data.getDataSize()) {
return -1;
}
try {
int value = data.getByte(position) & 0xFF;
position++;
return value;
} catch (ArrayIndexOutOfBoundsException ex) {
return -1;
}
}
@Override
public int read(byte[] output, int offset, int length) throws IOException {
if (output.length == 0 || length == 0) {
return 0;
}
long dataSize = data.getDataSize();
if (position >= dataSize) {
return -1;
}
if (position + length > dataSize) {
length = (int) (dataSize - position);
}
data.copyToArray(position, output, offset, length);
position += length;
return length;
}
@Override
public void close() throws IOException {
finish();
}
@Override
public int available() throws IOException {
long available = data.getDataSize() - position;
return (available > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) available;
}
@Override
public void seek(long position) throws IOException {
this.position = position;
}
@Override
public long finish() throws IOException {
position = data.getDataSize();
return position;
}
@Override
public long getProcessedSize() {
return position;
}
@Override
public long getStreamSize() {
return data.getDataSize();
}
@Override
public boolean markSupported() {
return true;
}
@Override
public synchronized void reset() throws IOException {
position = mark;
}
@Override
public synchronized void mark(int readlimit) {
mark = position;
}
@Override
public long skip(long n) throws IOException {
long dataSize = data.getDataSize();
if (position + n < dataSize) {
position += n;
return n;
}
long skipped = dataSize - position;
position = dataSize;
return skipped;
}
}
@@ -0,0 +1,86 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data.delta;
import java.io.IOException;
import java.io.OutputStream;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
import org.exbin.auxiliary.binary_data.FinishableStream;
import org.exbin.auxiliary.binary_data.SeekableStream;
/**
* Delta document output stream.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public class DeltaDocumentOutputStream extends OutputStream implements SeekableStream, FinishableStream {
@Nonnull
private final DeltaDocumentWindow data;
private long position = 0;
public DeltaDocumentOutputStream(DeltaDocument document) {
this.data = new DeltaDocumentWindow(document);
}
@Override
public void write(int value) throws IOException {
long dataSize = data.getDataSize();
if (position == dataSize) {
dataSize++;
data.setDataSize(dataSize);
}
data.setByte(position++, (byte) value);
}
@Override
public void write(byte[] input, int offset, int length) throws IOException {
if (length == 0) {
return;
}
data.insert(position, input, offset, length);
position += length;
}
@Override
public void seek(long position) throws IOException {
this.position = position;
}
@Override
public long getStreamSize() {
return data.getDataSize();
}
@Override
public long getProcessedSize() {
return position;
}
@Override
public void close() throws IOException {
finish();
}
@Override
public long finish() throws IOException {
position = data.getDataSize();
return position;
}
}
@@ -0,0 +1,725 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data.delta;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import org.exbin.auxiliary.binary_data.BinaryData;
import org.exbin.auxiliary.binary_data.OutOfBoundsException;
import org.exbin.auxiliary.binary_data.delta.list.DefaultDoublyLinkedList;
/**
* Access window for delta document.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public class DeltaDocumentWindow {
@Nonnull
private final DeltaDocument document;
private final DataPointer pointer = new DataPointer();
public DeltaDocumentWindow(DeltaDocument document) {
this.document = document;
document.addChangeListener((DeltaDocumentWindow window) -> {
if (window != DeltaDocumentWindow.this) {
pointer.segment = window.pointer.segment;
pointer.position = window.pointer.position;
}
});
}
public long getDataSize() {
return document.getDataSize();
}
public byte getByte(long position) {
focusSegment(position);
DataSegment targetSegment = pointer.segment;
if (targetSegment == null) {
throw new NullPointerException("Missing segment for position " + position);
}
if (targetSegment instanceof SourceSegment) {
return ((SourceSegment) targetSegment).getByte(targetSegment.getStartPosition() + (position - pointer.position));
} else {
return ((MemorySegment) targetSegment).getByte(targetSegment.getStartPosition() + (position - pointer.position));
}
}
public void setByte(long position, byte value) {
DefaultDoublyLinkedList<DataSegment> segments = document.getSegments();
SegmentsRepository repository = document.getRepository();
focusSegment(position);
if (pointer.segment instanceof SourceSegment) {
if (pointer.position != position) {
splitSegment(position);
focusSegment(position);
}
DataSegment prev = segments.prevTo(pointer.segment);
if (prev instanceof MemorySegment) {
repository.setMemoryByte((MemorySegment) prev, prev.getLength(), value);
} else {
MemorySegment segment = repository.createMemorySegment(repository.openMemorySource(), 0, 0);
repository.setMemoryByte(segment, 0, value);
segments.addBefore(pointer.segment, segment);
}
pointer.position++;
SourceSegment documentSegment = ((SourceSegment) pointer.segment);
if (documentSegment.getLength() == 1) {
segments.remove(documentSegment);
repository.dropSegment(documentSegment);
pointer.position = 0;
pointer.segment = null;
} else {
repository.updateSegment(documentSegment, documentSegment.getStartPosition() + 1, documentSegment.getLength() - 1);
}
} else {
if (pointer.segment == null) {
pointer.segment = repository.createMemorySegment(repository.openMemorySource(), 0, 0);
segments.add(pointer.segment);
}
repository.setMemoryByte((MemorySegment) pointer.segment, position - pointer.position, value);
}
if (position >= getDataSize()) {
document.setDataLength(position + 1);
}
document.notifyChangeListeners(this);
}
public void insertUninitialized(long startFrom, long length) {
if (length == 0) {
return;
}
DefaultDoublyLinkedList<DataSegment> segments = document.getSegments();
SegmentsRepository repository = document.getRepository();
focusSegment(startFrom);
long targetLength = document.getDataSize() + length;
if (pointer.segment instanceof MemorySegment) {
repository.insertUninitializedMemoryData((MemorySegment) pointer.segment, startFrom - pointer.position, length);
document.setDataLength(targetLength);
} else if (pointer.segment != null && pointer.position == startFrom && pointer.segment.getPrev() instanceof MemorySegment
&& pointer.segment.getPrev().getStartPosition() + pointer.segment.getPrev().getLength() == ((MemorySegment) pointer.segment.getPrev()).getSource().getDataSize()) {
MemorySegment prevSegment = (MemorySegment) pointer.segment.getPrev();
prevSegment.getSource().insertUninitialized(prevSegment.getSource().getDataSize(), length);
repository.updateSegmentLength(prevSegment, prevSegment.getLength() + length);
pointer.position++;
document.setDataLength(targetLength);
} else if (pointer.segment == null && segments.last() instanceof MemorySegment
&& segments.last().getStartPosition() + segments.last().getLength() == ((MemorySegment) segments.last()).getSource().getDataSize()) {
MemorySegment prevSegment = (MemorySegment) segments.last();
prevSegment.getSource().insertUninitialized(prevSegment.getSource().getDataSize(), length);
repository.updateSegmentLength(prevSegment, prevSegment.getLength() + length);
pointer.position++;
document.setDataLength(targetLength);
} else {
if (startFrom > pointer.position) {
splitSegment(startFrom);
focusSegment(startFrom);
}
MemorySegment insertedSegment = repository.createMemorySegment();
repository.insertUninitializedMemoryData(insertedSegment, 0, length);
if (pointer.segment == null) {
segments.add(insertedSegment);
} else {
segments.addBefore(pointer.segment, insertedSegment);
}
pointer.segment = insertedSegment;
document.setDataLength(targetLength);
tryMergeArea(startFrom, length);
}
document.notifyChangeListeners(this);
}
public void insert(long startFrom, long length) {
DefaultDoublyLinkedList<DataSegment> segments = document.getSegments();
SegmentsRepository repository = document.getRepository();
if (length == 0) {
return;
}
focusSegment(startFrom);
long targetLength = document.getDataSize() + length;
if (pointer.segment instanceof MemorySegment) {
repository.insertMemoryData((MemorySegment) pointer.segment, startFrom - pointer.position, length);
document.setDataLength(targetLength);
} else if (pointer.segment != null && pointer.position == startFrom && pointer.segment.getPrev() instanceof MemorySegment
&& pointer.segment.getPrev().getStartPosition() + pointer.segment.getPrev().getLength() == ((MemorySegment) pointer.segment.getPrev()).getSource().getDataSize()) {
MemorySegment prevSegment = (MemorySegment) pointer.segment.getPrev();
prevSegment.getSource().insert(prevSegment.getSource().getDataSize(), length);
repository.updateSegmentLength(prevSegment, prevSegment.getLength() + length);
pointer.position++;
document.setDataLength(targetLength);
} else if (pointer.segment == null && segments.last() instanceof MemorySegment
&& segments.last().getStartPosition() + segments.last().getLength() == ((MemorySegment) segments.last()).getSource().getDataSize()) {
MemorySegment prevSegment = (MemorySegment) segments.last();
prevSegment.getSource().insert(prevSegment.getSource().getDataSize(), length);
repository.updateSegmentLength(prevSegment, prevSegment.getLength() + length);
pointer.position++;
document.setDataLength(targetLength);
} else {
if (startFrom > pointer.position) {
splitSegment(startFrom);
focusSegment(startFrom);
}
MemorySegment insertedSegment = repository.createMemorySegment();
repository.insertMemoryData(insertedSegment, 0, length);
if (pointer.segment == null) {
segments.add(insertedSegment);
} else {
segments.addBefore(pointer.segment, insertedSegment);
}
pointer.segment = insertedSegment;
document.setDataLength(targetLength);
tryMergeArea(startFrom, length);
}
document.notifyChangeListeners(this);
}
public void insert(long startFrom, byte[] insertedData) {
DefaultDoublyLinkedList<DataSegment> segments = document.getSegments();
SegmentsRepository repository = document.getRepository();
if (insertedData.length == 0) {
return;
}
focusSegment(startFrom);
long targetLength = document.getDataSize() + insertedData.length;
if (pointer.segment instanceof MemorySegment) {
repository.insertMemoryData((MemorySegment) pointer.segment, startFrom - pointer.position, insertedData);
document.setDataLength(targetLength);
} else {
if (startFrom > pointer.position) {
splitSegment(startFrom);
focusSegment(startFrom);
}
MemorySegment insertedSegment = repository.createMemorySegment();
repository.insertMemoryData(insertedSegment, 0, insertedData);
if (pointer.segment == null) {
segments.add(insertedSegment);
} else {
segments.addBefore(pointer.segment, insertedSegment);
}
pointer.segment = insertedSegment;
document.setDataLength(targetLength);
tryMergeArea(startFrom, insertedData.length);
}
document.notifyChangeListeners(this);
}
public void insert(long startFrom, byte[] insertedData, int insertedDataOffset, int insertedDataLength) {
DefaultDoublyLinkedList<DataSegment> segments = document.getSegments();
SegmentsRepository repository = document.getRepository();
focusSegment(startFrom);
long targetLength = document.getDataSize() + insertedDataLength;
if (pointer.segment instanceof MemorySegment) {
repository.insertMemoryData((MemorySegment) pointer.segment, startFrom - pointer.position, insertedData);
document.setDataLength(targetLength);
} else {
if (startFrom > pointer.position) {
splitSegment(startFrom);
focusSegment(startFrom);
}
MemorySegment insertedSegment = repository.createMemorySegment();
repository.insertMemoryData(insertedSegment, 0, insertedData);
if (pointer.segment == null) {
segments.add(insertedSegment);
} else {
segments.addBefore(pointer.segment, insertedSegment);
}
pointer.segment = insertedSegment;
document.setDataLength(targetLength);
tryMergeArea(startFrom, insertedData.length);
}
document.notifyChangeListeners(this);
}
public void insert(long startFrom, BinaryData insertedData) {
if (insertedData.isEmpty()) {
return;
}
DefaultDoublyLinkedList<DataSegment> segments = document.getSegments();
SegmentsRepository repository = document.getRepository();
focusSegment(startFrom);
long targetLength = document.getDataSize() + insertedData.getDataSize();
if (insertedData instanceof DeltaDocument) {
if (pointer.position < startFrom) {
splitSegment(startFrom);
focusSegment(startFrom);
}
// Copy all segments from inserted document
DeltaDocument insertedDocument = (DeltaDocument) insertedData;
DataSegment segment = insertedDocument.getSegments().first();
DataSegment copy = repository.copySegment(segment);
DataSegment first = copy;
if (pointer.segment == null) {
segments.add(copy);
} else {
segments.addBefore(pointer.segment, copy);
}
DataSegment next = segment.getNext();
while (next != null) {
DataSegment nextCopy = repository.copySegment(next);
segments.addAfter(copy, nextCopy);
copy = nextCopy;
next = next.getNext();
}
pointer.segment = first;
document.setDataLength(targetLength);
tryMergeArea(startFrom, insertedData.getDataSize());
} else if (pointer.segment instanceof MemorySegment) {
repository.insertMemoryData((MemorySegment) pointer.segment, startFrom - pointer.position, insertedData);
document.setDataLength(targetLength);
} else {
if (pointer.position < startFrom) {
splitSegment(startFrom);
focusSegment(startFrom);
}
MemorySegment insertedSegment = repository.createMemorySegment();
repository.insertMemoryData(insertedSegment, 0, insertedData);
if (pointer.segment == null) {
segments.add(insertedSegment);
} else {
segments.addBefore(pointer.segment, insertedSegment);
}
pointer.segment = insertedSegment;
document.setDataLength(targetLength);
tryMergeArea(startFrom, insertedData.getDataSize());
}
document.notifyChangeListeners(this);
}
public void insert(long startFrom, BinaryData insertedData, long insertedDataOffset, long insertedDataLength) {
if (insertedDataLength == 0) {
return;
}
DefaultDoublyLinkedList<DataSegment> segments = document.getSegments();
SegmentsRepository repository = document.getRepository();
focusSegment(startFrom);
long targetLength = document.getDataSize() + insertedDataLength;
if (insertedData instanceof DeltaDocument) {
if (pointer.position < startFrom) {
splitSegment(startFrom);
focusSegment(startFrom);
}
// Copy all segments from inserted document
DeltaDocument insertedDocument = (DeltaDocument) insertedData;
long position = insertedDataOffset;
long length = insertedDataLength;
DataSegment segment = insertedDocument.getPartCopy(position, length);
position += segment.getLength();
length -= segment.getLength();
DataSegment first = segment;
if (pointer.segment == null) {
segments.add(segment);
} else {
segments.addBefore(pointer.segment, segment);
}
DataSegment next = segment.getNext();
while (length > 0) {
DataSegment nextSegment = insertedDocument.getPartCopy(position, length);
position += nextSegment.getLength();
length -= nextSegment.getLength();
segments.addAfter(segment, nextSegment);
segment = nextSegment;
next = next.getNext();
}
pointer.segment = first;
document.setDataLength(targetLength);
tryMergeArea(startFrom, insertedData.getDataSize());
} /*else if (pointer.segment instanceof MemorySegment) {
// TODO can't do that directly, also it breaks
repository.insertMemoryData((MemorySegment) pointer.segment, startFrom - pointer.position, insertedData, insertedDataOffset, insertedDataLength);
document.setDataLength(targetLength);
} */ else {
if (pointer.position < startFrom) {
splitSegment(startFrom);
focusSegment(startFrom);
}
MemorySegment insertedSegment = repository.createMemorySegment();
repository.insertMemoryData(insertedSegment, 0, insertedData, insertedDataOffset, insertedDataLength);
if (pointer.segment == null) {
segments.add(insertedSegment);
} else {
segments.addBefore(pointer.segment, insertedSegment);
}
pointer.segment = insertedSegment;
document.setDataLength(targetLength);
tryMergeArea(startFrom, insertedData.getDataSize());
}
document.notifyChangeListeners(this);
}
/**
* Directly inserts segment into given position.
*
* @param startFrom start position
* @param insertedSegment inserted segment
*/
public void insertSegment(long startFrom, DataSegment insertedSegment) {
DefaultDoublyLinkedList<DataSegment> segments = document.getSegments();
long targetLength = document.getDataSize() + insertedSegment.getLength();
focusSegment(startFrom);
if (pointer.position < startFrom) {
splitSegment(startFrom);
focusSegment(startFrom);
}
if (pointer.segment == null) {
segments.add(insertedSegment);
} else {
segments.addBefore(pointer.segment, insertedSegment);
}
pointer.segment = insertedSegment;
document.setDataLength(targetLength);
document.notifyChangeListeners(this);
}
public void remove(long startFrom, long length) {
if (startFrom + length > document.getDataSize()) {
throw new OutOfBoundsException("Removed area is out of bounds");
}
DefaultDoublyLinkedList<DataSegment> segments = document.getSegments();
SegmentsRepository repository = document.getRepository();
if (length > 0) {
long targetLength = document.getDataSize() - length;
focusSegment(startFrom + length);
splitSegment(startFrom + length);
focusSegment(startFrom);
splitSegment(startFrom);
focusSegment(startFrom);
// Save position to return to
DataSegment prevSegment = (DataSegment) pointer.segment.getPrev();
long prevPointerPosition = prevSegment == null ? 0 : pointer.position - prevSegment.getLength();
// Drop all segments in given range
while (length > 0) {
length -= pointer.segment.getLength();
DataSegment next = segments.nextTo(pointer.segment);
repository.dropSegment(pointer.segment);
segments.remove(pointer.segment);
pointer.segment = next;
}
// Set pointer position
pointer.segment = prevSegment;
pointer.position = prevPointerPosition;
document.setDataLength(targetLength);
tryMergeSegments(startFrom);
}
document.notifyChangeListeners(this);
}
public void reset() {
pointer.setPointer(0, document.getSegments().first());
}
public void setDataSize(long dataSize) {
document.setDataSize(dataSize);
}
@Nonnull
public BinaryData copy() {
SegmentsRepository repository = document.getRepository();
DefaultDoublyLinkedList<DataSegment> segments = document.getSegments();
DeltaDocument copy = repository.createDocument();
copy.setDataLength(getDataSize());
for (DataSegment segment : segments) {
copy.getSegments().add(repository.copySegment(segment));
}
return copy;
}
@Nonnull
public BinaryData copy(long startFrom, long length) {
SegmentsRepository repository = document.getRepository();
DeltaDocument copy = repository.createDocument();
copy.setDataLength(length);
focusSegment(startFrom);
if (length > 0) {
DefaultDoublyLinkedList<DataSegment> segments = document.getSegments();
DataSegment segment = pointer.segment;
if (segment == null) {
throw new NullPointerException("Segment on given position not found");
}
long offset = startFrom - pointer.position;
while (length > 0) {
long segmentLength = segment.getLength();
long copyLength = segmentLength - offset;
if (copyLength > length) {
copyLength = length;
}
if (offset == 0 && copyLength == segmentLength) {
copy.getSegments().add(repository.copySegment(segment));
} else if (segment instanceof MemorySegment) {
MemorySegment memorySegment = (MemorySegment) segment;
copy.getSegments().add(repository.createMemorySegment(memorySegment.getSource(), memorySegment.getStartPosition() + offset, copyLength));
} else {
SourceSegment fileSegment = (SourceSegment) segment;
copy.getSegments().add(repository.createSourceSegment(fileSegment.getSource(), fileSegment.getStartPosition() + offset, copyLength));
}
length -= copyLength;
offset = 0;
segment = segments.nextTo(segment);
if (length > 0 && segment == null) {
throw new NullPointerException("Unexpected end of segments sequence");
}
}
}
return copy;
}
public void copyToArray(long startFrom, byte[] target, int offset, int length) {
document.copyToArray(startFrom, target, offset, length);
}
/**
* Splits current pointer segment on given absolute position.
*
* @param position split position
*/
public void splitSegment(long position) {
long pointerPosition = pointer.position;
DataSegment pointerSegment = pointer.segment;
if (position < pointerPosition || (pointerSegment != null && position > pointerPosition + pointerSegment.getLength())) {
throw new IllegalStateException("Split position is out of current segment");
}
if (pointerPosition == position || pointerSegment == null || pointerPosition + pointerSegment.getLength() == position) {
// No action needed
return;
}
DefaultDoublyLinkedList<DataSegment> segments = document.getSegments();
SegmentsRepository repository = document.getRepository();
long firstPartSize = position - pointerPosition;
if (pointerSegment instanceof MemorySegment) {
MemorySegment memorySegment = (MemorySegment) pointerSegment;
MemorySegment newSegment = repository.createMemorySegment(memorySegment.getSource(), memorySegment.getStartPosition() + firstPartSize, memorySegment.getLength() - firstPartSize);
repository.updateSegmentLength(memorySegment, firstPartSize);
segments.addAfter(pointerSegment, newSegment);
} else {
SourceSegment fileSegment = (SourceSegment) pointerSegment;
SourceSegment newSegment = repository.createSourceSegment(fileSegment.getSource(), fileSegment.getStartPosition() + firstPartSize, fileSegment.getLength() - firstPartSize);
repository.updateSegmentLength(fileSegment, firstPartSize);
segments.addAfter(fileSegment, newSegment);
}
}
@Nullable
public DataSegment getSegment(long position) {
focusSegment(position);
return pointer.segment;
}
/**
* Returns segment starting from given position or copy of part of the
* segment starting from given position up to the end of length.
*
* @param position position
* @param length length
* @return data segment or null
*/
@Nullable
public DataSegment getPartCopy(long position, long length) {
focusSegment(position);
if (pointer.segment == null) {
return null;
}
SegmentsRepository repository = document.getRepository();
long offset = position - pointer.position;
long partLength = length;
if (pointer.segment.getLength() - offset < partLength) {
partLength = pointer.segment.getLength() - offset;
}
return repository.copySegment(pointer.segment, offset, partLength);
}
/**
* Focuses segment starting at or before given position and ending after it.
* <p>
* Returns null if position is at the end of the document and throws out of
* bounds exception otherwise.
*
* @param position requested position
* @throws OutOfBoundsException if position is before or after document
*/
private void focusSegment(long position) {
DefaultDoublyLinkedList<DataSegment> segments = document.getSegments();
long dataSize = getDataSize();
if (position == 0) {
pointer.position = 0;
pointer.segment = segments.first();
return;
} else if (position == dataSize) {
pointer.position = dataSize;
pointer.segment = null;
return;
} else if (position < 0 || position > dataSize) {
throw new OutOfBoundsException("Position index out of range");
}
if (pointer.segment == null) {
pointer.segment = segments.last();
pointer.position = dataSize - pointer.segment.getLength();
}
if (position < pointer.position) {
if (pointer.segment == null && position == dataSize) {
pointer.segment = segments.last();
if (pointer.segment == null) {
throwNullSegmentException();
}
pointer.position -= pointer.segment.getLength();
}
while (position < pointer.position) {
pointer.segment = segments.prevTo(pointer.segment);
if (pointer.segment == null) {
throwNullSegmentException();
}
pointer.position -= pointer.segment.getLength();
}
} else {
if (pointer.segment == null) {
throwNullSegmentException();
}
while (position >= pointer.position + pointer.segment.getLength()) {
if ((pointer.segment.getNext() == null) && (position == pointer.position + pointer.segment.getLength())) {
break;
}
pointer.position += pointer.segment.getLength();
pointer.segment = segments.nextTo(pointer.segment);
if (pointer.segment == null) {
throwNullSegmentException();
}
}
}
}
private void tryMergeArea(long position, long length) {
tryMergeSegments(position);
// TODO
tryMergeSegments(position + length);
}
/**
* Attempts to merge segments at specified position.
*
* @param position target position
*/
private boolean tryMergeSegments(long position) {
if (position == 0 || position >= getDataSize()) {
return false;
}
DefaultDoublyLinkedList<DataSegment> segments = document.getSegments();
SegmentsRepository repository = document.getRepository();
focusSegment(position);
DataSegment nextSegment = pointer.segment;
focusSegment(position - 1);
DataSegment segment = pointer.segment;
if (segment == nextSegment) {
return false;
}
if (segment instanceof SourceSegment && nextSegment instanceof SourceSegment) {
if (((SourceSegment) segment).getStartPosition() + segment.getLength() == ((SourceSegment) nextSegment).getStartPosition()) {
repository.updateSegmentLength(segment, segment.getLength() + nextSegment.getLength());
repository.dropSegment(nextSegment);
segments.remove(nextSegment);
return true;
}
return false;
}
if (segment instanceof MemorySegment && nextSegment instanceof MemorySegment) {
MemorySegment memorySegment = (MemorySegment) segment;
MemorySegment nextMemorySegment = (MemorySegment) nextSegment;
if (memorySegment.getSource() == nextMemorySegment.getSource()) {
if (memorySegment.getStartPosition() + segment.getLength() == nextMemorySegment.getStartPosition()) {
repository.updateSegmentLength(memorySegment, segment.getLength() + nextSegment.getLength());
repository.dropSegment(nextSegment);
segments.remove(nextSegment);
return true;
}
}
// TODO join two single memory segments?
}
return false;
}
public void validatePointerPosition() {
if (pointer.segment == null) {
if (pointer.position > 0 && pointer.position < getDataSize()) {
throw new IllegalStateException("Illegal pointer position " + pointer.position + " for null segment");
}
} else {
long segmentsLengthSum = 0;
DataSegment segment = pointer.segment.getPrev();
while (segment != null) {
segmentsLengthSum += segment.getLength();
segment = segment.getPrev();
}
if (segmentsLengthSum != pointer.position) {
throw new IllegalStateException("Illegal pointer position " + pointer.position);
}
}
}
private static void throwNullSegmentException() {
throw new IllegalStateException("Unexpected null segment");
}
/**
* POJO structure for sliding data pointer.
*/
private static class DataPointer {
long position;
@Nullable
DataSegment segment;
void setPointer(long position, @Nullable DataSegment segment) {
this.position = position;
this.segment = segment;
}
}
}
@@ -0,0 +1,184 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data.delta;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
import org.exbin.auxiliary.binary_data.BinaryData;
import org.exbin.auxiliary.binary_data.EditableBinaryData;
/**
* Data source for binary data stored in memory.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public class MemoryDataSource implements EditableBinaryData {
@Nonnull
private final EditableBinaryData data;
public MemoryDataSource(EditableBinaryData data) {
this.data = data;
}
@Override
public void setDataSize(long size) {
data.setDataSize(size);
}
@Override
public void setByte(long position, byte value) {
data.setByte(position, value);
}
@Override
public void insert(long startFrom, long length) {
data.insert(startFrom, length);
}
@Override
public void insert(long startFrom, byte[] insertedData) {
data.insert(startFrom, insertedData);
}
@Override
public void insert(long startFrom, BinaryData insertedData) {
data.insert(startFrom, insertedData);
}
@Override
public void remove(long startFrom, long length) {
data.remove(startFrom, length);
}
@Override
public boolean isEmpty() {
return data.isEmpty();
}
@Override
public long getDataSize() {
return data.getDataSize();
}
@Override
public byte getByte(long position) {
return data.getByte(position);
}
@Override
public void saveToStream(OutputStream outputStream) throws IOException {
data.saveToStream(outputStream);
}
@Nonnull
@Override
public BinaryData copy() {
return data.copy();
}
@Nonnull
@Override
public BinaryData copy(long startFrom, long length) {
return data.copy(startFrom, length);
}
@Override
public void insertUninitialized(long startFrom, long length) {
data.insertUninitialized(startFrom, length);
}
@Override
public void insert(long startFrom, byte[] insertedData, int insertedDataOffset, int insertedDataLength) {
data.insert(startFrom, insertedData, insertedDataOffset, insertedDataLength);
}
@Override
public void insert(long startFrom, BinaryData insertedData, long insertedDataOffset, long insertedDataLength) {
data.insert(startFrom, insertedData, insertedDataOffset, insertedDataLength);
}
@Override
public long insert(long startFrom, InputStream inputStream, long maximumDataSize) throws IOException {
return data.insert(startFrom, inputStream, maximumDataSize);
}
@Override
public void replace(long targetPosition, BinaryData replacingData) {
data.replace(targetPosition, replacingData);
}
@Override
public void replace(long targetPosition, BinaryData replacingData, long startFrom, long length) {
data.replace(targetPosition, replacingData, startFrom, length);
}
@Override
public void replace(long targetPosition, byte[] replacingData) {
data.replace(targetPosition, replacingData);
}
@Override
public void replace(long targetPosition, byte[] replacingData, int replacingDataOffset, int length) {
data.replace(targetPosition, replacingData, replacingDataOffset, length);
}
@Override
public void fillData(long startFrom, long length) {
data.fillData(startFrom, length);
}
@Override
public void fillData(long startFrom, long length, byte fill) {
data.fillData(startFrom, length, fill);
}
@Override
public void clear() {
data.clear();
}
@Override
public void loadFromStream(InputStream inputStream) throws IOException {
data.loadFromStream(inputStream);
}
@Nonnull
@Override
public OutputStream getDataOutputStream() {
return data.getDataOutputStream();
}
@Override
public void copyToArray(long startFrom, byte[] target, int offset, int length) {
data.copyToArray(startFrom, target, offset, length);
}
@Nonnull
@Override
public InputStream getDataInputStream() {
return data.getDataInputStream();
}
@Override
public void dispose() {
data.dispose();
}
}
@@ -0,0 +1,76 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data.delta;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
/**
* Data segment pointing to memory block.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public class MemorySegment extends DataSegment {
@Nonnull
private MemoryDataSource source;
private long startPosition;
private long length;
public MemorySegment(MemoryDataSource source, long startPosition, long length) {
this.source = source;
this.startPosition = startPosition;
this.length = length;
}
@Nonnull
public MemoryDataSource getSource() {
return source;
}
public void setSource(MemoryDataSource source) {
this.source = source;
}
@Override
public long getStartPosition() {
return startPosition;
}
public void setStartPosition(long startPosition) {
this.startPosition = startPosition;
}
@Override
public long getLength() {
return length;
}
public void setLength(long length) {
this.length = length;
}
public byte getByte(long position) {
return source.getByte(position);
}
@Nonnull
@Override
public DataSegment copy() {
return new MemorySegment(null, startPosition, length);
}
}
@@ -0,0 +1,37 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data.delta;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
import org.exbin.auxiliary.binary_data.EditableBinaryData;
/**
* Memory segment creator.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public interface MemorySegmentCreator {
/**
* Creates new memory segment.
*
* @return data page
*/
@Nonnull
EditableBinaryData createSegment();
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,77 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data.delta;
import java.io.IOException;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
/**
* Data segment pointing to source data.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public class SourceSegment extends DataSegment {
@Nonnull
private final DataSource source;
private long startPosition;
private long length;
public SourceSegment(DataSource source, long startPosition, long length) {
this.source = source;
this.startPosition = startPosition;
this.length = length;
}
@Nonnull
public DataSource getSource() {
return source;
}
@Override
public long getStartPosition() {
return startPosition;
}
public void setStartPosition(long startPosition) {
this.startPosition = startPosition;
}
@Override
public long getLength() {
return length;
}
public void setLength(long length) {
this.length = length;
}
public byte getByte(long position) {
try {
return source.getByte(position);
} catch (IOException ex) {
throw new RuntimeException("Error while processing data source", ex);
}
}
@Nonnull
@Override
public DataSegment copy() {
return new SourceSegment(source, startPosition, length);
}
}
@@ -0,0 +1,62 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data.delta;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
/**
* Space placeholder segment.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public class SpaceSegment extends DataSegment {
private long length;
public SpaceSegment(long length) {
this.length = length;
}
@Override
public long getStartPosition() {
return 0;
}
public void setStartPosition(long startPosition) {
throw new IllegalStateException("Unable to set start position on space placeholder segment");
}
@Override
public long getLength() {
return length;
}
public void setLength(long length) {
this.length = length;
}
public byte getByte(long position) {
throw new IllegalStateException("Unable to read data from space placeholder segment");
}
@Nonnull
@Override
public DataSegment copy() {
return new SpaceSegment(length);
}
}
@@ -0,0 +1,115 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data.delta.file;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
/**
* Access window for delta data.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public class DeltaDataPageWindow {
public static final int PAGE_SIZE = 1024;
@Nonnull
private final FileDataSource data;
@Nonnull
private final DataPage[] dataPages = new DataPage[]{new DataPage(), new DataPage()};
private int activeDataPage = 1;
public DeltaDataPageWindow(FileDataSource data) {
this.data = data;
dataPages[0].pageIndex = 0;
loadPage(0);
data.addCacheClearListener(() -> {
DeltaDataPageWindow.this.clearCache();
});
}
private void loadPage(int index) {
long pageIndex = dataPages[index].pageIndex;
long pagePosition = pageIndex * PAGE_SIZE;
try {
long fileLength = data.getDataLength();
byte[] page = dataPages[index].page;
int offset = 0;
int toRead = PAGE_SIZE;
if (pagePosition + PAGE_SIZE > fileLength) {
toRead = (int) (fileLength - pagePosition);
}
while (toRead > 0) {
int read = data.read(pagePosition, page, offset, toRead);
if (read == -1) {
throw new IOException("Unexpected read error ");
}
toRead -= read;
offset += read;
pagePosition += read;
}
} catch (IOException ex) {
Logger.getLogger(DeltaDataPageWindow.class.getName()).log(Level.SEVERE, null, ex);
}
}
public byte getByte(long position) {
long targetPageIndex = position / PAGE_SIZE;
int index = -1;
long pageIndex1 = dataPages[0].pageIndex;
long pageIndex2 = dataPages[1].pageIndex;
if (pageIndex1 == targetPageIndex) {
index = 0;
} else if (pageIndex2 == targetPageIndex) {
index = 1;
}
if (index == -1) {
DataPage dataPage = dataPages[activeDataPage];
dataPage.pageIndex = targetPageIndex;
loadPage(activeDataPage);
activeDataPage = (activeDataPage + 1) & 1;
return dataPage.page[(int) (position % PAGE_SIZE)];
}
return dataPages[index].page[(int) (position % PAGE_SIZE)];
}
/**
* Clears window cache.
*/
public void clearCache() {
dataPages[0].pageIndex = -1;
dataPages[1].pageIndex = -1;
}
/**
* Simple structure for data page.
*/
private static class DataPage {
long pageIndex = -1;
byte[] page;
public DataPage() {
page = new byte[PAGE_SIZE];
}
}
}
@@ -0,0 +1,157 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data.delta.file;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
import org.exbin.auxiliary.binary_data.delta.DataSource;
/**
* Data source for access to file resource locking it for exclusive access.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public class FileDataSource implements DataSource {
@Nonnull
private final File file;
@Nonnull
private final RandomAccessFile accessFile;
@Nonnull
private final DeltaDataPageWindow window;
private boolean closed = false;
private final List<CacheClearListener> listeners = new ArrayList<>();
public FileDataSource(File sourceFile, EditMode editMode) throws FileNotFoundException, IOException {
file = sourceFile;
accessFile = new RandomAccessFile(sourceFile, editMode.getFileAccessMode());
window = new DeltaDataPageWindow(this);
}
public FileDataSource(File sourceFile) throws FileNotFoundException, IOException {
this(sourceFile, EditMode.READ_WRITE);
}
@Override
public long getDataLength() throws IOException {
checkClosed();
return accessFile.length();
}
@Override
public void setDataLength(long length) throws IOException {
checkClosed();
accessFile.setLength(length);
}
@Nonnull
public File getFile() {
return file;
}
@Nonnull
/* package */ RandomAccessFile getAccessFile() {
checkClosed();
return accessFile;
}
@Override
public byte getByte(long position) throws IOException {
checkClosed();
return window.getByte(position);
}
@Override
public void setByte(long position, byte value) throws IOException {
checkClosed();
accessFile.seek(position);
accessFile.writeByte(value);
}
@Override
public int read(long position, byte[] buffer, int offset, int length) throws IOException {
accessFile.seek(position);
return accessFile.read(buffer, offset, length);
}
@Override
public void write(long position, byte[] buffer, int offset, int length) throws IOException {
accessFile.seek(position);
accessFile.write(buffer, offset, length);
}
/**
* Clears cache window.
*/
@Override
public void clearCache() {
for (CacheClearListener listener : listeners) {
listener.clearCache();
}
}
@Override
public void close() throws IOException {
checkClosed();
accessFile.close();
closed = true;
}
private void checkClosed() {
if (closed) {
throw new IllegalStateException("File was already closed");
}
}
public void addCacheClearListener(CacheClearListener listener) {
listeners.add(listener);
}
public void removeCacheClearListener(CacheClearListener listener) {
listeners.remove(listener);
}
public static interface CacheClearListener {
void clearCache();
}
@ParametersAreNonnullByDefault
public static enum EditMode {
READ_WRITE("rw"),
READ_ONLY("r");
@Nonnull
private final String fileAccessMode;
private EditMode(String fileAccessMode) {
this.fileAccessMode = fileAccessMode;
}
@Nonnull
public String getFileAccessMode() {
return fileAccessMode;
}
}
}
@@ -0,0 +1,448 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data.delta.list;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Objects;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import org.exbin.auxiliary.binary_data.OutOfBoundsException;
/**
* Default implementation of doubly linked list of items.
*
* @author ExBin Project (https://exbin.org)
* @param <T> doubly linked list item
*/
@ParametersAreNonnullByDefault
public class DefaultDoublyLinkedList<T extends DoublyLinkedItem<T>> implements DoublyLinkedList<T> {
private T first;
/* Cached values. */
private T last;
private int size = 0;
@Nullable
@Override
public T first() {
return first;
}
@Nullable
@Override
public T last() {
return last;
}
@Nullable
@Override
public T nextTo(T item) {
return item.getNext();
}
@Nullable
@Override
public T prevTo(T item) {
return item.getPrev();
}
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Nullable
@Override
public T get(int index) {
T item = first;
while (index > 0) {
item = nextTo(item);
index--;
}
return item;
}
@Nonnull
@Override
public T set(int index, T item) {
T origItem = first;
while (index > 0) {
origItem = nextTo(origItem);
index--;
}
if (origItem == null) {
throw new OutOfBoundsException("No item for index " + index);
}
T itemPrev = origItem.getPrev();
T itemNext = origItem.getNext();
origItem.setNext(null);
origItem.setPrev(null);
item.setPrev(itemPrev);
item.setNext(itemNext);
if (last == origItem) {
last = item;
}
return origItem;
}
@Override
public boolean contains(Object item) {
T checkedItem = first;
while (checkedItem != null) {
if (checkedItem.equals(item)) {
return true;
}
checkedItem = nextTo(checkedItem);
}
return false;
}
@Override
@SuppressWarnings("unchecked")
public boolean containsAll(Collection<?> items) {
for (Object item : items) {
if (!contains((T) item)) {
return false;
}
}
return true;
}
@Nonnull
@Override
public Iterator<T> iterator() {
return new Iterator<T>() {
private T current = first;
@Override
public boolean hasNext() {
return current != null;
}
@Override
public T next() {
T result = current;
current = result.getNext();
return result;
}
@Override
public void remove() {
throw new UnsupportedOperationException("Not supported yet.");
}
};
}
@Nonnull
@Override
public Object[] toArray() {
if (last == null) {
return new Object[0];
}
int count = indexOf(last);
Object[] result = new Object[count];
int index = 0;
T item = first;
while (item != null) {
result[index] = item;
item = nextTo(item);
index++;
}
return result;
}
@Nonnull
@Override
@SuppressWarnings("unchecked")
public <X> X[] toArray(X[] template) {
int count = last == null ? 0 : indexOf(last);
X[] result = template.length >= count ? template : Arrays.copyOf(template, count);
int index = 0;
T item = first;
while (item != null) {
result[index] = (X) item;
item = nextTo(item);
index++;
}
return result;
}
@Override
public boolean add(T item) {
if (last != null) {
last.setNext(item);
item.setPrev(last);
item.setNext(null);
last = item;
} else {
first = item;
last = item;
item.setNext(null);
item.setPrev(null);
}
size++;
return true;
}
@Override
public void add(int index, T item) {
if (index == 0 && size == 0) {
add(item);
return;
}
if (index > size) {
throw new OutOfBoundsException("Index " + index + " is greater than " + size);
} else if (index == size) {
last.setNext(item);
item.setPrev(last);
item.setNext(null);
last = item;
} else if (index == 0) {
first.setPrev(item);
item.setNext(first);
first = item;
} else {
T indexItem = Objects.requireNonNull(get(index));
T prevItem = Objects.requireNonNull(indexItem.getPrev());
item.setPrev(prevItem);
item.setNext(indexItem);
prevItem.setNext(item);
indexItem.setPrev(item);
}
size++;
}
/**
* Adds item after given listItem.
* <p>
* ListItem must be part of the list otherwise list will become broken. Item
* should not be member of other list
*
* @param listItem item from the current list
* @param item newly added item
*/
public void addAfter(T listItem, T item) {
T listItemNext = listItem.getNext();
if (listItemNext == null) {
listItem.setNext(item);
item.setPrev(listItem);
item.setNext(null);
last = item;
} else {
listItem.setNext(item);
listItemNext.setPrev(item);
item.setPrev(listItem);
item.setNext(listItemNext);
}
size++;
}
public void addBefore(T targetItem, T item) {
T prev = targetItem.getPrev();
if (prev == null) {
targetItem.setPrev(item);
item.setNext(targetItem);
item.setPrev(null);
first = item;
} else {
targetItem.setPrev(item);
prev.setNext(item);
item.setNext(targetItem);
item.setPrev(prev);
}
size++;
}
@Override
public boolean addAll(Collection<? extends T> elementsToAdd) {
boolean result = false;
for (T element : elementsToAdd) {
result |= add(element);
}
return result;
}
@Override
public boolean addAll(int index, Collection<? extends T> c) {
for (T t : c) {
add(index, t);
index++;
}
return true;
}
@Override
public int indexOf(Object searchedItem) {
T item = first;
int index = 0;
while (item != null) {
if (item.equals(searchedItem)) {
return index;
}
item = item.getNext();
index++;
}
return -1;
}
@Override
@Nullable
public T remove(int index) {
T item = get(index);
if (item != null) {
removeItem(item);
return item;
}
return null;
}
@Override
public boolean remove(Object o) {
@SuppressWarnings("unchecked")
T item = (T) o;
if (item != null) {
removeItem(item);
return true;
}
return false;
}
private void removeItem(T item) {
if (item == first) {
T itemNext = item.getNext();
if (itemNext != null) {
itemNext.setPrev(null);
} else {
last = null;
}
first = itemNext;
} else {
T prev = Objects.requireNonNull(item.getPrev());
T next = item.getNext();
prev.setNext(next);
if (next != null) {
next.setPrev(prev);
} else {
last = prev;
}
}
size--;
item.setPrev(null);
item.setNext(null);
}
@Override
public boolean removeAll(Collection<?> collection) {
boolean result = false;
for (Object item : collection) {
@SuppressWarnings("unchecked")
T itemList = (T) item;
result |= remove(itemList);
}
return result;
}
@Override
public boolean retainAll(Collection<?> retainList) {
boolean changed = false;
if (first == null) {
return false;
}
T item = first;
do {
if (!retainList.contains(item)) {
changed = true;
T nextItem = item.getNext();
removeItem(item);
item = nextItem;
} else {
item = item.getNext();
}
} while (item != null);
return changed;
}
@Override
public void clear() {
first = null;
last = null;
size = 0;
}
@Override
public int lastIndexOf(Object searchedItem) {
if (size > 0) {
T item = last;
int index = size - 1;
while (item != null) {
if (item.equals(searchedItem)) {
return index;
}
item = item.getPrev();
index--;
}
}
return -1;
}
@Nonnull
@Override
public ListIterator<T> listIterator() {
throw new UnsupportedOperationException("Not supported yet.");
}
@Nonnull
@Override
public ListIterator<T> listIterator(int index) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Nonnull
@Override
public List<T> subList(int fromIndex, int toIndex) {
throw new UnsupportedOperationException("Not supported yet.");
}
}
@@ -0,0 +1,59 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data.delta.list;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
/**
* Interface for item of doubly linked list.
*
* @author ExBin Project (https://exbin.org)
* @param <T> instance class
*/
@ParametersAreNonnullByDefault
public interface DoublyLinkedItem<T> {
/**
* Returns next linked item.
*
* @return next item
*/
@Nullable
T getNext();
/**
* Sets next linked item.
*
* @param next next item
*/
void setNext(@Nullable T next);
/**
* Returns previous linked item.
*
* @return next item
*/
@Nullable
T getPrev();
/**
* Sets previous linked item.
*
* @param previous previous item
*/
void setPrev(@Nullable T previous);
}
@@ -0,0 +1,64 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data.delta.list;
import java.util.List;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
/**
* Doubly linked list of items.
*
* @author ExBin Project (https://exbin.org)
* @param <T> doubly linked list item
*/
@ParametersAreNonnullByDefault
public interface DoublyLinkedList<T> extends List<T> {
/**
* Returns first item of the list.
*
* @return first item
*/
@Nullable
T first();
/**
* Returns last item of the list.
*
* @return last item
*/
@Nullable
T last();
/**
* Returns item next to given item.
*
* @param item item
* @return next item or null
*/
@Nullable
T nextTo(T item);
/**
* Returns item previous to given item.
*
* @param item item
* @return previous item or null
*/
@Nullable
T prevTo(T item);
}
@@ -0,0 +1,91 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data.jna;
import com.sun.jna.Memory;
import java.nio.ByteBuffer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import org.exbin.auxiliary.binary_data.buffer.BufferData;
/**
* Implementation of binary data interface using JNA byte buffer.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public class JnaBufferData extends BufferData {
public JnaBufferData() {
this((ByteBuffer) null);
}
/**
* Creates instance directly wrapping provided byte buffer.
*
* @param data byte buffer
*/
public JnaBufferData(@Nullable ByteBuffer data) {
super(data != null ? data : JnaBufferData.allocateBufferInt(0));
}
/**
* Creates instance setting value to provided byte array.
*
* @param data byte array
*/
public JnaBufferData(@Nullable byte[] data) {
super(JnaBufferData.allocateBufferInt(data));
}
/**
* Creates instance with specified size.
*
* @param dataSize data size
*/
public JnaBufferData(int dataSize) {
super(JnaBufferData.allocateBufferInt(dataSize));
}
@Nonnull
@Override
protected ByteBuffer allocateBuffer(int capacity) {
return JnaBufferData.allocateBufferInt(capacity);
}
@Nonnull
private static ByteBuffer allocateBufferInt(int capacity) {
try {
return new Memory(capacity).getByteBuffer(0, capacity);
} catch (Throwable tw) {
// Fallback to regular byte buffer
return ByteBuffer.allocateDirect(capacity);
}
}
@Nonnull
private static ByteBuffer allocateBufferInt(@Nullable byte[] data) {
if (data == null) {
return JnaBufferData.allocateBufferInt(0);
} else {
ByteBuffer buffer = JnaBufferData.allocateBufferInt(data.length);
buffer.put(data);
buffer.clear();
return buffer;
}
}
}
@@ -0,0 +1,91 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data.jna;
import com.sun.jna.Memory;
import java.nio.ByteBuffer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import org.exbin.auxiliary.binary_data.buffer.BufferEditableData;
/**
* Implementation of editable binary data interface using JNA byte buffer.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public class JnaBufferEditableData extends BufferEditableData {
public JnaBufferEditableData() {
this((ByteBuffer) null);
}
/**
* Creates instance directly wrapping provided byte buffer.
*
* @param data byte buffer
*/
public JnaBufferEditableData(@Nullable ByteBuffer data) {
super(data != null ? data : JnaBufferEditableData.allocateBufferInt(0));
}
/**
* Creates instance setting value to provided byte array.
*
* @param data byte array
*/
public JnaBufferEditableData(@Nullable byte[] data) {
super(JnaBufferEditableData.allocateBufferInt(data));
}
/**
* Creates instance with specified size.
*
* @param dataSize data size
*/
public JnaBufferEditableData(int dataSize) {
super(JnaBufferEditableData.allocateBufferInt(dataSize));
}
@Nonnull
@Override
protected ByteBuffer allocateBuffer(int capacity) {
return JnaBufferEditableData.allocateBufferInt(capacity);
}
@Nonnull
private static ByteBuffer allocateBufferInt(int capacity) {
try {
return new Memory(capacity).getByteBuffer(0, capacity);
} catch (Throwable tw) {
// Fallback to regular byte buffer
return ByteBuffer.allocateDirect(capacity);
}
}
@Nonnull
private static ByteBuffer allocateBufferInt(@Nullable byte[] data) {
if (data == null) {
return JnaBufferEditableData.allocateBufferInt(0);
} else {
ByteBuffer buffer = JnaBufferEditableData.allocateBufferInt(data.length);
buffer.put(data);
buffer.clear();
return buffer;
}
}
}
@@ -0,0 +1,77 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data.jna.paged;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import org.exbin.auxiliary.binary_data.EditableBinaryData;
import org.exbin.auxiliary.binary_data.jna.JnaBufferData;
import org.exbin.auxiliary.binary_data.buffer.paged.BufferPagedData;
import org.exbin.auxiliary.binary_data.paged.DataPageCreator;
/**
* Paged data stored using JNA byte buffer.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public class JnaBufferPagedData extends BufferPagedData {
public JnaBufferPagedData() {
}
public JnaBufferPagedData(DataPageCreator dataPageCreator) {
this.dataPageCreator = dataPageCreator;
}
public JnaBufferPagedData(int pageSize) {
this.pageSize = pageSize;
}
@Nonnull
@Override
protected JnaBufferData createNewPage(byte[] pageData) {
if (dataPageCreator != null) {
EditableBinaryData page = dataPageCreator.createPage(pageData.length);
page.replace(0, pageData);
return (JnaBufferData) page;
}
return new JnaBufferData(pageData);
}
@Nonnull
@Override
protected JnaBufferData createNewPage(int pageDataSize) {
if (dataPageCreator != null) {
return (JnaBufferData) dataPageCreator.createPage(pageDataSize);
}
return new JnaBufferData(pageDataSize);
}
@Nullable
@Override
public DataPageCreator getDataPageCreator() {
return dataPageCreator;
}
@Override
public void setDataPageCreator(@Nullable DataPageCreator dataPageCreator) {
this.dataPageCreator = dataPageCreator;
}
}
@@ -0,0 +1,43 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data.paged;
import java.util.Optional;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Paged data with support for customizable page creator.
*
* @author ExBin Project (https://exbin.org)
*/
public interface CustomizablePagedData extends PagedData {
/**
* Returns data page creator.
*
* @return data page creator
*/
@Nonnull
Optional<DataPageCreator> getDataPageCreator();
/**
* Sets data page creator.
*
* @param dataPageCreator data page creator
*/
void setDataPageCreator(@Nullable DataPageCreator dataPageCreator);
}
@@ -0,0 +1,36 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data.paged;
import javax.annotation.Nonnull;
import org.exbin.auxiliary.binary_data.EditableBinaryData;
/**
* Data page creator.
*
* @author ExBin Project (https://exbin.org)
*/
public interface DataPageCreator {
/**
* Creates new data page with specified size.
*
* @param dataSize data size
* @return data page
*/
@Nonnull
EditableBinaryData createPage(int dataSize);
}
@@ -0,0 +1,64 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.auxiliary.binary_data.paged;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
import org.exbin.auxiliary.binary_data.BinaryData;
import org.exbin.auxiliary.binary_data.EditableBinaryData;
/**
* Interface for paged data.
* <p>
* Data are stored using block of data of the same size. Last page might be
* shorter than page size, but not empty.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public interface PagedData extends EditableBinaryData {
/**
* Returns number of pages currently used.
*
* @return count of pages
*/
int getPagesCount();
/**
* Returns currently used page size.
*
* @return page size in bytes
*/
int getPageSize();
/**
* Gets data page allowing direct access to it.
*
* @param pageIndex page index
* @return data page
*/
@Nonnull
BinaryData getPage(int pageIndex);
/**
* Sets data page replacing existing page by reference.
*
* @param pageIndex page index
* @param dataPage data page
*/
void setPage(int pageIndex, BinaryData dataPage);
}
@@ -38,27 +38,30 @@ import javax.annotation.ParametersAreNonnullByDefault;
@ParametersAreNonnullByDefault
public class CharsetStreamTranslator extends InputStream {
private static final String UNMAPPABLE_CHARACTER_ERROR = "Unmappable character should be handled automatically";
private static final String MALFORMED_CHARACTER_ERROR = "Malformed character should be handled automatically";
public static final String DEFAULT_ENCODING = "UTF-8";
public static final int DEFAULT_MAX_BYTES_PER_CHAR = 8;
public static final int BYTE_BUFFER_SIZE = 16;
@Nonnull
private final CharsetEncoder encoder;
protected final CharsetEncoder encoder;
@Nonnull
private final CharsetDecoder decoder;
protected final CharsetDecoder decoder;
@Nonnull
private final InputStream source;
protected final InputStream source;
@Nonnull
private final ByteBuffer inputBuffer;
protected final ByteBuffer inputBuffer;
@Nonnull
private final ByteBuffer outputBuffer;
protected final ByteBuffer outputBuffer;
@Nonnull
private final CharBuffer charBuffer;
private boolean endOfInput = false;
protected final CharBuffer charBuffer;
protected boolean endOfInput = false;
private int maxInputCharSize;
private int maxOutputCharSize;
protected int maxInputCharSize;
protected int maxOutputCharSize;
public CharsetStreamTranslator(Charset inputCharset, Charset outputCharset, InputStream source, int bufferSize) {
this.source = source;
@@ -110,7 +113,7 @@ public class CharsetStreamTranslator extends InputStream {
}
}
return outputBuffer.get();
return outputBuffer.get() & 0xFF;
}
@Override
@@ -162,9 +165,9 @@ public class CharsetStreamTranslator extends InputStream {
inputBuffer.position(inputBuffer.position() + 1);
}
} else if (decodeResult.isUnmappable()) {
throw new IllegalStateException("Unmappable character should be handled automatically");
throw new IllegalStateException(UNMAPPABLE_CHARACTER_ERROR);
} else if (decodeResult.isMalformed()) {
throw new IllegalStateException("Malformed character should be handled automatically");
throw new IllegalStateException(MALFORMED_CHARACTER_ERROR);
}
} while (charBuffer.position() == 0);
@@ -182,9 +185,9 @@ public class CharsetStreamTranslator extends InputStream {
} else if (encodeResult.isError()) {
throw new IllegalStateException("Unexpected error character");
} else if (encodeResult.isUnmappable()) {
throw new IllegalStateException("Unmappable character should be handled automatically");
throw new IllegalStateException(UNMAPPABLE_CHARACTER_ERROR);
} else if (encodeResult.isMalformed()) {
throw new IllegalStateException("Malformed character should be handled automatically");
throw new IllegalStateException(MALFORMED_CHARACTER_ERROR);
}
}
@@ -223,15 +226,15 @@ public class CharsetStreamTranslator extends InputStream {
int offset = position;
while (toRead > 0) {
try {
int red = source.read(buffer, offset, toRead);
if (red < 0) {
int read = source.read(buffer, offset, toRead);
if (read < 0) {
inputBuffer.limit(offset);
endOfInput = true;
break;
}
offset += red;
toRead -= red;
offset += read;
toRead -= read;
} catch (IOException ex) {
Logger.getLogger(CharsetStreamTranslator.class.getName()).log(Level.SEVERE, null, ex);
}
@@ -17,6 +17,7 @@ package org.exbin.bined;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
/**
* Selection between two positions where begin represents origin point and end
@@ -24,6 +25,7 @@ import javax.annotation.Nullable;
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public class CodeAreaSelection {
private long start;
@@ -21,6 +21,7 @@ import java.util.Objects;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import org.exbin.auxiliary.binary_data.BinaryData;
import org.exbin.auxiliary.binary_data.EditableBinaryData;
/**
@@ -36,6 +37,8 @@ public class CodeAreaUtils {
private static final int CODE_BUFFER_LENGTH = 16;
public static final String MIME_CLIPBOARD_BINARY = "application/octet-stream";
public static final String BINED_CLIPBOARD_MIME = "application/x-bined";
public static final String BINED_CLIPBOARD_MIME_FULL = BINED_CLIPBOARD_MIME + "; class=" + BinaryData.class.getCanonicalName();
public static final String CONTENT_DATA_ERROR = "Content data is null";
public static final String NULL_FIELD_ERROR = "Field cannot be null";
@@ -339,7 +342,7 @@ public class CodeAreaUtils {
* @param keyValue keyboard key value
* @param codeOffset current code offset
* @param codeType current code type
* @return true if key value value is valid
* @return true if key value is valid
*/
public static boolean isValidCodeKeyValue(char keyValue, int codeOffset, CodeType codeType) {
boolean validKey = false;
@@ -0,0 +1,25 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.bined;
/**
* Enumeration of scrollbar orientation variants.
*
* @author ExBin Project (https://exbin.org)
*/
public enum ScrollBarOrientation {
HORIZONTAL, VERTICAL
}
+12 -2
View File
@@ -49,10 +49,10 @@ public class SelectionRange {
/**
* Creates selection range from start to end including the start and not
* including the end position.
* including character at the end position.
*
* @param start selection start position
* @param end selection end position without actual end position itself
* @param end selection end position without character at the end position
*/
public SelectionRange(long start, long end) {
if (start < 0) {
@@ -67,10 +67,20 @@ public class SelectionRange {
this.end = end;
}
/**
* Returns selection start position.
*
* @return selection start position.
*/
public long getStart() {
return start;
}
/**
* Returns selection end position where selection is not including character at the end position.
*
* @return selection end position.
*/
public long getEnd() {
return end;
}
@@ -33,28 +33,28 @@ import org.exbin.bined.capability.BasicScrollingCapable;
public class BasicCodeAreaScrolling {
@Nonnull
private final CodeAreaScrollPosition scrollPosition = new CodeAreaScrollPosition();
protected final CodeAreaScrollPosition scrollPosition = new CodeAreaScrollPosition();
@Nonnull
private ScrollBarVerticalScale scrollBarVerticalScale = ScrollBarVerticalScale.NORMAL;
private int horizontalExtentDifference;
private int verticalExtentDifference;
private int horizontalScrollBarHeight;
private int verticalScrollBarWidth;
protected ScrollBarVerticalScale scrollBarVerticalScale = ScrollBarVerticalScale.NORMAL;
protected int horizontalExtentDifference;
protected int verticalExtentDifference;
protected int horizontalScrollBarHeight;
protected int verticalScrollBarWidth;
private int lastVerticalScrollingValue = -1;
protected int lastVerticalScrollingValue = -1;
@Nonnull
private VerticalScrollUnit verticalScrollUnit = VerticalScrollUnit.ROW;
protected VerticalScrollUnit verticalScrollUnit = VerticalScrollUnit.ROW;
@Nonnull
private ScrollBarVisibility verticalScrollBarVisibility = ScrollBarVisibility.IF_NEEDED;
protected ScrollBarVisibility verticalScrollBarVisibility = ScrollBarVisibility.IF_NEEDED;
@Nonnull
private HorizontalScrollUnit horizontalScrollUnit = HorizontalScrollUnit.PIXEL;
protected HorizontalScrollUnit horizontalScrollUnit = HorizontalScrollUnit.PIXEL;
@Nonnull
private ScrollBarVisibility horizontalScrollBarVisibility = ScrollBarVisibility.IF_NEEDED;
protected ScrollBarVisibility horizontalScrollBarVisibility = ScrollBarVisibility.IF_NEEDED;
@Nonnull
private final CodeAreaScrollPosition maximumScrollPosition = new CodeAreaScrollPosition();
protected final CodeAreaScrollPosition maximumScrollPosition = new CodeAreaScrollPosition();
private static final long ROW_POSITION_LIMIT = Long.MAX_VALUE / Integer.MAX_VALUE;
protected int maximumScrollBarHeight = Integer.MAX_VALUE;
@Nullable
private Runnable verticalExtentChangeListener = null;
@@ -73,8 +73,7 @@ public class BasicCodeAreaScrolling {
this.verticalScrollBarWidth = verticalScrollBarWidth;
}
@Nonnull
public ScrollViewDimension computeViewDimension(int dataViewWidth, int dataViewHeight, BasicCodeAreaLayout layout, BasicCodeAreaStructure structure, int characterWidth, int rowHeight) {
public void computeViewDimension(ScrollViewDimension outputDimension, int dataViewWidth, int dataViewHeight, BasicCodeAreaLayout layout, BasicCodeAreaStructure structure, int characterWidth, int rowHeight) {
int charsPerRow = structure.getCharactersPerRow();
int dataWidth = layout.computePositionX(charsPerRow, characterWidth);
boolean fitsHorizontally = computeFitsHorizontally(dataViewWidth, dataWidth);
@@ -105,15 +104,14 @@ public class BasicCodeAreaScrolling {
height = recomputeScrollViewHeight(dataViewHeight, rowHeight, rowsPerData);
}
// TODO avoid creation of instance
return new ScrollViewDimension(dataViewWidth, dataViewHeight, width, height);
outputDimension.setDimension(dataViewWidth, dataViewHeight, width, height);
}
private boolean computeFitsHorizontally(int dataViewWidth, int dataWidth) {
protected boolean computeFitsHorizontally(int dataViewWidth, int dataWidth) {
return dataWidth <= dataViewWidth;
}
private boolean computeFitsVertically(int dataViewHeight, long rowsPerData, int rowHeight) {
protected boolean computeFitsVertically(int dataViewHeight, long rowsPerData, int rowHeight) {
int availableRows = (dataViewHeight + rowHeight - 1) / rowHeight;
if (rowsPerData > availableRows) {
return false;
@@ -122,7 +120,7 @@ public class BasicCodeAreaScrolling {
return rowsPerData * rowHeight <= dataViewHeight;
}
private int recomputeScrollViewWidth(int dataViewWidth, int characterWidth, int dataWidth, int charsPerRow) {
protected int recomputeScrollViewWidth(int dataViewWidth, int characterWidth, int dataWidth, int charsPerRow) {
int scrollViewWidth = 0;
switch (horizontalScrollUnit) {
case PIXEL: {
@@ -143,13 +141,13 @@ public class BasicCodeAreaScrolling {
return scrollViewWidth;
}
private int recomputeScrollViewHeight(int dataViewHeight, int rowHeight, long rowsPerData) {
protected int recomputeScrollViewHeight(int dataViewHeight, int rowHeight, long rowsPerData) {
int scrollViewHeight = 0;
switch (verticalScrollUnit) {
case PIXEL: {
if (rowsPerData > Integer.MAX_VALUE / rowHeight) {
if (rowsPerData > maximumScrollBarHeight / rowHeight) {
scrollBarVerticalScale = ScrollBarVerticalScale.SCALED;
scrollViewHeight = Integer.MAX_VALUE;
scrollViewHeight = maximumScrollBarHeight;
changeVerticalExtentDifference(0);
} else {
scrollBarVerticalScale = ScrollBarVerticalScale.NORMAL;
@@ -159,9 +157,9 @@ public class BasicCodeAreaScrolling {
break;
}
case ROW: {
if (rowsPerData > (Integer.MAX_VALUE - dataViewHeight)) {
if (rowsPerData > (maximumScrollBarHeight - dataViewHeight)) {
scrollBarVerticalScale = ScrollBarVerticalScale.SCALED;
scrollViewHeight = Integer.MAX_VALUE;
scrollViewHeight = maximumScrollBarHeight;
changeVerticalExtentDifference(0);
} else {
scrollBarVerticalScale = ScrollBarVerticalScale.NORMAL;
@@ -219,7 +217,7 @@ public class BasicCodeAreaScrolling {
long rest = rowsPerDocumentToLastPage % maxValue;
targetRow += (rest * scrollBarValue) / maxValue;
} else {
targetRow = (scrollBarValue * rowsPerDocumentToLastPage) / Integer.MAX_VALUE;
targetRow = (scrollBarValue * rowsPerDocumentToLastPage) / maximumScrollBarHeight;
}
scrollPosition.setRowPosition(targetRow);
}
@@ -245,7 +243,7 @@ public class BasicCodeAreaScrolling {
long rest = rowsPerDocumentToLastPage % maxValue;
targetRow += (rest * scrollBarValue) / maxValue;
} else {
targetRow = (scrollBarValue * rowsPerDocumentToLastPage) / Integer.MAX_VALUE;
targetRow = (scrollBarValue * rowsPerDocumentToLastPage) / maximumScrollBarHeight;
}
scrollPosition.setRowPosition(targetRow);
}
@@ -270,10 +268,10 @@ public class BasicCodeAreaScrolling {
case PIXEL: {
if (scrollBarVerticalScale == ScrollBarVerticalScale.SCALED) {
int scrollValue;
if (scrollPosition.getRowPosition() < ROW_POSITION_LIMIT) {
scrollValue = (int) ((scrollPosition.getRowPosition() * Integer.MAX_VALUE) / rowsPerDocument);
if (scrollPosition.getRowPosition() < Long.MAX_VALUE / maximumScrollBarHeight) {
scrollValue = (int) ((scrollPosition.getRowPosition() * maximumScrollBarHeight) / rowsPerDocument);
} else {
scrollValue = (int) (scrollPosition.getRowPosition() / (rowsPerDocument / Integer.MAX_VALUE));
scrollValue = (int) (scrollPosition.getRowPosition() / (rowsPerDocument / maximumScrollBarHeight));
}
return scrollValue;
}
@@ -282,10 +280,10 @@ public class BasicCodeAreaScrolling {
case ROW: {
if (scrollBarVerticalScale == ScrollBarVerticalScale.SCALED) {
int scrollValue;
if (scrollPosition.getRowPosition() < ROW_POSITION_LIMIT) {
scrollValue = (int) ((scrollPosition.getRowPosition() * Integer.MAX_VALUE) / rowsPerDocument);
if (scrollPosition.getRowPosition() < Long.MAX_VALUE / maximumScrollBarHeight) {
scrollValue = (int) ((scrollPosition.getRowPosition() * maximumScrollBarHeight) / rowsPerDocument);
} else {
scrollValue = (int) (scrollPosition.getRowPosition() / (rowsPerDocument / Integer.MAX_VALUE));
scrollValue = (int) (scrollPosition.getRowPosition() / (rowsPerDocument / maximumScrollBarHeight));
}
return scrollValue;
}
@@ -315,6 +313,14 @@ public class BasicCodeAreaScrolling {
this.horizontalExtentChangeListener = horizontalExtentChangeListener;
}
public int getMaximumScrollBarHeight() {
return maximumScrollBarHeight;
}
public void setMaximumScrollBarHeight(int maximumScrollBarHeight) {
this.maximumScrollBarHeight = maximumScrollBarHeight;
}
@Nonnull
public CodeAreaScrollPosition computeScrolling(CodeAreaScrollPosition startPosition, ScrollingDirection direction, int rowsPerPage, long rowsPerDocument) {
CodeAreaScrollPosition targetPosition = new CodeAreaScrollPosition();
@@ -469,7 +475,7 @@ public class BasicCodeAreaScrolling {
}
@Nonnull
private PositionScrollVisibility checkTopScrollVisibility(long rowPosition) {
protected PositionScrollVisibility checkTopScrollVisibility(long rowPosition) {
if (verticalScrollUnit == VerticalScrollUnit.ROW) {
return rowPosition < scrollPosition.getRowPosition() ? PositionScrollVisibility.NOT_VISIBLE : PositionScrollVisibility.VISIBLE;
}
@@ -485,7 +491,7 @@ public class BasicCodeAreaScrolling {
}
@Nonnull
private PositionScrollVisibility checkBottomScrollVisibility(long rowPosition, int rowsPerPage, int rowOffset, int rowHeight) {
protected PositionScrollVisibility checkBottomScrollVisibility(long rowPosition, int rowsPerPage, int rowOffset, int rowHeight) {
int sumOffset = scrollPosition.getRowOffset() + rowOffset;
long lastFullRow = scrollPosition.getRowPosition() + rowsPerPage;
@@ -507,7 +513,7 @@ public class BasicCodeAreaScrolling {
}
@Nonnull
private PositionScrollVisibility checkLeftScrollVisibility(int charsPosition) {
protected PositionScrollVisibility checkLeftScrollVisibility(int charsPosition) {
int charPos = scrollPosition.getCharPosition();
if (horizontalScrollUnit != HorizontalScrollUnit.PIXEL) {
return charsPosition < charPos ? PositionScrollVisibility.NOT_VISIBLE : PositionScrollVisibility.VISIBLE;
@@ -524,7 +530,7 @@ public class BasicCodeAreaScrolling {
}
@Nonnull
private PositionScrollVisibility checkRightScrollVisibility(int charsPosition, int charsPerPage, int charOffset, int characterWidth) {
protected PositionScrollVisibility checkRightScrollVisibility(int charsPosition, int charsPerPage, int charOffset, int characterWidth) {
int sumOffset = scrollPosition.getCharOffset() + charOffset;
int lastFullChar = scrollPosition.getCharPosition() + charsPerPage;
@@ -628,7 +634,7 @@ public class BasicCodeAreaScrolling {
}
}
private void setHorizontalScrollPosition(CodeAreaScrollPosition scrollPosition, int charPos, int pixelOffset, int characterWidth) {
protected void setHorizontalScrollPosition(CodeAreaScrollPosition scrollPosition, int charPos, int pixelOffset, int characterWidth) {
switch (horizontalScrollUnit) {
case CHARACTER: {
scrollPosition.setCharPosition(charPos);
@@ -719,7 +725,7 @@ public class BasicCodeAreaScrolling {
lastVerticalScrollingValue = -1;
}
private void changeVerticalExtentDifference(int newDifference) {
protected void changeVerticalExtentDifference(int newDifference) {
if (verticalExtentDifference != newDifference) {
verticalExtentDifference = newDifference;
if (verticalExtentChangeListener != null) {
@@ -728,7 +734,7 @@ public class BasicCodeAreaScrolling {
}
}
private void changeHorizontalExtentDifference(int newDifference) {
protected void changeHorizontalExtentDifference(int newDifference) {
if (horizontalExtentDifference != newDifference) {
horizontalExtentDifference = newDifference;
if (horizontalExtentChangeListener != null) {
@@ -33,22 +33,22 @@ import org.exbin.bined.capability.ViewModeCapable;
@ParametersAreNonnullByDefault
public class BasicCodeAreaStructure {
private final BasicCodeAreaLayout layout = new BasicCodeAreaLayout();
protected final BasicCodeAreaLayout layout = new BasicCodeAreaLayout();
@Nonnull
private CodeAreaViewMode viewMode = CodeAreaViewMode.DUAL;
protected CodeAreaViewMode viewMode = CodeAreaViewMode.DUAL;
@Nonnull
private CodeType codeType = CodeType.HEXADECIMAL;
protected CodeType codeType = CodeType.HEXADECIMAL;
private long dataSize;
protected long dataSize;
@Nonnull
private RowWrappingMode rowWrapping = RowWrappingMode.NO_WRAPPING;
private int maxBytesPerLine;
private int wrappingBytesGroupSize;
protected RowWrappingMode rowWrapping = RowWrappingMode.NO_WRAPPING;
protected int maxBytesPerLine;
protected int wrappingBytesGroupSize;
private long rowsPerDocument;
private int bytesPerRow;
private int charactersPerRow;
protected long rowsPerDocument;
protected int bytesPerRow;
protected int charactersPerRow;
public BasicCodeAreaStructure() {
}
@@ -30,22 +30,22 @@ public class CodeAreaScrollPosition {
/**
* Scrollbar document row position.
*/
private long rowPosition = 0;
protected long rowPosition = 0;
/**
* Scrollbar document row pixel offset position.
*/
private int rowOffset = 0;
protected int rowOffset = 0;
/**
* Scrollbar document character position.
*/
private int charPosition = 0;
protected int charPosition = 0;
/**
* Scrollbar document character pixel offset position.
*/
private int charOffset = 0;
protected int charOffset = 0;
public CodeAreaScrollPosition() {
}
@@ -26,7 +26,7 @@ public enum ScrollBarVerticalScale {
*/
NORMAL,
/**
* Height is more than available range/precission of the scrollbar and must
* Height is more than available range/precision of the scrollbar and must
* be scaled.
*/
SCALED
@@ -15,22 +15,22 @@
*/
package org.exbin.bined.basic;
import javax.annotation.concurrent.Immutable;
/**
* Scrolling view dimensions.
*
* @author ExBin Project (https://exbin.org)
*/
@Immutable
public class ScrollViewDimension {
private final int dataViewWidth;
private final int dataViewHeight;
private final int width;
private final int height;
protected int dataViewWidth;
protected int dataViewHeight;
protected int width;
protected int height;
public ScrollViewDimension(int dataViewWidth, int dataViewHeight, int width, int height) {
public ScrollViewDimension() {
}
public void setDimension(int dataViewWidth, int dataViewHeight, int width, int height) {
this.dataViewWidth = dataViewWidth;
this.dataViewHeight = dataViewHeight;
this.width = width;
@@ -0,0 +1,32 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.bined.basic;
/**
* Enumeration of selection selecting modes.
*
* @author ExBin Project (https://exbin.org)
*/
public enum SelectingMode {
/**
* Selection is not updated.
*/
NONE,
/**
* Selection is updated.
*/
SELECTING
}
@@ -22,7 +22,7 @@ package org.exbin.bined.basic;
*/
public enum TabKeyHandlingMode {
/**
* Handle enter using java platform detection (default).
* Handle tab key using java platform detection (default).
*/
PLATFORM_SPECIFIC,
/**
@@ -60,7 +60,7 @@ public interface BasicScrollingCapable {
void setVerticalScrollUnit(VerticalScrollUnit verticalScrollUnit);
/**
* Returns horizotal scrollbar visibility mode.
* Returns horizontal scrollbar visibility mode.
*
* @return scrollbar visibility mode
*/
@@ -68,7 +68,7 @@ public interface BasicScrollingCapable {
ScrollBarVisibility getHorizontalScrollBarVisibility();
/**
* Sets horizotal scrollbar visibility mode.
* Sets horizontal scrollbar visibility mode.
*
* @param horizontalScrollBarVisibility scrollbar visibility mode
*/
@@ -107,11 +107,11 @@ public interface CaretCapable {
*
* @param positionX x-coordinate
* @param positionY y-coordinate
* @param overflowMode overflow mode
* @param overlapMode caret overlapping mode
* @return mouse position
*/
@Nonnull
CodeAreaCaretPosition mousePositionToClosestCaretPosition(int positionX, int positionY, CaretOverlapMode overflowMode);
CodeAreaCaretPosition mousePositionToClosestCaretPosition(int positionX, int positionY, CaretOverlapMode overlapMode);
/**
* Returns if cursor should be visible in other sections.
@@ -28,7 +28,7 @@ import org.exbin.bined.basic.CodeAreaViewMode;
public interface ViewModeCapable {
/**
* Returns curret view mode.
* Returns current view mode.
*
* @return view mode
*/
@@ -21,7 +21,7 @@ import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
/**
* Enumeration of unprintable color types.
* Enumeration of decoration color types.
*
* @author ExBin Project (https://exbin.org)
*/
@@ -19,7 +19,7 @@ import javax.annotation.ParametersAreNonnullByDefault;
import org.exbin.bined.operation.BinaryDataCommand;
/**
* Undoable command sequence with suppord for appendable commands.
* Undoable command sequence with support for appendable commands.
*
* @author ExBin Project (https://exbin.org)
*/
@@ -30,7 +30,7 @@ public interface BinaryDataAppendableUndoRedo extends BinaryDataUndoRedo {
* Attempts to execute command as an append to existing command.
*
* @param command command
* @return true if sucessfully appended
* @return true if successfully appended
*/
boolean appendExecute(BinaryDataCommand command);
}
+21
View File
@@ -12,6 +12,22 @@
# public *;
#}
-keep class kotlinx.coroutines.CoroutineExceptionHandler
-keep class kotlinx.coroutines.internal.MainDispatcherFactory
-dontwarn java.awt.*
-keep class com.sun.jna.* { *; }
-keepclassmembers class * extends com.sun.jna.* { public *; }
-keep class androidx.appcompat.view.menu.MenuBuilder {
void setOptionalIconsVisible(boolean);
}
-keepclassmembernames class androidx.appcompat.view.menu.MenuBuilder {
void setOptionalIconsVisible(boolean);
}
-keep class java.util.Optional { *; }
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
@@ -19,3 +35,8 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-dontobfuscate
#-dontusemixedcaseclassnames
#-dontpreverify
#-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
#-optimizationpasses 5
@@ -21,8 +21,8 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
import org.exbin.auxiliary.binary_data.ByteArrayEditableData;
import org.exbin.auxiliary.binary_data.EditableBinaryData;
import org.exbin.auxiliary.binary_data.jna.JnaBufferEditableData;
/**
* Tests for codeArea component.
@@ -42,7 +42,7 @@ public class CodeAreaTest {
@Nonnull
public static EditableBinaryData getSampleData(String dataPath) {
ByteArrayEditableData data = new ByteArrayEditableData();
JnaBufferEditableData data = new JnaBufferEditableData();
try (InputStream stream = CodeAreaTest.class.getResourceAsStream(dataPath)) {
data.loadFromStream(stream);
} catch (IOException ex) {
@@ -17,8 +17,8 @@ package org.exbin.bined;
import javax.annotation.ParametersAreNonnullByDefault;
import org.exbin.auxiliary.binary_data.BinaryData;
import org.exbin.auxiliary.binary_data.ByteArrayEditableData;
import org.exbin.auxiliary.binary_data.EditableBinaryData;
import org.exbin.auxiliary.binary_data.jna.JnaBufferEditableData;
import org.junit.Assert;
import org.junit.Test;
@@ -38,11 +38,11 @@ public class CodeAreaUtilsTest {
}
expectedData[32] = (byte) 0xff;
EditableBinaryData resultData = new ByteArrayEditableData();
EditableBinaryData resultData = new JnaBufferEditableData();
CodeAreaUtils.insertHexStringIntoData("00 01 02 03 04 05 06 07 08 9 0A 0b c 0D E 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f ff", resultData, CodeType.HEXADECIMAL);
checkResultData(expectedData, resultData);
resultData = new ByteArrayEditableData();
resultData = new JnaBufferEditableData();
CodeAreaUtils.insertHexStringIntoData("000102030405060708090A0b0c0D0E0f101112131415161718191a1b1c1d1e1fFf", resultData, CodeType.HEXADECIMAL);
checkResultData(expectedData, resultData);
}
@@ -57,7 +57,7 @@ public class CodeAreaUtilsTest {
expectedData[21] = (byte) 200;
expectedData[22] = (byte) 255;
EditableBinaryData resultData = new ByteArrayEditableData();
EditableBinaryData resultData = new JnaBufferEditableData();
CodeAreaUtils.insertHexStringIntoData("00 01 02 03 04 05 06 07 08 9 10 11 12 13 14 15 16 17 18 19 100 200 255", resultData, CodeType.DECIMAL);
checkResultData(expectedData, resultData);
}
@@ -21,7 +21,7 @@ import java.io.OutputStream;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
import org.exbin.auxiliary.binary_data.BinaryData;
import org.exbin.auxiliary.binary_data.ByteArrayData;
import org.exbin.auxiliary.binary_data.jna.JnaBufferData;
/**
* Simulation of huge binary data source.
@@ -60,7 +60,7 @@ public class HugeBinaryData implements BinaryData {
}
byte[] dataArray = new byte[(int) length];
copyToArray(startFrom, dataArray, 0, (int) length);
return new ByteArrayData(dataArray);
return new JnaBufferData(dataArray);
}
@Override
@@ -0,0 +1,277 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.bined.android.basic;
import org.exbin.auxiliary.binary_data.BinaryData;
import org.exbin.auxiliary.binary_data.EditableBinaryData;
import org.exbin.bined.CodeAreaTest;
import org.exbin.bined.EditMode;
import org.exbin.bined.EditOperation;
import org.exbin.bined.android.CodeAreaCore;
import org.exbin.bined.capability.CaretCapable;
import org.exbin.bined.capability.EditModeCapable;
import org.exbin.bined.editor.android.R;
import org.junit.Assert;
import org.junit.Test;
import javax.annotation.ParametersAreNonnullByDefault;
/**
* Tests clipboard actions for CodeArea command component.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public class CodeAreaCommandClipboardTest extends CodeAreaComponentTest {
public CodeAreaCommandClipboardTest() {
}
@Test
public void testDeleteAll() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
codeArea.setContentData(CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES));
codeArea.selectAll();
codeArea.delete();
Assert.assertEquals(0, codeArea.getDataSize());
});
}
/* @Test
public void testCopySelection() {
CodeAreaCore codeArea = createCodeArea();
EditableBinaryData sampleData = CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES);
byte[] expectedData = new byte[2];
sampleData.copyToArray(5, expectedData, 0, 2);
codeArea.setContentData(sampleData);
((SelectionCapable) codeArea).setSelection(5, 7);
codeArea.copy();
try {
String text = (String) CodeAreaAndroidUtils.getClipboard().getData(DataFlavor.stringFlavor);
Assert.assertArrayEquals(expectedData, text.getBytes("UTF-8"));
} catch (UnsupportedFlavorException | IOException ex) {
Logger.getLogger(CodeAreaCommandClipboardTest.class.getName()).log(Level.SEVERE, null, ex);
Assert.fail();
}
} */
@Test
public void testCopyPasteAllInOverwriteMode() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
EditableBinaryData sampleData = CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES);
codeArea.setContentData(sampleData);
((EditModeCapable) codeArea).setEditOperation(EditOperation.OVERWRITE);
long dataSize = sampleData.getDataSize();
codeArea.selectAll();
codeArea.copy();
codeArea.clearSelection();
codeArea.paste();
Assert.assertEquals(dataSize, codeArea.getDataSize());
});
}
@Test
public void testCopyPasteAllInInsertMode() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
EditableBinaryData sampleData = CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES);
codeArea.setContentData(sampleData);
((EditModeCapable) codeArea).setEditOperation(EditOperation.INSERT);
long dataSize = sampleData.getDataSize();
codeArea.selectAll();
codeArea.copy();
codeArea.clearSelection();
codeArea.paste();
Assert.assertEquals(dataSize * 2, codeArea.getDataSize());
});
}
@Test
public void testCopyPasteAllInInplaceMode() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
((EditModeCapable) codeArea).setEditMode(EditMode.INPLACE);
EditableBinaryData sampleData = CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES);
codeArea.setContentData(sampleData);
long dataSize = sampleData.getDataSize();
codeArea.selectAll();
codeArea.copy();
codeArea.clearSelection();
codeArea.paste();
Assert.assertEquals(dataSize, codeArea.getDataSize());
});
}
@Test
public void testCopyPasteInOverwriteAtTheEnd() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
EditableBinaryData sampleData = CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES);
codeArea.setContentData(sampleData);
((EditModeCapable) codeArea).setEditOperation(EditOperation.OVERWRITE);
long dataSize = sampleData.getDataSize();
codeArea.selectAll();
codeArea.copy();
codeArea.clearSelection();
((CaretCapable) codeArea).setActiveCaretPosition(dataSize);
codeArea.paste();
Assert.assertEquals(dataSize * 2, codeArea.getDataSize());
});
}
@Test
public void testCopyPasteInInsertAtTheEnd() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
EditableBinaryData sampleData = CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES);
codeArea.setContentData(sampleData);
((EditModeCapable) codeArea).setEditOperation(EditOperation.INSERT);
long dataSize = sampleData.getDataSize();
codeArea.selectAll();
codeArea.copy();
codeArea.clearSelection();
((CaretCapable) codeArea).setActiveCaretPosition(dataSize);
codeArea.paste();
Assert.assertEquals(dataSize * 2, codeArea.getDataSize());
});
}
@Test
public void testCopyPasteInInplaceAtTheEnd() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
((EditModeCapable) codeArea).setEditMode(EditMode.INPLACE);
EditableBinaryData sampleData = CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES);
codeArea.setContentData(sampleData);
long dataSize = sampleData.getDataSize();
codeArea.selectAll();
codeArea.copy();
codeArea.clearSelection();
((CaretCapable) codeArea).setActiveCaretPosition(dataSize);
codeArea.paste();
Assert.assertEquals(dataSize, codeArea.getDataSize());
});
}
@Test
public void testCopyPasteInOverwriteWithOverflow() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
EditableBinaryData sampleData = CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES);
int dataSize = (int) sampleData.getDataSize();
int expectedSize = (int) (dataSize / 2 + dataSize);
byte[] expectedData = new byte[expectedSize];
sampleData.copyToArray(0, expectedData, 0, dataSize / 2);
sampleData.copyToArray(0, expectedData, dataSize / 2, dataSize);
codeArea.setContentData(sampleData);
((EditModeCapable) codeArea).setEditOperation(EditOperation.OVERWRITE);
codeArea.selectAll();
codeArea.copy();
codeArea.clearSelection();
((CaretCapable) codeArea).setActiveCaretPosition(dataSize / 2);
codeArea.paste();
Assert.assertEquals(expectedSize, codeArea.getDataSize());
checkResultData(expectedData, codeArea.getContentData());
});
}
@Test
public void testCopyPasteInInsertWithOverflow() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
EditableBinaryData sampleData = CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES);
int dataSize = (int) sampleData.getDataSize();
int expectedSize = dataSize * 2;
byte[] expectedData = new byte[expectedSize];
sampleData.copyToArray(0, expectedData, 0, dataSize / 2);
sampleData.copyToArray(0, expectedData, dataSize / 2, dataSize);
sampleData.copyToArray(dataSize / 2, expectedData, dataSize + dataSize / 2, dataSize / 2);
codeArea.setContentData(sampleData);
((EditModeCapable) codeArea).setEditOperation(EditOperation.INSERT);
codeArea.selectAll();
codeArea.copy();
codeArea.clearSelection();
((CaretCapable) codeArea).setActiveCaretPosition(dataSize / 2);
codeArea.paste();
Assert.assertEquals(expectedSize, codeArea.getDataSize());
checkResultData(expectedData, codeArea.getContentData());
});
}
@Test
public void testCopyPasteInInplaceWithOverflow() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
((EditModeCapable) codeArea).setEditMode(EditMode.INPLACE);
EditableBinaryData sampleData = CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES);
int dataSize = (int) sampleData.getDataSize();
int expectedSize = dataSize;
byte[] expectedData = new byte[expectedSize];
sampleData.copyToArray(0, expectedData, 0, dataSize / 2);
sampleData.copyToArray(0, expectedData, dataSize / 2, dataSize / 2);
codeArea.setContentData(sampleData);
codeArea.selectAll();
codeArea.copy();
codeArea.clearSelection();
((CaretCapable) codeArea).setActiveCaretPosition(dataSize / 2);
codeArea.paste();
Assert.assertEquals(expectedSize, codeArea.getDataSize());
checkResultData(expectedData, codeArea.getContentData());
});
}
public static void checkResultData(byte[] expectedData, BinaryData data) {
Assert.assertEquals(expectedData.length, data.getDataSize());
byte[] resultData = new byte[expectedData.length];
data.copyToArray(0, resultData, 0, expectedData.length);
Assert.assertArrayEquals(expectedData, resultData);
}
private static void resetCodeArea(CodeAreaCore codeArea) {
codeArea.setContentData(null);
((EditModeCapable) codeArea).setEditMode(EditMode.EXPANDING);
}
}
@@ -0,0 +1,277 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.bined.android.basic;
import android.view.KeyEvent;
import org.exbin.auxiliary.binary_data.BinaryData;
import org.exbin.auxiliary.binary_data.EditableBinaryData;
import org.exbin.bined.CodeAreaCaretPosition;
import org.exbin.bined.CodeAreaTest;
import org.exbin.bined.EditOperation;
import org.exbin.bined.android.CodeAreaCore;
import org.exbin.bined.capability.CaretCapable;
import org.exbin.bined.capability.EditModeCapable;
import org.exbin.bined.editor.android.R;
import org.junit.Assert;
import org.junit.Test;
import javax.annotation.ParametersAreNonnullByDefault;
/**
* Tests for CodeArea component.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public class CodeAreaCommandEditTest extends CodeAreaComponentTest {
public CodeAreaCommandEditTest() {
}
@Test
public void testOverwriteCodeBegin() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
EditableBinaryData sampleData = CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES);
int dataSize = (int) sampleData.getDataSize();
int expectedSize = dataSize;
byte[] expectedData = new byte[expectedSize];
expectedData[0] = (byte) 0xa0;
sampleData.copyToArray(1, expectedData, 1, dataSize - 1);
codeArea.setContentData(sampleData);
((EditModeCapable) codeArea).setEditOperation(EditOperation.OVERWRITE);
emulateKeyTyped(codeArea, KeyEvent.KEYCODE_UNKNOWN, 'a');
CodeAreaCaretPosition caretPosition = ((CaretCapable) codeArea).getActiveCaretPosition();
Assert.assertEquals(1, caretPosition.getCodeOffset());
Assert.assertEquals(0, caretPosition.getDataPosition());
checkResultData(expectedData, codeArea.getContentData());
});
}
@Test
public void testInsertCodeBegin() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
EditableBinaryData sampleData = CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES);
int dataSize = (int) sampleData.getDataSize();
int expectedSize = dataSize + 1;
byte[] expectedData = new byte[expectedSize];
expectedData[0] = (byte) 0xa0;
sampleData.copyToArray(0, expectedData, 1, dataSize);
codeArea.setContentData(sampleData);
((EditModeCapable) codeArea).setEditOperation(EditOperation.INSERT);
emulateKeyTyped(codeArea, KeyEvent.KEYCODE_UNKNOWN, 'a');
CodeAreaCaretPosition caretPosition = ((CaretCapable) codeArea).getActiveCaretPosition();
Assert.assertEquals(1, caretPosition.getCodeOffset());
Assert.assertEquals(0, caretPosition.getDataPosition());
checkResultData(expectedData, codeArea.getContentData());
});
}
@Test
public void testOverwriteCodeEnd() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
EditableBinaryData sampleData = CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES);
int dataSize = (int) sampleData.getDataSize();
int expectedSize = dataSize + 1;
byte[] expectedData = new byte[expectedSize];
expectedData[256] = (byte) 0xa0;
sampleData.copyToArray(0, expectedData, 0, dataSize);
codeArea.setContentData(sampleData);
((EditModeCapable) codeArea).setEditOperation(EditOperation.OVERWRITE);
((CaretCapable) codeArea).setActiveCaretPosition(256);
emulateKeyTyped(codeArea, KeyEvent.KEYCODE_UNKNOWN, 'a');
CodeAreaCaretPosition caretPosition = ((CaretCapable) codeArea).getActiveCaretPosition();
Assert.assertEquals(1, caretPosition.getCodeOffset());
Assert.assertEquals(256, caretPosition.getDataPosition());
checkResultData(expectedData, codeArea.getContentData());
});
}
@Test
public void testInsertCodeEnd() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
EditableBinaryData sampleData = CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES);
int dataSize = (int) sampleData.getDataSize();
int expectedSize = dataSize + 1;
byte[] expectedData = new byte[expectedSize];
expectedData[256] = (byte) 0xa0;
sampleData.copyToArray(0, expectedData, 0, dataSize);
codeArea.setContentData(sampleData);
((EditModeCapable) codeArea).setEditOperation(EditOperation.INSERT);
((CaretCapable) codeArea).setActiveCaretPosition(256);
emulateKeyTyped(codeArea, KeyEvent.KEYCODE_UNKNOWN, 'a');
CodeAreaCaretPosition caretPosition = ((CaretCapable) codeArea).getActiveCaretPosition();
Assert.assertEquals(1, caretPosition.getCodeOffset());
Assert.assertEquals(256, caretPosition.getDataPosition());
checkResultData(expectedData, codeArea.getContentData());
});
}
@Test
public void testDeleteKeyEmpty() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
emulateKeyPressed(codeArea, KeyEvent.KEYCODE_UNKNOWN, KeyEvent.KEYCODE_FORWARD_DEL);
CodeAreaCaretPosition caretPosition = ((CaretCapable) codeArea).getActiveCaretPosition();
Assert.assertEquals(0, caretPosition.getCodeOffset());
Assert.assertEquals(0, caretPosition.getDataPosition());
Assert.assertEquals(0, codeArea.getDataSize());
});
}
@Test
public void testDeleteKeyBegin() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
EditableBinaryData sampleData = CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES);
int dataSize = (int) sampleData.getDataSize();
int expectedSize = dataSize - 1;
byte[] expectedData = new byte[expectedSize];
sampleData.copyToArray(1, expectedData, 0, dataSize - 1);
codeArea.setContentData(sampleData);
emulateKeyPressed(codeArea, KeyEvent.KEYCODE_UNKNOWN, KeyEvent.KEYCODE_FORWARD_DEL);
CodeAreaCaretPosition caretPosition = ((CaretCapable) codeArea).getActiveCaretPosition();
Assert.assertEquals(0, caretPosition.getCodeOffset());
Assert.assertEquals(0, caretPosition.getDataPosition());
checkResultData(expectedData, codeArea.getContentData());
});
}
@Test
public void testDeleteKeyEnd() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
EditableBinaryData sampleData = CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES);
codeArea.setContentData(sampleData);
int dataSize = (int) sampleData.getDataSize();
int expectedSize = dataSize;
byte[] expectedData = new byte[expectedSize];
sampleData.copyToArray(0, expectedData, 0, dataSize);
((CaretCapable) codeArea).setActiveCaretPosition(256);
emulateKeyPressed(codeArea, KeyEvent.KEYCODE_UNKNOWN, KeyEvent.KEYCODE_FORWARD_DEL);
CodeAreaCaretPosition caretPosition = ((CaretCapable) codeArea).getActiveCaretPosition();
Assert.assertEquals(0, caretPosition.getCodeOffset());
Assert.assertEquals(256, caretPosition.getDataPosition());
checkResultData(expectedData, codeArea.getContentData());
});
}
@Test
public void testBackspaceKeyEmpty() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
emulateKeyPressed(codeArea, KeyEvent.KEYCODE_UNKNOWN, KeyEvent.KEYCODE_DEL);
CodeAreaCaretPosition caretPosition = ((CaretCapable) codeArea).getActiveCaretPosition();
Assert.assertEquals(0, caretPosition.getCodeOffset());
Assert.assertEquals(0, caretPosition.getDataPosition());
Assert.assertEquals(0, codeArea.getDataSize());
});
}
@Test
public void testBackspaceKeyBegin() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
EditableBinaryData sampleData = CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES);
int dataSize = (int) sampleData.getDataSize();
int expectedSize = dataSize;
byte[] expectedData = new byte[expectedSize];
sampleData.copyToArray(0, expectedData, 0, dataSize);
codeArea.setContentData(sampleData);
emulateKeyPressed(codeArea, KeyEvent.KEYCODE_UNKNOWN, KeyEvent.KEYCODE_DEL);
CodeAreaCaretPosition caretPosition = ((CaretCapable) codeArea).getActiveCaretPosition();
Assert.assertEquals(0, caretPosition.getCodeOffset());
Assert.assertEquals(0, caretPosition.getDataPosition());
checkResultData(expectedData, codeArea.getContentData());
});
}
@Test
public void testBackspaceKeyEnd() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
EditableBinaryData sampleData = CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES);
codeArea.setContentData(sampleData);
int dataSize = (int) sampleData.getDataSize();
int expectedSize = dataSize - 1;
byte[] expectedData = new byte[expectedSize];
sampleData.copyToArray(0, expectedData, 0, dataSize - 1);
((CaretCapable) codeArea).setActiveCaretPosition(256);
emulateKeyPressed(codeArea, KeyEvent.KEYCODE_UNKNOWN, KeyEvent.KEYCODE_DEL);
CodeAreaCaretPosition caretPosition = ((CaretCapable) codeArea).getActiveCaretPosition();
Assert.assertEquals(0, caretPosition.getCodeOffset());
Assert.assertEquals(255, caretPosition.getDataPosition());
checkResultData(expectedData, codeArea.getContentData());
});
}
private static void emulateKeyTyped(CodeAreaCore component, int keyEvent, char keyChar) {
component.getCommandHandler().keyTyped(keyChar, new KeyEvent(KeyEvent.ACTION_DOWN, keyEvent));
}
private static void emulateKeyPressed(CodeAreaCore component, int keyCode, int keyEvent) {
component.getCommandHandler().keyPressed(new KeyEvent(KeyEvent.ACTION_DOWN, keyEvent));
}
public static void checkResultData(byte[] expectedData, BinaryData data) {
Assert.assertEquals(expectedData.length, data.getDataSize());
byte[] resultData = new byte[expectedData.length];
data.copyToArray(0, resultData, 0, expectedData.length);
Assert.assertArrayEquals(expectedData, resultData);
}
private static void resetCodeArea(CodeAreaCore codeArea) {
codeArea.setContentData(null);
}
}
@@ -0,0 +1,253 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.bined.android.basic;
import android.view.KeyEvent;
import javax.annotation.ParametersAreNonnullByDefault;
import org.exbin.bined.CodeAreaCaretPosition;
import org.exbin.bined.CodeAreaTest;
import org.exbin.bined.capability.CaretCapable;
import org.exbin.bined.android.CodeAreaCore;
import org.exbin.bined.editor.android.R;
import org.junit.Assert;
import org.junit.Test;
/**
* Tests for CodeArea component.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public class CodeAreaCommandMovementTest extends CodeAreaComponentTest {
private static final char CHAR_UNDEFINED = ' ';
public CodeAreaCommandMovementTest() {
}
@Test
public void testMoveBeginLeft() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
codeArea.setContentData(CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES));
emulateKeyPressed(codeArea, KeyEvent.KEYCODE_DPAD_LEFT, CHAR_UNDEFINED);
CodeAreaCaretPosition caretPosition = ((CaretCapable) codeArea).getActiveCaretPosition();
Assert.assertEquals(0, caretPosition.getCodeOffset());
Assert.assertEquals(0, caretPosition.getDataPosition());
});
}
@Test
public void testMoveMiddleLeft() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
codeArea.setContentData(CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES));
((CaretCapable) codeArea).setActiveCaretPosition(128);
emulateKeyPressed(codeArea, KeyEvent.KEYCODE_DPAD_LEFT, CHAR_UNDEFINED);
CodeAreaCaretPosition caretPosition = ((CaretCapable) codeArea).getActiveCaretPosition();
Assert.assertEquals(1, caretPosition.getCodeOffset());
Assert.assertEquals(127, caretPosition.getDataPosition());
});
}
@Test
public void testMoveEndLeft() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
codeArea.setContentData(CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES));
((CaretCapable) codeArea).setActiveCaretPosition(257);
emulateKeyPressed(codeArea, KeyEvent.KEYCODE_DPAD_LEFT, CHAR_UNDEFINED);
CodeAreaCaretPosition caretPosition = ((CaretCapable) codeArea).getActiveCaretPosition();
Assert.assertEquals(1, caretPosition.getCodeOffset());
Assert.assertEquals(256, caretPosition.getDataPosition());
});
}
@Test
public void testMoveBeginRight() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
codeArea.setContentData(CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES));
emulateKeyPressed(codeArea, KeyEvent.KEYCODE_DPAD_RIGHT, CHAR_UNDEFINED);
CodeAreaCaretPosition caretPosition = ((CaretCapable) codeArea).getActiveCaretPosition();
Assert.assertEquals(1, caretPosition.getCodeOffset());
Assert.assertEquals(0, caretPosition.getDataPosition());
});
}
@Test
public void testMoveBeginRightTwice() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
codeArea.setContentData(CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES));
emulateKeyPressed(codeArea, KeyEvent.KEYCODE_DPAD_RIGHT, CHAR_UNDEFINED);
emulateKeyPressed(codeArea, KeyEvent.KEYCODE_DPAD_RIGHT, CHAR_UNDEFINED);
CodeAreaCaretPosition caretPosition = ((CaretCapable) codeArea).getActiveCaretPosition();
Assert.assertEquals(0, caretPosition.getCodeOffset());
Assert.assertEquals(1, caretPosition.getDataPosition());
});
}
@Test
public void testMoveMiddleRight() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
codeArea.setContentData(CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES));
((CaretCapable) codeArea).setActiveCaretPosition(128);
emulateKeyPressed(codeArea, KeyEvent.KEYCODE_DPAD_RIGHT, CHAR_UNDEFINED);
CodeAreaCaretPosition caretPosition = ((CaretCapable) codeArea).getActiveCaretPosition();
Assert.assertEquals(1, caretPosition.getCodeOffset());
Assert.assertEquals(128, caretPosition.getDataPosition());
});
}
@Test
public void testMoveEndRight() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
codeArea.setContentData(CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES));
((CaretCapable) codeArea).setActiveCaretPosition(257);
emulateKeyPressed(codeArea, KeyEvent.KEYCODE_DPAD_RIGHT, CHAR_UNDEFINED);
CodeAreaCaretPosition caretPosition = ((CaretCapable) codeArea).getActiveCaretPosition();
Assert.assertEquals(0, caretPosition.getCodeOffset());
Assert.assertEquals(257, caretPosition.getDataPosition());
});
}
@Test
public void testMoveBeginUp() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
codeArea.setContentData(CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES));
emulateKeyPressed(codeArea, KeyEvent.KEYCODE_DPAD_UP, CHAR_UNDEFINED);
CodeAreaCaretPosition caretPosition = ((CaretCapable) codeArea).getActiveCaretPosition();
Assert.assertEquals(0, caretPosition.getCodeOffset());
Assert.assertEquals(0, caretPosition.getDataPosition());
});
}
@Test
public void testMoveMiddleUp() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
codeArea.setContentData(CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES));
((CaretCapable) codeArea).setActiveCaretPosition(128);
emulateKeyPressed(codeArea, KeyEvent.KEYCODE_DPAD_UP, CHAR_UNDEFINED);
CodeAreaCaretPosition caretPosition = ((CaretCapable) codeArea).getActiveCaretPosition();
Assert.assertEquals(0, caretPosition.getCodeOffset());
Assert.assertEquals(112, caretPosition.getDataPosition());
});
}
@Test
public void testMoveEndUp() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
codeArea.setContentData(CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES));
((CaretCapable) codeArea).setActiveCaretPosition(257);
emulateKeyPressed(codeArea, KeyEvent.KEYCODE_DPAD_UP, CHAR_UNDEFINED);
CodeAreaCaretPosition caretPosition = ((CaretCapable) codeArea).getActiveCaretPosition();
Assert.assertEquals(0, caretPosition.getCodeOffset());
Assert.assertEquals(241, caretPosition.getDataPosition());
});
}
@Test
public void testMoveBeginDown() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
codeArea.setContentData(CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES));
emulateKeyPressed(codeArea, KeyEvent.KEYCODE_DPAD_DOWN, CHAR_UNDEFINED);
CodeAreaCaretPosition caretPosition = ((CaretCapable) codeArea).getActiveCaretPosition();
Assert.assertEquals(0, caretPosition.getCodeOffset());
Assert.assertEquals(16, caretPosition.getDataPosition());
});
}
@Test
public void testMoveMiddleDown() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
codeArea.setContentData(CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES));
((CaretCapable) codeArea).setActiveCaretPosition(128);
emulateKeyPressed(codeArea, KeyEvent.KEYCODE_DPAD_DOWN, CHAR_UNDEFINED);
CodeAreaCaretPosition caretPosition = ((CaretCapable) codeArea).getActiveCaretPosition();
Assert.assertEquals(0, caretPosition.getCodeOffset());
Assert.assertEquals(144, caretPosition.getDataPosition());
});
}
@Test
public void testMoveEndDown() {
rule.getScenario().onActivity(activity -> {
CodeAreaCore codeArea = activity.findViewById(R.id.codeArea);
resetCodeArea(codeArea);
codeArea.setContentData(CodeAreaTest.getSampleData(CodeAreaTest.SAMPLE_ALLBYTES));
((CaretCapable) codeArea).setActiveCaretPosition(257);
emulateKeyPressed(codeArea, KeyEvent.KEYCODE_DPAD_DOWN, CHAR_UNDEFINED);
CodeAreaCaretPosition caretPosition = ((CaretCapable) codeArea).getActiveCaretPosition();
Assert.assertEquals(0, caretPosition.getCodeOffset());
Assert.assertEquals(257, caretPosition.getDataPosition());
});
}
private void emulateKeyPressed(CodeAreaCore component, int keyEvent, char keyChar) {
component.getCommandHandler().keyPressed(new KeyEvent(KeyEvent.ACTION_DOWN, keyEvent));
}
private static void resetCodeArea(CodeAreaCore codeArea) {
codeArea.setContentData(null);
}
}
@@ -15,29 +15,24 @@
*/
package org.exbin.bined.android.basic;
import android.content.Context;
import android.util.AttributeSet;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.core.app.ApplicationProvider;
import org.exbin.bined.android.CodeAreaCore;
import javax.annotation.Nonnull;
import org.exbin.bined.editor.android.MainActivity;
import org.junit.Rule;
import org.junit.runner.RunWith;
/**
* Tests for CodeArea component.
*
* @author ExBin Project (https://exbin.org)
*/
@RunWith(AndroidJUnit4.class)
public class CodeAreaComponentTest {
@Rule
public ActivityScenarioRule<MainActivity> rule = new ActivityScenarioRule<>(MainActivity.class);
public CodeAreaComponentTest() {
}
@Nonnull
public CodeAreaCore createCodeArea() {
Context applicationContext = ApplicationProvider.getApplicationContext();
AttributeSet attributeSet = null;
return new CodeArea(applicationContext, attributeSet);
}
}
@@ -16,9 +16,6 @@
package org.exbin.bined.android.basic;
import org.exbin.bined.CodeAreaTest;
import org.exbin.bined.android.CodeAreaCore;
import org.junit.Assert;
import org.junit.Test;
/**
* Tests for CodeArea component.
+7 -1
View File
@@ -8,10 +8,15 @@
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-feature android:name="android.software.leanback" android:required="false" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<application
android:name=".ApplicationContext"
android:allowBackup="true"
android:icon="@mipmap/bined"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:banner="@drawable/banner"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme"
@@ -23,6 +28,7 @@
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

@@ -1,8 +1,7 @@
package com.examples.customtouch.widget;
package com.example.customtouch.widget;
/*from w w w. j a v a 2 s . c o m*/
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
@@ -112,8 +111,8 @@ public class TwoDimensionScrollView extends FrameLayout {
View child = getChildAt(0);
// TODO For some reason child size resets to zero, disabling this as workaround :-/
if (child.getWidth() > 0 && child.getHeight() > 0) {
x = clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(), child.getWidth());
y = clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(), child.getHeight());
x = clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(), child.getWidth());
y = clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(), child.getHeight());
}
if (x != getScrollX() || y != getScrollY()) {
super.scrollTo(x, y);
@@ -161,13 +160,13 @@ public class TwoDimensionScrollView extends FrameLayout {
*/
return 0;
}
if ((my+n) > child) {
if ((my + n) > child) {
/* this case:
* |------ me ------|
* |------ child ------|
* |-- mScrollX --|
*/
return child-my;
return child - my;
}
return n;
}
@@ -236,7 +235,7 @@ public class TwoDimensionScrollView extends FrameLayout {
float deltaY = mLastTouchY - y;
float deltaX = mLastTouchX - x;
//Check for slop on direct events
if (!mDragging && (Math.abs(deltaY) > mTouchSlop || Math.abs(deltaX) > mTouchSlop) ) {
if (!mDragging && (Math.abs(deltaY) > mTouchSlop || Math.abs(deltaX) > mTouchSlop)) {
mDragging = true;
}
if (mDragging) {
@@ -259,8 +258,8 @@ public class TwoDimensionScrollView extends FrameLayout {
// Compute the current velocity and start a fling if it is above
// the minimum threshold.
mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int velocityX = (int)mVelocityTracker.getXVelocity();
int velocityY = (int)mVelocityTracker.getYVelocity();
int velocityX = (int) mVelocityTracker.getXVelocity();
int velocityY = (int) mVelocityTracker.getYVelocity();
if (Math.abs(velocityX) > mMinimumVelocity || Math.abs(velocityY) > mMinimumVelocity) {
fling(-velocityX, -velocityY);
}
@@ -268,9 +267,4 @@ public class TwoDimensionScrollView extends FrameLayout {
}
return super.onTouchEvent(event);
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
}
}
@@ -19,7 +19,6 @@ import android.graphics.Canvas;
import org.exbin.bined.basic.CodeAreaScrollPosition;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
/**
@@ -17,6 +17,9 @@ package org.exbin.bined.android;
import android.view.KeyEvent;
import org.exbin.bined.ScrollBarOrientation;
import org.exbin.bined.basic.SelectingMode;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
@@ -78,11 +81,6 @@ public interface CodeAreaCommandHandler {
*/
void copy();
/**
* Copies selection to clipboard as code string.
*/
void copyAsCode();
/**
* Cuts selection to clipboard.
*/
@@ -93,11 +91,6 @@ public interface CodeAreaCommandHandler {
*/
void paste();
/**
* Pastes content of clipboard to cursor area analyzing string code.
*/
void pasteFromCode();
/**
* Returns true if paste action is possible.
*
@@ -130,7 +123,7 @@ public interface CodeAreaCommandHandler {
* @param scrollSize number of scroll units (positive or negative)
* @param orientation scrollbar orientation
*/
void wheelScroll(int scrollSize, ScrollbarOrientation orientation);
void wheelScroll(int scrollSize, ScrollBarOrientation orientation);
/**
* Checks whether edit is allowed.
@@ -139,14 +132,6 @@ public interface CodeAreaCommandHandler {
*/
boolean checkEditAllowed();
enum ScrollbarOrientation {
HORIZONTAL, VERTICAL
}
enum SelectingMode {
NONE, SELECTING
}
@ParametersAreNonnullByDefault
interface CodeAreaCommandHandlerFactory {
@@ -31,6 +31,7 @@ import org.exbin.auxiliary.binary_data.EmptyBinaryData;
import org.exbin.bined.CodeAreaControl;
import org.exbin.bined.DataChangedListener;
import org.exbin.bined.android.basic.DefaultCodeAreaCommandHandler;
import org.exbin.bined.basic.SelectingMode;
import org.exbin.bined.capability.ScrollingCapable;
import org.exbin.bined.capability.SelectionCapable;
@@ -50,7 +51,7 @@ import javax.annotation.ParametersAreNonnullByDefault;
public abstract class CodeAreaCore extends ViewGroup implements CodeAreaControl {
@Nonnull
protected BinaryData contentData = EmptyBinaryData.INSTANCE;
protected BinaryData contentData = EmptyBinaryData.getInstance();
@Nonnull
protected final PrimaryView primaryView;
@@ -115,10 +116,6 @@ public abstract class CodeAreaCore extends ViewGroup implements CodeAreaControl
commandHandler.copy();
}
public void copyAsCode() {
commandHandler.copyAsCode();
}
@Override
public void cut() {
commandHandler.cut();
@@ -129,10 +126,6 @@ public abstract class CodeAreaCore extends ViewGroup implements CodeAreaControl
commandHandler.paste();
}
public void pasteFromCode() {
commandHandler.pasteFromCode();
}
@Override
public void delete() {
commandHandler.delete();
@@ -173,7 +166,7 @@ public abstract class CodeAreaCore extends ViewGroup implements CodeAreaControl
}
public void setContentData(@Nullable BinaryData contentData) {
this.contentData = contentData == null ? EmptyBinaryData.INSTANCE : contentData;
this.contentData = contentData == null ? EmptyBinaryData.getInstance() : contentData;
notifyDataChanged();
repaint();
}
@@ -201,14 +194,16 @@ public abstract class CodeAreaCore extends ViewGroup implements CodeAreaControl
}
public void repaint() {
Activity activity = (Activity) getContext();
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
Context context = getContext();
if (context instanceof Activity) {
((Activity) context).runOnUiThread(() -> {
invalidate();
primaryView.invalidate();
}
});
});
} else {
invalidate();
primaryView.invalidate();
}
}
@Override
@@ -221,7 +216,7 @@ public abstract class CodeAreaCore extends ViewGroup implements CodeAreaControl
@Override
public boolean onTouchEvent(MotionEvent event) {
commandHandler.moveCaret((int) event.getX(), (int) event.getY(), CodeAreaCommandHandler.SelectingMode.NONE);
commandHandler.moveCaret((int) event.getX(), (int) event.getY(), SelectingMode.NONE);
((ScrollingCapable) this).revealCursor();
return true;
@@ -133,22 +133,22 @@ public class BasicCodeAreaDimensions {
return BasicCodeAreaZone.CODE_AREA;
}
private int computeCharactersPerRectangle(BasicCodeAreaMetrics metrics) {
protected int computeCharactersPerRectangle(BasicCodeAreaMetrics metrics) {
int characterWidth = metrics.getCharacterWidth();
return characterWidth == 0 ? 0 : (dataViewWidth + characterWidth - 1) / characterWidth;
}
private int computeCharactersPerPage(BasicCodeAreaMetrics metrics) {
protected int computeCharactersPerPage(BasicCodeAreaMetrics metrics) {
int characterWidth = metrics.getCharacterWidth();
return characterWidth == 0 ? 0 : dataViewWidth / characterWidth;
}
private int computeRowsPerRectangle(BasicCodeAreaMetrics metrics) {
protected int computeRowsPerRectangle(BasicCodeAreaMetrics metrics) {
int rowHeight = metrics.getRowHeight();
return rowHeight == 0 ? 0 : (dataViewHeight + rowHeight - 1) / rowHeight;
}
private int computeRowsPerPage(BasicCodeAreaMetrics metrics) {
protected int computeRowsPerPage(BasicCodeAreaMetrics metrics) {
int rowHeight = metrics.getRowHeight();
return rowHeight == 0 ? 0 : dataViewHeight / rowHeight;
}
@@ -21,7 +21,9 @@ import org.exbin.bined.CharsetStreamTranslator;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.util.Optional;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
@@ -79,9 +81,9 @@ public class BasicCodeAreaMetrics {
return rowHeight != 0 && characterWidth != 0;
}
@Nullable
public Paint getFontMetrics() {
return fontMetrics;
@Nonnull
public Optional<Paint> getFontMetrics() {
return Optional.ofNullable(fontMetrics);
}
public int getCharWidth(char value) {
@@ -51,7 +51,6 @@ public class BasicCodeAreaVisibility {
int bytesPerRow = structure.getBytesPerRow();
int characterWidth = metrics.getCharacterWidth();
int charsPerByte = structure.getCodeType().getMaxDigitsForByte() + 1;
previewRelativeX = previewCharPos * characterWidth;
CodeAreaViewMode viewMode = structure.getViewMode();
@@ -72,6 +71,7 @@ public class BasicCodeAreaVisibility {
} else {
previewCharPos = 0;
}
previewRelativeX = previewCharPos * characterWidth;
skipToCode = 0;
skipToChar = 0;
@@ -143,6 +143,12 @@ public class CodeArea extends CodeAreaCore implements DefaultCodeArea, CodeAreaA
init();
}
@Nonnull
@Override
public CodeAreaCommandHandler getCommandHandler() {
return super.getCommandHandler();
}
private void init() {
// TODO: Use swing color instead
//setBackground(Color.WHITE);
@@ -359,7 +365,9 @@ public class CodeArea extends CodeAreaCore implements DefaultCodeArea, CodeAreaA
}
Optional<CodeAreaScrollPosition> revealScrollPosition = painter.computeRevealScrollPosition(caretPosition);
revealScrollPosition.ifPresent(this::setScrollPosition);
if (revealScrollPosition.isPresent()) {
setScrollPosition(revealScrollPosition.get());
}
}
public void revealPosition(long dataPosition, int dataOffset, CodeAreaSection section) {
@@ -379,7 +387,9 @@ public class CodeArea extends CodeAreaCore implements DefaultCodeArea, CodeAreaA
}
Optional<CodeAreaScrollPosition> centerOnScrollPosition = painter.computeCenterOnScrollPosition(caretPosition);
centerOnScrollPosition.ifPresent(this::setScrollPosition);
if (centerOnScrollPosition.isPresent()) {
setScrollPosition(centerOnScrollPosition.get());
}
}
public void centerOnPosition(long dataPosition, int dataOffset, CodeAreaSection section) {
@@ -15,7 +15,13 @@
*/
package org.exbin.bined.android.basic;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderMalfunctionError;
import java.nio.charset.CodingErrorAction;
import java.util.Optional;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -32,66 +38,80 @@ import org.exbin.bined.android.CodeAreaPaintState;
@ParametersAreNonnullByDefault
public class DefaultCodeAreaCharAssessor implements CodeAreaCharAssessor {
protected final CodeAreaCharAssessor parentCharAssessor;
protected final CodeAreaCharAssessor parentAssessor;
@Nullable
protected Charset charMappingCharset = null;
@Nonnull
protected final char[] charMapping = new char[256];
protected char[] charMapping = null;
protected long dataSize;
protected int maxBytesPerChar;
protected byte[] rowData;
protected Charset charset;
private CharsetDecoder decoder;
private ByteBuffer byteBuffer;
private CharBuffer charBuffer = null;
public DefaultCodeAreaCharAssessor() {
parentCharAssessor = null;
parentAssessor = null;
}
public DefaultCodeAreaCharAssessor(@Nullable CodeAreaCharAssessor parentCharAssessor) {
this.parentCharAssessor = parentCharAssessor;
public DefaultCodeAreaCharAssessor(@Nullable CodeAreaCharAssessor parentAssessor) {
this.parentAssessor = parentAssessor;
}
@Override
public void startPaint(CodeAreaPaintState codeAreaPaintState) {
dataSize = codeAreaPaintState.getDataSize();
charset = codeAreaPaintState.getCharset();
rowData = codeAreaPaintState.getRowData();
maxBytesPerChar = codeAreaPaintState.getMaxBytesPerChar();
if (parentCharAssessor != null) {
parentCharAssessor.startPaint(codeAreaPaintState);
public void startPaint(CodeAreaPaintState codeAreaPainterState) {
dataSize = codeAreaPainterState.getDataSize();
Charset painterCharset = codeAreaPainterState.getCharset();
maxBytesPerChar = codeAreaPainterState.getMaxBytesPerChar();
if (charBuffer == null) {
charBuffer = CharBuffer.allocate(8);
}
if (charset != painterCharset) {
charMapping = null;
decoder = painterCharset.newDecoder();
decoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
decoder.onMalformedInput(CodingErrorAction.REPLACE);
byteBuffer = ByteBuffer.allocate(maxBytesPerChar);
this.charset = painterCharset;
}
rowData = codeAreaPainterState.getRowData();
}
@Override
public char getPreviewCharacter(long rowDataPosition, int byteOnRow, int charOnRow, CodeAreaSection section) {
if (maxBytesPerChar > 1) {
if (rowDataPosition + maxBytesPerChar > dataSize) {
maxBytesPerChar = (int) (dataSize - rowDataPosition);
}
if (byteOnRow > rowData.length - maxBytesPerChar) {
return ' ';
}
int charDataLength = maxBytesPerChar;
if (byteOnRow + charDataLength > rowData.length) {
charDataLength = rowData.length - byteOnRow;
if (maxBytesPerChar > 1) {
decoder.reset();
if (rowDataPosition + maxBytesPerChar > dataSize) {
byteBuffer.clear();
byteBuffer.put(rowData, byteOnRow, (int) (dataSize - rowDataPosition));
} else {
byteBuffer.rewind();
byteBuffer.put(rowData, byteOnRow, maxBytesPerChar);
}
String displayString = new String(rowData, byteOnRow, charDataLength, charset);
if (!displayString.isEmpty()) {
return displayString.charAt(0);
byteBuffer.rewind();
charBuffer.clear();
try {
decoder.decode(byteBuffer, charBuffer, true);
if (charBuffer.position() > 0) {
charBuffer.rewind();
return charBuffer.get();
}
} catch (CoderMalfunctionError | BufferUnderflowException ex) {
// ignore
}
} else {
if (charMappingCharset == null || charMappingCharset != charset) {
buildCharMapping(charset);
if (charMapping == null) {
buildCharMapping();
}
return charMapping[rowData[byteOnRow] & 0xFF];
}
if (parentCharAssessor != null) {
return parentCharAssessor.getPreviewCharacter(rowDataPosition, byteOnRow, charOnRow, section);
}
return ' ';
}
@@ -102,41 +122,60 @@ public class DefaultCodeAreaCharAssessor implements CodeAreaCharAssessor {
}
if (maxBytesPerChar > 1) {
String displayString = new String(cursorData, 0, cursorDataLength, charset);
if (!displayString.isEmpty()) {
return displayString.charAt(0);
decoder.reset();
byteBuffer.rewind();
byteBuffer.put(cursorData, 0, cursorDataLength);
byteBuffer.rewind();
charBuffer.clear();
try {
decoder.decode(byteBuffer, charBuffer, true);
if (charBuffer.position() > 0) {
charBuffer.rewind();
return charBuffer.get();
}
} catch (CoderMalfunctionError | BufferUnderflowException ex) {
// ignore
}
} else {
if (charMappingCharset == null || charMappingCharset != charset) {
buildCharMapping(charset);
if (charMapping == null) {
buildCharMapping();
}
return charMapping[cursorData[0] & 0xFF];
}
if (parentCharAssessor != null) {
return parentCharAssessor.getPreviewCursorCharacter(rowDataPosition, byteOnRow, charOnRow, cursorData, cursorDataLength, section);
}
return ' ';
}
@Nonnull
@Override
public Optional<CodeAreaCharAssessor> getParentCharAssessor() {
return Optional.ofNullable(parentCharAssessor);
return Optional.ofNullable(parentAssessor);
}
/**
* Precomputes widths for basic ascii characters.
*
* @param charset character set
*/
private void buildCharMapping(Charset charset) {
private void buildCharMapping() {
charMapping = new char[256];
ByteBuffer buffer = ByteBuffer.allocate(1);
for (int i = 0; i < 256; i++) {
charMapping[i] = new String(new byte[]{(byte) i}, charset).charAt(0);
buffer.rewind();
buffer.put((byte) i);
decoder.reset();
buffer.rewind();
charBuffer.clear();
try {
decoder.decode(buffer, charBuffer, true);
if (charBuffer.position() > 0) {
charBuffer.rewind();
charMapping[i] = charBuffer.get();
} else {
charMapping[i] = ' ';
}
} catch (CoderMalfunctionError | BufferUnderflowException ex) {
charMapping[i] = ' ';
}
}
charMappingCharset = charset;
}
}
@@ -23,10 +23,11 @@ import android.content.Context;
import android.view.KeyEvent;
import org.exbin.auxiliary.binary_data.ByteArrayData;
import org.exbin.auxiliary.binary_data.ByteArrayEditableData;
import org.exbin.auxiliary.binary_data.jna.JnaBufferData;
import org.exbin.auxiliary.binary_data.jna.JnaBufferEditableData;
import org.exbin.bined.CaretOverlapMode;
import org.exbin.bined.ClipboardHandlingMode;
import org.exbin.bined.ScrollBarOrientation;
import org.exbin.bined.android.CodeAreaAndroidUtils;
import org.exbin.bined.basic.BasicCodeAreaSection;
import org.exbin.bined.CodeAreaCaretPosition;
@@ -43,6 +44,7 @@ import org.exbin.bined.basic.CodeAreaScrollPosition;
import org.exbin.bined.basic.EnterKeyHandlingMode;
import org.exbin.bined.basic.MovementDirection;
import org.exbin.bined.basic.ScrollingDirection;
import org.exbin.bined.basic.SelectingMode;
import org.exbin.bined.basic.TabKeyHandlingMode;
import org.exbin.bined.capability.CaretCapable;
import org.exbin.bined.capability.CharsetCapable;
@@ -70,28 +72,26 @@ import javax.annotation.ParametersAreNonnullByDefault;
@ParametersAreNonnullByDefault
public class DefaultCodeAreaCommandHandler implements CodeAreaCommandHandler {
public static final String BINED_CLIPBOARD_MIME = "application/x-bined";
public static final String BINED_CLIPBOARD_MIME_FULL = BINED_CLIPBOARD_MIME + "; class=" + BinaryData.class.getCanonicalName();
public static final int LAST_CONTROL_CODE = 31;
private static final char DELETE_CHAR = (char) 0x7f;
private final int metaMask = KeyEvent.META_CTRL_MASK;
@Nonnull
private final CodeAreaCore codeArea;
protected final CodeAreaCore codeArea;
@Nonnull
private EnterKeyHandlingMode enterKeyHandlingMode = EnterKeyHandlingMode.PLATFORM_SPECIFIC;
protected EnterKeyHandlingMode enterKeyHandlingMode = EnterKeyHandlingMode.PLATFORM_SPECIFIC;
@Nonnull
private TabKeyHandlingMode tabKeyHandlingMode = TabKeyHandlingMode.PLATFORM_SPECIFIC;
private final boolean codeTypeSupported;
private final boolean viewModeSupported;
protected TabKeyHandlingMode tabKeyHandlingMode = TabKeyHandlingMode.PLATFORM_SPECIFIC;
protected final boolean codeTypeSupported;
protected final boolean viewModeSupported;
private Context context;
private ClipboardManager clipboard;
private boolean canPaste = false;
private ClipDescription binedDataFlavor;
private ClipDescription binaryDataFlavor;
private ClipData currentClipboardData = null;
protected Context context;
protected ClipboardManager clipboard;
protected boolean canPaste = false;
protected ClipDescription binedDataFlavor;
protected ClipDescription binaryDataFlavor;
protected ClipData currentClipboardData = null;
public DefaultCodeAreaCommandHandler(Context context, CodeAreaCore codeArea) {
this.context = context;
@@ -107,7 +107,7 @@ public class DefaultCodeAreaCommandHandler implements CodeAreaCommandHandler {
}
});
binedDataFlavor = new ClipDescription("BinEd Data", new String[] { BINED_CLIPBOARD_MIME });
binedDataFlavor = new ClipDescription("BinEd Data", new String[] { CodeAreaUtils.BINED_CLIPBOARD_MIME });
binaryDataFlavor = new ClipDescription("Binary Data", new String[] { CodeAreaUtils.MIME_CLIPBOARD_BINARY });
// try {
// clipboard.addFlavorListener((FlavorEvent e) -> {
@@ -215,21 +215,8 @@ public class DefaultCodeAreaCommandHandler implements CodeAreaCommandHandler {
break;
}
case KeyEvent.KEYCODE_INSERT: {
EditMode editMode = ((EditModeCapable) codeArea).getEditMode();
if (editMode == EditMode.EXPANDING || editMode == EditMode.CAPPED) {
EditOperation editOperation = ((EditModeCapable) codeArea).getEditOperation();
switch (editOperation) {
case INSERT: {
((EditModeCapable) codeArea).setEditOperation(EditOperation.OVERWRITE);
// keyEvent.consume();
break;
}
case OVERWRITE: {
((EditModeCapable) codeArea).setEditOperation(EditOperation.INSERT);
// keyEvent.consume();
break;
}
}
if (changeEditOperation()) {
// keyEvent.consume();
}
break;
}
@@ -446,10 +433,6 @@ public class DefaultCodeAreaCommandHandler implements CodeAreaCommandHandler {
}
public void tabPressed(SelectingMode selectingMode) {
if (!checkEditAllowed()) {
return;
}
if (tabKeyHandlingMode == TabKeyHandlingMode.PLATFORM_SPECIFIC || tabKeyHandlingMode == TabKeyHandlingMode.CYCLE_TO_NEXT_SECTION || tabKeyHandlingMode == TabKeyHandlingMode.CYCLE_TO_PREVIOUS_SECTION) {
if (viewModeSupported && ((ViewModeCapable) codeArea).getViewMode() == CodeAreaViewMode.DUAL) {
move(selectingMode, MovementDirection.SWITCH_SECTION);
@@ -464,6 +447,7 @@ public class DefaultCodeAreaCommandHandler implements CodeAreaCommandHandler {
}
}
}
@Override
public void backSpacePressed() {
if (!checkEditAllowed()) {
@@ -477,16 +461,17 @@ public class DefaultCodeAreaCommandHandler implements CodeAreaCommandHandler {
} else {
DefaultCodeAreaCaret caret = (DefaultCodeAreaCaret) ((CaretCapable) codeArea).getCodeAreaCaret();
long dataPosition = ((CaretCapable) codeArea).getDataPosition();
if (dataPosition > 0 && dataPosition <= codeArea.getDataSize()) {
caret.setCodeOffset(0);
move(SelectingMode.NONE, MovementDirection.LEFT);
caret.setCodeOffset(0);
((EditableBinaryData) data).remove(dataPosition - 1, 1);
codeArea.notifyDataChanged();
((CaretCapable) codeArea).setActiveCaretPosition(caret.getCaretPosition());
revealCursor();
clearSelection();
if (dataPosition == 0 || dataPosition > codeArea.getDataSize()) {
return;
}
caret.setCodeOffset(0);
move(SelectingMode.NONE, MovementDirection.LEFT);
caret.setCodeOffset(0);
((EditableBinaryData) data).remove(dataPosition - 1, 1);
codeArea.notifyDataChanged();
((CaretCapable) codeArea).setActiveCaretPosition(caret.getCaretPosition());
revealCursor();
clearSelection();
}
}
@@ -504,16 +489,17 @@ public class DefaultCodeAreaCommandHandler implements CodeAreaCommandHandler {
BinaryData data = CodeAreaUtils.requireNonNullContentData(codeArea.getContentData());
DefaultCodeAreaCaret caret = (DefaultCodeAreaCaret) ((CaretCapable) codeArea).getCodeAreaCaret();
long dataPosition = caret.getDataPosition();
if (dataPosition < codeArea.getDataSize()) {
((EditableBinaryData) data).remove(dataPosition, 1);
codeArea.notifyDataChanged();
if (caret.getCodeOffset() > 0) {
caret.setCodeOffset(0);
}
((CaretCapable) codeArea).setActiveCaretPosition(caret.getCaretPosition());
clearSelection();
revealCursor();
if (dataPosition >= codeArea.getDataSize()) {
return;
}
((EditableBinaryData) data).remove(dataPosition, 1);
codeArea.notifyDataChanged();
if (caret.getCodeOffset() > 0) {
caret.setCodeOffset(0);
}
((CaretCapable) codeArea).setActiveCaretPosition(caret.getCaretPosition());
clearSelection();
revealCursor();
}
}
@@ -568,7 +554,6 @@ public class DefaultCodeAreaCommandHandler implements CodeAreaCommandHandler {
}
}
@Override
public void copyAsCode() {
SelectionRange selection = ((SelectionCapable) codeArea).getSelection();
if (!selection.isEmpty()) {
@@ -640,7 +625,7 @@ public class DefaultCodeAreaCommandHandler implements CodeAreaCommandHandler {
ClipDescription description = primaryClip.getDescription();
try {
if (!description.hasMimeType(BINED_CLIPBOARD_MIME) && !description.hasMimeType(CodeAreaUtils.MIME_CLIPBOARD_BINARY) && !description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
if (!description.hasMimeType(CodeAreaUtils.BINED_CLIPBOARD_MIME) && !description.hasMimeType(CodeAreaUtils.MIME_CLIPBOARD_BINARY) && !description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
return;
}
} catch (IllegalStateException ex) {
@@ -648,7 +633,7 @@ public class DefaultCodeAreaCommandHandler implements CodeAreaCommandHandler {
}
ClipData.Item clipItem = primaryClip.getItemAt(0);
if (description.hasMimeType(BINED_CLIPBOARD_MIME)) {
if (description.hasMimeType(CodeAreaUtils.BINED_CLIPBOARD_MIME)) {
ContentResolver contentResolver = context.getContentResolver();
// ContentProviderClient contentProviderClient = contentResolver.acquireContentProviderClient(clipItem.getUri());
// Object clipboardData = clipboard.getData(binedDataFlavor);
@@ -668,7 +653,7 @@ public class DefaultCodeAreaCommandHandler implements CodeAreaCommandHandler {
CharSequence clipboardData = clipItem.getText();
if (clipboardData != null) {
byte[] bytes = clipboardData.toString().getBytes(Charset.forName(CodeAreaAndroidUtils.DEFAULT_ENCODING));
BinaryData pastedData = new ByteArrayData(bytes);
BinaryData pastedData = new JnaBufferData(bytes);
pasteBinaryData(pastedData);
}
}
@@ -678,7 +663,7 @@ public class DefaultCodeAreaCommandHandler implements CodeAreaCommandHandler {
}
private void pasteBinaryData(BinaryData pastedData) {
BinaryData data = CodeAreaUtils.requireNonNullContentData(codeArea.getContentData());
BinaryData data = codeArea.getContentData();
EditMode editMode = ((EditModeCapable) codeArea).getEditMode();
EditOperation editOperation = ((EditModeCapable) codeArea).getActiveOperation();
@@ -690,23 +675,23 @@ public class DefaultCodeAreaCommandHandler implements CodeAreaCommandHandler {
DefaultCodeAreaCaret caret = (DefaultCodeAreaCaret) ((CaretCapable) codeArea).getCodeAreaCaret();
long dataPosition = caret.getDataPosition();
long dataSize = pastedData.getDataSize();
long toRemove = dataSize;
long clipDataSize = pastedData.getDataSize();
long toReplace = clipDataSize;
if (editMode == EditMode.INPLACE) {
if (dataPosition + toRemove > codeArea.getDataSize()) {
toRemove = codeArea.getDataSize() - dataPosition;
if (dataPosition + toReplace > codeArea.getDataSize()) {
toReplace = codeArea.getDataSize() - dataPosition;
}
((EditableBinaryData) data).replace(dataPosition, pastedData, 0, toRemove);
((EditableBinaryData) data).replace(dataPosition, pastedData, 0, toReplace);
} else {
if (editMode == EditMode.EXPANDING && editOperation == EditOperation.OVERWRITE) {
if (dataPosition + toRemove > codeArea.getDataSize()) {
toRemove = codeArea.getDataSize() - dataPosition;
if (dataPosition + toReplace > codeArea.getDataSize()) {
toReplace = codeArea.getDataSize() - dataPosition;
}
((EditableBinaryData) data).remove(dataPosition, toRemove);
((EditableBinaryData) data).remove(dataPosition, toReplace);
}
((EditableBinaryData) data).insert(dataPosition, pastedData);
caret.setCaretPosition(caret.getDataPosition() + dataSize);
caret.setCaretPosition(caret.getDataPosition() + clipDataSize);
updateSelection(SelectingMode.NONE, caret.getCaretPosition());
}
@@ -718,7 +703,6 @@ public class DefaultCodeAreaCommandHandler implements CodeAreaCommandHandler {
clearSelection();
}
@Override
public void pasteFromCode() {
if (!checkEditAllowed()) {
return;
@@ -759,7 +743,7 @@ public class DefaultCodeAreaCommandHandler implements CodeAreaCommandHandler {
long dataPosition = caret.getDataPosition();
CodeType codeType = getCodeType();
ByteArrayEditableData pastedData = new ByteArrayEditableData();
EditableBinaryData pastedData = new JnaBufferEditableData();
CodeAreaUtils.insertHexStringIntoData((String) insertedData, pastedData, codeType);
long length = pastedData.getDataSize();
@@ -847,7 +831,7 @@ public class DefaultCodeAreaCommandHandler implements CodeAreaCommandHandler {
@Override
public void moveCaret(int positionX, int positionY, SelectingMode selecting) {
CodeAreaCaretPosition caretPosition = ((CaretCapable) codeArea).mousePositionToClosestCaretPosition(positionX, positionY, CaretOverlapMode.PARTIAL_OVERLAP);
((CaretCapable) codeArea).getCodeAreaCaret().setCaretPosition(caretPosition);
((CaretCapable) codeArea).setActiveCaretPosition(caretPosition);
updateSelection(selecting, caretPosition);
sequenceBreak();
@@ -860,6 +844,8 @@ public class DefaultCodeAreaCommandHandler implements CodeAreaCommandHandler {
if (!caretPosition.equals(movePosition)) {
((CaretCapable) codeArea).setActiveCaretPosition(movePosition);
updateSelection(selectingMode, movePosition);
} else if (selectingMode == SelectingMode.NONE){
clearSelection();
}
}
@@ -873,7 +859,7 @@ public class DefaultCodeAreaCommandHandler implements CodeAreaCommandHandler {
}
@Override
public void wheelScroll(int scrollSize, ScrollbarOrientation direction) {
public void wheelScroll(int scrollSize, ScrollBarOrientation direction) {
// TODO
scroll(ScrollingDirection.UP);
@@ -926,6 +912,26 @@ public class DefaultCodeAreaCommandHandler implements CodeAreaCommandHandler {
// }
}
public boolean changeEditOperation() {
EditMode editMode = ((EditModeCapable) codeArea).getEditMode();
if (editMode == EditMode.EXPANDING || editMode == EditMode.CAPPED) {
EditOperation editOperation = ((EditModeCapable) codeArea).getEditOperation();
switch (editOperation) {
case INSERT: {
((EditModeCapable) codeArea).setEditOperation(EditOperation.OVERWRITE);
break;
}
case OVERWRITE: {
((EditModeCapable) codeArea).setEditOperation(EditOperation.INSERT);
break;
}
}
return true;
}
return false;
}
public boolean isValidChar(char value) {
return ((CharsetCapable) codeArea).getCharset().canEncode();
}
@@ -939,7 +945,7 @@ public class DefaultCodeAreaCommandHandler implements CodeAreaCommandHandler {
}
@Nonnull
private CodeType getCodeType() {
protected CodeType getCodeType() {
if (codeTypeSupported) {
return ((CodeTypeCapable) codeArea).getCodeType();
}
@@ -947,7 +953,7 @@ public class DefaultCodeAreaCommandHandler implements CodeAreaCommandHandler {
return CodeType.HEXADECIMAL;
}
private void revealCursor() {
protected void revealCursor() {
((ScrollingCapable) codeArea).revealCursor();
codeArea.repaint();
}
@@ -958,7 +964,7 @@ public class DefaultCodeAreaCommandHandler implements CodeAreaCommandHandler {
}
@Nonnull
private static SelectingMode isSelectingMode(KeyEvent keyEvent) {
protected static SelectingMode isSelectingMode(KeyEvent keyEvent) {
return (keyEvent.getModifiers() & KeyEvent.META_SHIFT_MASK) > 0 ? SelectingMode.SELECTING : SelectingMode.NONE;
}
}
@@ -18,8 +18,8 @@ package org.exbin.bined.android.basic;
import android.view.MotionEvent;
import android.view.View;
import org.exbin.bined.android.CodeAreaCommandHandler.SelectingMode;
import org.exbin.bined.android.CodeAreaCore;
import org.exbin.bined.basic.SelectingMode;
import org.exbin.bined.capability.ScrollingCapable;
import javax.annotation.ParametersAreNonnullByDefault;
@@ -30,7 +30,7 @@ import javax.annotation.ParametersAreNonnullByDefault;
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public class DefaultCodeAreaMouseListener implements View.OnTouchListener, View.OnLongClickListener, View.OnContextClickListener {
public class DefaultCodeAreaMouseListener implements View.OnTouchListener, View.OnLongClickListener {
public static final int MOUSE_SCROLL_LINES = 3;
@@ -69,7 +69,6 @@ public class DefaultCodeAreaMouseListener implements View.OnTouchListener, View.
return codeArea.showContextMenu();
}
@Override
public boolean onContextClick(View v) {
return codeArea.performContextClick();
}
@@ -22,7 +22,6 @@ import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
@@ -136,6 +135,7 @@ public class DefaultCodeAreaPainter implements CodeAreaPainter, BasicColorsCapab
protected EditOperation editOperation;
@Nullable
protected BasicBackgroundPaintMode backgroundPaintMode;
protected final ScrollViewDimension viewDimension = new ScrollViewDimension();
protected boolean showMirrorCursor;
protected int rowPositionLength;
@@ -169,7 +169,7 @@ public class DefaultCodeAreaPainter implements CodeAreaPainter, BasicColorsCapab
scrolling.updateHorizontalScrollBarValue(horizontal, metrics.getCharacterWidth());
int maxValue = Integer.MAX_VALUE; // - scrollPanel.getVerticalScrollBar().getVisibleAmount();
int maxValue = scrolling.getMaximumScrollBarHeight(); // - scrollPanel.getVerticalScrollBar().getVisibleAmount();
long rowsPerDocumentToLastPage = structure.getRowsPerDocument() - dimensions.getRowsPerRect();
scrolling.updateVerticalScrollBarValue(vertical, metrics.getRowHeight(), maxValue, rowsPerDocumentToLastPage);
@@ -178,7 +178,9 @@ public class DefaultCodeAreaPainter implements CodeAreaPainter, BasicColorsCapab
dataViewOffsetX = scrollOffsetX - dimensions.getScrollPanelX();
dataViewOffsetY = scrollOffsetY - dimensions.getScrollPanelY();
((ScrollingCapable) codeArea).setScrollPosition(scrolling.getScrollPosition());
if (!adjusting) {
((ScrollingCapable) codeArea).setScrollPosition(scrolling.getScrollPosition());
}
}
};
scrollPanel.setLongClickable(true);
@@ -206,7 +208,7 @@ public class DefaultCodeAreaPainter implements CodeAreaPainter, BasicColorsCapab
codeArea.addView(scrollPanel, wrapLayout);
dataView.setOnTouchListener(codeAreaMouseListener);
dataView.setOnLongClickListener(codeAreaMouseListener);
dataView.setOnContextClickListener(codeAreaMouseListener);
// dataView.setOnContextClickListener(codeAreaMouseListener);
codeArea.addDataChangedListener(codeAreaDataChangeListener);
}
@@ -215,7 +217,7 @@ public class DefaultCodeAreaPainter implements CodeAreaPainter, BasicColorsCapab
this.codeArea.removeView(scrollPanel);
dataView.setOnTouchListener(null);
dataView.setOnLongClickListener(null);
dataView.setOnContextClickListener(null);
// dataView.setOnContextClickListener(null);
codeArea.removeDataChangedListener(codeAreaDataChangeListener);
}
@@ -287,13 +289,13 @@ public class DefaultCodeAreaPainter implements CodeAreaPainter, BasicColorsCapab
layoutChanged = false;
}
private void updateCaret() {
protected void updateCaret() {
editOperation = ((EditModeCapable) codeArea).getActiveOperation();
caretChanged = false;
}
private void validateCaret() {
protected void validateCaret() {
CodeAreaCaret caret = ((CaretCapable) codeArea).getCodeAreaCaret();
CodeAreaCaretPosition caretPosition = caret.getCaretPosition();
if (caretPosition.getDataPosition() > codeArea.getDataSize()) {
@@ -301,7 +303,7 @@ public class DefaultCodeAreaPainter implements CodeAreaPainter, BasicColorsCapab
}
}
private void validateSelection() {
protected void validateSelection() {
CodeAreaSelection selectionHandler = ((SelectionCapable) codeArea).getSelectionHandler();
if (!selectionHandler.isEmpty()) {
long dataSize = codeArea.getDataSize();
@@ -327,7 +329,7 @@ public class DefaultCodeAreaPainter implements CodeAreaPainter, BasicColorsCapab
}
}
private void recomputeDimensions() {
protected void recomputeDimensions() {
int verticalScrollBarSize = getVerticalScrollBarSize();
int horizontalScrollBarSize = getHorizontalScrollBarSize();
int componentWidth = codeArea.getWidth();
@@ -340,7 +342,7 @@ public class DefaultCodeAreaPainter implements CodeAreaPainter, BasicColorsCapab
updateRowDataCache();
}
private void updateRowDataCache() {
protected void updateRowDataCache() {
if (rowDataCache == null) {
rowDataCache = new RowDataCache();
}
@@ -371,7 +373,7 @@ public class DefaultCodeAreaPainter implements CodeAreaPainter, BasicColorsCapab
initialized = true;
}
private void recomputeScrollState() {
protected void recomputeScrollState() {
scrolling.setScrollPosition(((ScrollingCapable) codeArea).getScrollPosition());
int characterWidth = metrics.getCharacterWidth();
@@ -713,7 +715,7 @@ public class DefaultCodeAreaPainter implements CodeAreaPainter, BasicColorsCapab
}
}
private void prepareRowData(long dataPosition) {
protected void prepareRowData(long dataPosition) {
int maxBytesPerChar = metrics.getMaxBytesPerChar();
CodeAreaViewMode viewMode = structure.getViewMode();
int bytesPerRow = structure.getBytesPerRow();
@@ -1317,7 +1319,7 @@ public class DefaultCodeAreaPainter implements CodeAreaPainter, BasicColorsCapab
return new Point(caretX, caretY);
}
private void updateMirrorCursorRect(long dataPosition, CodeAreaSection section) {
protected void updateMirrorCursorRect(long dataPosition, CodeAreaSection section) {
CodeType codeType = structure.getCodeType();
Point mirrorCursorPoint = getPositionPoint(dataPosition, 0, section == BasicCodeAreaSection.CODE_MATRIX ? BasicCodeAreaSection.TEXT_PREVIEW : BasicCodeAreaSection.CODE_MATRIX);
if (mirrorCursorPoint == null) {
@@ -1354,8 +1356,8 @@ public class DefaultCodeAreaPainter implements CodeAreaPainter, BasicColorsCapab
}
@Override
public void setBasicColors(BasicCodeAreaColorsProfile colors) {
this.colorsProfile = colors;
public void setBasicColors(BasicCodeAreaColorsProfile colorsProfile) {
this.colorsProfile = colorsProfile;
}
/**
@@ -1416,7 +1418,7 @@ public class DefaultCodeAreaPainter implements CodeAreaPainter, BasicColorsCapab
g.drawText(drawnChars, charOffset, length, positionX, positionY, paint);
}
private int getRowPositionLength() {
protected int getRowPositionLength() {
if (minRowPositionLength > 0 && minRowPositionLength == maxRowPositionLength) {
return minRowPositionLength;
}
@@ -1465,7 +1467,7 @@ public class DefaultCodeAreaPainter implements CodeAreaPainter, BasicColorsCapab
* <p>
* Doesn't include character at offset end.
*/
private void renderBackgroundSequence(Canvas g, int startOffset, int endOffset, int rowPositionX, int positionY) {
protected void renderBackgroundSequence(Canvas g, int startOffset, int endOffset, int rowPositionX, int positionY) {
int characterWidth = metrics.getCharacterWidth();
int rowHeight = metrics.getRowHeight();
g.drawRect(dataViewOffsetX + rowPositionX + startOffset * characterWidth, dataViewOffsetY + 1 + positionY - rowHeight, dataViewOffsetX + rowPositionX + endOffset * characterWidth, dataViewOffsetY + 1 + positionY, paint);
@@ -1515,7 +1517,7 @@ public class DefaultCodeAreaPainter implements CodeAreaPainter, BasicColorsCapab
if (rowHeight > 0 && characterWidth > 0) {
scrolling.updateCache(codeArea, getHorizontalScrollBarSize(), getVerticalScrollBarSize());
ScrollViewDimension viewDimension = scrolling.computeViewDimension(codeArea.getWidth(), codeArea.getHeight(), layout, structure, characterWidth, rowHeight);
scrolling.computeViewDimension(viewDimension, codeArea.getWidth(), codeArea.getHeight(), layout, structure, characterWidth, rowHeight);
dataView.setMinimumWidth(viewDimension.getWidth());
dataView.setMinimumHeight(viewDimension.getHeight());
@@ -1525,9 +1527,9 @@ public class DefaultCodeAreaPainter implements CodeAreaPainter, BasicColorsCapab
long rowsPerData = (structure.getDataSize() / structure.getBytesPerRow()) + 1;
int documentDataHeight;
if (rowsPerData > Integer.MAX_VALUE / rowHeight) {
if (rowsPerData > scrolling.getMaximumScrollBarHeight() / rowHeight) {
scrolling.setScrollBarVerticalScale(ScrollBarVerticalScale.SCALED);
documentDataHeight = Integer.MAX_VALUE;
documentDataHeight = scrolling.getMaximumScrollBarHeight();
} else {
scrolling.setScrollBarVerticalScale(ScrollBarVerticalScale.NORMAL);
documentDataHeight = (int) (rowsPerData * rowHeight);
@@ -1568,7 +1570,7 @@ public class DefaultCodeAreaPainter implements CodeAreaPainter, BasicColorsCapab
updateScrollBars();
}
private void dataChanged() {
protected void dataChanged() {
validateCaret();
validateSelection();
recomputeLayout();
@@ -1635,7 +1637,7 @@ public class DefaultCodeAreaPainter implements CodeAreaPainter, BasicColorsCapab
return ((SelectionCapable) codeArea).getSelectionHandler();
}
private int getHorizontalScrollBarSize() {
protected int getHorizontalScrollBarSize() {
// JScrollBar horizontalScrollBar = scrollPanel.getHorizontalScrollBar();
// int size;
// if (horizontalScrollBar.isVisible()) {
@@ -1648,7 +1650,7 @@ public class DefaultCodeAreaPainter implements CodeAreaPainter, BasicColorsCapab
return 10;
}
private int getVerticalScrollBarSize() {
protected int getVerticalScrollBarSize() {
// JScrollBar verticalScrollBar = scrollPanel.getVerticalScrollBar();
// int size;
// if (verticalScrollBar.isVisible()) {
@@ -1661,7 +1663,7 @@ public class DefaultCodeAreaPainter implements CodeAreaPainter, BasicColorsCapab
return 10;
}
private static class RowDataCache {
protected static class RowDataCache {
char[] headerChars;
byte[] rowData;
@@ -1669,7 +1671,7 @@ public class DefaultCodeAreaPainter implements CodeAreaPainter, BasicColorsCapab
char[] rowCharacters;
}
private static class CursorDataCache {
protected static class CursorDataCache {
Rect caretRect = new Rect();
Rect mirrorCursorRect = new Rect();
@@ -18,7 +18,7 @@ package org.exbin.bined.android.basic;
import android.content.Context;
import android.util.AttributeSet;
import com.examples.customtouch.widget.TwoDimensionScrollView;
import com.example.customtouch.widget.TwoDimensionScrollView;
import javax.annotation.ParametersAreNonnullByDefault;
@@ -218,6 +218,6 @@ public class BasicCodeAreaColorsProfile implements CodeAreaColorsProfile {
}
private boolean isDarkMode() {
return context != null && (context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
return context != null && (context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_YES) > 0;
}
}
@@ -15,8 +15,6 @@
*/
package org.exbin.bined.android.basic.color;
import org.exbin.bined.android.basic.color.BasicCodeAreaColorsProfile;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
@@ -19,6 +19,7 @@ import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.Html;
import android.text.method.LinkMovementMethod;
@@ -61,8 +62,17 @@ public class AboutDialog extends AppCompatDialogFragment {
// dialog layout
View aboutView = inflater.inflate(R.layout.about_view, null);
TextView textView = aboutView.findViewById(R.id.textView);
String htmlString = String.format(getResources().getString(R.string.app_about), appVersion).replace("\n", "<br/>");
textView.setText(Html.fromHtml(htmlString, Html.FROM_HTML_MODE_COMPACT));
String htmlString = String.format(getResources().getString(R.string.app_about), appVersion,
"https://exbin.org", "https://www.apache.org/licenses/LICENSE-2.0", "https://bined.exbin.org/android").replace("\n", "<br/>");
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
textView.setText(Html.fromHtml(htmlString, Html.FROM_HTML_MODE_COMPACT));
} else {
textView.setText(Html.fromHtml(htmlString));
}
} catch (NoSuchMethodError ex) {
textView.setText(htmlString, TextView.BufferType.SPANNABLE);
}
textView.setMovementMethod(LinkMovementMethod.getInstance());
builder.setView(aboutView);
builder.setPositiveButton(getResources().getString(R.string.button_close), (dialog, which) -> {
@@ -17,8 +17,13 @@ package org.exbin.bined.editor.android;
import android.app.Application;
import org.exbin.auxiliary.binary_data.delta.SegmentsRepository;
import org.exbin.auxiliary.binary_data.jna.JnaBufferEditableData;
import org.exbin.bined.android.basic.CodeArea;
import org.exbin.bined.editor.android.preference.BinaryEditorPreferences;
import org.exbin.bined.editor.android.preference.PreferencesWrapper;
import org.exbin.bined.editor.android.search.BinarySearch;
import org.exbin.bined.editor.android.search.SearchParameters;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -33,8 +38,11 @@ import javax.annotation.ParametersAreNonnullByDefault;
public class ApplicationContext extends Application {
private BinaryEditorPreferences appPreferences;
private final SegmentsRepository segmentsRepository = new SegmentsRepository(() -> new JnaBufferEditableData());
private BinEdFileHandler fileHandler = null;
private boolean searchActive = false;
private SearchParameters searchParameters = null;
@Override
public void onCreate() {
@@ -53,7 +61,28 @@ public class ApplicationContext extends Application {
return fileHandler;
}
public void setFileHandler(BinEdFileHandler fileHandler) {
this.fileHandler = fileHandler;
@Nonnull
public BinEdFileHandler createFileHandler(CodeArea codeArea) {
fileHandler = new BinEdFileHandler(codeArea);
fileHandler.setSegmentsRepository(segmentsRepository);
fileHandler.setNewData(appPreferences.getEditorPreferences().getFileHandlingMode());
return fileHandler;
}
@Nullable
public SearchParameters getSearchParameters() {
return searchParameters;
}
public void setSearchParameters(@Nullable SearchParameters searchParameters) {
this.searchParameters = searchParameters;
}
public boolean isSearchActive() {
return searchActive;
}
public void setSearchActive(boolean searchActive) {
this.searchActive = searchActive;
}
}
@@ -16,11 +16,15 @@
package org.exbin.bined.editor.android;
import android.content.ContentResolver;
import android.content.res.XmlResourceParser;
import android.net.Uri;
import org.exbin.auxiliary.binary_data.BinaryData;
import org.exbin.auxiliary.binary_data.ByteArrayEditableData;
import org.exbin.auxiliary.binary_data.EditableBinaryData;
import org.exbin.auxiliary.binary_data.delta.DeltaDocument;
import org.exbin.auxiliary.binary_data.delta.SegmentsRepository;
import org.exbin.auxiliary.binary_data.jna.JnaBufferEditableData;
import org.exbin.auxiliary.binary_data.jna.paged.JnaBufferPagedData;
import org.exbin.auxiliary.binary_data.paged.PagedData;
import org.exbin.bined.EditOperation;
import org.exbin.bined.android.CodeAreaPainter;
import org.exbin.bined.android.basic.CodeArea;
@@ -29,6 +33,7 @@ import org.exbin.bined.android.capability.ColorAssessorPainterCapable;
import org.exbin.bined.operation.android.CodeAreaOperationCommandHandler;
import org.exbin.bined.operation.android.CodeAreaUndoRedo;
import org.exbin.framework.bined.BinEdCodeAreaAssessor;
import org.exbin.framework.bined.FileHandlingMode;
import java.io.IOException;
import java.io.InputStream;
@@ -48,8 +53,12 @@ import javax.annotation.ParametersAreNonnullByDefault;
@ParametersAreNonnullByDefault
public class BinEdFileHandler {
private SegmentsRepository segmentsRepository;
private CodeArea codeArea;
private CodeAreaUndoRedo undoRedo;
private BinEdCodeAreaAssessor codeAreaAssessor;
private CodeAreaTableMapAssessor codeAreaTableMapAssessor = new CodeAreaTableMapAssessor();
private long documentOriginalSize = 0;
private Uri currentFileUri = null;
@@ -58,41 +67,79 @@ public class BinEdFileHandler {
public BinEdFileHandler(CodeArea codeArea) {
// ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
this.codeArea = codeArea;
codeArea.setContentData(new ByteArrayEditableData());
// TODO Check for memory limit:
// https://stackoverflow.com/questions/2298208/how-do-i-discover-memory-usage-of-my-application-in-android
// possibly? long nativeHeapFreeSize = Debug.getNativeHeapFreeSize();
/* long lPtr = Native.malloc(capacity);
if (lPtr == 0)
throw new Error("Failed to allocate direct byte buffer memory");
return Memory.getByteBuffer(lPtr, capacity);
buffer.clear();
Pointer javaPointer = Native.getDirectBufferPointer(buffer);
long lPtr = Pointer.nativeValue(javaPointer);
Native.free(lPtr); */
codeArea.setContentData(new JnaBufferEditableData());
codeArea.setEditOperation(EditOperation.INSERT);
undoRedo = new CodeAreaUndoRedo(codeArea);
CodeAreaOperationCommandHandler commandHandler = new CodeAreaOperationCommandHandler(codeArea.getContext(), codeArea, undoRedo);
commandHandler.setCodeAreaTableMapAssessor(codeAreaTableMapAssessor);
codeArea.setCommandHandler(commandHandler);
CodeAreaPainter painter = codeArea.getPainter();
BinEdCodeAreaAssessor codeAreaAssessor = new BinEdCodeAreaAssessor(((ColorAssessorPainterCapable) painter).getColorAssessor(), ((CharAssessorPainterCapable) painter).getCharAssessor());
codeAreaAssessor = new BinEdCodeAreaAssessor(((ColorAssessorPainterCapable) painter).getColorAssessor(), codeAreaTableMapAssessor); // ((CharAssessorPainterCapable) painter).getCharAssessor()
((ColorAssessorPainterCapable) painter).setColorAssessor(codeAreaAssessor);
((CharAssessorPainterCapable) painter).setCharAssessor(codeAreaAssessor);
codeArea.setPainter(painter);
}
public void newFile() {
codeArea.setContentData(new ByteArrayEditableData());
@Nonnull
public CodeAreaTableMapAssessor getCodeAreaTableMapAssessor() {
return codeAreaTableMapAssessor;
}
public void setNewData(FileHandlingMode fileHandlingMode) {
if (fileHandlingMode == FileHandlingMode.DELTA) {
codeArea.setContentData(segmentsRepository.createDocument());
} else {
codeArea.setContentData(new JnaBufferPagedData());
}
undoRedo.clear();
currentFileUri = null;
documentOriginalSize = 0;
}
public void openFile(ContentResolver contentResolver, Uri fileUri) {
ByteArrayEditableData fileData = new ByteArrayEditableData();
public void openFile(ContentResolver contentResolver, Uri fileUri, FileHandlingMode fileHandlingMode) {
BinaryData oldData = codeArea.getContentData();
try {
InputStream inputStream = contentResolver.openInputStream(fileUri);
if (inputStream == null) {
return;
if (fileHandlingMode == FileHandlingMode.DELTA) {
ContentDataSource dataSource = new ContentDataSource(contentResolver, fileUri);
segmentsRepository.addDataSource(dataSource);
DeltaDocument document = segmentsRepository.createDocument(dataSource);
codeArea.setContentData(document);
oldData.dispose();
} else {
BinaryData data = oldData;
if (!(data instanceof PagedData)) {
data = new JnaBufferPagedData();
oldData.dispose();
}
InputStream inputStream = contentResolver.openInputStream(fileUri);
if (inputStream == null) {
return;
}
((EditableBinaryData) data).loadFromStream(inputStream);
inputStream.close();
codeArea.setContentData(data);
}
fileData.loadFromStream(inputStream);
inputStream.close();
documentOriginalSize = fileData.getDataSize();
undoRedo.clear();
codeArea.setContentData(fileData);
currentFileUri = fileUri;
pickerInitialUri = fileUri;
fileSync();
} catch (IOException ex) {
Logger.getLogger(BinEdFileHandler.class.getName()).log(Level.SEVERE, null, ex);
}
@@ -101,21 +148,45 @@ public class BinEdFileHandler {
public void saveFile(ContentResolver contentResolver, Uri fileUri) {
BinaryData contentData = codeArea.getContentData();
try {
OutputStream outputStream = contentResolver.openOutputStream(fileUri);
if (outputStream == null) {
return;
if (contentData instanceof DeltaDocument) {
// TODO freezes window / replace with progress bar
DeltaDocument document = (DeltaDocument) contentData;
ContentDataSource fileSource = (ContentDataSource) document.getDataSource();
if (fileSource == null || !fileUri.equals(fileSource.getFileUri())) {
fileSource = new ContentDataSource(contentResolver, fileUri);
segmentsRepository.addDataSource(fileSource);
document.setDataSource(fileSource);
}
if (fileSource == null) {
throw new IllegalStateException("Unexpected state");
}
segmentsRepository.saveDocument(document);
fileSync();
currentFileUri = fileUri;
pickerInitialUri = fileUri;
} else {
OutputStream outputStream = contentResolver.openOutputStream(fileUri);
if (outputStream == null) {
return;
}
contentData.saveToStream(outputStream);
outputStream.close();
fileSync();
currentFileUri = fileUri;
pickerInitialUri = fileUri;
}
codeArea.getContentData().saveToStream(outputStream);
outputStream.close();
documentOriginalSize = contentData.getDataSize();
undoRedo.setSyncPosition();
currentFileUri = fileUri;
pickerInitialUri = fileUri;
} catch (IOException ex) {
Logger.getLogger(BinEdFileHandler.class.getName()).log(Level.SEVERE, null, ex);
}
}
private void fileSync() {
documentOriginalSize = getCodeArea().getDataSize();
undoRedo.setSyncPosition();
}
@Nonnull
public CodeArea getCodeArea() {
return codeArea;
@@ -126,10 +197,24 @@ public class BinEdFileHandler {
return undoRedo;
}
@Nonnull
public BinEdCodeAreaAssessor getCodeAreaAssessor() {
return codeAreaAssessor;
}
public long getDocumentOriginalSize() {
return documentOriginalSize;
}
@Nonnull
public FileHandlingMode getFileHandlingMode() {
return getCodeArea().getContentData() instanceof DeltaDocument ? FileHandlingMode.DELTA : FileHandlingMode.MEMORY;
}
public void setSegmentsRepository(SegmentsRepository segmentsRepository) {
this.segmentsRepository = segmentsRepository;
}
@Nullable
public Uri getCurrentFileUri() {
return currentFileUri;
@@ -0,0 +1,318 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.bined.editor.android;
import android.content.ContentResolver;
import android.net.Uri;
import org.exbin.bined.CodeAreaSection;
import org.exbin.bined.android.CodeAreaCharAssessor;
import org.exbin.bined.android.CodeAreaPaintState;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderMalfunctionError;
import java.nio.charset.CodingErrorAction;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
/**
* Table map character assessor.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public class CodeAreaTableMapAssessor implements CodeAreaCharAssessor {
protected final CodeAreaCharAssessor parentAssessor;
protected char[] charMapping = null;
protected long dataSize;
protected int maxBytesPerChar;
protected byte[] rowData;
protected Charset charset;
private CharsetDecoder decoder;
private ByteBuffer byteBuffer;
private CharBuffer charBuffer = null;
protected boolean useTable = false;
protected final Map<Integer, Character> characterTable = new HashMap<>();
protected final Map<Character, Integer> keyPressTable = new HashMap<>();
public CodeAreaTableMapAssessor() {
parentAssessor = null;
}
public CodeAreaTableMapAssessor(@Nullable CodeAreaCharAssessor parentAssessor) {
this.parentAssessor = parentAssessor;
}
@Override
public void startPaint(CodeAreaPaintState codeAreaPainterState) {
dataSize = codeAreaPainterState.getDataSize();
Charset painterCharset = codeAreaPainterState.getCharset();
maxBytesPerChar = codeAreaPainterState.getMaxBytesPerChar();
if (charBuffer == null) {
charBuffer = CharBuffer.allocate(8);
}
if (charset != painterCharset) {
charMapping = null;
decoder = painterCharset.newDecoder();
decoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
decoder.onMalformedInput(CodingErrorAction.REPLACE);
byteBuffer = ByteBuffer.allocate(maxBytesPerChar);
this.charset = painterCharset;
}
rowData = codeAreaPainterState.getRowData();
}
@Override
public char getPreviewCharacter(long rowDataPosition, int byteOnRow, int charOnRow, CodeAreaSection section) {
if (byteOnRow > rowData.length - maxBytesPerChar) {
return ' ';
}
if (maxBytesPerChar > 1) {
decoder.reset();
if (rowDataPosition + maxBytesPerChar > dataSize) {
byteBuffer.clear();
byteBuffer.put(rowData, byteOnRow, (int) (dataSize - rowDataPosition));
} else {
byteBuffer.rewind();
byteBuffer.put(rowData, byteOnRow, maxBytesPerChar);
}
if (useTable) {
byteBuffer.rewind();
int value0 = byteBuffer.get(0) & 0xff;
int value1 = ((byteBuffer.get(1) & 0xff) << 8) + value0;
Character character = characterTable.get(value1);
if (character != null) {
return character;
}
character = characterTable.get(value0);
if (character != null) {
return character;
}
}
byteBuffer.rewind();
charBuffer.clear();
try {
decoder.decode(byteBuffer, charBuffer, true);
if (charBuffer.position() > 0) {
charBuffer.rewind();
return charBuffer.get();
}
} catch (CoderMalfunctionError | BufferUnderflowException ex) {
// ignore
}
} else {
if (useTable) {
Character character = characterTable.get(rowData[byteOnRow] & 0xff);
if (character != null) {
return character;
}
}
if (charMapping == null) {
buildCharMapping();
}
return charMapping[rowData[byteOnRow] & 0xff];
}
return ' ';
}
@Override
public char getPreviewCursorCharacter(long rowDataPosition, int byteOnRow, int charOnRow, byte[] cursorData, int cursorDataLength, CodeAreaSection section) {
if (cursorDataLength == 0) {
return ' ';
}
if (maxBytesPerChar > 1) {
decoder.reset();
byteBuffer.rewind();
byteBuffer.put(cursorData, 0, cursorDataLength);
if (useTable) {
byteBuffer.rewind();
int value0 = byteBuffer.get(0) & 0xff;
int value1 = ((byteBuffer.get(1) & 0xff) << 8) + value0;
Character character = characterTable.get(value1);
if (character != null) {
return character;
}
character = characterTable.get(value0);
if (character != null) {
return character;
}
}
byteBuffer.rewind();
charBuffer.clear();
try {
decoder.decode(byteBuffer, charBuffer, true);
if (charBuffer.position() > 0) {
charBuffer.rewind();
return charBuffer.get();
}
} catch (CoderMalfunctionError | BufferUnderflowException ex) {
// ignore
}
} else {
if (useTable) {
Character character = characterTable.get(cursorData[0] & 0xff);
if (character != null) {
return character;
}
}
if (charMapping == null) {
buildCharMapping();
}
return charMapping[cursorData[0] & 0xff];
}
return ' ';
}
@Nonnull
@Override
public Optional<CodeAreaCharAssessor> getParentCharAssessor() {
return Optional.ofNullable(parentAssessor);
}
public boolean isUseTable() {
return useTable;
}
public void setUseTable(boolean useTable) {
this.useTable = useTable;
}
public void openFile(ContentResolver contentResolver, Uri fileUri) {
characterTable.clear();
keyPressTable.clear();
try {
try (InputStream inputStream = contentResolver.openInputStream(fileUri); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"))) {
String line = reader.readLine();
while (line != null) {
int valuePos = line.indexOf("=");
if (valuePos >= 0) {
valuePos++;
} else {
line = reader.readLine();
continue;
}
Integer code = 0;
for (int i = 0; i < valuePos - 1; i++) {
code = code << 4;
char codeChar = line.charAt(i);
if (codeChar >= '0' && codeChar <= '9') {
code |= (byte) (codeChar - '0');
} else if (codeChar >= 'a' && codeChar <= 'f') {
code |= (byte) (codeChar + 10 - 'a');
} else if (codeChar >= 'A' && codeChar <= 'F') {
code |= (byte) (codeChar + 10 - 'A');
} else {
throw new IllegalArgumentException("Invalid character " + codeChar);
}
}
if (line.length() > valuePos) {
characterTable.put(code, line.charAt(valuePos));
if (line.length() == valuePos + 1) {
keyPressTable.put(line.charAt(valuePos), code);
}
}
line = reader.readLine();
}
}
} catch (IOException ex) {
Logger.getLogger(CodeAreaTableMapAssessor.class.getName()).log(Level.SEVERE, null, ex);
}
useTable = true;
}
@Nonnull
public Map<Integer, Character> getCharacterTable() {
return characterTable;
}
@Nullable
public byte[] translateKey(char key) {
Integer code = keyPressTable.get(key);
if (code == null) {
return null;
}
byte[] result;
if (code > 256) {
result = new byte[2];
result[1] = (byte) ((code >> 8) & 0xff);
} else {
result = new byte[1];
}
result[0] = (byte) (code & 0xff);
return result;
}
/**
* Precomputes widths for basic ascii characters.
*/
private void buildCharMapping() {
charMapping = new char[256];
ByteBuffer buffer = ByteBuffer.allocate(1);
for (int i = 0; i < 256; i++) {
buffer.rewind();
buffer.put((byte) i);
decoder.reset();
buffer.rewind();
charBuffer.clear();
try {
decoder.decode(buffer, charBuffer, true);
if (charBuffer.position() > 0) {
charBuffer.rewind();
charMapping[i] = charBuffer.get();
} else {
charMapping[i] = ' ';
}
} catch (CoderMalfunctionError | BufferUnderflowException ex) {
charMapping[i] = ' ';
}
}
}
}
@@ -0,0 +1,76 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.bined.editor.android;
import android.app.LocaleManager;
import android.content.Context;
import android.os.Build;
import android.os.LocaleList;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.os.LocaleListCompat;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
/**
* Compatibility utilities.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public class CompatUtils {
private CompatUtils() {
}
public static void setApplicationLocales(Context context, LocaleListCompat languageLocaleList) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.getSystemService(LocaleManager.class).setApplicationLocales((LocaleList) Objects.requireNonNull(languageLocaleList.unwrap()));
return;
}
try {
Method setApplicationLocalesMethod = AppCompatDelegate.class.getMethod("setApplicationLocales", LocaleListCompat.class);
setApplicationLocalesMethod.invoke(null, languageLocaleList);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
// No switching available
}
}
@Nonnull
public static LocaleListCompat getApplicationLocales(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
LocaleList localeList = context.getSystemService(LocaleManager.class).getApplicationLocales();
return LocaleListCompat.wrap(localeList);
}
try {
Method getApplicationLocalesMethod = AppCompatDelegate.class.getMethod("getApplicationLocales");
Object result = getApplicationLocalesMethod.invoke(null);
if (result instanceof LocaleListCompat) {
return (LocaleListCompat) result;
}
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
// No switching available
}
return LocaleListCompat.getEmptyLocaleList();
}
}
@@ -0,0 +1,172 @@
/*
* Copyright (C) ExBin 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exbin.bined.editor.android;
import android.content.ContentResolver;
import android.content.res.AssetFileDescriptor;
import android.net.Uri;
import org.exbin.auxiliary.binary_data.delta.DataSource;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
/**
* Data source using android data content.
*
* @author ExBin Project (https://exbin.org)
*/
@ParametersAreNonnullByDefault
public class ContentDataSource implements DataSource {
private final ContentResolver contentResolver;
private final Uri fileUri;
private AssetFileDescriptor descriptor;
private final ByteBuffer byteBuffer = ByteBuffer.allocate(1);
@Nonnull
private final DeltaDataPageWindow window;
private FileInputStream inputStream;
private FileOutputStream outputStream;
private FileChannel fileChannel;
private final List<CacheClearListener> listeners = new ArrayList<>();
private long dataLength;
private boolean closed = false;
public ContentDataSource(ContentResolver contentResolver, Uri fileUri) throws IOException {
this.contentResolver = contentResolver;
this.fileUri = fileUri;
descriptor = contentResolver.openAssetFileDescriptor(fileUri, "rw");
inputStream = descriptor.createInputStream();
outputStream = descriptor.createOutputStream();
fileChannel = outputStream.getChannel();
dataLength = descriptor.getLength();
window = new DeltaDataPageWindow(this);
}
@Nonnull
public ContentResolver getContentResolver() {
return contentResolver;
}
@Nonnull
public FileInputStream getInputStream() {
return inputStream;
}
@Nonnull
public Uri getFileUri() {
return fileUri;
}
@Override
public long getDataLength() throws IOException {
return dataLength;
}
@Override
public void setDataLength(long dataLength) throws IOException {
this.dataLength = dataLength;
fileChannel.truncate(dataLength);
}
@Override
public byte getByte(long position) throws IOException {
checkClosed();
return window.getByte(position);
/*
try (InputStream stream = contentResolver.openInputStream(fileUri)) {
stream.skip(position);
int value = stream.read();
if (value == -1) {
throw new IOException("Read exception");
}
return (byte) value;
} */
}
@Override
public void setByte(long position, byte value) throws IOException {
byteBuffer.put(0, value);
fileChannel.write(byteBuffer, position);
if (position > dataLength) {
dataLength = position + 1;
}
}
@Override
public int read(long position, byte[] buffer, int offset, int length) throws IOException {
checkClosed();
return window.read(position, buffer, offset, length);
}
@Override
public void write(long position, byte[] buffer, int offset, int length) throws IOException {
ByteBuffer writeBuffer = ByteBuffer.wrap(buffer, offset, length);
int written = fileChannel.write(writeBuffer, position);
if (written == -1) {
throw new IllegalStateException("Writing error at position " + position);
}
if (position + length > dataLength) {
dataLength = position + length;
}
}
@Override
public void clearCache() {
for (CacheClearListener listener : listeners) {
listener.clearCache();
};
}
@Override
public void close() throws IOException {
checkClosed();
fileChannel.close();
inputStream.close();
outputStream.close();
descriptor.close();
closed = true;
}
private void checkClosed() {
if (closed) {
throw new IllegalStateException("File was already closed");
}
}
public void addCacheClearListener(CacheClearListener listener) {
listeners.add(listener);
}
public void removeCacheClearListener(CacheClearListener listener) {
listeners.remove(listener);
}
public interface CacheClearListener {
void clearCache();
}
}

Some files were not shown because too many files have changed in this diff Show More