Commit | Line | Data |
---|---|---|
f8177ba2 FC |
1 | /******************************************************************************* |
2 | * Copyright (c) 2012 Ericsson | |
3 | * | |
4 | * All rights reserved. This program and the accompanying materials are | |
5 | * made available under the terms of the Eclipse Public License v1.0 which | |
6 | * accompanies this distribution, and is available at | |
7 | * http://www.eclipse.org/legal/epl-v10.html | |
8 | * | |
9 | * Contributors: | |
10 | * Francois Chouinard - Initial API and implementation | |
11 | *******************************************************************************/ | |
12 | ||
13 | package org.eclipse.linuxtools.tmf.core.event; | |
14 | ||
15 | import java.text.DecimalFormat; | |
16 | import java.text.ParseException; | |
17 | import java.text.SimpleDateFormat; | |
18 | import java.util.ArrayList; | |
19 | import java.util.Calendar; | |
20 | import java.util.Date; | |
21 | import java.util.List; | |
f8177ba2 FC |
22 | import java.util.regex.Matcher; |
23 | import java.util.regex.Pattern; | |
24 | ||
25 | import org.eclipse.linuxtools.tmf.core.signal.TmfSignalManager; | |
26 | import org.eclipse.linuxtools.tmf.core.signal.TmfTimestampFormatUpdateSignal; | |
27 | ||
28 | /** | |
29 | * A formatting and parsing facility that can handle timestamps that span the | |
30 | * epoch with a precision down to the nanosecond. It can be understood as a | |
31 | * simplified and more constrained version of SimpleDateFormat as it limits the | |
32 | * number of allowed pattern characters and the acceptable timestamp formats. | |
33 | * <p> | |
34 | * The timestamp representation is broken down into a number of optional | |
35 | * components that can be assembled into a fairly simple way. | |
36 | * | |
37 | * <h4>Date Pattern</h4> | |
38 | * <blockquote> | |
39 | * <table border=0 cellspacing=3 cellpadding=0 > | |
40 | * <tr bgcolor="#ccccff"> | |
41 | * <th align=left>Format | |
42 | * <th align=left>Description | |
43 | * <th align=left>Value Range | |
44 | * <th align=left>Example | |
45 | * <tr> | |
46 | * <td><code>yyyy</code> | |
47 | * <td>Year | |
48 | * <td><code>1970-...</code> | |
49 | * <td><code>2012</code> | |
50 | * <tr bgcolor="#eeeeff"> | |
51 | * <td><code>MM</code> | |
52 | * <td>Month in year | |
53 | * <td><code>01-12</code> | |
54 | * <td><code>09</code> | |
55 | * <tr> | |
56 | * <td><code>dd</code> | |
57 | * <td>Day in month | |
58 | * <td><code>01-31</code> | |
59 | * <td><code>22</code> | |
60 | * </table> | |
61 | * </blockquote> | |
62 | * | |
63 | * <h4>Time Pattern</h4> | |
64 | * <blockquote> | |
65 | * <table border=0 cellspacing=3 cellpadding=0 > | |
66 | * <tr bgcolor="#ccccff"> | |
67 | * <th align=left>Format | |
68 | * <th align=left>Description | |
69 | * <th align=left>Value Range | |
70 | * <th align=left>Example | |
71 | * <tr> | |
72 | * <td><code>HH</code> | |
73 | * <td>Hour in day | |
74 | * <td><code>00-23</code> | |
75 | * <td><code>07</code> | |
76 | * <tr bgcolor="#eeeeff"> | |
77 | * <td><code>mm</code> | |
78 | * <td>Minute in hour | |
79 | * <td><code>00-59</code> | |
80 | * <td><code>35</code> | |
81 | * <tr> | |
82 | * <td><code>ss</code> | |
83 | * <td>Second in minute | |
84 | * <td><code>00-59</code> | |
85 | * <td><code>41</code> | |
86 | * <tr bgcolor="#eeeeff"> | |
87 | * <td><code>T</code> | |
88 | * <td>The seconds since the epoch | |
89 | * <td><code>00-...</code> | |
90 | * <td><code>1332170682</code> | |
91 | * </table> | |
92 | * </blockquote> | |
93 | * | |
94 | * <h4>Sub-Seconds Pattern</h4> | |
95 | * <blockquote> | |
96 | * <table border=0 cellspacing=3 cellpadding=0 > | |
97 | * <tr bgcolor="#ccccff"> | |
98 | * <th align=left>Format | |
99 | * <th align=left>Description | |
100 | * <th align=left>Value Range | |
101 | * <th align=left>Example | |
102 | * <tr> | |
103 | * <td><code>SSS</code> | |
104 | * <td>Millisecond in second | |
105 | * <td><code>000-999</code> | |
106 | * <td><code>123</code> | |
107 | * <tr bgcolor="#eeeeff"> | |
108 | * <td><code>CCC</code> | |
109 | * <td>Microseconds in ms | |
110 | * <td><code>000-999</code> | |
111 | * <td><code>456</code> | |
112 | * <tr> | |
113 | * <td><code>NNN</code> | |
114 | * <td>Nanosecond in µs | |
115 | * <td><code>000-999</code> | |
116 | * <td><code>789</code> | |
117 | * </table> | |
118 | * </blockquote> | |
119 | * | |
120 | * <strong>Note: </strong>If "T" is used, no other Date or Time pattern | |
121 | * can be used. Also, "T" should be used for time intervals. | |
122 | * <p> | |
123 | * <strong>Note: </strong>Each sub-field can be separated by a single, | |
124 | * optional character delimiter. However, the between Date/Time and the | |
125 | * Sub-seconds pattern is mandatory (if there is a fractional part) and | |
126 | * has to be separated from Date/time by "." (period). | |
127 | * <p> | |
128 | * The recognized delimiters are: | |
129 | * <ul> | |
130 | * <li>Space ("<code> </code>") | |
131 | * <li>Period (<code>".</code>") | |
132 | * <li>Comma ("<code>,</code>") | |
133 | * <li>Dash ("<code>-</code>") | |
134 | * <li>Underline ("<code>_</code>") | |
135 | * <li>Colon ("<code>:</code>") | |
136 | * <li>Semicolon ("<code>;</code>") | |
137 | * <li>Slash ("<code>/</code>") | |
138 | * <li>Double-quote ("<code>"</code>") | |
139 | * </ul> | |
140 | * | |
141 | * <h4>Examples</h4> | |
142 | * The following examples show how timestamp patterns are interpreted in | |
143 | * the U.S. locale. The given timestamp is 1332170682539677389L, the number | |
144 | * of nanoseconds since 1970/01/01. | |
145 | * | |
146 | * <blockquote> | |
147 | * <table border=0 cellspacing=3 cellpadding=0> | |
148 | * <tr bgcolor="#ccccff"> | |
149 | * <th align=left>Date and Time Pattern | |
150 | * <th align=left>Result | |
151 | * <tr> | |
152 | * <td><code>"yyyy-MM-dd HH:mm:ss.SSS.CCC.NNN"</code> | |
153 | * <td><code>2012-03-19 11:24:42.539.677.389</code> | |
154 | * <tr bgcolor="#eeeeff"> | |
155 | * <td><code>"yyyy-MM-dd HH:mm:ss.SSS.CCC"</code> | |
156 | * <td><code>2012-03-19 11:24:42.539.677</code> | |
157 | * <tr> | |
158 | * <td><code>"yyyy-D HH:mm:ss.SSS.CCC"</code> | |
159 | * <td><code>2012-79 11:24:42.539.677</code> | |
160 | * <tr bgcolor="#eeeeff"> | |
161 | * <td><code>"ss.SSSCCCNNN"</code> | |
162 | * <td><code>42.539677389</code> | |
163 | * <tr> | |
164 | * <td><code>"T.SSS CCC NNN"</code> | |
165 | * <td><code>1332170682.539 677 389</code> | |
166 | * <tr bgcolor="#eeeeff"> | |
167 | * <td><code>"T"</code> | |
168 | * <td><code>1332170682</code> | |
169 | * </table> | |
170 | * </blockquote> | |
171 | * <p> | |
172 | * @version 1.0 | |
173 | * @since 2.0 | |
174 | * @author Francois Chouinard | |
175 | */ | |
176 | public class TmfTimestampFormat extends SimpleDateFormat { | |
177 | ||
178 | // ------------------------------------------------------------------------ | |
179 | // Constants | |
180 | // ------------------------------------------------------------------------ | |
181 | ||
182 | /** | |
183 | * This class' serialization ID | |
184 | */ | |
185 | private static final long serialVersionUID = 2835829763122454020L; | |
186 | ||
187 | /** | |
188 | * The default timestamp pattern | |
189 | */ | |
d96e9054 | 190 | public static final String DEFAULT_TIME_PATTERN = "HH:mm:ss.SSS CCC NNN"; //$NON-NLS-1$ |
f8177ba2 FC |
191 | |
192 | /** | |
193 | * The LTTng 0.x legacy timestamp format | |
194 | */ | |
d96e9054 | 195 | public static final String DEFAULT_INTERVAL_PATTERN = "TTT.SSS CCC NNN"; //$NON-NLS-1$ |
f8177ba2 FC |
196 | |
197 | // Fractions of seconds supported patterns | |
198 | private static final String DOT_RE = "\\."; //$NON-NLS-1$ | |
199 | private static final String SEP_RE = "[ \\.,-_:;/\\\"]?"; //$NON-NLS-1$ | |
200 | private static final String DGTS_3_RE = "(\\d{3})"; //$NON-NLS-1$ | |
201 | private static final String DGTS_13_RE = "(\\d{1,3})"; //$NON-NLS-1$ | |
202 | ||
203 | private static final String MILLISEC_RE = DOT_RE + DGTS_13_RE; | |
204 | private static final String MICROSEC_RE = DOT_RE + DGTS_3_RE + SEP_RE + DGTS_13_RE; | |
205 | private static final String NANOSEC_RE = DOT_RE + DGTS_3_RE + SEP_RE + DGTS_3_RE + SEP_RE + DGTS_13_RE; | |
206 | ||
207 | private static final Pattern MILLISEC_PAT = Pattern.compile(MILLISEC_RE); | |
208 | private static final Pattern MICROSEC_PAT = Pattern.compile(MICROSEC_RE); | |
209 | private static final Pattern NANOSEC_PAT = Pattern.compile(NANOSEC_RE); | |
210 | ||
211 | // ------------------------------------------------------------------------ | |
212 | // Attributes | |
213 | // ------------------------------------------------------------------------ | |
214 | ||
215 | // The default timestamp pattern | |
216 | private static String fDefaultTimePattern = null; | |
217 | private static TmfTimestampFormat fDefaultTimeFormat = null; | |
218 | ||
219 | // The default time interval format | |
220 | private static String fDefaultIntervalPattern = null; | |
221 | private static TmfTimestampFormat fDefaultIntervalFormat = null; | |
222 | ||
223 | // The timestamp pattern | |
224 | private String fPattern; | |
225 | ||
226 | // The timestamp pattern | |
227 | private List<String> fSupplPatterns = new ArrayList<String>(); | |
228 | ||
229 | /** | |
230 | * The supplementary pattern letters. Can be redefined by sub-classes | |
231 | * to either override existing letters or augment the letter set. | |
232 | * If so, the format() method must provide the (re-)implementation of the | |
233 | * pattern. | |
234 | */ | |
235 | protected String fSupplPatternLetters = "TSCN"; //$NON-NLS-1$ | |
236 | ||
6f4e8ec0 | 237 | /* |
f8177ba2 FC |
238 | * The bracketing symbols used to mitigate the risk of a format string |
239 | * that contains escaped sequences that would conflict with our format | |
240 | * extension. | |
241 | */ | |
6f4e8ec0 | 242 | /** The open bracket symbol */ |
f8177ba2 | 243 | protected String fOpenBracket = "[&"; //$NON-NLS-1$ |
6f4e8ec0 AM |
244 | |
245 | /** The closing bracket symbol */ | |
f8177ba2 FC |
246 | protected String fCloseBracket = "&]"; //$NON-NLS-1$ |
247 | ||
248 | // ------------------------------------------------------------------------ | |
249 | // Constructors | |
250 | // ------------------------------------------------------------------------ | |
251 | ||
252 | /** | |
253 | * The default constructor (uses the default pattern) | |
254 | */ | |
255 | public TmfTimestampFormat() { | |
256 | this(fDefaultTimePattern); | |
257 | } | |
258 | ||
259 | /** | |
260 | * The normal constructor | |
261 | * | |
262 | * @param pattern the format pattern | |
263 | */ | |
264 | public TmfTimestampFormat(String pattern) { | |
f8177ba2 FC |
265 | applyPattern(pattern); |
266 | } | |
267 | ||
268 | /** | |
269 | * The copy constructor | |
270 | * | |
271 | * @param other the other format pattern | |
272 | */ | |
273 | public TmfTimestampFormat(TmfTimestampFormat other) { | |
274 | this(other.fPattern); | |
275 | } | |
276 | ||
277 | // ------------------------------------------------------------------------ | |
278 | // Getters/setters | |
279 | // ------------------------------------------------------------------------ | |
280 | ||
281 | /** | |
282 | * @param pattern the new default time pattern | |
283 | */ | |
5419a136 | 284 | public static void setDefaultTimeFormat(final String pattern) { |
f8177ba2 FC |
285 | fDefaultTimePattern = pattern; |
286 | fDefaultTimeFormat = new TmfTimestampFormat(fDefaultTimePattern); | |
287 | TmfSignalManager.dispatchSignal(new TmfTimestampFormatUpdateSignal(null)); | |
288 | } | |
289 | ||
290 | /** | |
291 | * @return the default time format pattern | |
292 | */ | |
5419a136 | 293 | public static TmfTimestampFormat getDefaulTimeFormat() { |
f8177ba2 FC |
294 | if (fDefaultTimeFormat == null) { |
295 | fDefaultTimeFormat = new TmfTimestampFormat(DEFAULT_TIME_PATTERN); | |
296 | } | |
297 | return fDefaultTimeFormat; | |
298 | } | |
299 | ||
300 | /** | |
301 | * @param pattern the new default interval pattern | |
302 | */ | |
5419a136 | 303 | public static void setDefaultIntervalFormat(final String pattern) { |
f8177ba2 FC |
304 | fDefaultIntervalPattern = pattern; |
305 | fDefaultIntervalFormat = new TmfTimestampFormat(fDefaultIntervalPattern); | |
306 | TmfSignalManager.dispatchSignal(new TmfTimestampFormatUpdateSignal(null)); | |
307 | } | |
308 | ||
309 | /** | |
310 | * @return the default interval format pattern | |
311 | */ | |
5419a136 | 312 | public static TmfTimestampFormat getDefaulIntervalFormat() { |
f8177ba2 FC |
313 | if (fDefaultIntervalFormat == null) { |
314 | fDefaultIntervalFormat = new TmfTimestampFormat(DEFAULT_INTERVAL_PATTERN); | |
315 | } | |
316 | return fDefaultIntervalFormat; | |
317 | } | |
318 | ||
319 | /* (non-Javadoc) | |
320 | * @see java.text.SimpleDateFormat#applyPattern(java.lang.String) | |
321 | */ | |
322 | @Override | |
323 | public void applyPattern(String pattern) { | |
324 | fPattern = pattern; | |
325 | String quotedPattern = quoteSpecificTags(pattern); | |
326 | super.applyPattern(quotedPattern); | |
327 | } | |
328 | ||
329 | /* (non-Javadoc) | |
330 | * @see java.text.SimpleDateFormat#toPattern() | |
331 | */ | |
332 | @Override | |
333 | public String toPattern() { | |
334 | return fPattern; | |
335 | } | |
336 | ||
337 | // ------------------------------------------------------------------------ | |
338 | // Operations | |
339 | // ------------------------------------------------------------------------ | |
340 | ||
341 | /** | |
342 | * Format the timestamp according to its pattern. | |
343 | * | |
344 | * @param value the timestamp value to format (in ns) | |
345 | * @return the formatted timestamp | |
346 | */ | |
1e10705b | 347 | public synchronized String format(long value) { |
f8177ba2 FC |
348 | |
349 | // Split the timestamp value into its sub-components | |
350 | long sec = value / 1000000000; // seconds | |
351 | long ms = value % 1000000000 / 1000000; // milliseconds | |
352 | long cs = value % 1000000 / 1000; // microseconds | |
353 | long ns = value % 1000; // nanoseconds | |
354 | ||
355 | // Let the base class fill the stuff it knows about | |
356 | StringBuffer result = new StringBuffer(super.format(sec * 1000 + ms)); | |
357 | ||
358 | // In the case where there is no separation between 2 supplementary | |
359 | // fields, the pattern will have the form "..'[pat-1]''[pat-2]'.." and | |
360 | // the base class format() will interpret the 2 adjacent quotes as a | |
361 | // wanted character in the result string as ("..[pat-1]'[pat-2].."). | |
362 | // Remove these extra quotes before filling the supplementary fields. | |
363 | int loc = result.indexOf(fCloseBracket + "'" + fOpenBracket); //$NON-NLS-1$ | |
364 | while (loc != -1) { | |
365 | result.deleteCharAt(loc + fCloseBracket.length()); | |
366 | loc = result.indexOf(fCloseBracket + "'" + fOpenBracket); //$NON-NLS-1$ | |
367 | } | |
368 | ||
369 | // Fill in our extensions | |
370 | for (String pattern : fSupplPatterns) { | |
371 | int length = pattern.length(); | |
372 | ||
373 | // Prepare the format buffer | |
374 | StringBuffer fmt = new StringBuffer(length); | |
375 | for (int i = 0; i < length; i++) { | |
376 | fmt.append("0"); //$NON-NLS-1$ | |
377 | } | |
378 | DecimalFormat dfmt = new DecimalFormat(fmt.toString()); | |
379 | String fmtVal = ""; //$NON-NLS-1$; | |
380 | ||
381 | // Format the proper value as per the pattern | |
382 | switch (pattern.charAt(0)) { | |
383 | case 'T': | |
384 | fmtVal = dfmt.format(sec); | |
385 | break; | |
386 | case 'S': | |
387 | fmtVal = dfmt.format(ms); | |
388 | break; | |
389 | case 'C': | |
390 | fmtVal = dfmt.format(cs); | |
391 | break; | |
392 | case 'N': | |
393 | fmtVal = dfmt.format(ns); | |
394 | break; | |
395 | default: | |
396 | break; | |
397 | } | |
398 | ||
399 | // Substitute the placeholder with the formatted value | |
400 | String ph = new StringBuffer(fOpenBracket + pattern + fCloseBracket).toString(); | |
401 | loc = result.indexOf(ph); | |
402 | result.replace(loc, loc + length + fOpenBracket.length() + fCloseBracket.length(), fmtVal); | |
403 | } | |
404 | ||
405 | return result.toString(); | |
406 | } | |
407 | ||
408 | /** | |
409 | * Parse a string according to the format pattern | |
410 | * | |
411 | * @param string the source string | |
412 | * @param ref the reference (base) time | |
413 | * @return the parsed value | |
414 | * @throws ParseException if the string has an invalid format | |
415 | */ | |
1e10705b | 416 | public synchronized long parseValue(final String string, final long ref) throws ParseException { |
f8177ba2 FC |
417 | |
418 | // Trivial case | |
419 | if (string == null || string.length() == 0) { | |
420 | return 0; | |
421 | } | |
422 | ||
423 | // The timestamp sub-components | |
424 | long seconds = -1; | |
425 | long millisec = 0; | |
426 | long microsec = 0; | |
427 | long nanosec = 0; | |
428 | ||
429 | // Since we are processing the fractional part, substitute it with | |
430 | // its pattern so the base parser doesn't complain | |
431 | StringBuilder sb = new StringBuilder(string); | |
432 | int dot = string.indexOf('.'); | |
433 | if (dot == -1) { | |
434 | sb.append('.'); | |
435 | dot = string.length(); | |
436 | } | |
437 | sb = new StringBuilder(string.substring(0, dot)); | |
438 | String basePattern = super.toPattern(); | |
439 | int dot2 = basePattern.indexOf('.'); | |
440 | if (dot2 != -1) { | |
441 | sb.append(basePattern.substring(dot2)); | |
442 | } | |
443 | ||
444 | // Fill in our extensions | |
445 | for (String pattern : fSupplPatterns) { | |
446 | String pat = fOpenBracket + pattern + fCloseBracket; | |
447 | Matcher matcher; | |
448 | ||
449 | // Extract the substring corresponding to the extra pattern letters | |
450 | // and replace with the pattern so the base parser can do its job. | |
451 | switch (pattern.charAt(0)) { | |
452 | case 'T': | |
453 | // Remove everything up to the first "." and compute the | |
454 | // number of seconds since the epoch. If there is no period, | |
455 | // assume an integer value and return immediately | |
456 | if (dot < 1) { | |
457 | return new DecimalFormat("0").parse(string).longValue() * 1000000000; //$NON-NLS-1$ | |
458 | } | |
459 | seconds = new DecimalFormat("0").parse(string.substring(0, dot)).longValue(); //$NON-NLS-1$ | |
460 | sb.delete(0, dot); | |
461 | sb.insert(0, pat); | |
462 | break; | |
463 | case 'S': | |
464 | matcher = MILLISEC_PAT.matcher(string.substring(dot)); | |
465 | if (matcher.find()) { | |
466 | millisec = new Long(matcher.group(1)); | |
467 | for (int l = matcher.group(1).length(); l < 3; l++) { | |
468 | millisec *= 10; | |
469 | } | |
470 | } | |
471 | stripQuotes(sb, pattern); | |
472 | break; | |
473 | case 'C': | |
474 | matcher = MICROSEC_PAT.matcher(string.substring(dot)); | |
475 | if (matcher.find()) { | |
476 | microsec = new Long(matcher.group(2)); | |
477 | for (int l = matcher.group(2).length(); l < 3; l++) { | |
478 | microsec *= 10; | |
479 | } | |
480 | } | |
481 | stripQuotes(sb, pattern); | |
482 | break; | |
483 | case 'N': | |
484 | matcher = NANOSEC_PAT.matcher(string.substring(dot)); | |
485 | if (matcher.find()) { | |
486 | nanosec = new Long(matcher.group(3)); | |
487 | for (int l = matcher.group(3).length(); l < 3; l++) { | |
488 | nanosec *= 10; | |
489 | } | |
490 | } | |
491 | stripQuotes(sb, pattern); | |
492 | break; | |
493 | default: | |
494 | break; | |
495 | } | |
496 | } | |
497 | ||
498 | // If there was no "T" (thus not an interval), parse as a date | |
499 | if (seconds == -1) { | |
500 | Date baseDate = super.parse(sb.toString()); | |
501 | ||
06441eb5 | 502 | Calendar refTime = Calendar.getInstance(getTimeZone()); |
f8177ba2 | 503 | refTime.setTimeInMillis(ref / 1000000); |
06441eb5 | 504 | Calendar newTime = Calendar.getInstance(getTimeZone()); |
f8177ba2 FC |
505 | newTime.setTimeInMillis(baseDate.getTime()); |
506 | ||
06441eb5 | 507 | int[] fields = new int[] { Calendar.YEAR, Calendar.MONTH, Calendar.DATE, Calendar.HOUR_OF_DAY, Calendar.MINUTE, Calendar.SECOND }; |
f8177ba2 FC |
508 | for (int field : fields) { |
509 | int value = newTime.get(field); | |
510 | // Do some adjustments... | |
511 | if (field == Calendar.YEAR) { | |
512 | value -= 1970; | |
06441eb5 | 513 | } else if (field == Calendar.DATE) { |
f8177ba2 FC |
514 | value -= 1; |
515 | } | |
516 | // ... and fill-in the empty fields | |
517 | if (value == 0) { | |
518 | newTime.set(field, refTime.get(field)); | |
f8177ba2 FC |
519 | } else { |
520 | break; // Get out as soon as we have a significant value | |
521 | } | |
522 | } | |
523 | seconds = newTime.getTimeInMillis() / 1000; | |
524 | } | |
525 | ||
526 | // Compute the value in ns | |
527 | return seconds * 1000000000 + millisec * 1000000 + microsec * 1000 + nanosec; | |
528 | } | |
529 | ||
530 | /** | |
531 | * Parse a string according to the format pattern | |
532 | * | |
533 | * @param string the source string | |
534 | * @return the parsed value | |
535 | * @throws ParseException if the string has an invalid format | |
536 | */ | |
537 | public long parseValue(final String string) throws ParseException { | |
538 | long result = parseValue(string, 0); | |
539 | return result; | |
540 | ||
541 | } | |
542 | ||
543 | // ------------------------------------------------------------------------ | |
544 | // Helper functions | |
545 | // ------------------------------------------------------------------------ | |
546 | ||
547 | /** | |
548 | * Copy the pattern but quote (bracket with "[&" and "&]") the | |
549 | * TmfTimestampFormat specific tags so these fields are treated as | |
550 | * comments by the base class. | |
551 | * | |
552 | * It also keeps track of the corresponding quoted fields so they can be | |
553 | * properly populated later on (by format()). | |
554 | * | |
555 | * @param pattern the 'extended' pattern | |
556 | * @return the quoted and bracketed pattern | |
557 | */ | |
558 | private String quoteSpecificTags(final String pattern) { | |
559 | ||
560 | StringBuffer result = new StringBuffer(); | |
561 | ||
562 | int length = pattern.length(); | |
563 | boolean inQuote = false; | |
564 | ||
565 | for (int i = 0; i < length; i++) { | |
566 | char c = pattern.charAt(i); | |
567 | result.append(c); | |
568 | if (c == '\'') { | |
569 | // '' is treated as a single quote regardless of being | |
570 | // in a quoted section. | |
571 | if ((i + 1) < length) { | |
572 | c = pattern.charAt(i + 1); | |
573 | if (c == '\'') { | |
574 | i++; | |
575 | result.append(c); | |
576 | continue; | |
577 | } | |
578 | } | |
579 | inQuote = !inQuote; | |
580 | continue; | |
581 | } | |
582 | if (!inQuote) { | |
583 | if (fSupplPatternLetters.indexOf(c) != -1) { | |
584 | StringBuilder pat = new StringBuilder(); | |
585 | pat.append(c); | |
586 | result.insert(result.length() - 1, "'" + fOpenBracket); //$NON-NLS-1$ | |
587 | while ((i + 1) < length && pattern.charAt(i + 1) == c) { | |
588 | result.append(c); | |
589 | pat.append(c); | |
590 | i++; | |
591 | } | |
592 | result.append(fCloseBracket + "'"); //$NON-NLS-1$ | |
593 | fSupplPatterns.add(pat.toString()); | |
594 | } | |
595 | } | |
596 | } | |
597 | return result.toString(); | |
598 | } | |
599 | ||
600 | /** | |
601 | * Remove the quotes from the pattern | |
602 | * | |
603 | * @param sb | |
604 | * @param pattern | |
605 | */ | |
606 | private void stripQuotes(StringBuilder sb, String pattern) { | |
607 | String pt = "'" + fOpenBracket + pattern + fCloseBracket + "'"; //$NON-NLS-1$//$NON-NLS-2$ | |
608 | int l = sb.indexOf(pt); | |
609 | if (l != -1) { | |
610 | sb.delete(l + pt.length() - 1, l + pt.length()); | |
611 | sb.delete(l, l + 1); | |
612 | } | |
613 | } | |
614 | ||
615 | } |