Version 1.0.3: NullPointerException in PercentCodec.encod() verhindert
[percentcodec] / utils / src / main / java / de / halbekunst / juplo / utils / PercentCodec.java
1 package de.halbekunst.juplo.utils;
2
3 import java.nio.charset.Charset;
4
5 /**
6  * This class performes percent-encoding/-decoding like described in RFC 3986.
7  * <p>
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.
14  *
15  * @author kai
16  */
17 public class PercentCodec {
18   private final Charset charset;
19
20
21   public PercentCodec(String encoding) {
22     charset = Charset.forName(encoding);
23   }
24
25
26   public String encode(CharSequence in) {
27     if (in == null)
28       return "";
29     StringBuilder out = new StringBuilder();
30     int i = 0;
31     int length = in.length();
32     while (i < length) {
33       int codePoint = Character.codePointAt(in, i);
34       i += Character.charCount(codePoint);
35       switch (codePoint) {
36         case 'a':
37         case 'A':
38         case 'b':
39         case 'B':
40         case 'c':
41         case 'C':
42         case 'd':
43         case 'D':
44         case 'e':
45         case 'E':
46         case 'f':
47         case 'F':
48         case 'g':
49         case 'G':
50         case 'h':
51         case 'H':
52         case 'i':
53         case 'I':
54         case 'j':
55         case 'J':
56         case 'k':
57         case 'K':
58         case 'l':
59         case 'L':
60         case 'm':
61         case 'M':
62         case 'n':
63         case 'N':
64         case 'o':
65         case 'O':
66         case 'p':
67         case 'P':
68         case 'q':
69         case 'Q':
70         case 'r':
71         case 'R':
72         case 's':
73         case 'S':
74         case 't':
75         case 'T':
76         case 'u':
77         case 'U':
78         case 'v':
79         case 'V':
80         case 'w':
81         case 'W':
82         case 'x':
83         case 'X':
84         case 'y':
85         case 'Y':
86         case 'z':
87         case 'Z':
88         case '0':
89         case '1':
90         case '2':
91         case '3':
92         case '4':
93         case '5':
94         case '6':
95         case '7':
96         case '8':
97         case '9':
98         case '-':
99         case '_':
100         case '.':
101         case '~':
102           /**
103            * Unreserved characters can (and should!) stay unchanged!
104            * (See {@link http://en.wikipedia.org/wiki/Percent-encoding#Types_of_URI_characters})
105            */
106           out.append(Character.toChars(codePoint));
107           break;
108         default:
109           /**
110            * All other characters are reserved or special characters and,
111            * hence, must be encoded!
112            */
113           String encoded = new String(Character.toChars(codePoint));
114           byte[] bytes = encoded.getBytes(charset);
115           for (int j = 0; j < bytes.length; j++) {
116             out.append('%');
117             out.append(Character.forDigit((bytes[j] >> 4) & 0xF, 16));
118             out.append(Character.forDigit((bytes[j]) & 0xF, 16));
119           }
120       }
121     }
122     return out.toString();
123   }
124
125   public String decode(CharSequence in) {
126     StringBuilder out = new StringBuilder();
127     int i = 0;
128     int length = in.length();
129     while (i < length) {
130       char c = in.charAt(i);
131       if (c != '%') {
132         out.append(c);
133         i++;
134       }
135       else {
136         byte[] bytes = new byte[length-i/3];
137         int pos = 0;
138         while (i+2 < length && in.charAt(i) == '%' ) {
139           int b = 0;
140           switch (in.charAt(i+1)) {
141             case '0': break;
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;
151             case 'a':
152             case 'A': b = 16*10; break;
153             case 'b':
154             case 'B': b = 16*11; break;
155             case 'c':
156             case 'C': b = 16*12; break;
157             case 'd':
158             case 'D': b = 16*13; break;
159             case 'e':
160             case 'E': b = 16*14; break;
161             case 'f':
162             case 'F': b = 16*15; break;
163             default: throw new IllegalArgumentException("Illegal escape-sequence: %" + in.subSequence(i, i+3));
164           }
165           switch (in.charAt(i+2)) {
166             case '0': break;
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;
176             case 'a':
177             case 'A': b += 10; break;
178             case 'b':
179             case 'B': b += 11; break;
180             case 'c':
181             case 'C': b += 12; break;
182             case 'd':
183             case 'D': b += 13; break;
184             case 'e':
185             case 'E': b += 14; break;
186             case 'f':
187             case 'F': b += 15; break;
188             default: throw new IllegalArgumentException("Illegal escape-sequence: %" + in.subSequence(i, i+3));
189           }
190           bytes[pos++] = (byte)b;
191           i += 3;
192         }
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));
196       }
197     }
198     return out.toString();
199   }
200 }