From af5818913308e7aa8d9cb58f18f1e1b10beea8ba Mon Sep 17 00:00:00 2001 From: John Mertic Date: Wed, 6 Mar 2019 11:35:00 -0500 Subject: [PATCH 1/9] Add Slack link Signed-off-by: John Mertic Signed-off-by: Oleksandr Hubanov --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1344105..6fe082b 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ How to report problems and get support/help: If you have a problem / need help with TerseDecompress.java, please create a GitHub issue. -You can also send an email to: clientcenter@de.ibm.com +You can also send an email to: clientcenter@de.ibm.com or contact us on Slack at https://slack.openmainframeproject.org channel #tersedecompress Please do not send code changes, but explain in a mail From 1f303295f72fb150f919159eefc55147a11c207f Mon Sep 17 00:00:00 2001 From: Andrew Rowley Date: Tue, 16 Mar 2021 11:56:33 +1100 Subject: [PATCH 2/9] Squashed commit of the following: commit f2ee0f20687d7de706d2482fc1a806f4751c981e Author: Andrew Rowley Date: Tue Mar 16 10:43:50 2021 +1100 Update documentation. commit 6e3c2e35d7aba2672f7cf86656b95ea687685893 Author: Andrew Rowley Date: Mon Mar 15 21:58:56 2021 +1100 Update test data commit 487c005bad8f63055f0330ad81c5bb529108fbc5 Author: Andrew Rowley Date: Mon Mar 15 18:07:26 2021 +1100 Add additional tests for text/binary VB data - these probably don't add much but are added for completeness. commit 63dc3765f7f7dad8f407fefdc180ad54be609091 Author: Andrew Rowley Date: Mon Mar 15 17:40:31 2021 +1100 Add tests for RECFM=FB,LRECL=1 in text mode and RECFM=VB,LRECL=5 in text and binary mode. These tests should exercise cases where a single read from the compressed data generates multiple output records. commit 745bb4fbfd884fb6ddb18045a5992f00f3c65b69 Author: Andrew Rowley Date: Fri Mar 12 11:08:37 2021 +1100 Squashed commit of the following: commit 3c19fec273deff554cfc10466c8876d664496956 Author: Andrew Rowley Date: Fri Mar 12 11:02:04 2021 +1100 Add Open Mainframe Project test data commit 4d712bba11acf30f094a7e94b59287344f5b813c Author: Andrew Rowley Date: Fri Mar 12 10:57:06 2021 +1100 Remove test-data to replace with Open Mainframe Project copy commit 0008cb190341469ae652758e8342a03658cf5fae Author: Andrew Rowley Date: Fri Mar 12 10:27:46 2021 +1100 Delete tests for text processing using VB.ENWIK8.XML because this file contains non-text characters, which means that Git treats it as binary and line endings are not translated on non-Windows systems resulting in test failure. commit c8ed37fe5572a800506babe2ba598bb9a84513b2 Author: Andrew Rowley <38678886+andrew890@users.noreply.github.com> Date: Tue Feb 16 10:53:09 2021 +1100 Update README.md commit c95d6d827e3a262236ac8a35756525489ac695c5 Author: Andrew Rowley Date: Tue Feb 16 10:50:46 2021 +1100 Add test information commit 8c941171c9b048167e81b9fe30bde58590b62cb8 Author: Andrew Rowley Date: Mon Feb 15 21:50:30 2021 +1100 Change information to Open Mainframe Project commit 2cc707e8486362be92ec2782eba33109e83e747a Author: Andrew Rowley Date: Mon Feb 15 21:22:43 2021 +1100 Switch submodule to master commit 0c93f5b8b1bac7dd1a0ff63583e93af31885bf7f Merge: f605393 36f1b70 Author: Andrew Rowley Date: Mon Feb 15 20:58:28 2021 +1100 Merge branch 'master' into master-with-unittests commit f6053934081b3e5feabb0055d9f9ef7438f1b90c Merge: 3094bf9 15ea04f Author: Andrew Rowley Date: Mon Feb 15 20:57:40 2021 +1100 Merge remote-tracking branch 'remotes/andrew890/master-with-unittests' into master-with-unittests commit 15ea04fec1574ba401efcf5837ae151effb51236 Author: Andrew Rowley Date: Sun Jan 31 10:27:24 2021 +1100 Attempting to fix submodule error commit 34e6542f87ea22bc2304cef0c89a506044178fe3 Author: Andrew Rowley Date: Sun Jan 31 10:14:04 2021 +1100 Switch to open mainframe project version of test data commit d08352eb53a226a16cb656d4c87e605abf16488a Author: Andrew Rowley Date: Sun Jan 31 10:08:49 2021 +1100 Remove old test-data submodule commit fcd5b185db8ab857ef36e1ee735ccc2aac275663 Author: Andrew Rowley Date: Sun Jan 31 09:58:26 2021 +1100 Skip tests by default so project can be built without checking out test data submodule, to run unit tests build with: mvn -DskipTests=false clean package commit 3094bf92c291d246183bcee344b5818b637b5592 Merge: b3f0bb9 e9fdce4 Author: Andrew Rowley Date: Wed Dec 9 14:50:39 2020 +1100 Merge branch 'master' into master-with-unittests commit b3f0bb97345a43d86fa229e33613d116f8fde4c5 Merge: ccf6d06 4bed8e3 Author: Andrew Rowley Date: Wed Dec 9 14:26:55 2020 +1100 Merge branch 'master' into master-with-unittests commit ccf6d068fce412e6dadb60709ec89ad8858b34dc Merge: e7c47e7 842c734 Author: Andrew Rowley Date: Wed Dec 9 12:10:31 2020 +1100 Merge branch 'master' into master-with-unittests commit e7c47e735851f31455a4117a213c41a853f282f0 Author: Andrew Rowley Date: Wed Dec 9 12:10:18 2020 +1100 Update test data commit c5803a6a2278b98d42033f373c1362f68958dbe8 Author: Andrew Rowley Date: Wed Dec 9 10:38:59 2020 +1100 Disable FB.A.TXT.SPACK tests, which fail seemingly because of a bug creating the SPACK file on z/OS. Decompression on z/OS also generates incorrect results and the FB.A.TXT.SPACK file looks incomplete. commit 92b9b230892185055518b65c2b9181f3676fa352 Author: Andrew Rowley Date: Tue Dec 8 17:22:41 2020 +1100 Add test data as a git submodule. Modify tests to improve the information provided on failure. commit 386b63c6bae32931f0ce3d0048ed44ffa1a84c5f Author: Andrew Rowley Date: Tue Dec 8 15:34:02 2020 +1100 Add tests for TerseDecompress commit 36f1b70445524099c81cc057e6cb77a9606b8099 Merge: e9fdce4 d54d9f8 Author: Andrew Rowley Date: Wed Dec 9 14:54:14 2020 +1100 Merge branch 'master' of github.com:BlackHillSoftware/tersedecompress into master commit e9fdce4823f40806f551086003b5189e9ba39046 Author: Andrew Rowley Date: Wed Dec 9 14:50:16 2020 +1100 Update artifactId to correct jar file name. commit d54d9f8177c50bed4f0b3c7510b233b667d5a720 Merge: c4b93e4 4bed8e3 Author: Andrew Rowley <38678886+andrew890@users.noreply.github.com> Date: Wed Dec 9 14:39:41 2020 +1100 Merge pull request #1 from andrew890/master Update to version 5.0.0 : Support for VB binary data, code reorganization. commit 4bed8e338703fa45b475e4de6c96d53769a78596 Author: Andrew Rowley Date: Wed Dec 9 14:26:14 2020 +1100 Update version to 5.0.0 commit 842c7346a049b44036362c7c720876742c5c667a Author: Andrew Rowley Date: Tue Dec 8 17:25:30 2020 +1100 Delete copy of original TerseDecompress.java commit e5849ec088ad5718d25a4fdf8a18d46fc78ca265 Author: Andrew Rowley Date: Fri Dec 4 14:51:44 2020 +1100 Rename various files to better reflect evolved functions. commit 17d874014e014582bf5d920f2bb6e056dba67813 Author: Andrew Rowley Date: Fri Dec 4 14:39:56 2020 +1100 Move all logic to process the terse file into a separate class so that automated tests can be set up. commit edcc02992bdd36fc8bc05480ac8d4087162aabb1 Author: Andrew Rowley Date: Thu Dec 3 16:32:11 2020 +1100 TerseDecompress always reads 12 bits, so simplify the code by removing the ability to read a variable number of bits. commit 0fae190cf2d9be327f78c41a283b3a22671b247b Author: Andrew Rowley Date: Thu Dec 3 15:30:16 2020 +1100 Improve handling of command line arguments: -b argument no longer needs to be the last argument commit bb39b0757b53ea7ce8d2122a6d19d34424c7942f Author: Andrew Rowley Date: Thu Nov 26 13:34:19 2020 +1100 Update output writer to write an end of line marker at the end of the last record for variable length text data commit 8f1a25109433e7483bb4fc54b8cedfdaa0337b7a Author: Andrew Rowley Date: Thu Nov 26 13:09:19 2020 +1100 Modify TerseDecompress to work with binary variable length records (i.e. write rdws into output stream). - defer writing the record until the end of the record is found instead of a byte at a time - change DecompressedOutputWriter to implement AutoCloseable to ensure that the last record is written commit 11156c403c689c25fed2d10f7ead5eb96d72fbb7 Author: Andrew Rowley Date: Thu Nov 26 13:02:06 2020 +1100 Update dependency and TerseDecompress versions in pom. commit b1d50e1c923155ae9d24c3983e4deacc4cb795ce Author: Andrew Rowley Date: Mon Sep 7 10:19:48 2020 +1000 Testing showed that SPACK wrote an extra newline at the end of a VB text file, compared to PACK. On investigation, I found that the SPACK code passed the 0x000 end of file marker to PutChars and thence to PutChar. If the file is host LRECL=V and text format, PutChar writes a newline character when the argument is zero. In all other cases, the zero argument is ignored and nothing is written. PACK does not pass the end of file marker to PutChar hence the different output. Rearranged the loop and end of file testing so that SPACK does not send the EOF to PutChars. PACK and SPACK now produce the same output. commit 54ad17f7fd0334bf3b6d10e1ca2b04c42c8bebde Author: Andrew Rowley Date: Mon Aug 31 21:31:16 2020 +1000 Write System.lineseparator instead of constant for end of lines. FB binary and VB text test cases now work, EXCEPT that VB PACK format is missing a line separator in the final line. commit 7fbe680fdd7fc5e81c650ac8f098c07c5c6df52b Author: Andrew Rowley Date: Mon Aug 31 21:12:45 2020 +1000 All calls to FilePutRequired write 8 bits, so remove the code to write variable numbers of bits and replace it with direct writes to the output stream. This allows more flexible writing, e.g. System.lineseparator which can be 1 or 2 bytes depending on the system. commit 8f666349e236c084fc88130b451820a328d974e1 Author: Andrew Rowley Date: Wed Mar 4 16:06:13 2020 +1100 Add original file to compare code. Move header reading and validation to TerseHeader class Move SPACK and non-SPACK decoding routines out of main class into separate classes. commit d4e79479c05d1c92ab3a543edb5ed73369307111 Author: Andrew Rowley Date: Tue Mar 3 18:50:31 2020 +1100 Move constants out of main file. commit b1090ffc449e53724797f2ee0286541115194fdd Author: Andrew Rowley Date: Tue Mar 3 18:28:35 2020 +1100 Move input and output code into separate classes. commit 901687cc04b690e6cf4689232c852d58fc3bd13f Author: Andrew Rowley Date: Mon Mar 2 10:22:29 2020 +1100 Refactoring and breaking out classes into separate files to try to understand code. commit 858cd8ff51e149f15cfeef777d5aec93444f4d03 Author: Andrew Rowley Date: Tue Mar 3 11:26:41 2020 +1100 Add executable jar build to POM commit 26f0d6d69cc2811b814e9b00265d68852e002d97 Author: Andrew Rowley Date: Mon Mar 2 10:13:07 2020 +1100 Create Maven project before refactoring Signed-off-by: Andrew Rowley Signed-off-by: Oleksandr Hubanov --- .gitignore | 89 ++ .gitmodules | 3 + README.md | 73 +- TerseDecompress.java | 1112 ----------------- pom.xml | 52 + .../tersedecompress/Constants.java | 152 +++ .../tersedecompress/NonSpackDecompresser.java | 90 ++ .../tersedecompress/SpackDecompresser.java | 188 +++ .../tersedecompress/StackType.java | 14 + .../tersedecompress/TerseBlockReader.java | 70 ++ .../tersedecompress/TerseDecompress.java | 116 ++ .../tersedecompress/TerseDecompresser.java | 124 ++ .../tersedecompress/TerseHeader.java | 127 ++ .../tersedecompress/TreeRecord.java | 9 + .../tersedecompress/AppTest.java | 189 +++ test-data | 1 + tests/README.md | 12 + 17 files changed, 1274 insertions(+), 1147 deletions(-) create mode 100644 .gitmodules delete mode 100644 TerseDecompress.java create mode 100644 pom.xml create mode 100644 src/main/java/org/openmainframeproject/tersedecompress/Constants.java create mode 100644 src/main/java/org/openmainframeproject/tersedecompress/NonSpackDecompresser.java create mode 100644 src/main/java/org/openmainframeproject/tersedecompress/SpackDecompresser.java create mode 100644 src/main/java/org/openmainframeproject/tersedecompress/StackType.java create mode 100644 src/main/java/org/openmainframeproject/tersedecompress/TerseBlockReader.java create mode 100644 src/main/java/org/openmainframeproject/tersedecompress/TerseDecompress.java create mode 100644 src/main/java/org/openmainframeproject/tersedecompress/TerseDecompresser.java create mode 100644 src/main/java/org/openmainframeproject/tersedecompress/TerseHeader.java create mode 100644 src/main/java/org/openmainframeproject/tersedecompress/TreeRecord.java create mode 100644 src/test/java/org/openmainframeproject/tersedecompress/AppTest.java create mode 160000 test-data create mode 100644 tests/README.md diff --git a/.gitignore b/.gitignore index a1c2a23..30ed173 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,77 @@ + +# Created by https://www.gitignore.io/api/java,maven,eclipse +# Edit at https://www.gitignore.io/?templates=java,maven,eclipse + +### Eclipse ### +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# CDT- autotools +.autotools + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Annotation Processing +.apt_generated/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +### Eclipse Patch ### +# Eclipse Core +.project + +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# Annotation Processing +.apt_generated + +.sts4-cache/ + +### Java ### # Compiled class file *.class @@ -21,3 +95,18 @@ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* + +### Maven ### +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +.mvn/wrapper/maven-wrapper.jar +.flattened-pom.xml + +# End of https://www.gitignore.io/api/java,maven,eclipse \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..13273c8 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "test-data"] + path = test-data + url = git@github.com:openmainframeproject/tersedecompress-testdata.git diff --git a/README.md b/README.md index 6fe082b..2d41f3c 100644 --- a/README.md +++ b/README.md @@ -1,66 +1,69 @@ # tersedecompress -TerseDecompress is a java program that can be used to decompress files which have been previously compressed on an IBM Mainframe using the TERSE / AMATERSE program (on IBM z/OS or IBM z/VM). - - - -Description: - -============ TerseDecompress is a java program that can be used to decompress files - which have been previously compressed on an IBM Mainframe - using the TERSE / AMATERSE program (on IBM z/OS or IBM z/VM). -Purpose & benefit: - -================== +## Purpose & benefit ## As java programs can virtually run on any platform / operating system - with a JVM, this java decompression program can be very useful if you - don't have access to an IBM mainframe but need to analyze or process a - file which had been compressed on a IBM mainframe using TERSE on IBM z/OS. - With this java version of TerseDecompress, you can decompress those tersed - IBM Mainframe files on any workstation or laptop etc. that supports Java. -How to run it: +## Updates ## -============== +**Version 5: March 2021** -TerseDecompress will uncompress a file that was compressed using the TERSE +- Support for variable length binary records. Variable length records processed in binary mode will be prefixed with a 4 byte field in the same format as the IBM RDW i.e. 2 byte record length field (including RDW length, big-endian) followed by 2 bytes of zeros. -program on IBM z/OS. +## How to run it ## -For execution, TerseDecompress needs a JVM runtime environment +For execution, TerseDecompress needs a JVM runtime environment. -Usage: "TerseDecompress [-b]" +Usage: - Default mode is text mode, which will attempt ebcdic -> ASCII conversion +```java -jar tersedecompress-5.0.0.jar [-b] tersed-file output-file``` - The -b flag turns on binary mode, no conversion will be attempted +Default mode is text mode, which will attempt EBCDIC -> ASCII conversion. -How to build it: +The **-b** flag turns on binary mode, no conversion will be attempted. -================ +**Recommendation:** Use binary mode if possible. EBCDIC->ASCII conversion is not a lossless process, unless the data strictly contains only characters that are common to both code pages used for the translation, **and** the original data does not contain line ending characters. -For compiling the java source code, a JDK is required +## How to build it ## -Build command: "javac TerseDecompress.java" +To build tersedecompress you need the Java JDK and Apache Maven. -How to report problems and get support/help: +In the project directory (the directory containing **pom.xml**): -============================================ +```mvn clean package``` -If you have a problem / need help with TerseDecompress.java, please create a GitHub issue. +## Unit Tests ## + +The project contains unit tests to verify that the decompression is correct. + +Due to the size of the test data, it is stored in a separate git repository and referenced via a submodule. The test data is not required to build tersedecompress, unless you want to run the unit tests. + +The test data can be found here: +[https://github.com/openmainframeproject/tersedecompress-testdata](https://github.com/openmainframeproject/tersedecompress-testdata) + +Descriptions of the data are here: +[https://github.com/openmainframeproject/tersedecompress-testdata/tree/master/tests](https://github.com/openmainframeproject/tersedecompress-testdata/tree/master/tests) -You can also send an email to: clientcenter@de.ibm.com or contact us on Slack at https://slack.openmainframeproject.org channel #tersedecompress -Please do not send code changes, but explain in a mail +### Building with Unit Tests ### + +1. Initialize (download) the submodule containing the test data: +```git submodule update --init``` +2. Build with unit tests: +```mvn -DskipTests=false clean package``` + +## How to report problems and get support/help ## + +If you have a problem / need help with TerseDecompress.java, please create a GitHub issue. + +There is also a Slack channel: [https://slack.openmainframeproject.org](https://slack.openmainframeproject.org) channel #tersedecompress -what you want TerseDecompress to do. diff --git a/TerseDecompress.java b/TerseDecompress.java deleted file mode 100644 index c8b3a4a..0000000 --- a/TerseDecompress.java +++ /dev/null @@ -1,1112 +0,0 @@ -/** - Copyright Contributors to the TerseDecompress Project. - SPDX-License-Identifier: Apache-2.0 -**/ -/*****************************************************************************/ -/* Copyright 2018 IBM Corp. */ -/* */ -/* Licensed under the Apache License, Version 2.0 (the "License"); */ -/* you may not use this file except in compliance with the License. */ -/* You may obtain a copy of the License at */ -/* */ -/* http://www.apache.org/licenses/LICENSE-2.0 */ -/* */ -/* Unless required by applicable law or agreed to in writing, software */ -/* distributed under the License is distributed on an "AS IS" BASIS, */ -/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.*/ -/* See the License for the specific language governing permissions and */ -/* limitations under the License. */ -/*****************************************************************************/ -/* */ -/* For problems and requirements please create a GitHub issue */ -/* or contact: clientcenter@de.ibm.com */ -/* */ -/*****************************************************************************/ -/* */ -/* Author: Iain Lewis August 2004 (version 3) */ -/* */ -/*****************************************************************************/ -/* Version 4 with editorial changes for publication as open source code */ -/* Klaus Egeler, Boris Barth (clientcenter@de.ibm.com) */ -/*****************************************************************************/ - - - - -import java.io.*; - - -public class TerseDecompress { - - static public final boolean Debug = false; /* Control output of debug messages */ - - /* - * These appear to be control parameters to control i/o buffers, stack size etc - * and hence will affect how much memory we use. The only ones actually used - * in the current implementation are TreeSize and RecordMark - */ - static public final int StackSize = 0x07FF; /* 2k - 1 */ - static public final int BufferSize = 0x07FF; /* 2k - 1 */ - static public final int HashSize = 0x0FFF; /* 4k - 1 */ - static public final int TreeSize = 0x1000; /* 4k */ - static public final int RecordMark = 257; /*used in the file output functions*/ - - /* - Some Used in the unspack algorithm. I guess tuning affects the - way which compression/decompression works - Others are just useful constants - */ - static public final int Base = 0; - static public final int CodeSize = 257; /* 2**8+1, EOF, 256 Codepoints, RCM */ - static public final int EndOfFile =0; - /* This is the new line char that we write into the file. Not sure how - * this will translate on the various platforms - */ - static final char EOL = '\n'; - - /*Useful Constants*/ - static final int None = -1; - - - int [] Father = new int[TreeSize]; - int [] CharExt = new int[TreeSize]; - int [] Backward = new int[TreeSize]; - int [] Forward = new int[TreeSize]; - - File InputFile; - FileInputStream InputFileStream; - BufferedInputStream BufferedStream; - - File OutputFile; - FileOutputStream OutputFileStream; - BufferedOutputStream OutputBufferedStream; - - static final String DetailedHelp = new String( - "Usage: \"TerseDecompress [-b]\"\n\n" - +"Java TerseDecompress will decompress a file compressed using the terse program on z/OS\n" - +"Default mode is text mode, which will attempt ebcdic -> ASCII conversion\n" - +"The -b flag turns on binary mode, no conversion will be attempted\n" - +"Please mail comments/suggestions to: clientcenter@de.ibm.com\n" - ); - - static final String Version = new String ("version 4, December 2018"); - - - boolean ExamineFlag = false; /* display contents of tersed file header */ - boolean FixedFlag = false; /* host compatibility fixed block length */ - boolean HelpFlag = false; /* true when extended help is requested */ - boolean InfoFlag = false; /* true when statistics are requested */ - boolean QuietFlag = false; /* true when quiet is selected */ - boolean VariableFlag = true ; /* true for variable-length records */ - int XlateTableEbc = 37 ; /* ebcdic code page */ - int XlateTableAsc = 437 ; /* ascii code page */ - boolean XlateTableDef = true ; /* use default ALMCOPY table */ - long OutputPhase = 0 ; /* position in fixed-length output record */ - long OutputTotal = 0 ; /* total number of bytes */ - long RecordLength = 256 ; /* host perspective record length */ - - - - /* - * Default mapping tables for ascii to ebcdic conversions. The values actually used in the code are - * EbcToAsc and vice versa, so assign them below. If we use alternative conversion tables then this - * needs to be done dynamically. (Not implemented) - * It looks like the default tables are the ones used to generate the alternative tables for different - * locales. The alm tables are the ones we use when nothing else is specified. - */ - - static final int EbcToAscDef[] = { - 0x00,0x01,0x02,0x03,0xCF,0x09,0xD3,0x7F,0xD4,0xD5,0xC3,0x0B,0x0C,0x0D,0x0E,0x0F, - 0x10,0x11,0x12,0x13,0xC7,0xB4,0x08,0xC9,0x18,0x19,0xCC,0xCD,0x83,0x1D,0xD2,0x1F, - 0x81,0x82,0x1C,0x84,0x86,0x0A,0x17,0x1B,0x89,0x91,0x92,0x95,0xA2,0x05,0x06,0x07, - 0xE0,0xEE,0x16,0xE5,0xD0,0x1E,0xEA,0x04,0x8A,0xF6,0xC6,0xC2,0x14,0x15,0xC1,0x1A, - 0x20,0xA6,0xE1,0x80,0xEB,0x90,0x9F,0xE2,0xAB,0x8B,0x9B,0x2E,0x3C,0x28,0x2B,0x7C, - 0x26,0xA9,0xAA,0x9C,0xDB,0xA5,0x99,0xE3,0xA8,0x9E,0x21,0x24,0x2A,0x29,0x3B,0x5E, - 0x2D,0x2F,0xDF,0xDC,0x9A,0xDD,0xDE,0x98,0x9D,0xAC,0xBA,0x2C,0x25,0x5F,0x3E,0x3F, - 0xD7,0x88,0x94,0xB0,0xB1,0xB2,0xFC,0xD6,0xFB,0x60,0x3A,0x23,0x40,0x27,0x3D,0x22, - 0xF8,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x96,0xA4,0xF3,0xAF,0xAE,0xC5, - 0x8C,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,0x70,0x71,0x72,0x97,0x87,0xCE,0x93,0xF1,0xFE, - 0xC8,0x7E,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0xEF,0xC0,0xDA,0x5B,0xF2,0xF9, - 0xB5,0xB6,0xFD,0xB7,0xB8,0xB9,0xE6,0xBB,0xBC,0xBD,0x8D,0xD9,0xBF,0x5D,0xD8,0xC4, - 0x7B,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0xCB,0xCA,0xBE,0xE8,0xEC,0xED, - 0x7D,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,0x50,0x51,0x52,0xA1,0xAD,0xF5,0xF4,0xA3,0x8F, - 0x5C,0xE7,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0xA0,0x85,0x8E,0xE9,0xE4,0xD1, - 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0xB3,0xF7,0xF0,0xFA,0xA7,0xFF - }; - - static final int AscToEbcDef[] = { - 0x00,0x01,0x02,0x03,0x37,0x2D,0x2E,0x2F,0x16,0x05,0x25,0x0B,0x0C,0x0D,0x0E,0x0F, - 0x10,0x11,0x12,0x13,0x3C,0x3D,0x32,0x26,0x18,0x19,0x3F,0x27,0x22,0x1D,0x35,0x1F, - 0x40,0x5A,0x7F,0x7B,0x5B,0x6C,0x50,0x7D,0x4D,0x5D,0x5C,0x4E,0x6B,0x60,0x4B,0x61, - 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0x7A,0x5E,0x4C,0x7E,0x6E,0x6F, - 0x7C,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6, - 0xD7,0xD8,0xD9,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xAD,0xE0,0xBD,0x5F,0x6D, - 0x79,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x91,0x92,0x93,0x94,0x95,0x96, - 0x97,0x98,0x99,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xC0,0x4F,0xD0,0xA1,0x07, - 0x43,0x20,0x21,0x1C,0x23,0xEB,0x24,0x9B,0x71,0x28,0x38,0x49,0x90,0xBA,0xEC,0xDF, - 0x45,0x29,0x2A,0x9D,0x72,0x2B,0x8A,0x9A,0x67,0x56,0x64,0x4A,0x53,0x68,0x59,0x46, - 0xEA,0xDA,0x2C,0xDE,0x8B,0x55,0x41,0xFE,0x58,0x51,0x52,0x48,0x69,0xDB,0x8E,0x8D, - 0x73,0x74,0x75,0xFA,0x15,0xB0,0xB1,0xB3,0xB4,0xB5,0x6A,0xB7,0xB8,0xB9,0xCC,0xBC, - 0xAB,0x3E,0x3B,0x0A,0xBF,0x8F,0x3A,0x14,0xA0,0x17,0xCB,0xCA,0x1A,0x1B,0x9C,0x04, - 0x34,0xEF,0x1E,0x06,0x08,0x09,0x77,0x70,0xBE,0xBB,0xAC,0x54,0x63,0x65,0x66,0x62, - 0x30,0x42,0x47,0x57,0xEE,0x33,0xB6,0xE1,0xCD,0xED,0x36,0x44,0xCE,0xCF,0x31,0xAA, - 0xFC,0x9E,0xAE,0x8C,0xDD,0xDC,0x39,0xFB,0x80,0xAF,0xFD,0x78,0x76,0xB2,0x9F,0xFF - }; - - /*Alternative ascii to ebcdic conversion tables but they appear to be the ones actually used*/ - static final int EbcToAscAlmcopy[] = { - 0x00,0x01,0x02,0x03,0xCF,0x09,0xD3,0x7F,0xD4,0xD5,0xC3,0x0B,0x0C,0x0D,0x0E,0x0F, - 0x10,0x11,0x12,0x13,0xC7,0xB4,0x08,0xC9,0x18,0x19,0xCC,0xCD,0x83,0x1D,0xD2,0x1F, - 0x81,0x82,0x1C,0x84,0x86,0x0A,0x17,0x1B,0x89,0x91,0x92,0x95,0xA2,0x05,0x06,0x07, - 0xE0,0xEE,0x16,0xE5,0xD0,0x1E,0xEA,0x04,0x8A,0xF6,0xC6,0xC2,0x14,0x15,0xC1,0x1A, - 0x20,0xA6,0xE1,0x80,0xEB,0x90,0x9F,0xE2,0xAB,0x8B,0x9B,0x2E,0x3C,0x28,0x2B,0x7C, - 0x26,0xA9,0xAA,0x9C,0xDB,0xA5,0x99,0xE3,0xA8,0x9E,0x21,0x24,0x2A,0x29,0x3B,0x5E, - 0x2D,0x2F,0xDF,0xDC,0x9A,0xDD,0xDE,0x98,0x9D,0xAC,0xBA,0x2C,0x25,0x5F,0x3E,0x3F, - 0xD7,0x88,0x94,0xB0,0xB1,0xB2,0xFC,0xD6,0xFB,0x60,0x3A,0x23,0x40,0x27,0x3D,0x22, - 0xF8,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x96,0xA4,0xF3,0xAF,0xAE,0xC5, - 0x8C,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,0x70,0x71,0x72,0x97,0x87,0xCE,0x93,0xF1,0xFE, - 0xC8,0x7E,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0xEF,0xC0,0xDA,0x5B,0xF2,0xF9, - 0xB5,0xB6,0xFD,0xB7,0xB8,0xB9,0xE6,0xBB,0xBC,0xBD,0x8D,0xD9,0xBF,0x5D,0xD8,0xC4, - 0x7B,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0xCB,0xCA,0xBE,0xE8,0xEC,0xED, - 0x7D,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,0x50,0x51,0x52,0xA1,0xAD,0xF5,0xF4,0xA3,0x8F, - 0x5C,0xE7,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0xA0,0x85,0x8E,0xE9,0xE4,0xD1, - 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0xB3,0xF7,0xF0,0xFA,0xA7,0xFF - }; - - static final int AscToEbcAlmcopy[] = { - 0x00,0x01,0x02,0x03,0x37,0x2D,0x2E,0x2F,0x16,0x05,0x25,0x0B,0x0C,0x0D,0x0E,0x0F, - 0x10,0x11,0x12,0x13,0x3C,0x3D,0x32,0x26,0x18,0x19,0x3F,0x27,0x22,0x1D,0x35,0x1F, - 0x40,0x5A,0x7F,0x7B,0x5B,0x6C,0x50,0x7D,0x4D,0x5D,0x5C,0x4E,0x6B,0x60,0x4B,0x61, - 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0x7A,0x5E,0x4C,0x7E,0x6E,0x6F, - 0x7C,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6, - 0xD7,0xD8,0xD9,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xAD,0xE0,0xBD,0x5F,0x6D, - 0x79,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x91,0x92,0x93,0x94,0x95,0x96, - 0x97,0x98,0x99,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xC0,0x4F,0xD0,0xA1,0x07, - 0x43,0x20,0x21,0x1C,0x23,0xEB,0x24,0x9B,0x71,0x28,0x38,0x49,0x90,0xBA,0xEC,0xDF, - 0x45,0x29,0x2A,0x9D,0x72,0x2B,0x8A,0x9A,0x67,0x56,0x64,0x4A,0x53,0x68,0x59,0x46, - 0xEA,0xDA,0x2C,0xDE,0x8B,0x55,0x41,0xFE,0x58,0x51,0x52,0x48,0x69,0xDB,0x8E,0x8D, - 0x73,0x74,0x75,0xFA,0x15,0xB0,0xB1,0xB3,0xB4,0xB5,0x6A,0xB7,0xB8,0xB9,0xCC,0xBC, - 0xAB,0x3E,0x3B,0x0A,0xBF,0x8F,0x3A,0x14,0xA0,0x17,0xCB,0xCA,0x1A,0x1B,0x9C,0x04, - 0x34,0xEF,0x1E,0x06,0x08,0x09,0x77,0x70,0xBE,0xBB,0xAC,0x54,0x63,0x65,0x66,0x62, - 0x30,0x42,0x47,0x57,0xEE,0x33,0xB6,0xE1,0xCD,0xED,0x36,0x44,0xCE,0xCF,0x31,0xAA, - 0xFC,0x9E,0xAE,0x8C,0xDD,0xDC,0x39,0xFB,0x80,0xAF,0xFD,0x78,0x76,0xB2,0x9F,0xFF - }; - - /* - static int EbcToAsc[] = EbcToAscDef; - static int AscToEbc[] = AscToEbcDef; - */ - int EbcToAsc[] = EbcToAscAlmcopy; - int AscToEbc[] = AscToEbcAlmcopy; - - - /* - * These appear to be the flags for the terse file header. Some are used by - * CheckHeader() others are only used when writing a compressed file which - * this implementation doesn't do. - */ - static final int FlagUndef = 0x80; /* \ */ - static final int FlagCC1 = 0x40; /* \ */ - static final int FlagCC2 = 0x20; /* \ */ - static final int FlagVBS = 0x10; /* >-- values of Flags bits in HeaderRecord type */ - static final int FlagVS = 0x08; /* / */ - static final int FlagMVS = 0x04; /* / */ - static final int FlagRbits = 0x03; /* / */ - - /*Defaults for dump types*/ - boolean TextFlag = true; - boolean HostFlag = true; - boolean SpackFlag = true; - boolean DecodeFlag = false; - boolean EncodeFlag = false; /* true when encoding is selected */ - - - /* - * Data structure used only when checking the header initially - */ - - public class TerseHeader { - - public int VersionFlag; - public int VariableFlag; - public int RecordLen1; - public int Flags; - public int Ratio; - public int BlockSize; - public long RecordLen2; - - - public String toString() { - - return new String ( - "\n" - +"Version flag is " + VersionFlag +"\n" - +"Variable Flag is " + VariableFlag +"\n" - +"RecordLen1 is " + RecordLen1 +"\n" - +"Flags are " + Flags +"\n" - +"Ratio is " + Ratio +"\n" - +"Block Size is " + BlockSize +"\n" - +"RecordLen2 is " + RecordLen2 +"\n" - ); - - } - - } - - /* - * Check that the header of an input tersed file is consistent and set some of the static flags - * associated with it. - */ - - public boolean CheckHeader(File InFile) { - - TerseHeader Header = new TerseHeader(); - - FileInputStream filestream; - DataInputStream datastream; - - try { - - filestream = new FileInputStream(InFile); - datastream = new DataInputStream(filestream); - - Header.VersionFlag = datastream.readUnsignedByte(); - Header.VariableFlag = datastream.readUnsignedByte(); - Header.RecordLen1 = datastream.readUnsignedShort(); - Header.Flags = datastream.readUnsignedByte(); - Header.Ratio = datastream.readUnsignedByte(); - Header.BlockSize = datastream.readUnsignedShort(); - Header.RecordLen2 = readUnsignedInt(datastream); - datastream.close(); - - } catch (Exception e) { - System.err.println("Error while reading header from input file"); - System.err.println(e.getMessage()); - e.printStackTrace(); - System.exit(1); - } - - if (Debug) { - System.out.println("Header is:\n" +Header); - } - - switch (Header.VersionFlag) { - case 0x01: /* native binary mode, 4 byte header, versions 1.2+ */ - if (Header.VariableFlag != 0x89) - return false; - if (Lo( Header.RecordLen1) != 0x69) - return false; - if (Hi( Header.RecordLen1) != 0xA5) - return false; - if (HostFlag) { - if (TextFlag) { /* defaults */ - HostFlag = false; /* autoswitch to native mode */ - TextFlag = false; - } else { - return false; /* header conflicts with explicit "/b" switch */ - } - } - break; - - case 0x02: /* host PACK compatibility mode, 12 byte header */ - SpackFlag = false; - if ((Header.VariableFlag != 0x00) && (Header.VariableFlag != 0x01)) - return false; - if ( (Header.RecordLen1 == 0) ^ (Header.RecordLen2 != 0)) - return false; - if ((Header.Flags & FlagMVS) == 0) { - if ( Header.Flags != 0) return false; - if ( Header.Ratio != 0) return false; - if (Header.BlockSize != 0) return false; - } - if (!HostFlag) { - return false; /* header conflicts with explicit "-n" switch */ - } - break; - - case 0x05: /* host SPACK compatibility mode, 12 byte header */ - if ((Header.VariableFlag != 0x00) && (Header.VariableFlag != 0x01)) - return false; - if ( (Header.RecordLen1 == 0) ^ (Header.RecordLen2 != 0) ) - return false; - if ((Header.Flags & FlagMVS) == 0) { - if ( Header.Flags != 0) - return false; - if ( Header.Ratio != 0) - return false; - if (Header.BlockSize != 0) - return false; - } - if (!HostFlag) { - return false; /* header conflicts with explicit "-n" switch */ - } - break; - - case 0x07: /* native binary mode, 4 byte header, versions 1.1- */ - if (Header.VariableFlag != 0x89) - return false; - if (Lo( Header.RecordLen1 ) != 0x69) - return false; - if (Hi( Header.RecordLen1 ) != 0xA5) - return false; - if (HostFlag) { - if (TextFlag) { /* defaults */ - HostFlag = false; /* autoswitch to native mode */ - TextFlag = false; - } else { - return false; /* header conflicts with explicit "/b" switch */ - } - } - break; - default: - return false; - } - - return true; - - } - - - /* - * Reads a 32 bit unsigned value from the Input stream and puts it into a long variable. - * Only used by CheckHeader(). - */ - public long readUnsignedInt(InputStream stream) throws IOException { - - long rv =0 ; - long temp; - - for (int i=0; i < 4; i++) { - int bytes = stream.read(); - if (bytes < 0) { - throw new EOFException("End of File"); - } - temp = ((long)bytes) << ((3-i)*8); - rv = rv + temp; - } - return rv; - } - - - /* - * Couple of utility methods to do some bit manipulation. - * Returns the lowest 8 bits of an 16bit value stored in an int - */ - public int Lo (int value) { - return (value & 0xFF) ; - } - - - /* - * Returns the Hi 8 bits of a 16 bit value stored in an int. - */ - public int Hi(int value) { - value = value & 0xFF00; - value = value >>>8; - return value; - } - - - /* - * If condition is false, print a string to stderr and exit - */ - public void AssertString(String message, boolean condition) { - if (!condition) { - System.err.println(message); - System.exit(1); - } - } - - - - - /* A list of input masks for use by FilePut() and FileGetRequired */ - static final int Mask[] = { - 0, - 0x0001, 0x0002, 0x0004, 0x0008, - 0x0010, 0x0020, 0x0040, 0x0080, - 0x0100, 0x0200, 0x0400, 0x0800, - 0x1000, 0x2000, 0x4000, 0x8000, - 0x10000, 0x20000, 0x40000, 0x80000, - 0x100000, 0x200000, 0x400000, 0x800000, - 0x1000000, 0x2000000, 0x4000000, 0x8000000, - 0x10000000,0x20000000,0x40000000,0x80000000, - }; - - - - /* - * Read Bits number of bits. Assumes we will never read be asked for more - * than 16 bits of data. Returns them in the bottom of the returned int. - * If we hit an io exception then we are probably stuffed anyway, as - * we shouldn't get an EOF exception from any of the read methods that are used, so - * exit with an error message. - * Do need to take account of what happens when we get to the end of the file? - */ - - long buffer = 0; /*Used as an input buffer*/ - int index = 0; /*The number of bits currently in buffer*/ - long temp =0; - boolean endOfInput = false; - long data; - int red=0; - - public int FileGetRequired(int Bits, InputStream stream) { - - try { - - if (index < 16) { - /*We have less than 16 bits in the buffer so read in 16 more*/ - temp = 0; - temp = stream.read(); - red = red +1; - if (temp != -1) { - buffer = buffer << 8; - buffer = buffer | (temp & 0xFF); - index = index +8; - } else { - endOfInput = true; - } - - temp = 0; - temp = stream.read(); - red = red +1; - if (temp != -1) { - buffer = buffer << 8; - buffer = buffer | (temp & 0xFF); - index = index +8; - } else { - endOfInput = true; - } - } - - if (endOfInput && (index < Bits)) { - /* We have reached the end of the file and don't have enough - * data to satisfy the request - */ - return 0; - } - - data =0; - /*Loop until we have filled data with the number of bits requested*/ - while (Bits > 0) { - - /*Read in 1 byte into data*/ - if (Bits >7) { - temp =0; - /*make room in the bottom of data for another byte*/ - data = data << 8; - /*take the top byte of buffer*/ - temp = 0xFF & (buffer >>> (index-8)); - /*copy it into the bottom of data*/ - data = data | (temp & 0xFF); - /*update the various numbers*/ - index = index -8; - Bits = Bits - 8; - } - - /*Add one bit at at time to the bottom of data*/ - else if (Bits <8) { - temp = 0; - /*get the top bit of buffer to the bottom of temp*/ - temp = buffer & (long)(Mask[index]); - temp = temp >>> (index-1); - /*make space in the bottom of data and insert the new bit*/ - data = data <<1; - data = data | temp; - /*Update the input numbers*/ - index = index -1; - Bits = Bits - 1; - } - - } - } catch (IOException e) { - System.err.println("Unable to read from input file. Caught IOException:"); - System.err.println(e.getMessage()); - e.printStackTrace(); - System.exit(1); - } - - return (int)data; - } - - - /* - * Read in 12 bits of data, and put them in the bottom of the returned int - */ - - int GetBlok(InputStream stream) { - return FileGetRequired(12, stream); - } - - - - /* - * Write Bits number of bits from the bottom of Value to the output file - * This assumes that we are never asked to write more than 16 bits. - * Returns true if the requested number of bits was written, - * If we catch an exception while writing, exit, as we are stuffed. - * Need to clean up the output file on exceptions - * Uses a global byte OutputValue, and only writes when this is full - */ - - int OutputValue = 0 ; /* current output byte */ - int OutputMask = 0x80 ; /* mask to write next bit to */ - - public boolean FilePutRequired (int Bits, int Value, OutputStream stream) { - while (Bits > 0) { - if ((Bits > 7) && (OutputMask == 0x80)) { - OutputValue = ((Value >>> (Bits - 8)) & 0xFF); - - OutputTotal++; - try { - stream.write(OutputValue); - } catch (IOException e) { - System.err.println("Error while writing to output file: " + OutputFile); - System.err.println(e.getMessage()); - e.printStackTrace(); - System.exit(1); - } - - Bits = Bits - 8; - - } else { - if ((Value & Mask[Bits]) != 0) { - OutputValue = OutputValue | OutputMask; - } - if (OutputMask == 0x01) { - try { - stream.write(OutputValue); - } catch (IOException e) { - System.err.println("Error while writing to output file: " + OutputFile); - System.err.println(e.getMessage()); - e.printStackTrace(); - System.exit(1); - } - } else { - OutputMask = OutputMask >>> 1; - } - Bits--; - } - } - return true; - } - - - /* Write a new line to the output file*/ - /* This only works when a new line is a single /n. Which it may not be on Windows */ - public void PutNewline(OutputStream stream) { - FilePutRequired(8, (int)EOL, stream); - return; - } - - - /* - * Write some stuff to the output file - */ - - public void PutChar(int X, OutputStream stream) { - if (X == 0) { - if (HostFlag && TextFlag && VariableFlag) { - PutNewline(stream); - } - } else { - if (HostFlag && TextFlag) { - if (VariableFlag) { - if (X == RecordMark) { - PutNewline(stream); - } else { - FilePutRequired( 8, EbcToAsc[X-1], stream); - } - } else { - FilePutRequired( 8, EbcToAsc[X-1], stream); - OutputPhase++; - if (OutputPhase == RecordLength) { - PutNewline(stream); - OutputPhase = 0; - } - } - } else { - if (X < RecordMark) { /* discard record marks */ - FilePutRequired( 8, X-1, stream); - } - } - } - } - - /* - * Put multiple chars to the output file - * It looks like the logic is connected to the logic of the spack output - * code, so probably can't be separated. X is some kind of indication of - * how much data to write, but the data is taken directly from Tree - * The stack isn't initialized each time, as it looks like only bits we - * have written to on this iteration will be read from - */ - public class StackType { - int Head; - int Data[] = new int[StackSize+1]; - } - - StackType Stack = new StackType(); - - - public void PutChars(int X, OutputStream outstream) { - Stack.Head = 0; - - while (true) { - while (X > CodeSize) { - Stack.Head++; - Stack.Data[Stack.Head] = Tree[X].Right; - X = Tree[X].Left; - } - PutChar( X, outstream); - - if (Stack.Head > 0) { - X = Stack.Data[Stack.Head]; - Stack.Head--; - } else - break; - } - - } - - - /*Inner classe and globals used as data structures for spack decode*/ - /* Might be possible to wrap the Tree structure in a class along with - * its initializer method, which would be cleaner, but unnecessarily anal?? - */ - public class TreeRecord { - - int Left; - int Right; - int Back; - int NextCount; - } - - int TreeAvail; - - TreeRecord Tree[] = new TreeRecord[TreeSize+1]; - - - public void TreeInit() { - - for (int i =0; i < Tree.length; i ++) { - Tree[i] = new TreeRecord(); - } - - int init_index = Base; - while (init_index <= CodeSize) { - Tree[init_index].Left = None; - Tree[init_index].Right = init_index++; - } - for (init_index = CodeSize+1; init_index <= TreeSize-1; init_index++) { - Tree[init_index].NextCount = init_index+1; - Tree[init_index].Left = None; - Tree[init_index].Right = None; - } - Tree[TreeSize].NextCount = None; - Tree[Base].NextCount = Base; - Tree[Base].Back = Base; - for (init_index = 1; init_index <= CodeSize; init_index++) { - Tree[init_index].NextCount = None; - } - TreeAvail = CodeSize+1; - } - - - /* - * The following methods are all used by the spack decode algorithm - * The precise use of all of them is unknown!! - */ - - int lru_p = 0, lru_q = 0, lru_r = 0; - - public void LruKill() { - lru_p = Tree[0].NextCount; - lru_q = Tree[lru_p].NextCount; - lru_r = Tree[lru_p].Back; - Tree[lru_q].Back = lru_r; - Tree[lru_r].NextCount = lru_q; - DeleteRef(Tree[lru_p].Left); - DeleteRef(Tree[lru_p].Right); - Tree[lru_p].NextCount = TreeAvail; - TreeAvail = lru_p; - } - - public void DeleteRef(int dref) { - if (Tree[dref].NextCount == -1) { - LruAdd(dref); - } else { - Tree[dref].NextCount++; - } - } - - - int lru_back=0; - - public void LruAdd(int lru_next) { - lru_back = Tree[Base].Back; - Tree[lru_next].NextCount = Base; - Tree[Base].Back = lru_next; - Tree[lru_next].Back = lru_back; - Tree[lru_back].NextCount = lru_next; - } - - - int node =0; - - public int GetTreeNode() { - node = TreeAvail; - TreeAvail = Tree[node].NextCount; - return node; - } - - - int forwards = 0, prev = 0; - - public void BumpRef(int bref) { - if (Tree[bref].NextCount < 0) { - Tree[bref].NextCount--; - } else { - forwards = Tree[bref].NextCount; - prev = Tree[bref].Back; - Tree[prev].NextCount = forwards; - Tree[forwards].Back = prev; - Tree[bref].NextCount = -1; - } - } - - - /* - * Decode logic for a file compressed with the spack algorithm - * Inputstream should wrap the compressed data, outputstream is where we write - * the decompressed data to. - */ - - public void Decode1( InputStream stream, OutputStream outstream) { - - if (Debug) { - System.out.println("Text Flag is: " + TextFlag); - System.out.println("Host Flag is: " + HostFlag); - System.out.println("Spack Flag is: " + SpackFlag); - System.out.println("Decode Flag is: " + DecodeFlag); - System.out.println("Encode Flag is: " + EncodeFlag); - System.out.println("Examine Flag is: " + ExamineFlag); - System.out.println("Fixed Flag is: " + FixedFlag); - System.out.println("Variable Flag is: " + VariableFlag); - System.out.println("Help Flag is: " + HelpFlag); - } - - - - TreeAvail = 0; - int N = 0, G = 0, H = 0; - int H1 = 0, H2 = 0, H3 = 0, H4 = 0, H5 = 0, H6 = 0, H7 = 0; - if (HostFlag) { /* examine vm header */ - H1 = FileGetRequired(8, stream); /* terse version */ - H2 = FileGetRequired(8, stream); /* variable-length record flag */ - H3 = FileGetRequired(16, stream); /* record length */ - H4 = FileGetRequired(16, stream); /* filler */ - H5 = FileGetRequired(16, stream); /* filler */ - H6 = FileGetRequired(16, stream); /* filler */ - H7 = FileGetRequired(16, stream); /* filler */ - AssertString( "Invalid File Header: Terse Version Flag", H1 == 5); - AssertString( "Invalid File Header: Fixed/Variable Block Flag", (H2 == 0) || (H2 == 1)); - if (H3 == 0) { - AssertString( "Invalid File Header: Zero Record Length", (H6 != 0) || (H7 != 0)); - } else { - AssertString( "Invalid File Header: Ambiguous Record Length", (H6 == 0) && (H7 == 0)); - } - if ((H4 & 0x0400) == 0) { - AssertString( "Invalid File Header: Non-Zero Filler 1", H4 == 0); - AssertString( "Invalid File Header: Non-Zero Filler 2", H5 == 0); - } - VariableFlag = (H2 == 1); - if (H3 == 0) { - RecordLength = ((long) H6 << 16) | (long) H7; - } else { - RecordLength = (long) H3; - } - } else { - H1 = FileGetRequired(8, stream); /* terse version */ - H2 = FileGetRequired(8, stream); /* validation flag 1 */ - H3 = FileGetRequired(8, stream); /* validation flag 2 */ - H4 = FileGetRequired(8, stream); /* validation flag 3 */ - AssertString( "Invalid File Header: Terse Version Flag" , (H1 == 1) || (H1 == 7)); - AssertString( "Invalid File Header: Validation Flag One" , H2 == 0x89); - AssertString( "Invalid File Header: Validation Flag Two" , H3 == 0x69); - AssertString( "Invalid File Header: Validation Flag Three", H4 == 0xA5); - } - - TreeInit(); - Tree[TreeSize-1].NextCount = None; - - H = GetBlok(stream); - PutChars( H, outstream); - - while (H != EndOfFile) { - - G = GetBlok(stream); - if (TreeAvail == None) { - LruKill(); - } - PutChars(G, outstream); - N = GetTreeNode(); - Tree[N].Left = H; - Tree[N].Right = G; - BumpRef(H); - BumpRef(G); - LruAdd(N); - H = G; - } - - } - - - /* - * Decode the file on the other end of the input stream using a non spack decode. - * Write the output to the output stream. - * Assume that both streams are initialized and ready to be read from/written to. - */ - public void Decode2(InputStream stream, OutputStream outstream) { - - if (Debug) { - System.out.println("Decode2"); - } - - if (Debug) { - System.out.println("Text Flag is: " + TextFlag); - System.out.println("Host Flag is: " + HostFlag); - System.out.println("Spack Flag is: " + SpackFlag); - System.out.println("Decode Flag is: " + DecodeFlag); - System.out.println("Encode Flag is: " + EncodeFlag); - System.out.println("Examine Flag is: " + ExamineFlag); - System.out.println("Fixed Flag is: " + FixedFlag); - System.out.println("Variable Flag is: " + VariableFlag); - System.out.println("Help Flag is: " + HelpFlag); - } - - - int H1 = 0, H2 = 0, H3 = 0, H4 = 0, H5 = 0, H6 = 0, H7 = 0; - int x = 0, d = 0, y = 0, q = 0, r = 0, e = 0, p = 0, h = 0; - - H2 = 1 + AscToEbcDef[' ']; - - for (H1 = 258; H1 < 4096; H1++) { - Father [H1] = H2; - CharExt[H1] = 1 + AscToEbcDef[' ']; - H2 = H1; - } - - for (H1 = 258; H1 < 4095; H1++) { - Backward[H1+1] = H1 ; - Forward [H1 ] = H1+1; - } - - Backward [0] = 4095; - Forward [0] = 258; - Backward [258] = 0; - Forward [4095] = 0; - - if (Debug) { - System.out.println("Done setup in Decode2. About to read from the file"); - } - - - H1 = FileGetRequired(8, stream); /* terse version */ - H2 = FileGetRequired(8, stream); /* variable-length record flag */ - H3 = FileGetRequired(16, stream); /* record length */ - H4 = FileGetRequired(16, stream); /* filler */ - H5 = FileGetRequired(16, stream); /* filler */ - H6 = FileGetRequired(16, stream); /* filler */ - H7 = FileGetRequired(16, stream); /* filler */ - - - if (Debug) { - System.out.println("Have read header info again."); - System.out.println("h1 is " +H1 +" h2 is " +H2 +" h3 is " +H3 +" h4 is " +H4 +" h5 is " +H5 +" h6 is " +H6 +" h7 is " +H7); - } - - - - AssertString( "Invalid File Header: Terse Version Flag", H1 == 2); - AssertString( "Invalid File Header: Fixed/Variable Block Flag", (H2 == 0) || (H2 == 1)); - if (H3 == 0) { - AssertString( "Invalid File Header: Zero Record Length", (H6 != 0) || (H7 != 0)); - } else { - AssertString( "Invalid File Header: Ambiguous Record Length", (H6 == 0) && (H7 == 0)); - } - if ((H4 & 0x0400) == 0) { - AssertString( "Invalid File Header: Non-Zero Filler 1", H4 == 0); - AssertString( "Invalid File Header: Non-Zero Filler 2", H5 == 0); - } - - if (Debug) { - System.out.println("Checked the headers"); - } - - - VariableFlag = (H2 == 1); - if (H3 == 0) { - RecordLength = ( ((long)H6) << 16) | ((long)H7); - } else { - RecordLength = (long)H3; - } - - x=0; - d = GetBlok(stream); - - while (d != 0) { - h = 0; - y = Backward[0]; - q = Backward[y]; - Backward[0] = q; - Forward [q] = 0; - h = y; - p = 0; - while (d > 257) { - q = Forward [d]; - r = Backward[d]; - Forward [r] = q; - Backward[q] = r; - Forward [d] = h; - Backward[h] = d; - h = d; - e = Father[d]; - Father[d] = p; - p = d; - d = e; - } - q = Forward[0]; - Forward [y] = q; - Backward[q] = y; - Forward [0] = h; - Backward[h] = 0; - CharExt [x] = d; - PutChar(d, outstream); - x = y; - while (p != 0) { - e = Father[p]; - PutChar( CharExt[p], outstream); - Father[p] = d; - d = p; - p = e; - } - Father[y] = d; - d = GetBlok(stream); - } - - return; - } - - /* - * Currently working towards a decompress in binary mode only implementation. - * We assume that we get "TerseDecompress ". Otherwise exit with - * an error message. - */ - - public void process (String args[]) { - - if (args.length == 0 || args.length > 3) { - System.out.println(DetailedHelp); - System.out.println(Version); - System.exit(0); - /* - if (args[0].equals("-b")) { - TextFlag = false; - System.out.println("Using binary decompression. No ascii/ebcdic conversion will be performed"); - } else { - System.out.println("Unsupported option: " +args[0]); - } - } else { - System.out.println("Text mode decompression. Ascii/ebcdic conversion will be done"); - */ - } - - if (args.length == 1) { - System.out.println(DetailedHelp); - System.out.println(Version); - System.exit(0); - } - - if(Debug) { - System.out.println("Input args exist. About to check header"); - } - - - boolean header_rv; - /*Apparently need to do this to set the spack flag, but the results are thrown away*/ - header_rv = CheckHeader(new File(args[0])); - - if (!header_rv) { - System.err.println("Failed to read header of input file."); - System.exit(1); - } - - if(Debug) { - System.out.println("Header is checked. About to open streams."); - } - - - try { - InputFile = new File(args[0]); - InputFileStream = new FileInputStream(InputFile); - BufferedStream = new BufferedInputStream(InputFileStream); - - } catch (Exception e) { - System.err.println("Can't open input file: " +InputFile); - System.exit(1); - } - - try { - OutputFile = new File(args[1]); - OutputFileStream = new FileOutputStream(OutputFile); - OutputBufferedStream = new BufferedOutputStream(OutputFileStream); - - } catch (Exception e) { - System.err.println("Can't open output file: " +OutputFile); - System.exit(1); - } - - if (args.length == 3) { - TextFlag = false; - System.out.println("3rd argument (" +args[2] +") found, so using binary mode."); - } - - - if(Debug) { - System.out.println("Input and output streams opened. About to decode"); - } - - System.out.println("Attempting to decompress input file (" +InputFile +") to output file (" +OutputFile +")"); - - if (!SpackFlag) { - Decode2(BufferedStream, OutputBufferedStream); - } else { - Decode1(BufferedStream, OutputBufferedStream); - } - - try { - BufferedStream.close(); - OutputBufferedStream.flush(); - OutputBufferedStream.close(); - } catch (IOException e) { - System.err.println("Error while closing file streams"); - System.err.println(e.getMessage()); - e.printStackTrace(); - } - - System.out.println("Processing completed"); - if (Debug) { - System.err.println("Read " +red +" bytes"); - } - - } - - public static void main (String args[]) { - - TerseDecompress tersed = new TerseDecompress(); - tersed.process(args); - } - -} diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..af9b839 --- /dev/null +++ b/pom.xml @@ -0,0 +1,52 @@ + + 4.0.0 + + org.openmainframeproject.tersedecompress + tersedecompress + 5.0.0 + jar + + tersedecompress + https://github.com/openmainframeproject/tersedecompress-testdata + + + UTF-8 + true + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + + true + org.openmainframeproject.tersedecompress.TerseDecompress + + + + + + + + + junit + junit + 4.13.1 + test + + + diff --git a/src/main/java/org/openmainframeproject/tersedecompress/Constants.java b/src/main/java/org/openmainframeproject/tersedecompress/Constants.java new file mode 100644 index 0000000..c24705e --- /dev/null +++ b/src/main/java/org/openmainframeproject/tersedecompress/Constants.java @@ -0,0 +1,152 @@ +package org.openmainframeproject.tersedecompress; + +class Constants { + + + /* + * Default mapping tables for ascii to ebcdic conversions. The values actually used in the code are + * EbcToAsc and vice versa, so assign them below. If we use alternative conversion tables then this + * needs to be done dynamically. (Not implemented) + * It looks like the default tables are the ones used to generate the alternative tables for different + * locales. The alm tables are the ones we use when nothing else is specified. + */ + + static final int EbcToAscDef[] = { + 0x00,0x01,0x02,0x03,0xCF,0x09,0xD3,0x7F,0xD4,0xD5,0xC3,0x0B,0x0C,0x0D,0x0E,0x0F, + 0x10,0x11,0x12,0x13,0xC7,0xB4,0x08,0xC9,0x18,0x19,0xCC,0xCD,0x83,0x1D,0xD2,0x1F, + 0x81,0x82,0x1C,0x84,0x86,0x0A,0x17,0x1B,0x89,0x91,0x92,0x95,0xA2,0x05,0x06,0x07, + 0xE0,0xEE,0x16,0xE5,0xD0,0x1E,0xEA,0x04,0x8A,0xF6,0xC6,0xC2,0x14,0x15,0xC1,0x1A, + 0x20,0xA6,0xE1,0x80,0xEB,0x90,0x9F,0xE2,0xAB,0x8B,0x9B,0x2E,0x3C,0x28,0x2B,0x7C, + 0x26,0xA9,0xAA,0x9C,0xDB,0xA5,0x99,0xE3,0xA8,0x9E,0x21,0x24,0x2A,0x29,0x3B,0x5E, + 0x2D,0x2F,0xDF,0xDC,0x9A,0xDD,0xDE,0x98,0x9D,0xAC,0xBA,0x2C,0x25,0x5F,0x3E,0x3F, + 0xD7,0x88,0x94,0xB0,0xB1,0xB2,0xFC,0xD6,0xFB,0x60,0x3A,0x23,0x40,0x27,0x3D,0x22, + 0xF8,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x96,0xA4,0xF3,0xAF,0xAE,0xC5, + 0x8C,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,0x70,0x71,0x72,0x97,0x87,0xCE,0x93,0xF1,0xFE, + 0xC8,0x7E,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0xEF,0xC0,0xDA,0x5B,0xF2,0xF9, + 0xB5,0xB6,0xFD,0xB7,0xB8,0xB9,0xE6,0xBB,0xBC,0xBD,0x8D,0xD9,0xBF,0x5D,0xD8,0xC4, + 0x7B,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0xCB,0xCA,0xBE,0xE8,0xEC,0xED, + 0x7D,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,0x50,0x51,0x52,0xA1,0xAD,0xF5,0xF4,0xA3,0x8F, + 0x5C,0xE7,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0xA0,0x85,0x8E,0xE9,0xE4,0xD1, + 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0xB3,0xF7,0xF0,0xFA,0xA7,0xFF + }; + + static final int AscToEbcDef[] = { + 0x00,0x01,0x02,0x03,0x37,0x2D,0x2E,0x2F,0x16,0x05,0x25,0x0B,0x0C,0x0D,0x0E,0x0F, + 0x10,0x11,0x12,0x13,0x3C,0x3D,0x32,0x26,0x18,0x19,0x3F,0x27,0x22,0x1D,0x35,0x1F, + 0x40,0x5A,0x7F,0x7B,0x5B,0x6C,0x50,0x7D,0x4D,0x5D,0x5C,0x4E,0x6B,0x60,0x4B,0x61, + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0x7A,0x5E,0x4C,0x7E,0x6E,0x6F, + 0x7C,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6, + 0xD7,0xD8,0xD9,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xAD,0xE0,0xBD,0x5F,0x6D, + 0x79,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x91,0x92,0x93,0x94,0x95,0x96, + 0x97,0x98,0x99,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xC0,0x4F,0xD0,0xA1,0x07, + 0x43,0x20,0x21,0x1C,0x23,0xEB,0x24,0x9B,0x71,0x28,0x38,0x49,0x90,0xBA,0xEC,0xDF, + 0x45,0x29,0x2A,0x9D,0x72,0x2B,0x8A,0x9A,0x67,0x56,0x64,0x4A,0x53,0x68,0x59,0x46, + 0xEA,0xDA,0x2C,0xDE,0x8B,0x55,0x41,0xFE,0x58,0x51,0x52,0x48,0x69,0xDB,0x8E,0x8D, + 0x73,0x74,0x75,0xFA,0x15,0xB0,0xB1,0xB3,0xB4,0xB5,0x6A,0xB7,0xB8,0xB9,0xCC,0xBC, + 0xAB,0x3E,0x3B,0x0A,0xBF,0x8F,0x3A,0x14,0xA0,0x17,0xCB,0xCA,0x1A,0x1B,0x9C,0x04, + 0x34,0xEF,0x1E,0x06,0x08,0x09,0x77,0x70,0xBE,0xBB,0xAC,0x54,0x63,0x65,0x66,0x62, + 0x30,0x42,0x47,0x57,0xEE,0x33,0xB6,0xE1,0xCD,0xED,0x36,0x44,0xCE,0xCF,0x31,0xAA, + 0xFC,0x9E,0xAE,0x8C,0xDD,0xDC,0x39,0xFB,0x80,0xAF,0xFD,0x78,0x76,0xB2,0x9F,0xFF + }; + + /*Alternative ascii to ebcdic conversion tables but they appear to be the ones actually used*/ + static final int EbcToAscAlmcopy[] = { + 0x00,0x01,0x02,0x03,0xCF,0x09,0xD3,0x7F,0xD4,0xD5,0xC3,0x0B,0x0C,0x0D,0x0E,0x0F, + 0x10,0x11,0x12,0x13,0xC7,0xB4,0x08,0xC9,0x18,0x19,0xCC,0xCD,0x83,0x1D,0xD2,0x1F, + 0x81,0x82,0x1C,0x84,0x86,0x0A,0x17,0x1B,0x89,0x91,0x92,0x95,0xA2,0x05,0x06,0x07, + 0xE0,0xEE,0x16,0xE5,0xD0,0x1E,0xEA,0x04,0x8A,0xF6,0xC6,0xC2,0x14,0x15,0xC1,0x1A, + 0x20,0xA6,0xE1,0x80,0xEB,0x90,0x9F,0xE2,0xAB,0x8B,0x9B,0x2E,0x3C,0x28,0x2B,0x7C, + 0x26,0xA9,0xAA,0x9C,0xDB,0xA5,0x99,0xE3,0xA8,0x9E,0x21,0x24,0x2A,0x29,0x3B,0x5E, + 0x2D,0x2F,0xDF,0xDC,0x9A,0xDD,0xDE,0x98,0x9D,0xAC,0xBA,0x2C,0x25,0x5F,0x3E,0x3F, + 0xD7,0x88,0x94,0xB0,0xB1,0xB2,0xFC,0xD6,0xFB,0x60,0x3A,0x23,0x40,0x27,0x3D,0x22, + 0xF8,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x96,0xA4,0xF3,0xAF,0xAE,0xC5, + 0x8C,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,0x70,0x71,0x72,0x97,0x87,0xCE,0x93,0xF1,0xFE, + 0xC8,0x7E,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0xEF,0xC0,0xDA,0x5B,0xF2,0xF9, + 0xB5,0xB6,0xFD,0xB7,0xB8,0xB9,0xE6,0xBB,0xBC,0xBD,0x8D,0xD9,0xBF,0x5D,0xD8,0xC4, + 0x7B,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0xCB,0xCA,0xBE,0xE8,0xEC,0xED, + 0x7D,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,0x50,0x51,0x52,0xA1,0xAD,0xF5,0xF4,0xA3,0x8F, + 0x5C,0xE7,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0xA0,0x85,0x8E,0xE9,0xE4,0xD1, + 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0xB3,0xF7,0xF0,0xFA,0xA7,0xFF + }; + + static final int AscToEbcAlmcopy[] = { + 0x00,0x01,0x02,0x03,0x37,0x2D,0x2E,0x2F,0x16,0x05,0x25,0x0B,0x0C,0x0D,0x0E,0x0F, + 0x10,0x11,0x12,0x13,0x3C,0x3D,0x32,0x26,0x18,0x19,0x3F,0x27,0x22,0x1D,0x35,0x1F, + 0x40,0x5A,0x7F,0x7B,0x5B,0x6C,0x50,0x7D,0x4D,0x5D,0x5C,0x4E,0x6B,0x60,0x4B,0x61, + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0x7A,0x5E,0x4C,0x7E,0x6E,0x6F, + 0x7C,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6, + 0xD7,0xD8,0xD9,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xAD,0xE0,0xBD,0x5F,0x6D, + 0x79,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x91,0x92,0x93,0x94,0x95,0x96, + 0x97,0x98,0x99,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xC0,0x4F,0xD0,0xA1,0x07, + 0x43,0x20,0x21,0x1C,0x23,0xEB,0x24,0x9B,0x71,0x28,0x38,0x49,0x90,0xBA,0xEC,0xDF, + 0x45,0x29,0x2A,0x9D,0x72,0x2B,0x8A,0x9A,0x67,0x56,0x64,0x4A,0x53,0x68,0x59,0x46, + 0xEA,0xDA,0x2C,0xDE,0x8B,0x55,0x41,0xFE,0x58,0x51,0x52,0x48,0x69,0xDB,0x8E,0x8D, + 0x73,0x74,0x75,0xFA,0x15,0xB0,0xB1,0xB3,0xB4,0xB5,0x6A,0xB7,0xB8,0xB9,0xCC,0xBC, + 0xAB,0x3E,0x3B,0x0A,0xBF,0x8F,0x3A,0x14,0xA0,0x17,0xCB,0xCA,0x1A,0x1B,0x9C,0x04, + 0x34,0xEF,0x1E,0x06,0x08,0x09,0x77,0x70,0xBE,0xBB,0xAC,0x54,0x63,0x65,0x66,0x62, + 0x30,0x42,0x47,0x57,0xEE,0x33,0xB6,0xE1,0xCD,0xED,0x36,0x44,0xCE,0xCF,0x31,0xAA, + 0xFC,0x9E,0xAE,0x8C,0xDD,0xDC,0x39,0xFB,0x80,0xAF,0xFD,0x78,0x76,0xB2,0x9F,0xFF + }; + + /* + static int EbcToAsc[] = EbcToAscDef; + static int AscToEbc[] = AscToEbcDef; + */ + static int EbcToAsc[] = EbcToAscAlmcopy; + static int AscToEbc[] = AscToEbcAlmcopy; + + + /* A list of input masks for use by FilePut() and FileGetRequired */ + static final int Mask[] = { + 0, + 0x0001, 0x0002, 0x0004, 0x0008, + 0x0010, 0x0020, 0x0040, 0x0080, + 0x0100, 0x0200, 0x0400, 0x0800, + 0x1000, 0x2000, 0x4000, 0x8000, + 0x10000, 0x20000, 0x40000, 0x80000, + 0x100000, 0x200000, 0x400000, 0x800000, + 0x1000000, 0x2000000, 0x4000000, 0x8000000, + 0x10000000,0x20000000,0x40000000,0x80000000, + }; + + /* + * These appear to be control parameters to control i/o buffers, stack size etc + * and hence will affect how much memory we use. The only ones actually used + * in the current implementation are TreeSize and RecordMark + */ + static final int STACKSIZE = 0x07FF; /* 2k - 1 */ + static final int BUFFERSIZE = 0x07FF; /* 2k - 1 */ + static final int HASHSIZE = 0x0FFF; /* 4k - 1 */ + static final int TREESIZE = 0x1000; /* 4k */ + static final int RECORDMARK = 257; /*used in the file output functions*/ + + /* + Some Used in the unspack algorithm. I guess tuning affects the + way which compression/decompression works + Others are just useful constants + */ + static final int BASE = 0; + static final int CODESIZE = 257; /* 2**8+1, EOF, 256 Codepoints, RCM */ + static final int ENDOFFILE =0; + /* This is the new line char that we write into the file. Not sure how + * this will translate on the various platforms + */ + static final char EOL = '\n'; + + /*Useful Constants*/ + static final int NONE = -1; + + /* + * These appear to be the flags for the terse file header. Some are used by + * CheckHeader() others are only used when writing a compressed file which + * this implementation doesn't do. + */ + static final int FLAGUNDEF = 0x80; /* \ */ + static final int FLAGCC1 = 0x40; /* \ */ + static final int FLAGCC2 = 0x20; /* \ */ + static final int FLAGVBS = 0x10; /* >-- values of Flags bits in HeaderRecord type */ + static final int FLAGVS = 0x08; /* / */ + static final int FLAGMVS = 0x04; /* / */ + static final int FLAGRBITS = 0x03; /* / */ + +} diff --git a/src/main/java/org/openmainframeproject/tersedecompress/NonSpackDecompresser.java b/src/main/java/org/openmainframeproject/tersedecompress/NonSpackDecompresser.java new file mode 100644 index 0000000..5d952d9 --- /dev/null +++ b/src/main/java/org/openmainframeproject/tersedecompress/NonSpackDecompresser.java @@ -0,0 +1,90 @@ +package org.openmainframeproject.tersedecompress; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +class NonSpackDecompresser extends TerseDecompresser { + + NonSpackDecompresser(InputStream instream, OutputStream outputStream, TerseHeader header) + { + super(instream, outputStream, header); + } + + /* + * Decode the file on the other end of the input stream using a non spack decode. + * Write the output to the output stream. + * Assume that both streams are initialized and ready to be read from/written to. + */ + public void decode() throws IOException { + + int [] Father = new int[Constants.TREESIZE]; + int [] CharExt = new int[Constants.TREESIZE]; + int [] Backward = new int[Constants.TREESIZE]; + int [] Forward = new int[Constants.TREESIZE]; + + int H1 = 0, H2 = 0; + int x = 0, d = 0, y = 0, q = 0, r = 0, e = 0, p = 0, h = 0; + + H2 = 1 + Constants.AscToEbcDef[' ']; + + for (H1 = 258; H1 < 4096; H1++) { + Father [H1] = H2; + CharExt[H1] = 1 + Constants.AscToEbcDef[' ']; + H2 = H1; + } + + for (H1 = 258; H1 < 4095; H1++) { + Backward[H1+1] = H1 ; + Forward [H1 ] = H1+1; + } + + Backward [0] = 4095; + Forward [0] = 258; + Backward [258] = 0; + Forward [4095] = 0; + + x=0; + d = input.GetBlok(); + + while (d != Constants.ENDOFFILE) { + h = 0; + y = Backward[0]; + q = Backward[y]; + Backward[0] = q; + Forward [q] = 0; + h = y; + p = 0; + while (d > 257) { + q = Forward [d]; + r = Backward[d]; + Forward [r] = q; + Backward[q] = r; + Forward [d] = h; + Backward[h] = d; + h = d; + e = Father[d]; + Father[d] = p; + p = d; + d = e; + } + q = Forward[0]; + Forward [y] = q; + Backward[q] = y; + Forward [0] = h; + Backward[h] = 0; + CharExt [x] = d; + PutChar(d); + x = y; + while (p != 0) { + e = Father[p]; + PutChar( CharExt[p]); + Father[p] = d; + d = p; + p = e; + } + Father[y] = d; + d = input.GetBlok(); + } + } +} diff --git a/src/main/java/org/openmainframeproject/tersedecompress/SpackDecompresser.java b/src/main/java/org/openmainframeproject/tersedecompress/SpackDecompresser.java new file mode 100644 index 0000000..a019380 --- /dev/null +++ b/src/main/java/org/openmainframeproject/tersedecompress/SpackDecompresser.java @@ -0,0 +1,188 @@ +package org.openmainframeproject.tersedecompress; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +class SpackDecompresser extends TerseDecompresser { + + SpackDecompresser(InputStream instream, OutputStream outputStream, TerseHeader header) + { + super(instream, outputStream, header); + } + + private int node =0; + + private int TreeAvail; + + private TreeRecord Tree[] = new TreeRecord[Constants.TREESIZE+1]; + + StackType Stack = new StackType(); + + private void PutChars(int X) throws IOException { + Stack.Head = 0; + + while (true) { + while (X > Constants.CODESIZE) { + Stack.Head++; + Stack.Data[Stack.Head] = Tree[X].Right; + X = Tree[X].Left; + } + PutChar( X ); + + if (Stack.Head > 0) { + X = Stack.Data[Stack.Head]; + Stack.Head--; + } else + break; + } + + } + + private void TreeInit() { + + for (int i =0; i < Tree.length; i ++) { + Tree[i] = new TreeRecord(); + } + + int init_index = Constants.BASE; + while (init_index <= Constants.CODESIZE) { + Tree[init_index].Left = Constants.NONE; + Tree[init_index].Right = init_index++; + } + for (init_index = Constants.CODESIZE+1; init_index <= Constants.TREESIZE-1; init_index++) { + Tree[init_index].NextCount = init_index+1; + Tree[init_index].Left = Constants.NONE; + Tree[init_index].Right = Constants.NONE; + } + Tree[Constants.TREESIZE].NextCount = Constants.NONE; + Tree[Constants.BASE].NextCount = Constants.BASE; + Tree[Constants.BASE].Back = Constants.BASE; + for (init_index = 1; init_index <= Constants.CODESIZE; init_index++) { + Tree[init_index].NextCount = Constants.NONE; + } + TreeAvail = Constants.CODESIZE+1; + } + + private int GetTreeNode() { + node = TreeAvail; + TreeAvail = Tree[node].NextCount; + return node; + } + + + private int forwards = 0, prev = 0; + + private void BumpRef(int bref) { + if (Tree[bref].NextCount < 0) { + Tree[bref].NextCount--; + } else { + forwards = Tree[bref].NextCount; + prev = Tree[bref].Back; + Tree[prev].NextCount = forwards; + Tree[forwards].Back = prev; + Tree[bref].NextCount = -1; + } + } + + + /* + * The following methods are all used by the spack decode algorithm + * The precise use of all of them is unknown!! + */ + + private int lru_p = 0, lru_q = 0, lru_r = 0; + + private void LruKill() { + lru_p = Tree[0].NextCount; + lru_q = Tree[lru_p].NextCount; + lru_r = Tree[lru_p].Back; + Tree[lru_q].Back = lru_r; + Tree[lru_r].NextCount = lru_q; + DeleteRef(Tree[lru_p].Left); + DeleteRef(Tree[lru_p].Right); + Tree[lru_p].NextCount = TreeAvail; + TreeAvail = lru_p; + } + + private void DeleteRef(int dref) { + if (Tree[dref].NextCount == -1) { + LruAdd(dref); + } else { + Tree[dref].NextCount++; + } + } + + + private int lru_back=0; + + private void LruAdd(int lru_next) { + lru_back = Tree[Constants.BASE].Back; + Tree[lru_next].NextCount = Constants.BASE; + Tree[Constants.BASE].Back = lru_next; + Tree[lru_next].Back = lru_back; + Tree[lru_back].NextCount = lru_next; + } + + /* + * Decode logic for a file compressed with the spack algorithm + * Inputstream should wrap the compressed data, outputstream is where we write + * the decompressed data to. + */ + + public void decode() throws IOException { + + TreeAvail = 0; + int N = 0, G = 0, H = 0; + + TreeInit(); + Tree[Constants.TREESIZE-1].NextCount = Constants.NONE; + + // Testing showed that SPACK wrote an extra newline at the end of a VB text file, + // compared to PACK. + // On investigation, I found that the SPACK code passed the 0x000 end of file + // marker to PutChars and thence to PutChar. + // If the file is host LRECL=V and text format, PutChar writes a newline character + // when the argument is zero. In all other cases, the zero argument is ignored and + // nothing is written. + // PACK does not pass the end of file marker to PutChar hence the different output. + + // Rearranged the loop and end of file testing so that SPACK does not send the + // EOF to PutChars. + + // However, the additional newline as written by SPACK might be the more correct + // format. Should all lines of a text file be terminated by an end of line character? + // That seems to be how e.g. FTP does it. If so we probably want to write the newline, + // but write it from both PACK and SPACK - probably when closing the writer. + + // Terse will not process an empty file so we probably don't have to distinguish + // between an empty file and 1 record with no data. + + H = input.GetBlok(); + + if (H != Constants.ENDOFFILE) + { + PutChars( H ); + G = input.GetBlok(); + + while (G != Constants.ENDOFFILE) { + + if (TreeAvail == Constants.NONE) { + LruKill(); + } + + PutChars(G); + N = GetTreeNode(); + Tree[N].Left = H; + Tree[N].Right = G; + BumpRef(H); + BumpRef(G); + LruAdd(N); + H = G; + G = input.GetBlok(); + } + } + + } + +} diff --git a/src/main/java/org/openmainframeproject/tersedecompress/StackType.java b/src/main/java/org/openmainframeproject/tersedecompress/StackType.java new file mode 100644 index 0000000..c755c01 --- /dev/null +++ b/src/main/java/org/openmainframeproject/tersedecompress/StackType.java @@ -0,0 +1,14 @@ +package org.openmainframeproject.tersedecompress; + +/* + * Put multiple chars to the output file + * It looks like the logic is connected to the logic of the spack output + * code, so probably can't be separated. X is some kind of indication of + * how much data to write, but the data is taken directly from Tree + * The stack isn't initialized each time, as it looks like only bits we + * have written to on this iteration will be read from + */ +class StackType { + int Head; + int Data[] = new int[Constants.STACKSIZE+1]; +} diff --git a/src/main/java/org/openmainframeproject/tersedecompress/TerseBlockReader.java b/src/main/java/org/openmainframeproject/tersedecompress/TerseBlockReader.java new file mode 100644 index 0000000..adcd932 --- /dev/null +++ b/src/main/java/org/openmainframeproject/tersedecompress/TerseBlockReader.java @@ -0,0 +1,70 @@ +package org.openmainframeproject.tersedecompress; + +import java.io.*; + +class TerseBlockReader implements AutoCloseable +{ + InputStream stream; + + public TerseBlockReader(InputStream instream) + { + this.stream = instream; + } + + int bitsAvailable = 0; + int savedBits = 0; + int red = 0; + + /* + * Read in 12 bits of data, and put them in the bottom of the returned int + */ + + int GetBlok() throws IOException { + + if (bitsAvailable == 0) + { + int byte1 = stream.read(); + if (byte1 == -1) + { + return Constants.ENDOFFILE; + } + red++; + int byte2 = stream.read(); + if (byte2 == -1) + { + throw new IOException("Tried to read 12 bits but found EOF after reading 8 bits."); + } + red++; + // save the last 4 bits of the second byte + savedBits = byte2 & 0x0F; + bitsAvailable = 4; + + return (byte1 << 4) | (byte2 >> 4); + } + else + { + if (bitsAvailable != 4) + { + // should never happen, if it does we made an error + throw new IOException("Unexpected count of bits available"); + } + + int byte2 = stream.read(); + if (byte2 == -1) + { + // assume the 4 bits in the last block were the last real data and + // these 4 bits only exist because you can't write 1/2 a byte + // i.e. this is EOF + return Constants.ENDOFFILE; + } + red++; + bitsAvailable = 0; + return (savedBits << 8) | byte2; + } + } + + @Override + public void close() throws Exception { + stream.close(); + } +} diff --git a/src/main/java/org/openmainframeproject/tersedecompress/TerseDecompress.java b/src/main/java/org/openmainframeproject/tersedecompress/TerseDecompress.java new file mode 100644 index 0000000..497e28c --- /dev/null +++ b/src/main/java/org/openmainframeproject/tersedecompress/TerseDecompress.java @@ -0,0 +1,116 @@ +package org.openmainframeproject.tersedecompress; + +/** + Copyright Contributors to the TerseDecompress Project. + SPDX-License-Identifier: Apache-2.0 +**/ +/*****************************************************************************/ +/* Copyright 2018 IBM Corp. */ +/* */ +/* Licensed under the Apache License, Version 2.0 (the "License"); */ +/* you may not use this file except in compliance with the License. */ +/* You may obtain a copy of the License at */ +/* */ +/* http://www.apache.org/licenses/LICENSE-2.0 */ +/* */ +/* Unless required by applicable law or agreed to in writing, software */ +/* distributed under the License is distributed on an "AS IS" BASIS, */ +/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.*/ +/* See the License for the specific language governing permissions and */ +/* limitations under the License. */ +/*****************************************************************************/ +/* */ +/* For problems and requirements please create a GitHub issue */ +/* */ +/*****************************************************************************/ +/* */ +/* Author: Iain Lewis August 2004 (version 3) */ +/* */ +/*****************************************************************************/ +/* Version 4 with editorial changes for publication as open source code */ +/* Klaus Egeler, Boris Barth (clientcenter@de.ibm.com) */ +/*****************************************************************************/ +/* Version 5: support for variable length binary records */ +/* Andrew Rowley, Black Hill Software */ +/* Mario Bezzi, Watson Walker */ +/*****************************************************************************/ + +import java.io.*; + +class TerseDecompress { + + private static final String DetailedHelp = new String( + "Usage: \"TerseDecompress [-b]\"\n\n" + +"Java TerseDecompress will decompress a file compressed using the terse program on z/OS\n" + +"Default mode is text mode, which will attempt EBCDIC -> ASCII conversion\n" + +"The -b flag turns on binary mode, no conversion will be attempted\n" + ); + + private static final String Version = new String ("Version 5, March 2021"); + + private void printUsageAndExit() { + System.out.println(DetailedHelp); + System.out.println(Version); + System.exit(0); + } + + private void process (String args[]) throws Exception { + + String inputFileName = null; + String outputFileName = null; + boolean textMode = true; + + if (args.length == 0) + { + printUsageAndExit(); + } + + for (int i=0; i < args.length; i++) + { + if (args[i].equals("-h") || args[i].equals("--help")) + { + printUsageAndExit(); + } + else if (args[i].equals("-b")) + { + textMode = false; + } + // first non-flag argument is the input file name + else if (inputFileName == null) + { + inputFileName = args[i]; + } + // second non-flag argument is the input file name + else if (outputFileName == null) + { + outputFileName = args[i]; + } + else // we have more args than we know what to do with + { + printUsageAndExit(); + } + } + if (inputFileName == null || outputFileName == null) + { + printUsageAndExit(); + } + + + try (TerseDecompresser outputWriter + = TerseDecompresser.create(new FileInputStream(inputFileName), new FileOutputStream(outputFileName))) + { + outputWriter.TextFlag = textMode; + System.out.println("Attempting to decompress input file (" + inputFileName +") to output file (" + outputFileName +")"); + outputWriter.decode(); + } + + System.out.println("Processing completed"); + } + + public static void main (String args[]) throws Exception { + + TerseDecompress tersed = new TerseDecompress(); + tersed.process(args); + } + +} diff --git a/src/main/java/org/openmainframeproject/tersedecompress/TerseDecompresser.java b/src/main/java/org/openmainframeproject/tersedecompress/TerseDecompresser.java new file mode 100644 index 0000000..2e68d42 --- /dev/null +++ b/src/main/java/org/openmainframeproject/tersedecompress/TerseDecompresser.java @@ -0,0 +1,124 @@ +package org.openmainframeproject.tersedecompress; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +abstract class TerseDecompresser implements AutoCloseable +{ + TerseBlockReader input; + ByteArrayOutputStream record; + DataOutputStream stream; + + boolean HostFlag; + boolean TextFlag; + boolean VariableFlag; + + long OutputTotal = 0 ; /* total number of bytes */ + int RecordLength; /* host perspective record length */ + + byte[] lineseparator = System.lineSeparator().getBytes(); + + public abstract void decode() throws IOException; + + public static TerseDecompresser create(InputStream inputStream, OutputStream outputStream) throws IOException + { + DataInputStream input = new DataInputStream(new BufferedInputStream(inputStream)); + TerseHeader header_rv = TerseHeader.CheckHeader(input); + + if (!header_rv.SpackFlag) { + return new NonSpackDecompresser(input, outputStream, header_rv); + } else { + return new SpackDecompresser(input, outputStream, header_rv); + } + } + + public TerseDecompresser(InputStream instream, OutputStream outputStream, TerseHeader header) + { + this.RecordLength = header.RecordLength; + this.HostFlag = header.HostFlag; + this.VariableFlag = header.RecfmV; + this.input = new TerseBlockReader(instream); + this.stream = new DataOutputStream(new BufferedOutputStream(outputStream)); + + this.record = new ByteArrayOutputStream(RecordLength); + } + + /* Write a new line to the output file*/ + public void endRecord() throws IOException + { + if (VariableFlag && !TextFlag) + { + // write a RDW + int recordlength = record.size() + 4; + int rdw = recordlength << 16; + stream.writeInt(rdw); + } + + stream.write(record.toByteArray()); + record.reset(); + + if (TextFlag) + { + stream.write(lineseparator); + } + } + + /* + * Write some stuff to the output record + */ + + public void PutChar(int X) throws IOException { + if (X == 0) { + if (HostFlag && TextFlag && VariableFlag) { + endRecord(); + } + } else { + if (HostFlag && TextFlag) { + if (VariableFlag) { + if (X == Constants.RECORDMARK) { + endRecord(); + } else { + record.write(Constants.EbcToAsc[X-1]); + } + } else { + record.write(Constants.EbcToAsc[X-1]); + if (record.size() == RecordLength) { + endRecord(); + } + } + } + else + { + if (X == Constants.RECORDMARK) + { + if (VariableFlag) + { + endRecord(); + } + /* else discard record marks */ + } + else + { + record.write(X-1); + } + } + } + } + + @Override + public void close() throws Exception { + if (record.size() > 0 + || TextFlag && VariableFlag) + { + endRecord(); + } + this.stream.close(); + this.input.close(); + } +} diff --git a/src/main/java/org/openmainframeproject/tersedecompress/TerseHeader.java b/src/main/java/org/openmainframeproject/tersedecompress/TerseHeader.java new file mode 100644 index 0000000..8ca2f38 --- /dev/null +++ b/src/main/java/org/openmainframeproject/tersedecompress/TerseHeader.java @@ -0,0 +1,127 @@ +package org.openmainframeproject.tersedecompress; + +import java.io.DataInputStream; +import java.io.IOException; + +/* + * Data structure used only when checking the header initially + */ + +class TerseHeader { + + public int VersionFlag; + public int VariableFlag; + public int RecordLen1; + public int Flags; + public int Ratio; + public int BlockSize; + public int RecordLen2; + + public int RecordLength; + + public boolean RecfmV = false; + + /*Defaults for dump types*/ + boolean TextFlag = true; + boolean HostFlag = true; + boolean SpackFlag = true; + + public String toString() { + + return new String ( + "\n" + +"Version flag is " + VersionFlag +"\n" + +"Variable Flag is " + VariableFlag +"\n" + +"RecordLen1 is " + RecordLen1 +"\n" + +"Flags are " + Flags +"\n" + +"Ratio is " + Ratio +"\n" + +"Block Size is " + BlockSize +"\n" + +"RecordLen2 is " + RecordLen2 +"\n" + ); + + } + + /* + * Check that the header of an input tersed file is consistent and set some of the static flags + * associated with it. + */ + + static TerseHeader CheckHeader(DataInputStream datastream) throws IOException + { + TerseHeader header = new TerseHeader(); + + header.VersionFlag = datastream.readUnsignedByte(); + + switch (header.VersionFlag) { + case 0x01: /* native binary mode, 4 byte header, versions 1.2+ */ + case 0x07: /* native binary mode, 4 byte header, versions 1.1- */ + + int byte2 = datastream.readUnsignedByte(); + int byte3 = datastream.readUnsignedByte(); + int byte4 = datastream.readUnsignedByte(); + header.RecordLen1 = datastream.readUnsignedShort(); + + if (byte2 != 0x89 || byte3 != 0x69 || byte4 != 0xA5) + { + throw new IOException("Invalid header validation flags"); + } + header.HostFlag = false; /* autoswitch to native mode */ + header.TextFlag = false; + + break; + + case 0x02: /* host PACK compatibility mode, 12 byte header */ + case 0x05: /* host SPACK compatibility mode, 12 byte header */ + + header.VariableFlag = datastream.readUnsignedByte(); + header.RecordLen1 = datastream.readUnsignedShort(); + header.Flags = datastream.readUnsignedByte(); + header.Ratio = datastream.readUnsignedByte(); + header.BlockSize = datastream.readUnsignedShort(); + + // We will assume that the record length doesn't exceed the maximum value + // for a signed int, for the convenience of using an int instead of a long. + header.RecordLen2 = datastream.readInt(); + // but check... + if (header.RecordLen2 < 0) + { + throw new IOException("Record length exceeds " + Integer.MAX_VALUE); + + } + + header.SpackFlag = (header.VersionFlag == 0x05); + + if ((header.VariableFlag != 0x00) && (header.VariableFlag != 0x01)) + throw new IOException("Record format flag not recognized : " + Integer.toHexString(header.VariableFlag)); + + if (header.RecordLen1 == 0 && header.RecordLen2 == 0) + throw new IOException("Record length is 0"); + + if (header.RecordLen1 != 0 && header.RecordLen2 != 0 + && header.RecordLen1 != header.RecordLen2) + throw new IOException("Ambiguous record length"); + + header.RecordLength = header.RecordLen1 != 0 ? header.RecordLen1 : header.RecordLen2; + + header.RecfmV = (header.VariableFlag == 0x01); + + // Preserve checks from previous version, I don't know why these cases are invalid + if ((header.Flags & Constants.FLAGMVS) == 0) { + if ( header.Flags != 0) throw new IOException("Flags specified for non-MVS"); + if ( header.Ratio != 0) throw new IOException("Ratio specified for non-MVS"); + if (header.BlockSize != 0) throw new IOException("BlockSize specified for non-MVS"); + } + + header.HostFlag = true; + + break; + default: + throw new IOException("Terse header version not recognized : " + Integer.toHexString(header.VersionFlag)); + } + + return header; + + } + + +} diff --git a/src/main/java/org/openmainframeproject/tersedecompress/TreeRecord.java b/src/main/java/org/openmainframeproject/tersedecompress/TreeRecord.java new file mode 100644 index 0000000..2b11c0c --- /dev/null +++ b/src/main/java/org/openmainframeproject/tersedecompress/TreeRecord.java @@ -0,0 +1,9 @@ +package org.openmainframeproject.tersedecompress; + +class TreeRecord { + + int Left; + int Right; + int Back; + int NextCount; +} diff --git a/src/test/java/org/openmainframeproject/tersedecompress/AppTest.java b/src/test/java/org/openmainframeproject/tersedecompress/AppTest.java new file mode 100644 index 0000000..2fa679e --- /dev/null +++ b/src/test/java/org/openmainframeproject/tersedecompress/AppTest.java @@ -0,0 +1,189 @@ +package org.openmainframeproject.tersedecompress; + +import static org.junit.Assert.*; + +import java.io.*; +import java.nio.file.*; + +import org.junit.*; + +public class AppTest +{ + static final String location = "test-data"; + + @Test public void testBinaryPack01() throws Exception { testBinary("FB.A.TXT", "PACK"); } + @Test public void testBinaryPack02() throws Exception { testBinary("FB.AAA.TXT", "PACK"); } + @Test public void testBinaryPack03() throws Exception { testBinary("FB.ALICE29.TXT", "PACK"); } + @Test public void testBinaryPack04() throws Exception { testBinary("FB.ALPHABET.TXT", "PACK"); } + @Test public void testBinaryPack05() throws Exception { testBinary("FB.ASYOULIK.TXT", "PACK"); } + @Test public void testBinaryPack06() throws Exception { testBinary("FB.BIBLE.TXT", "PACK"); } + @Test public void testBinaryPack07() throws Exception { testBinary("FB.CP.HTML", "PACK"); } + @Test public void testBinaryPack08() throws Exception { testBinary("FB.E.COLI", "PACK"); } + @Test public void testBinaryPack09() throws Exception { testBinary("FB.FIELDS.C", "PACK"); } + @Test public void testBinaryPack10() throws Exception { testBinary("FB.GRAMMAR.LSP", "PACK"); } + @Test public void testBinaryPack11() throws Exception { testBinary("FB.KENNEDY.XLS", "PACK"); } + @Test public void testBinaryPack12() throws Exception { testBinary("FB.LCET10.TXT", "PACK"); } + @Test public void testBinaryPack13() throws Exception { testBinary("FB.PI.TXT", "PACK"); } + @Test public void testBinaryPack14() throws Exception { testBinary("FB.PLRABN12.TXT", "PACK"); } + @Test public void testBinaryPack15() throws Exception { testBinary("FB.PTT5", "PACK"); } + @Test public void testBinaryPack16() throws Exception { testBinary("FB.RANDOM.TXT", "PACK"); } + @Test public void testBinaryPack17() throws Exception { testBinary("FB.SUM", "PACK"); } + @Test public void testBinaryPack18() throws Exception { testBinary("FB.WORLD192.TXT", "PACK"); } + @Test public void testBinaryPack19() throws Exception { testBinary("FB.XARGS", "PACK"); } + @Test public void testBinaryPack20() throws Exception { testBinary("VB.BIBLE.TXT", "PACK"); } + @Test public void testBinaryPack21() throws Exception { testBinary("VB.CP.HTML", "PACK"); } + @Test public void testBinaryPack22() throws Exception { testBinary("VB.ENWIK8.XML", "PACK"); } + @Test public void testBinaryPack23() throws Exception { testBinary("VB.FIELDS.C", "PACK"); } + @Test public void testBinaryPack24() throws Exception { testBinary("VB.GRAMMAR.LSP", "PACK"); } + @Test public void testBinaryPack25() throws Exception { testBinary("VB.LCET10.TXT", "PACK"); } + @Test public void testBinaryPack26() throws Exception { testBinary("VB.WORLD192.TXT", "PACK"); } + @Test public void testBinaryPack27() throws Exception { testBinary("VB.XARGS", "PACK"); } + @Test public void testBinaryPack28() throws Exception { testBinary("VB.A.TXT", "PACK"); } + @Test public void testBinaryPack29() throws Exception { testBinary("VB.AAA.TXT", "PACK"); } + @Test public void testBinaryPack30() throws Exception { testBinary("VB.ALPHABET.TXT", "PACK"); } + @Test public void testBinaryPack31() throws Exception { testBinary("VB.E.COLI", "PACK"); } + @Test public void testBinaryPack32() throws Exception { testBinary("VB.PI.TXT", "PACK"); } + @Test public void testBinaryPack33() throws Exception { testBinary("VB.RANDOM.TXT", "PACK"); } + @Test public void testBinaryPack34() throws Exception { testBinary("VB.ALICE29.TXT", "PACK"); } + @Test public void testBinaryPack35() throws Exception { testBinary("VB.ASYOULIK.TXT", "PACK"); } + @Test public void testBinaryPack36() throws Exception { testBinary("VB.PLRABN12.TXT", "PACK"); } + + + // The following test fails, but also uncompresses incorrectly using AMATERSE on z/OS. + // The failure seems to be in the SPACK compression using AMATERSE. + //@Test public void testBinarySPack01() throws Exception { testBinary("FB.A.TXT", "SPACK"); } + @Test public void testBinarySPack02() throws Exception { testBinary("FB.AAA.TXT", "SPACK"); } + @Test public void testBinarySPack03() throws Exception { testBinary("FB.ALICE29.TXT", "SPACK"); } + @Test public void testBinarySPack04() throws Exception { testBinary("FB.ALPHABET.TXT", "SPACK"); } + @Test public void testBinarySPack05() throws Exception { testBinary("FB.ASYOULIK.TXT", "SPACK"); } + @Test public void testBinarySPack06() throws Exception { testBinary("FB.BIBLE.TXT", "SPACK"); } + @Test public void testBinarySPack07() throws Exception { testBinary("FB.CP.HTML", "SPACK"); } + @Test public void testBinarySPack08() throws Exception { testBinary("FB.E.COLI", "SPACK"); } + @Test public void testBinarySPack09() throws Exception { testBinary("FB.FIELDS.C", "SPACK"); } + @Test public void testBinarySPack10() throws Exception { testBinary("FB.GRAMMAR.LSP", "SPACK"); } + @Test public void testBinarySPack11() throws Exception { testBinary("FB.KENNEDY.XLS", "SPACK"); } + @Test public void testBinarySPack12() throws Exception { testBinary("FB.LCET10.TXT", "SPACK"); } + @Test public void testBinarySPack13() throws Exception { testBinary("FB.PI.TXT", "SPACK"); } + @Test public void testBinarySPack14() throws Exception { testBinary("FB.PLRABN12.TXT", "SPACK"); } + @Test public void testBinarySPack15() throws Exception { testBinary("FB.PTT5", "SPACK"); } + @Test public void testBinarySPack16() throws Exception { testBinary("FB.RANDOM.TXT", "SPACK"); } + @Test public void testBinarySPack17() throws Exception { testBinary("FB.SUM", "SPACK"); } + @Test public void testBinarySPack18() throws Exception { testBinary("FB.WORLD192.TXT", "SPACK"); } + @Test public void testBinarySPack19() throws Exception { testBinary("FB.XARGS", "SPACK"); } + @Test public void testBinarySPack20() throws Exception { testBinary("VB.BIBLE.TXT", "SPACK"); } + @Test public void testBinarySPack21() throws Exception { testBinary("VB.CP.HTML", "SPACK"); } + @Test public void testBinarySPack22() throws Exception { testBinary("VB.ENWIK8.XML", "SPACK"); } + @Test public void testBinarySPack23() throws Exception { testBinary("VB.FIELDS.C", "SPACK"); } + @Test public void testBinarySPack24() throws Exception { testBinary("VB.GRAMMAR.LSP", "SPACK"); } + @Test public void testBinarySPack25() throws Exception { testBinary("VB.LCET10.TXT", "SPACK"); } + @Test public void testBinarySPack26() throws Exception { testBinary("VB.WORLD192.TXT", "SPACK"); } + @Test public void testBinarySPack27() throws Exception { testBinary("VB.XARGS", "SPACK"); } + // The following test fails, but also uncompresses incorrectly using AMATERSE on z/OS. + // The failure seems to be in the SPACK compression using AMATERSE. + //@Test public void testBinarySPack28() throws Exception { testBinary("VB.A.TXT", "SPACK"); } + @Test public void testBinarySPack29() throws Exception { testBinary("VB.AAA.TXT", "SPACK"); } + @Test public void testBinarySPack30() throws Exception { testBinary("VB.ALPHABET.TXT", "SPACK"); } + @Test public void testBinarySPack31() throws Exception { testBinary("VB.E.COLI", "SPACK"); } + @Test public void testBinarySPack32() throws Exception { testBinary("VB.PI.TXT", "SPACK"); } + @Test public void testBinarySPack33() throws Exception { testBinary("VB.RANDOM.TXT", "SPACK"); } + @Test public void testBinarySPack34() throws Exception { testBinary("VB.ALICE29.TXT", "SPACK"); } + @Test public void testBinarySPack35() throws Exception { testBinary("VB.ASYOULIK.TXT", "SPACK"); } + @Test public void testBinarySPack36() throws Exception { testBinary("VB.PLRABN12.TXT", "SPACK"); } + + @Test public void testTextPack01() throws Exception { testText("FB.A.TXT", "PACK"); } + @Test public void testTextPack02() throws Exception { testText("FB.AAA.TXT", "PACK"); } + @Test public void testTextPack04() throws Exception { testText("FB.ALPHABET.TXT", "PACK"); } + @Test public void testTextPack06() throws Exception { testText("FB.BIBLE.TXT", "PACK"); } + @Test public void testTextPack07() throws Exception { testText("FB.CP.HTML", "PACK"); } + @Test public void testTextPack08() throws Exception { testText("FB.E.COLI", "PACK"); } + @Test public void testTextPack09() throws Exception { testText("FB.FIELDS.C", "PACK"); } + @Test public void testTextPack10() throws Exception { testText("FB.GRAMMAR.LSP", "PACK"); } + @Test public void testTextPack12() throws Exception { testText("FB.LCET10.TXT", "PACK"); } + @Test public void testTextPack13() throws Exception { testText("FB.PI.TXT", "PACK"); } + @Test public void testTextPack16() throws Exception { testText("FB.RANDOM.TXT", "PACK"); } + @Test public void testTextPack18() throws Exception { testText("FB.WORLD192.TXT", "PACK"); } + @Test public void testTextPack19() throws Exception { testText("FB.XARGS", "PACK"); } + @Test public void testTextPack20() throws Exception { testText("VB.BIBLE.TXT", "PACK"); } + @Test public void testTextPack21() throws Exception { testText("VB.CP.HTML", "PACK"); } + @Test public void testTextPack23() throws Exception { testText("VB.FIELDS.C", "PACK"); } + @Test public void testTextPack24() throws Exception { testText("VB.GRAMMAR.LSP", "PACK"); } + @Test public void testTextPack25() throws Exception { testText("VB.LCET10.TXT", "PACK"); } + @Test public void testTextPack26() throws Exception { testText("VB.WORLD192.TXT", "PACK"); } + @Test public void testTextPack27() throws Exception { testText("VB.XARGS", "PACK"); } + @Test public void testTextPack28() throws Exception { testText("VB.A.TXT", "PACK"); } + @Test public void testTextPack29() throws Exception { testText("VB.AAA.TXT", "PACK"); } + @Test public void testTextPack30() throws Exception { testText("VB.ALPHABET.TXT", "PACK"); } + @Test public void testTextPack31() throws Exception { testText("VB.E.COLI", "PACK"); } + @Test public void testTextPack32() throws Exception { testText("VB.PI.TXT", "PACK"); } + @Test public void testTextPack33() throws Exception { testText("VB.RANDOM.TXT", "PACK"); } + + // The following test fails, but the file also uncompresses incorrectly using AMATERSE on z/OS. + // The failure seems to be in the SPACK compression using AMATERSE. + //@Test public void testTextSPack01() throws Exception { testText("FB.A.TXT", "SPACK"); } + @Test public void testTextSPack02() throws Exception { testText("FB.AAA.TXT", "SPACK"); } + @Test public void testTextSPack04() throws Exception { testText("FB.ALPHABET.TXT", "SPACK"); } + @Test public void testTextSPack06() throws Exception { testText("FB.BIBLE.TXT", "SPACK"); } + @Test public void testTextSPack07() throws Exception { testText("FB.CP.HTML", "SPACK"); } + @Test public void testTextSPack08() throws Exception { testText("FB.E.COLI", "SPACK"); } + @Test public void testTextSPack09() throws Exception { testText("FB.FIELDS.C", "SPACK"); } + @Test public void testTextSPack10() throws Exception { testText("FB.GRAMMAR.LSP", "SPACK"); } + @Test public void testTextSPack12() throws Exception { testText("FB.LCET10.TXT", "SPACK"); } + @Test public void testTextSPack13() throws Exception { testText("FB.PI.TXT", "SPACK"); } + @Test public void testTextSPack16() throws Exception { testText("FB.RANDOM.TXT", "SPACK"); } + @Test public void testTextSPack18() throws Exception { testText("FB.WORLD192.TXT", "SPACK"); } + @Test public void testTextSPack19() throws Exception { testText("FB.XARGS", "SPACK"); } + @Test public void testTextSPack20() throws Exception { testText("VB.BIBLE.TXT", "SPACK"); } + @Test public void testTextSPack21() throws Exception { testText("VB.CP.HTML", "SPACK"); } + @Test public void testTextSPack23() throws Exception { testText("VB.FIELDS.C", "SPACK"); } + @Test public void testTextSPack24() throws Exception { testText("VB.GRAMMAR.LSP", "SPACK"); } + @Test public void testTextSPack25() throws Exception { testText("VB.LCET10.TXT", "SPACK"); } + @Test public void testTextSPack26() throws Exception { testText("VB.WORLD192.TXT", "SPACK"); } + @Test public void testTextSPack27() throws Exception { testText("VB.XARGS", "SPACK"); } + // The following test fails, but the file also uncompresses incorrectly using AMATERSE on z/OS. + // The failure seems to be in the SPACK compression using AMATERSE. + //@Test public void testTextSPack28() throws Exception { testText("VB.A.TXT", "SPACK"); } + @Test public void testTextSPack29() throws Exception { testText("VB.AAA.TXT", "SPACK"); } + @Test public void testTextSPack30() throws Exception { testText("VB.ALPHABET.TXT", "SPACK"); } + @Test public void testTextSPack31() throws Exception { testText("VB.E.COLI", "SPACK"); } + @Test public void testTextSPack32() throws Exception { testText("VB.PI.TXT", "SPACK"); } + @Test public void testTextSPack33() throws Exception { testText("VB.RANDOM.TXT", "SPACK"); } + + private void testBinary(String file, String packSpack) throws Exception + { + String tersed = location + "/TERSED/" + file + "." + packSpack; + String untersed = location + "/ZOSBINARY/" + file; + + byte[] expected = Files.readAllBytes(Paths.get(untersed)); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + try (TerseDecompresser outputWriter + = TerseDecompresser.create(new FileInputStream(tersed), out)) + { + outputWriter.decode(); + } + assertArrayEquals(file, expected, out.toByteArray()); + } + + private void testText(String file, String packSpack) throws Exception + { + String tersed = location + "/TERSED/" + file + "." + packSpack; + String untersed = location + "/ZOSTEXT/" + file; + + // Assumes that the record separators in the ZOSTEXT file are correct for this system, + // i.e. the data was checked out in text mode with git crlf conversion or otherwise + // converted. + byte[] expected = Files.readAllBytes(Paths.get(untersed)); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + try (TerseDecompresser outputWriter + = TerseDecompresser.create(new FileInputStream(tersed), out)) + { + outputWriter.TextFlag = true; + outputWriter.decode(); + } + assertArrayEquals(file, expected, out.toByteArray()); + } + +} diff --git a/test-data b/test-data new file mode 160000 index 0000000..f490ed0 --- /dev/null +++ b/test-data @@ -0,0 +1 @@ +Subproject commit f490ed0b48bb2308e5900f89461ef96993a99c76 diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..ed4915e --- /dev/null +++ b/tests/README.md @@ -0,0 +1,12 @@ +# Testing TerseDecompress # + +TerseDecompress has unit tests defined in the Maven project. + +The tests are skipped by default, and the test data is in a submodule repository due to the size of the test data files. This means that TerseDecompress can be built without downloading all the test data. + +### To run the TerseDecompress tests: ### + +1. Initialize (download) the submodule containing the test data: +```git submodule update --init``` +2. Build with unit tests: +```mvn -DskipTests=false clean package``` From 5f1901a2a8c3195507210b1dc81ad4a09adbfa40 Mon Sep 17 00:00:00 2001 From: John Mertic Date: Wed, 1 Mar 2023 09:48:57 -0500 Subject: [PATCH 3/9] Update README.md Signed-off-by: John Mertic Signed-off-by: Oleksandr Hubanov --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 2d41f3c..c8a5ff8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +**NOTICE: This project is in [Emeritus status](https://tac.openmainframeproject.org/process/lifecycle.html#emeritus-stage) and no longer maintained** + # tersedecompress TerseDecompress is a java program that can be used to decompress files From b124e7075792f09d4088d48c002002768d47cbe0 Mon Sep 17 00:00:00 2001 From: John Mertic Date: Thu, 6 Apr 2023 12:23:50 -0400 Subject: [PATCH 4/9] Update README.md Signed-off-by: John Mertic Signed-off-by: Oleksandr Hubanov --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index c8a5ff8..2d41f3c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -**NOTICE: This project is in [Emeritus status](https://tac.openmainframeproject.org/process/lifecycle.html#emeritus-stage) and no longer maintained** - # tersedecompress TerseDecompress is a java program that can be used to decompress files From db6c6126de0357ee4a8b11a28f2691698537c409 Mon Sep 17 00:00:00 2001 From: Oleksandr Hubanov Date: Thu, 6 Jul 2023 12:46:01 +0300 Subject: [PATCH 5/9] ignore .vscode Signed-off-by: Oleksandr Hubanov --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 30ed173..8c4a81e 100644 --- a/.gitignore +++ b/.gitignore @@ -109,4 +109,6 @@ buildNumber.properties .mvn/wrapper/maven-wrapper.jar .flattened-pom.xml -# End of https://www.gitignore.io/api/java,maven,eclipse \ No newline at end of file +# End of https://www.gitignore.io/api/java,maven,eclipse + +.vscode \ No newline at end of file From 93315770b99e6fe82784023f043265de582df95b Mon Sep 17 00:00:00 2001 From: Oleksandr Hubanov Date: Thu, 6 Jul 2023 12:46:42 +0300 Subject: [PATCH 6/9] target java 11 and bump plugins Signed-off-by: Oleksandr Hubanov --- pom.xml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index af9b839..3f9ced4 100644 --- a/pom.xml +++ b/pom.xml @@ -20,16 +20,15 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.11.0 - 1.8 - 1.8 + 11 org.apache.maven.plugins maven-jar-plugin - 3.2.0 + 3.3.0 @@ -45,7 +44,7 @@ junit junit - 4.13.1 + 4.13.2 test From 81ed98407a112d683bb7c318088951d4252c1154 Mon Sep 17 00:00:00 2001 From: Oleksandr Hubanov Date: Thu, 6 Jul 2023 13:58:23 +0300 Subject: [PATCH 7/9] catch exceptions, to gracefully exit application, in case of failure Signed-off-by: Oleksandr Hubanov --- .../tersedecompress/TerseDecompress.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/openmainframeproject/tersedecompress/TerseDecompress.java b/src/main/java/org/openmainframeproject/tersedecompress/TerseDecompress.java index 497e28c..9bb7567 100644 --- a/src/main/java/org/openmainframeproject/tersedecompress/TerseDecompress.java +++ b/src/main/java/org/openmainframeproject/tersedecompress/TerseDecompress.java @@ -95,14 +95,16 @@ else if (outputFileName == null) printUsageAndExit(); } - + System.out.println("Attempting to decompress input file (" + inputFileName +") to output file (" + outputFileName +")"); try (TerseDecompresser outputWriter = TerseDecompresser.create(new FileInputStream(inputFileName), new FileOutputStream(outputFileName))) { outputWriter.TextFlag = textMode; - System.out.println("Attempting to decompress input file (" + inputFileName +") to output file (" + outputFileName +")"); outputWriter.decode(); - } + } + catch (Exception e) { + System.out.printf("Something went wrong, Exception %s\n", e.getMessage()); + } System.out.println("Processing completed"); } From d150fb49b8713179c0419d7311b144816b9780ec Mon Sep 17 00:00:00 2001 From: Oleksandr Hubanov Date: Thu, 6 Jul 2023 15:07:09 +0300 Subject: [PATCH 8/9] throw exception if decoded char is < 0, e.g. -1 or -2 Signed-off-by: Oleksandr Hubanov --- .../tersedecompress/SpackDecompresser.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/openmainframeproject/tersedecompress/SpackDecompresser.java b/src/main/java/org/openmainframeproject/tersedecompress/SpackDecompresser.java index a019380..ab46080 100644 --- a/src/main/java/org/openmainframeproject/tersedecompress/SpackDecompresser.java +++ b/src/main/java/org/openmainframeproject/tersedecompress/SpackDecompresser.java @@ -28,6 +28,10 @@ private void PutChars(int X) throws IOException { Stack.Data[Stack.Head] = Tree[X].Right; X = Tree[X].Left; } + if(X < 0) + { + throw new IOException("Unexpected sequence, seems like file is corrupted"); + } PutChar( X ); if (Stack.Head > 0) { From 3c1fc9374fbbe6a0ecea3f55b146b4e1a999c0ec Mon Sep 17 00:00:00 2001 From: Oleksandr Hubanov Date: Thu, 6 Jul 2023 15:44:18 +0300 Subject: [PATCH 9/9] allow user to specify only for text files, while will be .txt Signed-off-by: Oleksandr Hubanov --- .../tersedecompress/TerseDecompress.java | 73 +++++++++---------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/src/main/java/org/openmainframeproject/tersedecompress/TerseDecompress.java b/src/main/java/org/openmainframeproject/tersedecompress/TerseDecompress.java index 9bb7567..2be7333 100644 --- a/src/main/java/org/openmainframeproject/tersedecompress/TerseDecompress.java +++ b/src/main/java/org/openmainframeproject/tersedecompress/TerseDecompress.java @@ -43,10 +43,17 @@ class TerseDecompress { "Usage: \"TerseDecompress [-b]\"\n\n" +"Java TerseDecompress will decompress a file compressed using the terse program on z/OS\n" +"Default mode is text mode, which will attempt EBCDIC -> ASCII conversion\n" - +"The -b flag turns on binary mode, no conversion will be attempted\n" + +"If no provided in text mode, it will default to .txt\n" + +"Options:\n" + +"-b flag turns on binary mode, no conversion will be attempted\n" + +"-h or --help prints this message\n" ); private static final String Version = new String ("Version 5, March 2021"); + private String inputFileName = null; + private String outputFileName = null; + private boolean isHelpRequested = false; + private boolean textMode = true; private void printUsageAndExit() { System.out.println(DetailedHelp); @@ -55,45 +62,15 @@ private void printUsageAndExit() { } private void process (String args[]) throws Exception { - - String inputFileName = null; - String outputFileName = null; - boolean textMode = true; - - if (args.length == 0) + parseArgs(args); + if (args.length == 0 || (inputFileName == null && outputFileName == null) || (outputFileName == null && textMode == false) || isHelpRequested == true) { printUsageAndExit(); } - - for (int i=0; i < args.length; i++) - { - if (args[i].equals("-h") || args[i].equals("--help")) - { - printUsageAndExit(); - } - else if (args[i].equals("-b")) - { - textMode = false; - } - // first non-flag argument is the input file name - else if (inputFileName == null) - { - inputFileName = args[i]; - } - // second non-flag argument is the input file name - else if (outputFileName == null) - { - outputFileName = args[i]; - } - else // we have more args than we know what to do with - { - printUsageAndExit(); - } - } - if (inputFileName == null || outputFileName == null) - { - printUsageAndExit(); - } + + if (outputFileName == null) { + outputFileName = inputFileName + ".txt"; + } System.out.println("Attempting to decompress input file (" + inputFileName +") to output file (" + outputFileName +")"); try (TerseDecompresser outputWriter @@ -109,6 +86,28 @@ else if (outputFileName == null) System.out.println("Processing completed"); } + private void parseArgs(String args[]) { + for (int i = 0; i < args.length; i++) { + if (args[i].equals("-h") || args[i].equals("--help")) { + isHelpRequested = true; + } + else if (args[i].equals("-b")) { + textMode = false; + } + // first non-flag argument is the input file name + else if (inputFileName == null) { + inputFileName = args[i]; + } + // second non-flag argument is the input file name + else if (outputFileName == null) { + outputFileName = args[i]; + } + else // we have more args than we know what to do with + { + isHelpRequested = true; + } + } + } public static void main (String args[]) throws Exception { TerseDecompress tersed = new TerseDecompress();