|
17 | 17 |
|
18 | 18 | package org.apache.rocketmq.redis.replicator;
|
19 | 19 |
|
| 20 | +import sun.nio.cs.ThreadLocalCoders; |
| 21 | + |
| 22 | +import java.io.IOException; |
| 23 | +import java.io.InvalidObjectException; |
| 24 | +import java.io.ObjectInputStream; |
| 25 | +import java.io.ObjectOutputStream; |
| 26 | +import java.io.Serializable; |
20 | 27 | import java.net.MalformedURLException;
|
21 | 28 | import java.net.URI;
|
22 | 29 | import java.net.URISyntaxException;
|
23 | 30 | import java.net.URL;
|
24 | 31 | import java.nio.ByteBuffer;
|
25 | 32 | import java.nio.CharBuffer;
|
| 33 | +import java.nio.charset.CharacterCodingException; |
26 | 34 | import java.nio.charset.CharsetDecoder;
|
27 | 35 | import java.nio.charset.CoderResult;
|
28 | 36 | import java.nio.charset.CodingErrorAction;
|
| 37 | +import java.text.Normalizer; |
29 | 38 | import java.util.HashMap;
|
30 | 39 | import java.util.Map;
|
31 | 40 | import java.util.Objects;
|
32 |
| -import sun.nio.cs.ThreadLocalCoders; |
33 | 41 |
|
34 |
| -public final class RedisURI implements Comparable<RedisURI> { |
| 42 | +public final class RedisURI implements Comparable<RedisURI>, Serializable { |
| 43 | + |
| 44 | + private static final long serialVersionUID = 1L; |
| 45 | + |
| 46 | + private final static char[] HEXDIGITS = { |
| 47 | + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' |
| 48 | + }; |
35 | 49 |
|
36 |
| - private String host; |
37 |
| - private String path; |
38 |
| - private String query; |
39 |
| - private int port = -1; |
40 |
| - private String scheme; |
41 |
| - private String userInfo; |
42 |
| - private String fragment; |
43 |
| - private String authority; |
44 |
| - private FileType fileType; |
| 50 | + private String string; |
45 | 51 |
|
46 |
| - private URI uri; |
47 |
| - Map<String, String> parameters = new HashMap<>(); |
| 52 | + private transient String host; |
| 53 | + private transient String path; |
| 54 | + private transient String query; |
| 55 | + private transient int port = -1; |
| 56 | + private transient String scheme; |
| 57 | + private transient String userInfo; |
| 58 | + private transient String fragment; |
| 59 | + private transient String authority; |
| 60 | + private transient FileType fileType; |
| 61 | + |
| 62 | + private transient URI uri; |
| 63 | + transient Map<String, String> parameters = new HashMap<>(); |
48 | 64 |
|
49 | 65 | public RedisURI(String uri) throws URISyntaxException {
|
50 | 66 | parse(uri);
|
| 67 | + this.string = this.uri.toString(); |
| 68 | + } |
| 69 | + |
| 70 | + public int getPort() { |
| 71 | + return port; |
| 72 | + } |
| 73 | + |
| 74 | + public String getHost() { |
| 75 | + return host; |
| 76 | + } |
| 77 | + |
| 78 | + public String getPath() { |
| 79 | + return path; |
| 80 | + } |
| 81 | + |
| 82 | + public String getQuery() { |
| 83 | + return query; |
| 84 | + } |
| 85 | + |
| 86 | + public String getScheme() { |
| 87 | + return scheme; |
| 88 | + } |
| 89 | + |
| 90 | + public String getUserInfo() { |
| 91 | + return userInfo; |
| 92 | + } |
| 93 | + |
| 94 | + public String getFragment() { |
| 95 | + return fragment; |
| 96 | + } |
| 97 | + |
| 98 | + public String getAuthority() { |
| 99 | + return authority; |
| 100 | + } |
| 101 | + |
| 102 | + public FileType getFileType() { |
| 103 | + return fileType; |
| 104 | + } |
| 105 | + |
| 106 | + @Override |
| 107 | + public boolean equals(Object o) { |
| 108 | + if (!(o instanceof RedisURI)) |
| 109 | + return false; |
| 110 | + return this.uri.equals(((RedisURI) o).uri); |
| 111 | + } |
| 112 | + |
| 113 | + @Override |
| 114 | + public int hashCode() { |
| 115 | + return this.uri.hashCode(); |
| 116 | + } |
| 117 | + |
| 118 | + @Override |
| 119 | + public int compareTo(RedisURI that) { |
| 120 | + return this.uri.compareTo(that.uri); |
51 | 121 | }
|
52 | 122 |
|
| 123 | + public URL toURL() throws MalformedURLException { |
| 124 | + Objects.requireNonNull(getFileType()); |
| 125 | + try { |
| 126 | + return new URI("file", uri.getRawAuthority(), uri.getRawPath(), uri.getRawQuery(), uri.getRawFragment()).toURL(); |
| 127 | + } catch (URISyntaxException e) { |
| 128 | + throw new MalformedURLException(e.getMessage()); |
| 129 | + } |
| 130 | + } |
| 131 | + |
| 132 | + @Override |
| 133 | + public String toString() { |
| 134 | + return this.uri.toString(); |
| 135 | + } |
| 136 | + |
| 137 | + public String toASCIIString() { |
| 138 | + return encode(this.uri.toString()); |
| 139 | + } |
| 140 | + |
| 141 | + private void writeObject(ObjectOutputStream os) throws IOException { |
| 142 | + os.defaultWriteObject(); |
| 143 | + } |
| 144 | + |
| 145 | + private void readObject(ObjectInputStream is) throws ClassNotFoundException, IOException { |
| 146 | + this.port = -1; |
| 147 | + is.defaultReadObject(); |
| 148 | + try { |
| 149 | + parse(this.string); |
| 150 | + } catch (URISyntaxException x) { |
| 151 | + IOException y = new InvalidObjectException("Invalid Redis URI"); |
| 152 | + y.initCause(x); |
| 153 | + throw y; |
| 154 | + } |
| 155 | + } |
| 156 | + |
| 157 | + // helper |
| 158 | + |
53 | 159 | private void parse(String uri) throws URISyntaxException {
|
54 | 160 | this.uri = new URI(uri);
|
55 | 161 | if (this.uri.getScheme().equalsIgnoreCase("redis")) {
|
56 | 162 | this.scheme = "redis";
|
57 | 163 | } else {
|
58 | 164 | throw new IllegalArgumentException("scheme must be [redis].");
|
59 | 165 | }
|
60 |
| - this.path = this.uri.getPath(); |
61 | 166 | this.host = this.uri.getHost();
|
| 167 | + this.path = this.uri.getPath(); |
62 | 168 | this.query = this.uri.getQuery();
|
63 |
| - this.fragment = this.uri.getFragment(); |
| 169 | + this.port = this.uri.getPort() == -1 ? 6379 : this.uri.getPort(); |
64 | 170 | this.userInfo = this.uri.getUserInfo();
|
| 171 | + this.fragment = this.uri.getFragment(); |
65 | 172 | this.authority = this.uri.getAuthority();
|
66 |
| - this.port = this.uri.getPort() == -1 ? 6379 : this.uri.getPort(); |
67 | 173 | if (this.path != null && this.userInfo == null) {
|
68 | 174 | int idx = this.path.lastIndexOf('.');
|
69 | 175 | if (idx >= 0) {
|
@@ -115,8 +221,8 @@ private static String decode(String s) {
|
115 | 221 | ByteBuffer bb = ByteBuffer.allocate(n);
|
116 | 222 | CharBuffer cb = CharBuffer.allocate(n);
|
117 | 223 | CharsetDecoder dec = ThreadLocalCoders.decoderFor("UTF-8")
|
118 |
| - .onMalformedInput(CodingErrorAction.REPLACE) |
119 |
| - .onUnmappableCharacter(CodingErrorAction.REPLACE); |
| 224 | + .onMalformedInput(CodingErrorAction.REPLACE) |
| 225 | + .onUnmappableCharacter(CodingErrorAction.REPLACE); |
120 | 226 |
|
121 | 227 | char c = s.charAt(0);
|
122 | 228 | boolean betweenBrackets = false;
|
@@ -171,71 +277,42 @@ private static byte decode(char c1, char c2) {
|
171 | 277 | return (byte) (((decode(c1) & 0xF) << 4) | ((decode(c2) & 0xF) << 0));
|
172 | 278 | }
|
173 | 279 |
|
174 |
| - public int getPort() { |
175 |
| - return port; |
176 |
| - } |
177 |
| - |
178 |
| - public String getHost() { |
179 |
| - return host; |
180 |
| - } |
181 |
| - |
182 |
| - public String getPath() { |
183 |
| - return path; |
184 |
| - } |
185 |
| - |
186 |
| - public String getQuery() { |
187 |
| - return query; |
188 |
| - } |
189 |
| - |
190 |
| - public String getScheme() { |
191 |
| - return scheme; |
192 |
| - } |
193 |
| - |
194 |
| - public String getUserInfo() { |
195 |
| - return userInfo; |
196 |
| - } |
197 |
| - |
198 |
| - public String getFragment() { |
199 |
| - return fragment; |
200 |
| - } |
201 |
| - |
202 |
| - public String getAuthority() { |
203 |
| - return authority; |
204 |
| - } |
205 |
| - |
206 |
| - public FileType getFileType() { |
207 |
| - return fileType; |
208 |
| - } |
209 |
| - |
210 |
| - @Override |
211 |
| - public boolean equals(Object o) { |
212 |
| - if (!(o instanceof RedisURI)) |
213 |
| - return false; |
214 |
| - return this.uri.equals(((RedisURI) o).uri); |
215 |
| - } |
216 |
| - |
217 |
| - @Override |
218 |
| - public int hashCode() { |
219 |
| - return this.uri.hashCode(); |
220 |
| - } |
| 280 | + private static String encode(String s) { |
| 281 | + int n = s.length(); |
| 282 | + if (n == 0) |
| 283 | + return s; |
221 | 284 |
|
222 |
| - @Override |
223 |
| - public int compareTo(RedisURI that) { |
224 |
| - return this.uri.compareTo(that.uri); |
225 |
| - } |
| 285 | + int i = 0; |
| 286 | + while (true) { |
| 287 | + if (s.charAt(i) >= '\u0080') |
| 288 | + break; |
| 289 | + if (++i >= n) |
| 290 | + return s; |
| 291 | + } |
226 | 292 |
|
227 |
| - public URL toURL() throws MalformedURLException { |
228 |
| - Objects.requireNonNull(getFileType()); |
| 293 | + String ns = Normalizer.normalize(s, Normalizer.Form.NFC); |
| 294 | + ByteBuffer bb = null; |
229 | 295 | try {
|
230 |
| - return new URI("file", uri.getRawAuthority(), uri.getRawPath(), uri.getRawQuery(), uri.getRawFragment()).toURL(); |
231 |
| - } catch (URISyntaxException e) { |
232 |
| - throw new MalformedURLException(e.getMessage()); |
| 296 | + bb = ThreadLocalCoders.encoderFor("UTF-8") |
| 297 | + .encode(CharBuffer.wrap(ns)); |
| 298 | + } catch (CharacterCodingException x) { |
| 299 | + assert false; |
233 | 300 | }
|
234 |
| - } |
235 | 301 |
|
236 |
| - @Override |
237 |
| - public String toString() { |
238 |
| - return this.uri.toString(); |
| 302 | + StringBuilder sb = new StringBuilder(); |
| 303 | + while (bb.hasRemaining()) { |
| 304 | + int b = bb.get() & 0xff; |
| 305 | + if (b >= 0x80) |
| 306 | + appendEscape(sb, (byte) b); |
| 307 | + else |
| 308 | + sb.append((char) b); |
| 309 | + } |
| 310 | + return sb.toString(); |
239 | 311 | }
|
240 | 312 |
|
| 313 | + private static void appendEscape(StringBuilder sb, byte b) { |
| 314 | + sb.append('%'); |
| 315 | + sb.append(HEXDIGITS[(b >> 4) & 0x0F]); |
| 316 | + sb.append(HEXDIGITS[(b >> 0) & 0x0F]); |
| 317 | + } |
241 | 318 | }
|
0 commit comments