1 package de.halbekunst.juplo.utils;
3 import java.nio.charset.Charset;
6 * This class performes percent-encoding/-decoding like described in RFC 3986.
8 * Complete URI's are not handled by this implementation.
9 * That is done best with the original {@linkplain java.net.URI}-class from core Java.
10 * The purpose of this class is to have a simple tool to encode/decode the
11 * inner parts of an URI, like a segment of the URI-path (the part between two
12 * forward slashes) or a name or value segment of the query, where all reserved
13 * characters must be encoded/decoded.
17 public class PercentCodec {
18 private final Charset charset;
21 public PercentCodec(String encoding) {
22 charset = Charset.forName(encoding);
26 public String encode(CharSequence in) {
29 StringBuilder out = new StringBuilder();
31 int length = in.length();
33 int codePoint = Character.codePointAt(in, i);
34 i += Character.charCount(codePoint);
103 * Unreserved characters can (and should!) stay unchanged!
104 * (See {@link http://en.wikipedia.org/wiki/Percent-encoding#Types_of_URI_characters})
106 out.append(Character.toChars(codePoint));
110 * All other characters are reserved or special characters and,
111 * hence, must be encoded!
113 String encoded = new String(Character.toChars(codePoint));
114 byte[] bytes = encoded.getBytes(charset);
115 for (int j = 0; j < bytes.length; j++) {
117 out.append(Character.forDigit((bytes[j] >> 4) & 0xF, 16));
118 out.append(Character.forDigit((bytes[j]) & 0xF, 16));
122 return out.toString();
125 public String decode(CharSequence in) {
126 StringBuilder out = new StringBuilder();
128 int length = in.length();
130 char c = in.charAt(i);
136 byte[] bytes = new byte[length-i/3];
138 while (i+2 < length && in.charAt(i) == '%' ) {
140 switch (in.charAt(i+1)) {
142 case '1': b = 16*1; break;
143 case '2': b = 16*2; break;
144 case '3': b = 16*3; break;
145 case '4': b = 16*4; break;
146 case '5': b = 16*5; break;
147 case '6': b = 16*6; break;
148 case '7': b = 16*7; break;
149 case '8': b = 16*8; break;
150 case '9': b = 16*9; break;
152 case 'A': b = 16*10; break;
154 case 'B': b = 16*11; break;
156 case 'C': b = 16*12; break;
158 case 'D': b = 16*13; break;
160 case 'E': b = 16*14; break;
162 case 'F': b = 16*15; break;
163 default: throw new IllegalArgumentException("Illegal escape-sequence: %" + in.subSequence(i, i+3));
165 switch (in.charAt(i+2)) {
167 case '1': b += 1; break;
168 case '2': b += 2; break;
169 case '3': b += 3; break;
170 case '4': b += 4; break;
171 case '5': b += 5; break;
172 case '6': b += 6; break;
173 case '7': b += 7; break;
174 case '8': b += 8; break;
175 case '9': b += 9; break;
177 case 'A': b += 10; break;
179 case 'B': b += 11; break;
181 case 'C': b += 12; break;
183 case 'D': b += 13; break;
185 case 'E': b += 14; break;
187 case 'F': b += 15; break;
188 default: throw new IllegalArgumentException("Illegal escape-sequence: %" + in.subSequence(i, i+3));
190 bytes[pos++] = (byte)b;
193 out.append(new String(bytes, 0, pos, charset));
194 if (i < length && in.charAt(i) == '%')
195 throw new IllegalArgumentException("Incomplete escape-sequence: %" + in.subSequence(i, length));
198 return out.toString();