View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF 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  
18  package org.apache.commons.codec.binary;
19  
20  import static org.apache.commons.codec.binary.BaseNCodec.EOF;
21  
22  import java.io.FilterInputStream;
23  import java.io.IOException;
24  import java.io.InputStream;
25  
26  import org.apache.commons.codec.binary.BaseNCodec.Context;
27  
28  /**
29   * Abstract superclass for Base-N input streams.
30   *
31   * @since 1.5
32   * @version $Id: BaseNCodecInputStream.html 891688 2013-12-24 20:49:46Z ggregory $
33   */
34  public class BaseNCodecInputStream extends FilterInputStream {
35  
36      private final BaseNCodec baseNCodec;
37  
38      private final boolean doEncode;
39  
40      private final byte[] singleByte = new byte[1];
41  
42      private final Context context = new Context();
43  
44      protected BaseNCodecInputStream(final InputStream in, final BaseNCodec baseNCodec, final boolean doEncode) {
45          super(in);
46          this.doEncode = doEncode;
47          this.baseNCodec = baseNCodec;
48      }
49  
50      /**
51       * {@inheritDoc}
52       *
53       * @return <code>0</code> if the {@link InputStream} has reached <code>EOF</code>,
54       * <code>1</code> otherwise
55       * @since 1.7
56       */
57      @Override
58      public int available() throws IOException {
59          // Note: the logic is similar to the InflaterInputStream:
60          //       as long as we have not reached EOF, indicate that there is more
61          //       data available. As we do not know for sure how much data is left,
62          //       just return 1 as a safe guess.
63  
64          return context.eof ? 0 : 1;
65      }
66  
67      /**
68       * Marks the current position in this input stream.
69       * <p>The {@link #mark} method of {@link BaseNCodecInputStream} does nothing.</p>
70       *
71       * @param readLimit the maximum limit of bytes that can be read before the mark position becomes invalid.
72       * @since 1.7
73       */
74      @Override
75      public synchronized void mark(final int readLimit) {
76      }
77  
78      /**
79       * {@inheritDoc}
80       *
81       * @return always returns <code>false</code>
82       */
83      @Override
84      public boolean markSupported() {
85          return false; // not an easy job to support marks
86      }
87  
88      /**
89       * Reads one <code>byte</code> from this input stream.
90       *
91       * @return the byte as an integer in the range 0 to 255. Returns -1 if EOF has been reached.
92       * @throws IOException
93       *             if an I/O error occurs.
94       */
95      @Override
96      public int read() throws IOException {
97          int r = read(singleByte, 0, 1);
98          while (r == 0) {
99              r = read(singleByte, 0, 1);
100         }
101         if (r > 0) {
102             final byte b = singleByte[0];
103             return b < 0 ? 256 + b : b;
104         }
105         return EOF;
106     }
107 
108     /**
109      * Attempts to read <code>len</code> bytes into the specified <code>b</code> array starting at <code>offset</code>
110      * from this InputStream.
111      *
112      * @param b
113      *            destination byte array
114      * @param offset
115      *            where to start writing the bytes
116      * @param len
117      *            maximum number of bytes to read
118      *
119      * @return number of bytes read
120      * @throws IOException
121      *             if an I/O error occurs.
122      * @throws NullPointerException
123      *             if the byte array parameter is null
124      * @throws IndexOutOfBoundsException
125      *             if offset, len or buffer size are invalid
126      */
127     @Override
128     public int read(final byte b[], final int offset, final int len) throws IOException {
129         if (b == null) {
130             throw new NullPointerException();
131         } else if (offset < 0 || len < 0) {
132             throw new IndexOutOfBoundsException();
133         } else if (offset > b.length || offset + len > b.length) {
134             throw new IndexOutOfBoundsException();
135         } else if (len == 0) {
136             return 0;
137         } else {
138             int readLen = 0;
139             /*
140              Rationale for while-loop on (readLen == 0):
141              -----
142              Base32.readResults() usually returns > 0 or EOF (-1).  In the
143              rare case where it returns 0, we just keep trying.
144 
145              This is essentially an undocumented contract for InputStream
146              implementors that want their code to work properly with
147              java.io.InputStreamReader, since the latter hates it when
148              InputStream.read(byte[]) returns a zero.  Unfortunately our
149              readResults() call must return 0 if a large amount of the data
150              being decoded was non-base32, so this while-loop enables proper
151              interop with InputStreamReader for that scenario.
152              -----
153              This is a fix for CODEC-101
154             */
155             while (readLen == 0) {
156                 if (!baseNCodec.hasData(context)) {
157                     final byte[] buf = new byte[doEncode ? 4096 : 8192];
158                     final int c = in.read(buf);
159                     if (doEncode) {
160                         baseNCodec.encode(buf, 0, c, context);
161                     } else {
162                         baseNCodec.decode(buf, 0, c, context);
163                     }
164                 }
165                 readLen = baseNCodec.readResults(b, offset, len, context);
166             }
167             return readLen;
168         }
169     }
170 
171     /**
172      * Repositions this stream to the position at the time the mark method was last called on this input stream.
173      * <p>
174      * The {@link #reset} method of {@link BaseNCodecInputStream} does nothing except throw an {@link IOException}.
175      *
176      * @throws IOException if this method is invoked
177      * @since 1.7
178      */
179     @Override
180     public synchronized void reset() throws IOException {
181         throw new IOException("mark/reset not supported");
182     }
183 
184     /**
185      * {@inheritDoc}
186      *
187      * @throws IllegalArgumentException if the provided skip length is negative
188      * @since 1.7
189      */
190     @Override
191     public long skip(final long n) throws IOException {
192         if (n < 0) {
193             throw new IllegalArgumentException("Negative skip length: " + n);
194         }
195 
196         // skip in chunks of 512 bytes
197         final byte[] b = new byte[512];
198         long todo = n;
199 
200         while (todo > 0) {
201             int len = (int) Math.min(b.length, todo);
202             len = this.read(b, 0, len);
203             if (len == EOF) {
204                 break;
205             }
206             todo -= len;
207         }
208 
209         return n - todo;
210     }
211 }