001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.codec.binary; 019 020import static org.apache.commons.codec.binary.BaseNCodec.EOF; 021 022import java.io.FilterInputStream; 023import java.io.IOException; 024import java.io.InputStream; 025 026import org.apache.commons.codec.binary.BaseNCodec.Context; 027 028/** 029 * Abstract superclass for Base-N input streams. 030 * 031 * @since 1.5 032 */ 033public class BaseNCodecInputStream extends FilterInputStream { 034 035 private final BaseNCodec baseNCodec; 036 037 private final boolean doEncode; 038 039 private final byte[] singleByte = new byte[1]; 040 041 private final Context context = new Context(); 042 043 protected BaseNCodecInputStream(final InputStream in, final BaseNCodec baseNCodec, final boolean doEncode) { 044 super(in); 045 this.doEncode = doEncode; 046 this.baseNCodec = baseNCodec; 047 } 048 049 /** 050 * {@inheritDoc} 051 * 052 * @return <code>0</code> if the {@link InputStream} has reached <code>EOF</code>, 053 * <code>1</code> otherwise 054 * @since 1.7 055 */ 056 @Override 057 public int available() throws IOException { 058 // Note: the logic is similar to the InflaterInputStream: 059 // as long as we have not reached EOF, indicate that there is more 060 // data available. As we do not know for sure how much data is left, 061 // just return 1 as a safe guess. 062 063 return context.eof ? 0 : 1; 064 } 065 066 /** 067 * Marks the current position in this input stream. 068 * <p>The {@link #mark} method of {@link BaseNCodecInputStream} does nothing.</p> 069 * 070 * @param readLimit the maximum limit of bytes that can be read before the mark position becomes invalid. 071 * @see #markSupported() 072 * @since 1.7 073 */ 074 @Override 075 public synchronized void mark(final int readLimit) { 076 // noop 077 } 078 079 /** 080 * {@inheritDoc} 081 * 082 * @return Always returns <code>false</code> 083 */ 084 @Override 085 public boolean markSupported() { 086 return false; // not an easy job to support marks 087 } 088 089 /** 090 * Reads one <code>byte</code> from this input stream. 091 * 092 * @return the byte as an integer in the range 0 to 255. Returns -1 if EOF has been reached. 093 * @throws IOException 094 * if an I/O error occurs. 095 */ 096 @Override 097 public int read() throws IOException { 098 int r = read(singleByte, 0, 1); 099 while (r == 0) { 100 r = read(singleByte, 0, 1); 101 } 102 if (r > 0) { 103 final byte b = singleByte[0]; 104 return b < 0 ? 256 + b : b; 105 } 106 return EOF; 107 } 108 109 /** 110 * Attempts to read <code>len</code> bytes into the specified <code>b</code> array starting at <code>offset</code> 111 * from this InputStream. 112 * 113 * @param b 114 * destination byte array 115 * @param offset 116 * where to start writing the bytes 117 * @param len 118 * maximum number of bytes to read 119 * 120 * @return number of bytes read 121 * @throws IOException 122 * if an I/O error occurs. 123 * @throws NullPointerException 124 * if the byte array parameter is null 125 * @throws IndexOutOfBoundsException 126 * if offset, len or buffer size are invalid 127 */ 128 @Override 129 public int read(final byte b[], final int offset, final int len) throws IOException { 130 if (b == null) { 131 throw new NullPointerException(); 132 } else if (offset < 0 || len < 0) { 133 throw new IndexOutOfBoundsException(); 134 } else if (offset > b.length || offset + len > b.length) { 135 throw new IndexOutOfBoundsException(); 136 } else if (len == 0) { 137 return 0; 138 } else { 139 int readLen = 0; 140 /* 141 Rationale for while-loop on (readLen == 0): 142 ----- 143 Base32.readResults() usually returns > 0 or EOF (-1). In the 144 rare case where it returns 0, we just keep trying. 145 146 This is essentially an undocumented contract for InputStream 147 implementors that want their code to work properly with 148 java.io.InputStreamReader, since the latter hates it when 149 InputStream.read(byte[]) returns a zero. Unfortunately our 150 readResults() call must return 0 if a large amount of the data 151 being decoded was non-base32, so this while-loop enables proper 152 interop with InputStreamReader for that scenario. 153 ----- 154 This is a fix for CODEC-101 155 */ 156 while (readLen == 0) { 157 if (!baseNCodec.hasData(context)) { 158 final byte[] buf = new byte[doEncode ? 4096 : 8192]; 159 final int c = in.read(buf); 160 if (doEncode) { 161 baseNCodec.encode(buf, 0, c, context); 162 } else { 163 baseNCodec.decode(buf, 0, c, context); 164 } 165 } 166 readLen = baseNCodec.readResults(b, offset, len, context); 167 } 168 return readLen; 169 } 170 } 171 172 /** 173 * Repositions this stream to the position at the time the mark method was last called on this input stream. 174 * <p> 175 * The {@link #reset} method of {@link BaseNCodecInputStream} does nothing except throw an {@link IOException}. 176 * 177 * @throws IOException if this method is invoked 178 * @since 1.7 179 */ 180 @Override 181 public synchronized void reset() throws IOException { 182 throw new IOException("mark/reset not supported"); 183 } 184 185 /** 186 * {@inheritDoc} 187 * 188 * @throws IllegalArgumentException if the provided skip length is negative 189 * @since 1.7 190 */ 191 @Override 192 public long skip(final long n) throws IOException { 193 if (n < 0) { 194 throw new IllegalArgumentException("Negative skip length: " + n); 195 } 196 197 // skip in chunks of 512 bytes 198 final byte[] b = new byte[512]; 199 long todo = n; 200 201 while (todo > 0) { 202 int len = (int) Math.min(b.length, todo); 203 len = this.read(b, 0, len); 204 if (len == EOF) { 205 break; 206 } 207 todo -= len; 208 } 209 210 return n - todo; 211 } 212}