View Javadoc

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  		// INPUT_SIZE_LONG_DOUBLE = 'L',
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 		// Parse various fields of the format spec.  At the entrance to each parsing 
118 		// subsection, c contains the next character to be parsed.
119 		vcC = vpFs.next();
120 		// Parse flag characters (optional):
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 		// Parse width spec (optional):
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 		// Parse precision spec (optional):
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 		// Parse input size modifier (optional):
211 		switch (vcC)
212 		{
213 		case INPUT_SIZE_LONG:
214 		case INPUT_SIZE_SHORT:
215 			// case INPUT_SIZE_LONG_DOUBLE:
216 		case INPUT_SIZE_BYTE:
217 		case INPUT_SIZE_BIG:
218 			mcInputSize = vcC;
219 			vcC = vpFs.next();
220 		}
221 		// Parse conversion-type character (required):
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 		// Set the iterator to the start of the next substring:
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