Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f5d152cbd | ||
|
|
da66be3d57 | ||
|
|
15940b4357 | ||
|
|
1e9fdcae07 | ||
|
|
0a34b72a26 | ||
|
|
24a19e3e2f | ||
|
|
9362ce762c | ||
|
|
bfd2b19190 | ||
|
|
c78381a20a | ||
|
|
06e16367f1 | ||
|
|
72565964f2 | ||
|
|
03edbba8c4 | ||
|
|
908372c235 | ||
|
|
4e3872519a | ||
|
|
3f68527616 | ||
|
|
c0487ce0c3 | ||
|
|
4dc8540461 | ||
|
|
9d40b98a4c | ||
|
|
5c01dad173 | ||
|
|
a5328702af | ||
|
|
fc17fe853e | ||
|
|
80fbed262d | ||
|
|
a43e45f223 | ||
|
|
e21069fd11 | ||
|
|
4d8552ac66 | ||
|
|
2bcf3c6421 | ||
|
|
9331dccb8b | ||
|
|
424e9ce897 | ||
|
|
5679c26e99 | ||
|
|
d732892673 | ||
|
|
df32e610b1 | ||
|
|
15a6e5b222 | ||
|
|
89e0bb7cdc | ||
|
|
69ab704151 | ||
|
|
e11ce37438 | ||
|
|
db9c93bdf5 | ||
|
|
ecb061f65c | ||
|
|
21fe741eb8 | ||
|
|
5167e4c9b5 | ||
|
|
44d6ee0df6 | ||
|
|
ae3d07ebdd | ||
|
|
a90fedbc4c | ||
|
|
97c6694fd2 | ||
|
|
b863035f09 | ||
|
|
4e7279c766 | ||
|
|
16f8793308 | ||
|
|
6f8388a0b5 | ||
|
|
c02666d357 | ||
|
|
1df4d47ca6 | ||
|
|
0f9b9380cf | ||
|
|
a6b38be5a8 | ||
|
|
4775da61fc | ||
|
|
2e306e87ff | ||
|
|
8a71c42398 | ||
|
|
0c5e8ec482 | ||
|
|
f082e7209c | ||
|
|
0e9412af7f | ||
|
|
38e92e0fc3 | ||
|
|
3e146185df | ||
|
|
16c44bc7cd | ||
|
|
5ac5351dbe | ||
|
|
dbcfa995fb | ||
|
|
16bc17921c | ||
|
|
eb3b9f1e4a | ||
|
|
f1f274f5af | ||
|
|
1b0d715004 | ||
|
|
98cceb8184 | ||
|
|
7012192995 | ||
|
|
8d9e91693a | ||
|
|
604f967634 | ||
|
|
b7f5bf70bf | ||
|
|
dda92b2656 | ||
|
|
98f55f3554 | ||
|
|
29f2ec28d6 | ||
|
|
cf6ddbfebb | ||
|
|
608e788673 | ||
|
|
1f85b5941a | ||
|
|
beff277f6c | ||
|
|
4d0e36932c | ||
|
|
75ee4dc363 | ||
|
|
490eee7146 | ||
|
|
954f91410b | ||
|
|
7051207876 | ||
|
|
39475b6343 | ||
|
|
57044959b1 |
Generated
+6
@@ -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>
|
||||
Generated
+1
-1
@@ -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>
|
||||
Generated
+1
@@ -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" />
|
||||
|
||||
Generated
+1
-1
@@ -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">
|
||||
|
||||
Generated
+4
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Vendored
+21
@@ -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
|
||||
|
||||
+2
-2
@@ -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) {
|
||||
+4
-4
@@ -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);
|
||||
}
|
||||
+2
-2
@@ -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
|
||||
+277
@@ -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);
|
||||
}
|
||||
}
|
||||
+253
@@ -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);
|
||||
}
|
||||
}
|
||||
+9
-14
@@ -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);
|
||||
}
|
||||
}
|
||||
-3
@@ -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.
|
||||
@@ -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 |
+8
-14
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+80
-74
@@ -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;
|
||||
|
||||
|
||||
+1
-1
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
-2
@@ -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
Reference in New Issue
Block a user