001/* 002 * This file is part of the JDrupes non-blocking HTTP Codec 003 * Copyright (C) 2016, 2017 Michael N. Lipp 004 * 005 * This program is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU Lesser General Public License as published 007 * by the Free Software Foundation; either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, but 011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 013 * License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public License along 016 * with this program; if not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jdrupes.httpcodec.util; 020 021import java.nio.ByteBuffer; 022import java.nio.CharBuffer; 023import java.nio.charset.CharacterCodingException; 024import java.nio.charset.Charset; 025import java.nio.charset.CharsetDecoder; 026import java.nio.charset.CoderResult; 027import java.nio.charset.CodingErrorAction; 028 029/** 030 * Provides a wrapper that makes a {@link CharsetDecoder} behave like an 031 * optimized {@code CharsetDecoder}. 032 * <P> 033 * The default implementations of the {@link CharsetDecoder}s in the JRE leave 034 * data in the input buffer if there isn't sufficient data to decode the next 035 * character. The invoker is expected to add more data to the input buffer in 036 * response to the underflow result. This processing model can be used if you 037 * have a single buffer that you fill using some source and drain using the 038 * decoder. 039 * <P> 040 * If you use a model where one buffer is drained by the decoder while in 041 * parallel another buffer is being filled by the source, this processing model 042 * induces some difficulties, because an unknown number of bytes would have to 043 * be copied over from the newly filled buffer to the previously decoded buffer. 044 * This class provides what the documentation of 045 * {@link CharsetDecoder#decodeLoop} calls an "optimized implementation". It 046 * always drains the input buffer completely, caching any bytes that cannot be 047 * decoded (yet) because they don't encode a complete character. When the 048 * {@link #decode(ByteBuffer, CharBuffer, boolean)} method is invoked for the 049 * next time, as many bytes as needed to decode a complete character will be 050 * added to the cached bytes and the now available character is appended to the 051 * output. Then, the rest of the input is bulk processed as usual. 052 */ 053public class OptimizedCharsetDecoder { 054 055 private CharsetDecoder backing; 056 private ByteBuffer pending; 057 058 /** 059 * Creates a new instance. 060 * 061 * @param backing the backing charset decoder 062 */ 063 public OptimizedCharsetDecoder(CharsetDecoder backing) { 064 super(); 065 this.backing = backing; 066 pending = ByteBuffer.allocate(32); 067 } 068 069 /** 070 * @return the result 071 * @see java.nio.charset.CharsetDecoder#averageCharsPerByte() 072 */ 073 public final float averageCharsPerByte() { 074 return backing.averageCharsPerByte(); 075 } 076 077 /** 078 * @return the result 079 * @see java.nio.charset.CharsetDecoder#charset() 080 */ 081 public final Charset charset() { 082 return backing.charset(); 083 } 084 085 /** 086 * @param in the data to decode 087 * @param out the decoded data 088 * @param endOfInput {@code true} if the completes the input 089 * @return the result 090 * @see java.nio.charset.CharsetDecoder#decode(java.nio.ByteBuffer, 091 * java.nio.CharBuffer, boolean) 092 */ 093 public final CoderResult decode(ByteBuffer in, CharBuffer out, 094 boolean endOfInput) { 095 CoderResult result; 096 if (pending.position() > 0) { 097 if (!out.hasRemaining()) { 098 // There must be space for "the one" decoded character 099 return CoderResult.OVERFLOW; 100 } 101 while (true) { 102 if (!in.hasRemaining()) { 103 if (!endOfInput) { 104 return CoderResult.UNDERFLOW; 105 } 106 return backing.decode(pending, out, endOfInput); 107 } 108 pending.put(in.get()); 109 pending.flip(); 110 result = backing.decode(pending, out, endOfInput); 111 pending.compact(); 112 if (result.isOverflow() || result.isMalformed()) { 113 return result; 114 } 115 if (pending.position() > 0) { 116 continue; 117 } 118 break; 119 } 120 } 121 // Handle rest of input 122 result = backing.decode(in, out, endOfInput); 123 if (result.isUnderflow() && in.hasRemaining()) { 124 byte[] remaining = new byte[in.remaining()]; 125 in.get(remaining); 126 pending.put(remaining); 127 } 128 return result; 129 } 130 131 /** 132 * @param in the data 133 * @return the result 134 * @throws CharacterCodingException if an error occurred 135 * @see java.nio.charset.CharsetDecoder#decode(java.nio.ByteBuffer) 136 */ 137 public final CharBuffer decode(ByteBuffer in) 138 throws CharacterCodingException { 139 return backing.decode(in); 140 } 141 142 /** 143 * @return the result 144 * @see java.nio.charset.CharsetDecoder#detectedCharset() 145 */ 146 public Charset detectedCharset() { 147 return backing.detectedCharset(); 148 } 149 150 /** 151 * @param out the decoded data 152 * @return the result 153 * @see java.nio.charset.CharsetDecoder#flush(java.nio.CharBuffer) 154 */ 155 public final CoderResult flush(CharBuffer out) { 156 if (pending.position() > 0) { 157 // Shouldn't happen, hard to know what to do... 158 pending.clear(); 159 } 160 return backing.flush(out); 161 } 162 163 /** 164 * @return the result 165 * @see java.nio.charset.CharsetDecoder#isAutoDetecting() 166 */ 167 public boolean isAutoDetecting() { 168 return backing.isAutoDetecting(); 169 } 170 171 /** 172 * @return the result 173 * @see java.nio.charset.CharsetDecoder#isCharsetDetected() 174 */ 175 public boolean isCharsetDetected() { 176 return backing.isCharsetDetected(); 177 } 178 179 /** 180 * @return the result 181 * @see java.nio.charset.CharsetDecoder#malformedInputAction() 182 */ 183 public CodingErrorAction malformedInputAction() { 184 return backing.malformedInputAction(); 185 } 186 187 /** 188 * @return the result 189 * @see java.nio.charset.CharsetDecoder#maxCharsPerByte() 190 */ 191 public final float maxCharsPerByte() { 192 return backing.maxCharsPerByte(); 193 } 194 195 /** 196 * @param newAction the action 197 * @return the result 198 * @see java.nio.charset.CharsetDecoder#onMalformedInput 199 * (java.nio.charset.CodingErrorAction) 200 */ 201 public final CharsetDecoder onMalformedInput(CodingErrorAction newAction) { 202 return backing.onMalformedInput(newAction); 203 } 204 205 /** 206 * @param newAction the action 207 * @return the result 208 * @see java.nio.charset.CharsetDecoder#onUnmappableCharacter 209 * (java.nio.charset.CodingErrorAction) 210 */ 211 public final CharsetDecoder onUnmappableCharacter( 212 CodingErrorAction newAction) { 213 return backing.onUnmappableCharacter(newAction); 214 } 215 216 /** 217 * @param newReplacement the replacement 218 * @return the result 219 * @see java.nio.charset.CharsetDecoder#replaceWith(java.lang.String) 220 */ 221 public final CharsetDecoder replaceWith(String newReplacement) { 222 return backing.replaceWith(newReplacement); 223 } 224 225 /** 226 * @return the result 227 * @see java.nio.charset.CharsetDecoder#replacement() 228 */ 229 public final String replacement() { 230 return backing.replacement(); 231 } 232 233 /** 234 * @return the result 235 * @see java.nio.charset.CharsetDecoder#reset() 236 */ 237 public final CharsetDecoder reset() { 238 pending.clear(); 239 return backing.reset(); 240 } 241 242 /** 243 * @return the result 244 * @see java.lang.Object#toString() 245 */ 246 public String toString() { 247 return backing.toString(); 248 } 249 250 /** 251 * @return the result 252 * @see java.nio.charset.CharsetDecoder#unmappableCharacterAction() 253 */ 254 public CodingErrorAction unmappableCharacterAction() { 255 return backing.unmappableCharacterAction(); 256 } 257 258 /* (non-Javadoc) 259 * @see java.lang.Object#hashCode() 260 */ 261 @Override 262 public int hashCode() { 263 final int prime = 31; 264 int result = 1; 265 result = prime * result + ((backing == null) ? 0 : backing.hashCode()); 266 return result; 267 } 268 269 /* (non-Javadoc) 270 * @see java.lang.Object#equals(java.lang.Object) 271 */ 272 @Override 273 public boolean equals(Object obj) { 274 if (this == obj) { 275 return true; 276 } 277 if (obj == null) { 278 return false; 279 } 280 if (getClass() != obj.getClass()) { 281 return false; 282 } 283 OptimizedCharsetDecoder other = (OptimizedCharsetDecoder) obj; 284 if (backing == null) { 285 if (other.backing != null) { 286 return false; 287 } 288 } else if (!backing.equals(other.backing)) { 289 return false; 290 } 291 return true; 292 } 293 294}