1 /*
2  *  Licensed to the Apache Software Foundation (ASF) under one or more
3  *  contributor license agreements.  See the NOTICE file distributed with
4  *  this work for additional information regarding copyright ownership.
5  *  The ASF licenses this file to You under the Apache License, Version 2.0
6  *  (the "License"); you may not use this file except in compliance with
7  *  the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  */

17 package org.apache.tomcat.util.buf;
18
19 import java.io.IOException;
20 import java.nio.ByteBuffer;
21 import java.nio.CharBuffer;
22 import java.nio.charset.Charset;
23 import java.nio.charset.CharsetEncoder;
24 import java.nio.charset.CoderResult;
25 import java.nio.charset.CodingErrorAction;
26
27 /**
28  * NIO based character encoder.
29  */

30 public final class C2BConverter {
31
32     private final CharsetEncoder encoder;
33     private ByteBuffer bb = null;
34     private CharBuffer cb = null;
35
36     /**
37      * Leftover buffer used for multi-characters characters.
38      */

39     private final CharBuffer leftovers;
40
41     public C2BConverter(Charset charset) {
42         encoder = charset.newEncoder();
43         encoder.onUnmappableCharacter(CodingErrorAction.REPLACE)
44                 .onMalformedInput(CodingErrorAction.REPLACE);
45         char[] left = new char[4];
46         leftovers = CharBuffer.wrap(left);
47     }
48
49     /**
50      * Reset the encoder state.
51      */

52     public void recycle() {
53         encoder.reset();
54         leftovers.position(0);
55     }
56
57     public boolean isUndeflow() {
58         return (leftovers.position() > 0);
59     }
60
61     /**
62      * Convert the given characters to bytes.
63      *
64      * @param cc char input
65      * @param bc byte output
66      * @throws IOException An encoding error occurred
67      */

68     public void convert(CharChunk cc, ByteChunk bc) throws IOException {
69         if ((bb == null) || (bb.array() != bc.getBuffer())) {
70             // Create a new byte buffer if anything changed
71             bb = ByteBuffer.wrap(bc.getBuffer(), bc.getEnd(), bc.getBuffer().length - bc.getEnd());
72         } else {
73             // Initialize the byte buffer
74             bb.limit(bc.getBuffer().length);
75             bb.position(bc.getEnd());
76         }
77         if ((cb == null) || (cb.array() != cc.getBuffer())) {
78             // Create a new char buffer if anything changed
79             cb = CharBuffer.wrap(cc.getBuffer(), cc.getStart(), cc.getLength());
80         } else {
81             // Initialize the char buffer
82             cb.limit(cc.getEnd());
83             cb.position(cc.getStart());
84         }
85         CoderResult result = null;
86         // Parse leftover if any are present
87         if (leftovers.position() > 0) {
88             int pos = bb.position();
89             // Loop until one char is encoded or there is a encoder error
90             do {
91                 leftovers.put((char) cc.subtract());
92                 leftovers.flip();
93                 result = encoder.encode(leftovers, bb, false);
94                 leftovers.position(leftovers.limit());
95                 leftovers.limit(leftovers.array().length);
96             } while (result.isUnderflow() && (bb.position() == pos));
97             if (result.isError() || result.isMalformed()) {
98                 result.throwException();
99             }
100             cb.position(cc.getStart());
101             leftovers.position(0);
102         }
103         // Do the decoding and get the results into the byte chunk and the char
104         // chunk
105         result = encoder.encode(cb, bb, false);
106         if (result.isError() || result.isMalformed()) {
107             result.throwException();
108         } else if (result.isOverflow()) {
109             // Propagate current positions to the byte chunk and char chunk
110             bc.setEnd(bb.position());
111             cc.setOffset(cb.position());
112         } else if (result.isUnderflow()) {
113             // Propagate current positions to the byte chunk and char chunk
114             bc.setEnd(bb.position());
115             cc.setOffset(cb.position());
116             // Put leftovers in the leftovers char buffer
117             if (cc.getLength() > 0) {
118                 leftovers.limit(leftovers.array().length);
119                 leftovers.position(cc.getLength());
120                 cc.subtract(leftovers.array(), 0, cc.getLength());
121             }
122         }
123     }
124
125     /**
126      * Convert the given characters to bytes.
127      *
128      * @param cc char input
129      * @param bc byte output
130      * @throws IOException An encoding error occurred
131      */

132     public void convert(CharBuffer cc, ByteBuffer bc) throws IOException {
133         if ((bb == null) || (bb.array() != bc.array())) {
134             // Create a new byte buffer if anything changed
135             bb = ByteBuffer.wrap(bc.array(), bc.limit(), bc.capacity() - bc.limit());
136         } else {
137             // Initialize the byte buffer
138             bb.limit(bc.capacity());
139             bb.position(bc.limit());
140         }
141         if ((cb == null) || (cb.array() != cc.array())) {
142             // Create a new char buffer if anything changed
143             cb = CharBuffer.wrap(cc.array(), cc.arrayOffset() + cc.position(), cc.remaining());
144         } else {
145             // Initialize the char buffer
146             cb.limit(cc.limit());
147             cb.position(cc.position());
148         }
149         CoderResult result = null;
150         // Parse leftover if any are present
151         if (leftovers.position() > 0) {
152             int pos = bb.position();
153             // Loop until one char is encoded or there is a encoder error
154             do {
155                 leftovers.put(cc.get());
156                 leftovers.flip();
157                 result = encoder.encode(leftovers, bb, false);
158                 leftovers.position(leftovers.limit());
159                 leftovers.limit(leftovers.array().length);
160             } while (result.isUnderflow() && (bb.position() == pos));
161             if (result.isError() || result.isMalformed()) {
162                 result.throwException();
163             }
164             cb.position(cc.position());
165             leftovers.position(0);
166         }
167         // Do the decoding and get the results into the byte chunk and the char
168         // chunk
169         result = encoder.encode(cb, bb, false);
170         if (result.isError() || result.isMalformed()) {
171             result.throwException();
172         } else if (result.isOverflow()) {
173             // Propagate current positions to the byte chunk and char chunk
174             bc.limit(bb.position());
175             cc.position(cb.position());
176         } else if (result.isUnderflow()) {
177             // Propagate current positions to the byte chunk and char chunk
178             bc.limit(bb.position());
179             cc.position(cb.position());
180             // Put leftovers in the leftovers char buffer
181             if (cc.remaining() > 0) {
182                 leftovers.limit(leftovers.array().length);
183                 leftovers.position(cc.remaining());
184                 cc.get(leftovers.array(), 0, cc.remaining());
185             }
186         }
187     }
188
189     public Charset getCharset() {
190         return encoder.charset();
191     }
192 }
193