]>
Commit | Line | Data |
---|---|---|
1b2798f6 EK |
1 | /******************************************************************************* |
2 | * Copyright (c) 2010 IBM Corporation and others. | |
3 | * All rights reserved. This program and the accompanying materials | |
4 | * are made available under the terms of the Eclipse Public License v1.0 | |
5 | * which accompanies this distribution, and is available at | |
6 | * http://www.eclipse.org/legal/epl-v10.html | |
7 | * | |
8 | * Contributors: | |
9 | * IBM Corporation - initial API and implementation | |
10 | *******************************************************************************/ | |
11 | package org.eclipse.jdt.internal.ui.propertiesfileeditor; | |
12 | ||
13 | import org.eclipse.core.runtime.CoreException; | |
14 | import org.eclipse.core.runtime.IStatus; | |
15 | ||
16 | import org.eclipse.jdt.internal.corext.util.Messages; | |
17 | ||
18 | import org.eclipse.jdt.internal.ui.dialogs.StatusInfo; | |
19 | ||
20 | /** | |
21 | * Helper class to convert between Java chars and the escaped form that must be used in .properties | |
22 | * files. | |
23 | * | |
24 | * @since 3.7 | |
25 | */ | |
26 | public class PropertiesFileEscapes { | |
27 | ||
28 | private static final char[] HEX_DIGITS= { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; | |
29 | ||
30 | private static char toHex(int halfByte) { | |
31 | return HEX_DIGITS[(halfByte & 0xF)]; | |
32 | } | |
33 | ||
34 | /** | |
35 | * Returns the decimal value of the Hex digit, or -1 if the digit is not a valid Hex digit. | |
36 | * | |
37 | * @param digit the Hex digit | |
38 | * @return the decimal value of digit, or -1 if digit is not a valid Hex digit. | |
39 | */ | |
40 | private static int getHexDigitValue(char digit) { | |
41 | switch (digit) { | |
42 | case '0': | |
43 | case '1': | |
44 | case '2': | |
45 | case '3': | |
46 | case '4': | |
47 | case '5': | |
48 | case '6': | |
49 | case '7': | |
50 | case '8': | |
51 | case '9': | |
52 | return digit - '0'; | |
53 | case 'a': | |
54 | case 'b': | |
55 | case 'c': | |
56 | case 'd': | |
57 | case 'e': | |
58 | case 'f': | |
59 | return 10 + digit - 'a'; | |
60 | case 'A': | |
61 | case 'B': | |
62 | case 'C': | |
63 | case 'D': | |
64 | case 'E': | |
65 | case 'F': | |
66 | return 10 + digit - 'A'; | |
67 | default: | |
68 | return -1; | |
69 | } | |
70 | } | |
71 | ||
72 | /** | |
73 | * Convert a Java char to the escaped form that must be used in .properties files. | |
74 | * | |
75 | * @param c the Java char | |
76 | * @return escaped string | |
77 | */ | |
78 | public static String escape(char c) { | |
79 | return escape(c, true, true, true); | |
80 | } | |
81 | ||
82 | /** | |
83 | * Convert characters in a Java string to the escaped form that must be used in .properties | |
84 | * files. | |
85 | * | |
86 | * @param s the Java string | |
87 | * @param escapeWhitespaceChars if <code>true</code>, escape whitespace characters | |
88 | * @param escapeBackslash if <code>true</code>, escape backslash characters | |
89 | * @param escapeUnicodeChars if <code>true</code>, escape unicode characters | |
90 | * @return escaped string | |
91 | */ | |
92 | public static String escape(String s, boolean escapeWhitespaceChars, boolean escapeBackslash, boolean escapeUnicodeChars) { | |
93 | StringBuffer sb= new StringBuffer(s.length()); | |
94 | int length= s.length(); | |
95 | for (int i= 0; i < length; i++) { | |
96 | char c= s.charAt(i); | |
97 | sb.append(escape(c, escapeWhitespaceChars, escapeBackslash, escapeUnicodeChars)); | |
98 | } | |
99 | return sb.toString(); | |
100 | } | |
101 | ||
102 | /** | |
103 | * Convert a Java char to the escaped form that must be used in .properties files. | |
104 | * | |
105 | * @param c the Java char | |
106 | * @param escapeWhitespaceChars if <code>true</code>, escape whitespace characters | |
107 | * @param escapeBackslash if <code>true</code>, escape backslash characters | |
108 | * @param escapeUnicodeChars if <code>true</code>, escape unicode characters | |
109 | * @return escaped string | |
110 | */ | |
111 | public static String escape(char c, boolean escapeWhitespaceChars, boolean escapeBackslash, boolean escapeUnicodeChars) { | |
112 | switch (c) { | |
113 | case '\t': | |
114 | return escapeWhitespaceChars ? "\\t" : "\t"; //$NON-NLS-1$//$NON-NLS-2$ | |
115 | case '\n': | |
116 | return escapeWhitespaceChars ? "\\n" : "\n"; //$NON-NLS-1$//$NON-NLS-2$ | |
117 | case '\f': | |
118 | return escapeWhitespaceChars ? "\\f" : "\r"; //$NON-NLS-1$//$NON-NLS-2$ | |
119 | case '\r': | |
120 | return escapeWhitespaceChars ? "\\r" : "\r"; //$NON-NLS-1$//$NON-NLS-2$ | |
121 | case '\\': | |
122 | return escapeBackslash ? "\\\\" : "\\"; //$NON-NLS-1$ //$NON-NLS-2$ | |
123 | default: | |
124 | if (escapeUnicodeChars && ((c < 0x0020) || (c > 0x007e && c <= 0x00a0) || (c > 0x00ff))) { | |
125 | //NBSP (0x00a0) is escaped to differentiate from normal space character | |
126 | return new StringBuffer() | |
127 | .append('\\') | |
128 | .append('u') | |
129 | .append(toHex((c >> 12) & 0xF)) | |
130 | .append(toHex((c >> 8) & 0xF)) | |
131 | .append(toHex((c >> 4) & 0xF)) | |
132 | .append(toHex(c & 0xF)).toString(); | |
133 | ||
134 | } else | |
135 | return String.valueOf(c); | |
136 | } | |
137 | } | |
138 | ||
139 | /** | |
140 | * Convert an escaped string to a string composed of Java characters. | |
141 | * | |
142 | * @param s the escaped string | |
143 | * @return string composed of Java characters | |
144 | * @throws CoreException if the escaped string has a malformed \\uxxx sequence | |
145 | */ | |
146 | public static String unescape(String s) throws CoreException { | |
147 | boolean isValidEscapedString= true; | |
148 | if (s == null) | |
149 | return null; | |
150 | ||
151 | char aChar; | |
152 | int len= s.length(); | |
153 | StringBuffer outBuffer= new StringBuffer(len); | |
154 | ||
155 | for (int x= 0; x < len;) { | |
156 | aChar= s.charAt(x++); | |
157 | if (aChar == '\\') { | |
158 | if (x > len - 1) { | |
159 | return outBuffer.toString(); // silently ignore the \ | |
160 | } | |
161 | aChar= s.charAt(x++); | |
162 | if (aChar == 'u') { | |
163 | // Read the xxxx | |
164 | int value= 0; | |
165 | if (x > len - 4) { | |
166 | String exceptionMessage= Messages.format(PropertiesFileEditorMessages.PropertiesFileHover_MalformedEncoding, outBuffer.toString() + s.substring(x - 2)); | |
167 | throw new CoreException(new StatusInfo(IStatus.WARNING, exceptionMessage)); | |
168 | } | |
169 | StringBuffer buf= new StringBuffer("\\u"); //$NON-NLS-1$ | |
170 | int digit= 0; | |
171 | for (int i= 0; i < 4; i++) { | |
172 | aChar= s.charAt(x++); | |
173 | digit= getHexDigitValue(aChar); | |
174 | if (digit == -1) { | |
175 | isValidEscapedString= false; | |
176 | x--; | |
177 | break; | |
178 | } | |
179 | value= (value << 4) + digit; | |
180 | buf.append(aChar); | |
181 | } | |
182 | outBuffer.append(digit == -1 ? buf.toString() : String.valueOf((char)value)); | |
183 | } else if (aChar == 't') { | |
184 | outBuffer.append('\t'); | |
185 | } else if (aChar == 'n') { | |
186 | outBuffer.append('\n'); | |
187 | } else if (aChar == 'f') { | |
188 | outBuffer.append('\f'); | |
189 | } else if (aChar == 'r') { | |
190 | outBuffer.append('\r'); | |
191 | } else { | |
192 | outBuffer.append(aChar); // silently ignore the \ | |
193 | } | |
194 | } else | |
195 | outBuffer.append(aChar); | |
196 | } | |
197 | if (isValidEscapedString) { | |
198 | return outBuffer.toString(); | |
199 | } else { | |
200 | String exceptionMessage= Messages.format(PropertiesFileEditorMessages.PropertiesFileHover_MalformedEncoding, outBuffer.toString()); | |
201 | throw new CoreException(new StatusInfo(IStatus.WARNING, exceptionMessage)); | |
202 | } | |
203 | } | |
204 | ||
205 | /** | |
206 | * Unescape backslash characters in a string. | |
207 | * | |
208 | * @param s the escaped string | |
209 | * @return string with backslash characters unescaped | |
210 | */ | |
211 | public static String unescapeBackslashes(String s) { | |
212 | if (s == null) | |
213 | return null; | |
214 | ||
215 | char c; | |
216 | int length= s.length(); | |
217 | StringBuffer outBuffer= new StringBuffer(length); | |
218 | ||
219 | for (int i= 0; i < length;) { | |
220 | c= s.charAt(i++); | |
221 | if (c == '\\') { | |
222 | c= s.charAt(i++); | |
223 | } | |
224 | outBuffer.append(c); | |
225 | } | |
226 | ||
227 | return outBuffer.toString(); | |
228 | } | |
229 | ||
230 | /** | |
231 | * Tests if the given text contains any invalid escape sequence. | |
232 | * | |
233 | * @param text the text | |
234 | * @return <code>true</code> if text contains an invalid escape sequence, <code>false</code> | |
235 | * otherwise | |
236 | */ | |
237 | public static boolean containsInvalidEscapeSequence(String text) { | |
238 | try { | |
239 | //check for invalid unicode escapes | |
240 | unescape(text); | |
241 | } catch (CoreException e) { | |
242 | return true; | |
243 | } | |
244 | ||
245 | int length= text.length(); | |
246 | for (int i= 0; i < length; i++) { | |
247 | char c= text.charAt(i); | |
248 | if (c == '\\') { | |
249 | if (i < length - 1) { | |
250 | char nextC= text.charAt(i + 1); | |
251 | switch (nextC) { | |
252 | case 't': | |
253 | case 'n': | |
254 | case 'f': | |
255 | case 'r': | |
256 | case 'u': | |
257 | case '\n': | |
258 | case '\r': | |
259 | case '=': | |
260 | case ':': | |
261 | break; | |
262 | case '\\': | |
263 | i++; | |
264 | break; | |
265 | default: | |
266 | return true; | |
267 | } | |
268 | } else { | |
269 | return true; | |
270 | } | |
271 | } | |
272 | } | |
273 | return false; | |
274 | } | |
275 | ||
276 | /** | |
277 | * Tests if the given text contains an unescaped backslash character. | |
278 | * | |
279 | * @param text the text | |
280 | * @return <code>true</code> if text contains an unescaped backslash character, | |
281 | * <code>false</code> otherwise | |
282 | */ | |
283 | public static boolean containsUnescapedBackslash(String text) { | |
284 | int length= text.length(); | |
285 | for (int i= 0; i < length; i++) { | |
286 | char c= text.charAt(i); | |
287 | if (c == '\\') { | |
288 | if (i < length - 1) { | |
289 | char nextC= text.charAt(i + 1); | |
290 | switch (nextC) { | |
291 | case '\\': | |
292 | i++; | |
293 | break; | |
294 | default: | |
295 | return true; | |
296 | } | |
297 | } else { | |
298 | return true; | |
299 | } | |
300 | } | |
301 | } | |
302 | return false; | |
303 | } | |
304 | ||
305 | /** | |
306 | * Tests if the given text contains only escaped backslash characters and no unescaped backslash | |
307 | * character. | |
308 | * | |
309 | * @param text the text | |
310 | * @return <code>true</code> if text contains only escaped backslash characters, | |
311 | * <code>false</code> otherwise | |
312 | */ | |
313 | public static boolean containsEscapedBackslashes(String text) { | |
314 | boolean result= false; | |
315 | int length= text.length(); | |
316 | for (int i= 0; i < length; i++) { | |
317 | char c= text.charAt(i); | |
318 | if (c == '\\') { | |
319 | if (i < length - 1) { | |
320 | char nextC= text.charAt(i + 1); | |
321 | switch (nextC) { | |
322 | case '\\': | |
323 | i++; | |
324 | result= true; | |
325 | break; | |
326 | default: | |
327 | return false; | |
328 | } | |
329 | } else { | |
330 | return false; | |
331 | } | |
332 | } | |
333 | } | |
334 | return result; | |
335 | } | |
336 | } |