Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||||||
UTF7StyleCharsetEncoder |
|
| 3.8333333333333335;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 | } |