Coverage Report - com.beetstra.jutf7.UTF7StyleCharsetEncoder
 
Classes in this File Line Coverage Branch Coverage Complexity
UTF7StyleCharsetEncoder
98% 
100% 
3,833
 
 1  
 /* ====================================================================
 2  
  * Copyright (c) 2006 J.T. Beetstra
 3  
  *
 4  
  * Permission is hereby granted, free of charge, to any person obtaining 
 5  
  * a copy of this software and associated documentation files (the 
 6  
  * "Software"), to deal in the Software without restriction, including 
 7  
  * without limitation the rights to use, copy, modify, merge, publish, 
 8  
  * distribute, sublicense, and/or sell copies of the Software, and to 
 9  
  * permit persons to whom the Software is furnished to do so, subject to 
 10  
  * the following conditions:
 11  
  *
 12  
  * The above copyright notice and this permission notice shall be 
 13  
  * included in all copies or substantial portions of the Software.
 14  
  *
 15  
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
 16  
  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
 17  
  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
 18  
  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 
 19  
  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
 20  
  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
 21  
  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 22  
  * ====================================================================
 23  
  */
 24  
 package com.beetstra.jutf7;
 25  
 
 26  
 import java.nio.ByteBuffer;
 27  
 import java.nio.CharBuffer;
 28  
 import java.nio.charset.CharsetEncoder;
 29  
 import java.nio.charset.CoderResult;
 30  
 
 31  
 /**
 32  
  * <p>The CharsetEncoder used to encode both variants of the UTF-7 charset and the 
 33  
  * modified-UTF-7 charset.</p>
 34  
  * 
 35  
  * <p><strong>Please note this class does not behave strictly according to the specification in 
 36  
  * Sun Java VMs before 1.6.</strong> This is done to get around a bug in the implementation 
 37  
  * of {@link java.nio.charset.CharsetEncoder#encode(CharBuffer)}. Unfortunately, that method 
 38  
  * cannot be overridden.</p>
 39  
  *  
 40  
  * @see <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6221056">JDK bug 6221056</a>
 41  
  * 
 42  
  * @author Jaap Beetstra
 43  
  */
 44  
 class UTF7StyleCharsetEncoder extends CharsetEncoder {
 45  
         private static final float AVG_BYTES_PER_CHAR = 1.5f;
 46  
         private static final float MAX_BYTES_PER_CHAR = 5.0f;
 47  
         private final UTF7StyleCharset cs;
 48  
         private final Base64Util base64;
 49  
         private final byte shift;
 50  
         private final byte unshift;
 51  
         private final boolean strict;
 52  
         private boolean base64mode;
 53  
         private int bitsToOutput;
 54  
         private int sextet;
 55  
         static boolean useUglyHackToForceCallToFlushInJava5;
 56  
         static {
 57  2
                 String version = System.getProperty("java.specification.version");
 58  2
                 String vendor = System.getProperty("java.vm.vendor");
 59  2
                 useUglyHackToForceCallToFlushInJava5 = "1.4".equals(version) || "1.5".equals(version);
 60  2
                 useUglyHackToForceCallToFlushInJava5 &= "Sun Microsystems Inc.".equals(vendor);
 61  2
         }
 62  
 
 63  
         UTF7StyleCharsetEncoder(UTF7StyleCharset cs, Base64Util base64, boolean strict) {
 64  18
                 super(cs, AVG_BYTES_PER_CHAR, MAX_BYTES_PER_CHAR);
 65  18
                 this.cs = cs;
 66  18
                 this.base64 = base64;
 67  18
                 this.strict = strict;
 68  18
                 this.shift = cs.shift();
 69  18
                 this.unshift = cs.unshift();
 70  18
         }
 71  
 
 72  
         /* (non-Javadoc)
 73  
          * @see java.nio.charset.CharsetEncoder#implReset()
 74  
          */
 75  
         protected void implReset() {
 76  12024
                 base64mode = false;
 77  12024
                 sextet = 0;
 78  12024
                 bitsToOutput = 0;
 79  12024
         }
 80  
 
 81  
         /**
 82  
          * {@inheritDoc}
 83  
          * 
 84  
          * <p>Note that this method might return <code>CoderResult.OVERFLOW</code> (as is 
 85  
          * required by the specification) if insufficient space is available in the output 
 86  
          * buffer. However, calling it again on JDKs before Java 6 triggers a bug in 
 87  
          * {@link java.nio.charset.CharsetEncoder#flush(ByteBuffer)} causing it to throw an 
 88  
          * IllegalStateException (the buggy method is <code>final</code>, thus cannot be 
 89  
          * overridden).</p>
 90  
          * 
 91  
          * @see <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6227608">JDK bug 6227608</a>
 92  
          * @param out The output byte buffer
 93  
          * @return A coder-result object describing the reason for termination
 94  
          */
 95  
         protected CoderResult implFlush(ByteBuffer out) {
 96  11900
                 if (base64mode) {
 97  6070
                         if (out.remaining() < 2)
 98  2
                                 return CoderResult.OVERFLOW;
 99  6068
                         if (bitsToOutput != 0)
 100  5498
                                 out.put(base64.getChar(sextet));
 101  6068
                         out.put(unshift);
 102  
                 }
 103  11898
                 return CoderResult.UNDERFLOW;
 104  
         }
 105  
 
 106  
         /**
 107  
          * {@inheritDoc}
 108  
          * 
 109  
          * <p>Note that this method might return <code>CoderResult.OVERFLOW</code>, even 
 110  
          * though there is sufficient space available in the output buffer. This is done 
 111  
          * to force the broken implementation of 
 112  
          * {@link java.nio.charset.CharsetEncoder#encode(CharBuffer)} to call flush 
 113  
          * (the buggy method is <code>final</code>, thus cannot be overridden).</p>
 114  
          * <p>However, String.getBytes() fails if CoderResult.OVERFLOW is returned, since
 115  
          * this assumes it always allocates sufficient bytes (maxBytesPerChar * nr_of_chars).
 116  
          * Thus, as an extra check, the size of the input buffer is compared against the size
 117  
          * of the output buffer.
 118  
          * A static variable is used to indicate if a broken java version is used.</p>
 119  
          * <p>It is not possible to directly write the last few bytes, since more bytes 
 120  
          * might be waiting to be encoded then those available in the input buffer.</p> 
 121  
          * 
 122  
          * @see <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6221056">JDK bug 6221056</a>
 123  
          * @param in The input character buffer
 124  
          * @param out The output byte buffer
 125  
          * @return A coder-result object describing the reason for termination
 126  
          */
 127  
         protected CoderResult encodeLoop(CharBuffer in, ByteBuffer out) {
 128  684048
                 while (in.hasRemaining()) {
 129  654344
                         if (out.remaining() < 4)
 130  14622
                                 return CoderResult.OVERFLOW;
 131  639722
                         char ch = in.get();
 132  639722
                         if (cs.canEncodeDirectly(ch)) {
 133  297160
                                 unshift(out, ch);
 134  297160
                                 out.put((byte) ch);
 135  342562
                         } else if (!base64mode && ch == shift) {
 136  15594
                                 out.put(shift);
 137  15594
                                 out.put(unshift);
 138  
                         } else
 139  326968
                                 encodeBase64(ch, out);
 140  639722
                 }
 141  
                 /* <HACK type="ugly">
 142  
                  These lines are required to trick JDK 1.5 and earlier into flushing when using 
 143  
                  Charset.encode(String), Charset.encode(CharBuffer) or CharsetEncoder.encode(CharBuffer)
 144  
                  Without them, the last few bytes may be missing.
 145  
                  */
 146  29704
                 if (base64mode && useUglyHackToForceCallToFlushInJava5
 147  
                                 && out.limit() != MAX_BYTES_PER_CHAR * in.limit())
 148  0
                         return CoderResult.OVERFLOW;
 149  
                 /* </HACK> */
 150  29704
                 return CoderResult.UNDERFLOW;
 151  
         }
 152  
 
 153  
         /**
 154  
          * <p>Writes the bytes necessary to leave <i>base 64 mode</i>. This might include an unshift 
 155  
          * character.</p>
 156  
          *  
 157  
          * @param out
 158  
          * @param ch
 159  
          */
 160  
         private void unshift(ByteBuffer out, char ch) {
 161  297160
                 if (!base64mode)
 162  162706
                         return;
 163  134454
                 if (bitsToOutput != 0)
 164  115678
                         out.put(base64.getChar(sextet));
 165  134454
                 if (base64.contains(ch) || ch == unshift || strict)
 166  131732
                         out.put(unshift);
 167  134454
                 base64mode = false;
 168  134454
                 sextet = 0;
 169  134454
                 bitsToOutput = 0;
 170  134454
         }
 171  
 
 172  
         /**
 173  
          * <p>Writes the bytes necessary to encode a character in <i>base 64 mode</i>. All bytes
 174  
          * which are fully determined will be written. The fields <code>bitsToOutput</code> and
 175  
          * <code>sextet</code> are used to remember the bytes not yet fully determined.</p>
 176  
          *  
 177  
          * @param out
 178  
          * @param ch
 179  
          */
 180  
         private void encodeBase64(char ch, ByteBuffer out) {
 181  326968
                 if (!base64mode)
 182  140522
                         out.put(shift);
 183  326968
                 base64mode = true;
 184  326968
                 bitsToOutput += 16;
 185  1122646
                 while (bitsToOutput >= 6) {
 186  795678
                         bitsToOutput -= 6;
 187  795678
                         sextet += (ch >> bitsToOutput);
 188  795678
                         sextet &= 0x3F;
 189  795678
                         out.put(base64.getChar(sextet));
 190  795678
                         sextet = 0;
 191  
                 }
 192  326968
                 sextet = (ch << (6 - bitsToOutput)) & 0x3F;
 193  326968
         }
 194  
 }