UnixCompressFilter.java

  1. /* Copyright 2002-2018 CS Systèmes d'Information
  2.  * Licensed to CS Systèmes d'Information (CS) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * CS licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *   http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.orekit.data;

  18. import java.io.IOException;
  19. import java.io.InputStream;
  20. import java.util.Arrays;

  21. import org.hipparchus.util.FastMath;
  22. import org.orekit.errors.OrekitException;
  23. import org.orekit.errors.OrekitIOException;
  24. import org.orekit.errors.OrekitMessages;

  25. /** Filter for Unix compressed data.
  26.  * @author Luc Maisonobe
  27.  * @since 9.2
  28.  */
  29. public class UnixCompressFilter implements DataFilter {

  30.     /** Suffix for Unix compressed files. */
  31.     private static final String SUFFIX = ".Z";

  32.     /** {@inheritDoc} */
  33.     @Override
  34.     public NamedData filter(final NamedData original) {
  35.         final String                 oName   = original.getName();
  36.         final NamedData.StreamOpener oOpener = original.getStreamOpener();
  37.         if (oName.endsWith(SUFFIX)) {
  38.             final String                 fName   = oName.substring(0, oName.length() - SUFFIX.length());
  39.             final NamedData.StreamOpener fOpener = () -> new ZInputStream(oName, oOpener.openStream());
  40.             return new NamedData(fName, fOpener);
  41.         } else {
  42.             return original;
  43.         }
  44.     }

  45.     /** Filtering of Unix compressed stream. */
  46.     private static class ZInputStream extends InputStream {

  47.         /** First magic header byte. */
  48.         private static final int MAGIC_HEADER_1 = 0x1f;

  49.         /** Second magic header byte. */
  50.         private static final int MAGIC_HEADER_2 = 0x9d;

  51.         /** Byte bits width. */
  52.         private static final int BYTE_WIDTH = 8;

  53.         /** Initial bits width. */
  54.         private static final int INIT_WIDTH = 9;

  55.         /** Reset table code. */
  56.         private static final int RESET_TABLE = 256;

  57.         /** First non-predefined entry. */
  58.         private static final int FIRST = 257;

  59.         /** File name. */
  60.         private final String name;

  61.         /** Underlying compressed stream. */
  62.         private final InputStream input;

  63.         /** Common sequences table. */
  64.         private final UncompressedSequence[] table;

  65.         /** Next available entry in the table. */
  66.         private int available;

  67.         /** Flag for block mode when table is full. */
  68.         private final boolean blockMode;

  69.         /** Maximum width allowed. */
  70.         private final int maxWidth;

  71.         /** Current input width in bits. */
  72.         private int currentWidth;

  73.         /** Maximum key that can be encoded with current width. */
  74.         private int currentMaxKey;

  75.         /** Number of bits read since last reset. */
  76.         private int bitsRead;

  77.         /** Lookahead byte, already read but not yet used. */
  78.         private int lookAhead;

  79.         /** Number of bits in the lookahead byte. */
  80.         private int lookAheadWidth;

  81.         /** Previous uncompressed sequence output. */
  82.         private UncompressedSequence previousSequence;

  83.         /** Uncompressed sequence being output. */
  84.         private UncompressedSequence currentSequence;

  85.         /** Number of bytes of the current sequence already output. */
  86.         private int alreadyOutput;

  87.         /** Simple constructor.
  88.          * @param name file name
  89.          * @param input underlying compressed stream
  90.          * @exception IOException if first bytes cannot be read
  91.          * @exception OrekitException if the first bytes do not correspond to a compressed file
  92.          */
  93.         ZInputStream(final String name, final InputStream input)
  94.             throws IOException, OrekitException {

  95.             this.name  = name;
  96.             this.input = input;


  97.             // check header
  98.             if (input.read() != MAGIC_HEADER_1 || input.read() != MAGIC_HEADER_2) {
  99.                 throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_UNIX_COMPRESSED_FILE, name);
  100.             }

  101.             final int header3 = input.read();
  102.             this.blockMode = (header3 & 0x80) != 0;
  103.             this.maxWidth  = header3 & 0x1f;

  104.             // set up table, with at least all entries for one byte
  105.             this.table = new UncompressedSequence[1 << FastMath.max(INIT_WIDTH, maxWidth)];
  106.             for (int i = 0; i < FIRST; ++i) {
  107.                 table[i] = new UncompressedSequence(null, (byte) i);
  108.             }

  109.             // initialize decompression state
  110.             initialize();

  111.         }

  112.         /** Initialize compression state.
  113.          */
  114.         private void initialize() {
  115.             this.available        = FIRST;
  116.             this.bitsRead         = 0;
  117.             this.lookAhead        = 0;
  118.             this.lookAheadWidth   = 0;
  119.             this.currentWidth     = INIT_WIDTH;
  120.             this.currentMaxKey    = (1 << currentWidth) - 1;
  121.             this.previousSequence = null;
  122.             this.currentSequence  = null;
  123.             this.alreadyOutput    = 0;
  124.         }

  125.         /** Read next input key.
  126.          * @return next input key or -1 if end of stream is reached
  127.          * @exception IOException if a read error occurs
  128.          */
  129.         private int nextKey() throws IOException {

  130.             int keyMask = (1 << currentWidth) - 1;

  131.             while (true) {
  132.                 // initialize key with the last bits remaining from previous read
  133.                 int key = lookAhead & keyMask;

  134.                 // read more bits until key is complete
  135.                 for (int remaining = currentWidth - lookAheadWidth; remaining > 0; remaining -= BYTE_WIDTH) {
  136.                     lookAhead       = input.read();
  137.                     lookAheadWidth += BYTE_WIDTH;
  138.                     if (lookAhead < 0) {
  139.                         if (key == 0 || key == keyMask) {
  140.                             // the key is either a set of padding 0 bits
  141.                             // or a full key containing -1 if read() is called several times after EOF
  142.                             return -1;
  143.                         } else {
  144.                             // end of stream encountered in the middle of a read
  145.                             throw new OrekitIOException(OrekitMessages.UNEXPECTED_END_OF_FILE, name);
  146.                         }
  147.                     }
  148.                     key = (key | lookAhead << (currentWidth - remaining)) & keyMask;
  149.                 }

  150.                 // store the extra bits already read in the lookahead byte for next call
  151.                 lookAheadWidth -= currentWidth;
  152.                 lookAhead       = lookAhead >>> (BYTE_WIDTH - lookAheadWidth);

  153.                 bitsRead += currentWidth;

  154.                 if (blockMode && key == RESET_TABLE) {

  155.                     // skip the padding bits inserted when compressor flushed its buffer
  156.                     final int superSize = currentWidth * 8;
  157.                     int padding = (superSize - 1 - (bitsRead + superSize - 1) % superSize) / 8;
  158.                     while (padding-- > 0) {
  159.                         input.read();
  160.                     }

  161.                     // reset the table to handle a new block and read again next key
  162.                     Arrays.fill(table, FIRST, table.length, null);
  163.                     initialize();

  164.                     // reset the lookahead mask as the current width has changed
  165.                     keyMask = (1 << currentWidth) - 1;

  166.                 } else {
  167.                     // return key at current width
  168.                     return key;
  169.                 }

  170.             }

  171.         }

  172.         /** Select next uncompressed sequence to output.
  173.          * @return true if there is a next sequence
  174.          * @exception IOException if a read error occurs
  175.          */
  176.         private boolean selectNext() throws IOException {

  177.             // read next input key
  178.             final int key = nextKey();
  179.             if (key < 0) {
  180.                 // end of stream reached
  181.                 return false;
  182.             }

  183.             if (previousSequence != null && available < table.length) {
  184.                 // update the table with the next uncompressed byte appended to previous sequence
  185.                 final byte nextByte = (key == available) ? previousSequence.getByte(0) : table[key].getByte(0);
  186.                 table[available++] = new UncompressedSequence(previousSequence, nextByte);
  187.                 if (available > currentMaxKey && currentWidth < maxWidth) {
  188.                     // we need to increase the key size
  189.                     currentMaxKey = (1 << ++currentWidth) - 1;
  190.                 }
  191.             }

  192.             currentSequence = table[key];
  193.             if (currentSequence == null) {
  194.                 // the compressed file references a non-existent table entry
  195.                 // (this is not the well-known case of entry being used just before
  196.                 //  being defined, which is already handled above), the file is corrupted
  197.                 throw new OrekitIOException(OrekitMessages.CORRUPTED_FILE, name);
  198.             }
  199.             alreadyOutput   = 0;

  200.             return true;

  201.         }

  202.         /** {@inheritDoc} */
  203.         @Override
  204.         public int read() throws IOException {

  205.             if (currentSequence == null) {
  206.                 if (!selectNext()) {
  207.                     // we have reached end of data
  208.                     return -1;
  209.                 }
  210.             }

  211.             final int value = currentSequence.getByte(alreadyOutput++);
  212.             if (alreadyOutput >= currentSequence.length()) {
  213.                 // we have just exhausted the current sequence
  214.                 previousSequence = currentSequence;
  215.                 currentSequence  = null;
  216.                 alreadyOutput    = 0;
  217.             }

  218.             return value;

  219.         }

  220.     }

  221.     /** Uncompressed bits sequence. */
  222.     private static class UncompressedSequence {

  223.         /** Prefix sequence (null if this is a start sequence). */
  224.         private final UncompressedSequence prefix;

  225.         /** Last byte in the sequence. */
  226.         private final byte last;

  227.         /** Index of the last byte in the sequence (i.e. length - 1). */
  228.         private final int index;

  229.         /** Simple constructor.
  230.          * @param prefix prefix of the sequence (null if this is a start sequence)
  231.          * @param last last byte of the sequence
  232.          */
  233.         UncompressedSequence(final UncompressedSequence prefix, final byte last) {
  234.             this.prefix = prefix;
  235.             this.last   = last;
  236.             this.index  = prefix == null ? 0 : prefix.index + 1;
  237.         }

  238.         /** Get the length of the sequence.
  239.          * @return length of the sequence
  240.          */
  241.         public int length() {
  242.             return index + 1;
  243.         }

  244.         /** Get a byte from the sequence.
  245.          * @param outputIndex index of the byte in the sequence, counting from 0
  246.          * @return byte at {@code outputIndex}
  247.          */
  248.         public byte getByte(final int outputIndex) {
  249.             return index == outputIndex ? last : prefix.getByte(outputIndex);
  250.         }

  251.     }

  252. }