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.io.UnsupportedEncodingException;
27  import java.nio.ByteBuffer;
28  import java.nio.CharBuffer;
29  import java.nio.charset.CharacterCodingException;
30  import java.nio.charset.Charset;
31  import java.nio.charset.CharsetDecoder;
32  import java.nio.charset.CharsetEncoder;
33  import java.nio.charset.CoderResult;
34  import junit.framework.TestCase;
35  
36  public class AcceptanceTest extends TestCase {
37  	private CharsetProvider provider;
38  	private Charset charset;
39  	private CharsetDecoder decoder;
40  	private CharsetEncoder encoder;
41  
42  	protected void setUp() throws Exception {
43  		provider = new CharsetProvider();
44  	}
45  
46  	public void testUTF7() throws Exception {
47  		init("UTF-7");
48  		assertEquals("A+ImIDkQ.", encodeGetBytes("A\u2262\u0391."));
49  		assertEquals("A+ImIDkQ.", encodeCharsetEncode("A\u2262\u0391."));
50  		assertEquals("+ACEAIgAj-", encodeGetBytes("!\"#"));
51  		verifyAll();
52  	}
53  
54  	public void testUTF7o() throws Exception {
55  		init("X-UTF-7-OPTIONAL");
56  		assertEquals("A+ImIDkQ.", encodeGetBytes("A\u2262\u0391."));
57  		assertEquals("A+ImIDkQ.", encodeCharsetEncode("A\u2262\u0391."));
58  		assertEquals("!\"#", encodeGetBytes("!\"#"));
59  		verifyAll();
60  	}
61  
62  	public void testModifiedUTF7() throws Exception {
63  		init("x-IMAP4-MODIFIED-UTF7");
64  		assertEquals("A&ImIDkQ-.", encodeGetBytes("A\u2262\u0391."));
65  		assertEquals("A&ImIDkQ-.", encodeCharsetEncode("A\u2262\u0391."));
66  		verifyAll();
67  	}
68  
69  	private void init(final String init) {
70  		charset = provider.charsetForName(init);
71  		decoder = charset.newDecoder();
72  		encoder = charset.newEncoder();
73  	}
74  
75  	private void verifyAll() throws Exception {
76  		verifySymmetrical("áéíóúäëïöüàèìòùâêîôûãõçñ€");
77  		verifySymmetrical("aábécídóeúfägëhïiöjükàlèmìnòoùpâqêrîsôtûuãvõwçxñy€z");
78  		verifySymmetrical("abcáéídefóúäghiëïöjklüàèmnoìòùpqrâêîstuôûãvwxõçñyz€");
79  		verifySymmetrical("abcdefghijklmnopqrstuvwyxzáéíóúäëïöüàèìòùâêîôûãõçñ€abcdefghijklmnopqrstuvwyxz");
80  		verifySymmetrical("aáb+écí+-dóe-úfä-+gëh+ïiö+-jük-àlè-+mìn+òoù+-pâq-êrî-+sôt+ûuã+-võwç-xñy-+€z+");
81  		verifySymmetrical("á+éí+óúä+ëïö++ü++àè++ìòù+++â+++êî+++ôûã+++õçñ€");
82  		verifySymmetrical("á+-éí+-óúä+-ëïö++-ü++-àè++-ìòù+++-â+++-êî+++-ôûã+++-õçñ€");
83  		verifySymmetrical("++++++++");
84  		verifySymmetrical("+-++--+++---++");
85  		verifySymmetrical("+áéí+");
86  		verifySymmetrical("`~!@#$%^&*()_+-=[]\\{}|;':\",./<>?\u0000\r\n\t\b\f€");
87  		verifySymmetrical("#aáa#á#áá#ááá#");
88  	}
89  
90  	protected void verifySymmetrical(String s) throws Exception {
91  		final String encoded = encodeGetBytes(s);
92  		assertEquals(encoded, encodeCharsetEncode(s));
93  		assertEquals("problem decoding " + encoded, s, decode(encoded));
94  		for (int i = 4; i < encoded.length(); i++) {
95  			ByteBuffer in = CharsetTestUtil.wrap(encoded);
96  			decoder.reset();
97  			verifyChunkedOutDecode(i, in, s);
98  		}
99  		for (int i = 10; i < encoded.length(); i++) {
100 			CharBuffer in = CharBuffer.wrap(s);
101 			encoder.reset();
102 			verifyChunkedOutEncode(i, in, encoded);
103 		}
104 		for (int i = 10; i < encoded.length(); i++) {
105 			decoder.reset();
106 			verifyChunkedInDecode(i, encoded, s);
107 		}
108 		for (int i = 4; i < encoded.length(); i++) {
109 			encoder.reset();
110 			verifyChunkedInEncode(i, s, encoded);
111 		}
112 	}
113 
114 	private String encodeCharsetEncode(String string) throws UnsupportedEncodingException {
115 		String charsetEncode = CharsetTestUtil.asString(charset.encode(string));
116 		return charsetEncode;
117 	}
118 
119 	/* 
120 	 * simulate what is done in String.getBytes
121 	 * (cannot be used directly since Charset is not installed while testing)
122 	 */
123 	private String encodeGetBytes(String string) throws CharacterCodingException,
124 			UnsupportedEncodingException {
125 		ByteBuffer bb = ByteBuffer.allocate((int) (encoder.maxBytesPerChar() * string.length()));
126 		CharBuffer cb = CharBuffer.wrap(string);
127 		encoder.reset();
128 		CoderResult cr = encoder.encode(cb, bb, true);
129 		if (!cr.isUnderflow())
130 			cr.throwException();
131 		cr = encoder.flush(bb);
132 		if (!cr.isUnderflow())
133 			cr.throwException();
134 		bb.flip();
135 		String stringGetBytes = CharsetTestUtil.asString(bb);
136 		return stringGetBytes;
137 	}
138 
139 	protected String decode(String string) throws UnsupportedEncodingException {
140 		final ByteBuffer buffer = CharsetTestUtil.wrap(string);
141 		final CharBuffer decoded = charset.decode(buffer);
142 		return decoded.toString();
143 	}
144 
145 	protected void verifyChunkedInDecode(int i, String encoded, String decoded)
146 			throws UnsupportedEncodingException {
147 		ByteBuffer in = ByteBuffer.allocate(i);
148 		CharBuffer out = CharBuffer.allocate(decoded.length() + 5);
149 		int pos = 0;
150 		CoderResult result = CoderResult.UNDERFLOW;
151 		while (pos < encoded.length()) {
152 			int end = Math.min(encoded.length(), pos + i);
153 			in.put(CharsetTestUtil.wrap(encoded.substring(pos + in.position(), end)));
154 			in.flip();
155 			result = decoder.decode(in, out, false);
156 			assertEquals("at position: " + pos, CoderResult.UNDERFLOW, result);
157 			assertTrue("no progress after " + pos + " of " + encoded.length(), in.position() > 0);
158 			pos += in.position();
159 			in.compact();
160 		}
161 		in.limit(0);
162 		result = decoder.decode(in, out, true);
163 		assertEquals(CoderResult.UNDERFLOW, result);
164 		result = decoder.flush(out);
165 		assertEquals(CoderResult.UNDERFLOW, result);
166 		assertEquals(encoded.length(), pos);
167 		assertEquals(decoded.length(), out.position());
168 		out.flip();
169 		assertEquals("for length: " + i, decoded, out.toString());
170 	}
171 
172 	protected void verifyChunkedInEncode(int i, String decoded, String encoded)
173 			throws UnsupportedEncodingException {
174 		CharBuffer in = CharBuffer.allocate(i);
175 		ByteBuffer out = ByteBuffer.allocate(encoded.length() + 40);
176 		int pos = 0;
177 		CoderResult result = CoderResult.UNDERFLOW;
178 		while (pos < decoded.length()) {
179 			int end = Math.min(decoded.length(), pos + i);
180 			in.put(decoded.substring(pos + in.position(), end));
181 			in.flip();
182 			assertTrue("unexpected end at " + pos, in.limit() > 0);
183 			result = encoder.encode(in, out, false);
184 			if (result.isUnderflow())
185 				assertTrue("no progress after " + pos + " of " + decoded.length() + " in "
186 						+ decoded, in.position() > 0);
187 			pos += in.position();
188 			in.compact();
189 		}
190 		pos += in.position();
191 		in.limit(0);
192 		result = encoder.encode(in, out, true);
193 		result = encoder.flush(out);
194 		assertEquals(CoderResult.UNDERFLOW, result);
195 		out.flip();
196 		assertEquals("for length: " + i, encoded, CharsetTestUtil.asString(out));
197 	}
198 
199 	protected void verifyChunkedOutEncode(int i, CharBuffer in, String encoded)
200 			throws UnsupportedEncodingException {
201 		ByteBuffer out = ByteBuffer.allocate(i);
202 		int encodeCount = 0;
203 		StringBuffer sb = new StringBuffer();
204 		CoderResult result;
205 		while (in.hasRemaining()) {
206 			result = encoder.encode(in, out, false);
207 			encodeCount += out.position();
208 			if (in.hasRemaining()) {
209 				assertEquals("at position: " + encodeCount, CoderResult.OVERFLOW, result);
210 				assertTrue("at position: " + encodeCount, out.position() > 0);
211 			}
212 			CharsetTestUtil.outToSB(out, sb);
213 		}
214 		result = encoder.encode(in, out, true);
215 		assertFalse(!result.isOverflow() && in.hasRemaining());
216 		CharsetTestUtil.outToSB(out, sb);
217 		result = encoder.flush(out);
218 		CharsetTestUtil.outToSB(out, sb);
219 		assertEquals(encoded, sb.toString());
220 		in.rewind();
221 	}
222 
223 	protected void verifyChunkedOutDecode(int i, ByteBuffer in, String decoded) {
224 		CharBuffer out = CharBuffer.allocate(i);
225 		int decodeCount = 0;
226 		StringBuffer sb = new StringBuffer();
227 		CoderResult result = CoderResult.OVERFLOW;
228 		while (decodeCount < decoded.length()) {
229 			assertEquals("at position: " + decodeCount, CoderResult.OVERFLOW, result);
230 			result = decoder.decode(in, out, true);
231 			assertTrue(out.position() > 0);
232 			decodeCount += out.position();
233 			out.flip();
234 			sb.append(out.toString());
235 			out.clear();
236 		}
237 		assertEquals(decoded, sb.toString());
238 		in.rewind();
239 	}
240 }