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