1 package com.sharkysoft.printf;
2
3 import java.text.CharacterIterator;
4 import java.text.StringCharacterIterator;
5
6 final class FormatSpecifier
7 {
8 static final char
9 JUSTIFICATION_RIGHT = 0,
10 JUSTIFICATION_LEFT = '-',
11 JUSTIFICATION_CENTER = '^',
12 JUSTIFICATION_FULL = '0',
13 JUSTIFICATION_UNSPECIFIED = JUSTIFICATION_RIGHT;
14
15 char mcJustification = JUSTIFICATION_UNSPECIFIED;
16
17 static final char
18 PREFIX_NONE = 0,
19 PREFIX_PLUS = '+',
20 PREFIX_SPACE = ' ',
21 PREFIX_UNSPECIFIED = PREFIX_NONE;
22
23 char mcPositivePrefix = PREFIX_UNSPECIFIED;
24
25 static final char
26 ALTERNATE_OFF = 0,
27 ALTERNATE_ON = '#',
28 ALTERNATE_UNSPECIFIED = ALTERNATE_OFF;
29
30 char mcAlternate = ALTERNATE_UNSPECIFIED;
31
32 static final char
33 PADCHAR_SPACE = ' ',
34 PADCHAR_ZERO = '0',
35 PADCHAR_UNSPECIFIED = PADCHAR_SPACE;
36
37 char mcPadChar = PADCHAR_UNSPECIFIED;
38
39 static final int
40 WIDTH_DEFAULT = -2,
41 WIDTH_ARGUMENT = -1,
42 WIDTH_UNSPECIFIED = WIDTH_DEFAULT;
43
44 int mnWidth = WIDTH_UNSPECIFIED;
45
46 static final int
47 PRECISION_ANY = -2,
48 PRECISION_ARGUMENT = -1,
49 PRECISION_ZERO = 0,
50 PRECISION_UNSPECIFIED = PRECISION_ANY;
51
52 int mnPrecision = PRECISION_UNSPECIFIED;
53
54 static final char
55 INPUT_SIZE_NORMAL = 0,
56 INPUT_SIZE_SHORT = 'h',
57 INPUT_SIZE_LONG = 'l',
58
59 INPUT_SIZE_BYTE = 'b',
60 INPUT_SIZE_BIG = 'B',
61 INPUT_SIZE_UNSPECIFIED = INPUT_SIZE_NORMAL;
62
63 char mcInputSize = INPUT_SIZE_UNSPECIFIED;
64
65 static final char
66 TYPE_SIGNED_DEC = 'd',
67 TYPE_UNSIGNED_DEC = 'u',
68 TYPE_OCT = 'o',
69 TYPE_HEX_LOWER = 'x',
70 TYPE_HEX_UPPER = 'X',
71 TYPE_FLOAT = 'f',
72 TYPE_FLOAT_E_LOWER = 'e',
73 TYPE_FLOAT_E_UPPER = 'E',
74 TYPE_FLOAT_G_LOWER = 'g',
75 TYPE_FLOAT_G_UPPER = 'G',
76 TYPE_CHAR = 'c',
77 TYPE_STRING = 's',
78 TYPE_PERCENT = '%',
79 TYPE_ENDL = '\n',
80 TYPE_CHARCOUNT = 'n',
81 TYPE_POINTER = 'p',
82 TYPE_INTEGER_UPPER = 'Z',
83 TYPE_INTEGER_LOWER = 'z',
84 TYPE_UNSPECIFIED = 0,
85 TYPE_LITERAL = 0;
86
87 char mcType = TYPE_UNSPECIFIED;
88
89 int mnBase = 0;
90
91 final String msLiteral;
92
93 String msError = null;
94
95 private void setError(final String isError)
96 {
97 if (msError == null)
98 msError = isError;
99 }
100
101 FormatSpecifier(final StringCharacterIterator ipFs)
102 {
103 final PrintfStringCharacterIterator vpFs = new PrintfStringCharacterIterator(ipFs);
104 char vcC;
105 vcC = vpFs.current();
106 if (vcC != '%')
107 {
108 final StringBuffer vpBuff = new StringBuffer();
109 while (vcC != CharacterIterator.DONE && vcC != '%')
110 {
111 vpBuff.append(vcC);
112 vcC = vpFs.next();
113 }
114 msLiteral = vpBuff.toString();
115 return;
116 }
117
118
119 vcC = vpFs.next();
120
121 parse_flags : while (true)
122 {
123 switch (vcC)
124 {
125 case JUSTIFICATION_LEFT:
126 if (mcJustification != JUSTIFICATION_UNSPECIFIED)
127 setError("multiple justifications");
128 mcJustification = JUSTIFICATION_LEFT;
129 break;
130 case JUSTIFICATION_CENTER:
131 if (mcJustification != JUSTIFICATION_UNSPECIFIED)
132 setError("multiple justifications");
133 mcJustification = JUSTIFICATION_CENTER;
134 break;
135 case PADCHAR_ZERO:
136 if (mcJustification != JUSTIFICATION_UNSPECIFIED)
137 setError("multiple justifications");
138 mcPadChar = PADCHAR_ZERO;
139 mcJustification = JUSTIFICATION_FULL;
140 break;
141 case PREFIX_PLUS:
142 case PREFIX_SPACE:
143 if (mcPositivePrefix != PREFIX_UNSPECIFIED)
144 setError("multiple positive prefixes");
145 mcPositivePrefix = vcC;
146 break;
147 case ALTERNATE_ON:
148 if (mcAlternate != ALTERNATE_UNSPECIFIED)
149 setError("alternate format selected twice");
150 mcAlternate = ALTERNATE_ON;
151 break;
152 default:
153 break parse_flags;
154 }
155 vcC = vpFs.next();
156 }
157
158 switch (vcC)
159 {
160 case '*':
161 mnWidth = WIDTH_ARGUMENT;
162 vcC = vpFs.next();
163 break;
164 default:
165 if (!isdigit(vcC))
166 {
167 if (mcPadChar != PADCHAR_UNSPECIFIED)
168 setError("padding specified without field width");
169 if (mcJustification != JUSTIFICATION_UNSPECIFIED)
170 setError("justification specified without field width");
171 break;
172 }
173 int vnN = 0;
174 do
175 {
176 vnN = vnN * 10 + (vcC - '0');
177 vcC = vpFs.next();
178 }
179 while (isdigit(vcC));
180 mnWidth = vnN;
181 }
182
183 if (vcC == '.')
184 {
185 vcC = vpFs.next();
186 if (vcC == PRECISION_ZERO)
187 {
188 mnPrecision = PRECISION_ZERO;
189 vcC = vpFs.next();
190 }
191 else if (isdigit(vcC))
192 {
193 int n = 0;
194 do
195 {
196 n = n * 10 + (vcC - '0');
197 vcC = vpFs.next();
198 }
199 while (isdigit(vcC));
200 mnPrecision = n;
201 }
202 else if (vcC == '*')
203 {
204 mnPrecision = PRECISION_ARGUMENT;
205 vcC = vpFs.next();
206 }
207 else
208 setError("invalid precision specifier");
209 }
210
211 switch (vcC)
212 {
213 case INPUT_SIZE_LONG:
214 case INPUT_SIZE_SHORT:
215
216 case INPUT_SIZE_BYTE:
217 case INPUT_SIZE_BIG:
218 mcInputSize = vcC;
219 vcC = vpFs.next();
220 }
221
222 mcType = vcC;
223 if (mcType == 'i')
224 mcType = 'd';
225 switch (mcType)
226 {
227 case TYPE_UNSIGNED_DEC :
228 disallowPositivePrefix();
229 disallowAlternateFormat();
230 break;
231 case TYPE_SIGNED_DEC :
232 disallowAlternateFormat();
233 break;
234 case TYPE_CHAR :
235 disallowPrecision();
236 disallowPositivePrefix();
237 disallowFullJustification();
238 break;
239 case TYPE_STRING :
240 disallowAlternateFormat();
241 disallowPositivePrefix();
242 disallowFullJustification();
243 break;
244 case TYPE_POINTER :
245 disallowAlternateFormat();
246 disallowPositivePrefix();
247 break;
248 case TYPE_INTEGER_UPPER :
249 case TYPE_INTEGER_LOWER :
250 disallowPositivePrefix();
251 disallowAlternateFormat();
252 if (vpFs.next() != '[')
253 {
254 setError("'" + mcType + "' not followed by '['");
255 break;
256 }
257 StringBuffer digits = new StringBuffer();
258 while (true)
259 {
260 vcC = vpFs.next();
261 if (vcC == ']')
262 break;
263 if (vcC < '0' || '9' < vcC)
264 throw new PrintfTemplateException("illegal base characters for '" + mcType + "[]'");
265 digits.append(vcC);
266 }
267 if (digits.length() == 0)
268 throw new PrintfTemplateException("empty base for '" + mcType + "[]'");
269 mnBase = Integer.parseInt(digits.toString());
270 if (mnBase < 2 || mnBase > 36)
271 throw new PrintfTemplateException("illegal base for " + mcType + "[]: " + mnBase);
272 case TYPE_HEX_LOWER :
273 case TYPE_HEX_UPPER :
274 disallowPositivePrefix();
275 break;
276 case TYPE_OCT :
277 disallowPositivePrefix();
278 break;
279 case TYPE_FLOAT :
280 case TYPE_FLOAT_E_LOWER :
281 case TYPE_FLOAT_E_UPPER :
282 case TYPE_FLOAT_G_LOWER :
283 case TYPE_FLOAT_G_UPPER :
284 disallow_short_size();
285 break;
286 case TYPE_PERCENT :
287 case TYPE_ENDL :
288 case TYPE_CHARCOUNT :
289 disallow_size();
290 disallowAlternateFormat();
291 disallowPositivePrefix();
292 disallowWidth();
293 disallowPrecision();
294 break;
295 default :
296 setError("conversion type character missing or invalid");
297 }
298
299 vpFs.next();
300 msLiteral = vpFs.getSubstring();
301 if (msError != null)
302 throw new PrintfTemplateException("invalid format specifier: \"" + msLiteral + "\" (" + msError + ")");
303 }
304
305 private void disallow_size()
306 {
307 if (mcInputSize != INPUT_SIZE_NORMAL)
308 setError("input size not allowed for %" + mcType);
309 }
310
311 private void disallow_short_size()
312 {
313 switch (mcInputSize)
314 {
315 case INPUT_SIZE_SHORT :
316 case INPUT_SIZE_BYTE :
317 setError("short or byte input size not allowed for %" + mcType);
318 }
319 }
320
321 private void disallowAlternateFormat()
322 {
323 if (mcAlternate != ALTERNATE_UNSPECIFIED)
324 setError("alternate format not allowed for %" + mcType);
325 }
326
327 private void disallowPositivePrefix()
328 {
329 if (mcPositivePrefix != PREFIX_UNSPECIFIED)
330 setError("positive prefix not allowed for %" + mcType);
331 }
332
333 private void disallowFullJustification()
334 {
335 if (mcJustification == JUSTIFICATION_FULL)
336 setError("full justification not allowed for %" + mcType);
337 }
338
339 private void disallowWidth()
340 {
341 if (mnWidth != WIDTH_UNSPECIFIED)
342 setError("field width not allowed for %" + mcType);
343 }
344
345 private void disallowPrecision()
346 {
347 if (mnPrecision != PRECISION_UNSPECIFIED)
348 setError("mnPrecision not allowed for %" + mcType);
349 }
350
351 private void disallowType()
352 {
353 setError("sorry, %" + mcType + " not supported");
354 }
355
356 private String toNormalizedString()
357 {
358 final StringBuffer s = new StringBuffer("%");
359 if (mcJustification != JUSTIFICATION_UNSPECIFIED)
360 s.append(mcJustification);
361 if (mcPadChar != PADCHAR_UNSPECIFIED)
362 s.append(mcPadChar);
363 if (mcAlternate != ALTERNATE_UNSPECIFIED)
364 s.append(mcAlternate);
365 if (mnWidth != WIDTH_UNSPECIFIED)
366 {
367 if (mnWidth == WIDTH_ARGUMENT)
368 s.append('*');
369 else
370 s.append(mnWidth);
371 }
372 if (mnPrecision != PRECISION_UNSPECIFIED)
373 {
374 s.append('.');
375 if (mnPrecision == PRECISION_ARGUMENT)
376 s.append('*');
377 else
378 s.append(mnPrecision);
379 }
380 if (mcInputSize != INPUT_SIZE_UNSPECIFIED)
381 s.append(mcInputSize);
382 switch (mcType)
383 {
384 case '\n' :
385 s.append("//n");
386 break;
387 default :
388 s.append(mcType);
389 }
390 switch (mcType)
391 {
392 case TYPE_INTEGER_UPPER :
393 case TYPE_INTEGER_LOWER :
394 s.append("[" + mnBase + "]");
395 }
396 return s.toString();
397 }
398
399 public String toString()
400 {
401 return msLiteral;
402 }
403
404 /***
405 * <p><b>Details:</b> Returns <code>true</code> if inC is a decimal digit.
406 * Returns the result of <code>Character.isDigit(c)</code>.</p>
407 */
408 static boolean isdigit(final int inC)
409 {
410 return Character.isDigit((char) inC);
411 }
412 }
413