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 * @version $Id: BaseNCodecInputStream.java 1429868 2013-01-07 16:08:05Z ggregory $ 033 */ 034public class BaseNCodecInputStream extends FilterInputStream { 035 036 private final BaseNCodec baseNCodec; 037 038 private final boolean doEncode; 039 040 private final byte[] singleByte = new byte[1]; 041 042 private final Context context = new Context(); 043 044 protected BaseNCodecInputStream(final InputStream in, final BaseNCodec baseNCodec, final boolean doEncode) { 045 super(in); 046 this.doEncode = doEncode; 047 this.baseNCodec = baseNCodec; 048 } 049 050 /** 051 * {@inheritDoc} 052 * 053 * @return <code>0</code> if the {@link InputStream} has reached <code>EOF</code>, 054 * <code>1</code> otherwise 055 * @since 1.7 056 */ 057 @Override 058 public int available() throws IOException { 059 // Note: the logic is similar to the InflaterInputStream: 060 // as long as we have not reached EOF, indicate that there is more 061 // data available. As we do not know for sure how much data is left, 062 // just return 1 as a safe guess. 063 064 return context.eof ? 0 : 1; 065 } 066 067 /** 068 * Marks the current position in this input stream. 069 * <p>The {@link #mark} method of {@link BaseNCodecInputStream} does nothing.</p> 070 * 071 * @param readLimit the maximum limit of bytes that can be read before the mark position becomes invalid. 072 * @since 1.7 073 */ 074 @Override 075 public synchronized void mark(final int readLimit) { 076 } 077 078 /** 079 * {@inheritDoc} 080 * 081 * @return always returns <code>false</code> 082 */ 083 @Override 084 public boolean markSupported() { 085 return false; // not an easy job to support marks 086 } 087 088 /** 089 * Reads one <code>byte</code> from this input stream. 090 * 091 * @return the byte as an integer in the range 0 to 255. Returns -1 if EOF has been reached. 092 * @throws IOException 093 * if an I/O error occurs. 094 */ 095 @Override 096 public int read() throws IOException { 097 int r = read(singleByte, 0, 1); 098 while (r == 0) { 099 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}