3ea135bb |
1 | /**\r |
2 | * Copyright (C) 2003 <a href="http://www.lohndirekt.de/">lohndirekt.de</a>\r |
3 | *\r |
4 | * This library is free software; you can redistribute it and/or\r |
5 | * modify it under the terms of the GNU Lesser General Public\r |
6 | * License as published by the Free Software Foundation; either\r |
7 | * version 2.1 of the License, or (at your option) any later version.\r |
8 | * \r |
9 | * This library is distributed in the hope that it will be useful,\r |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of\r |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r |
12 | * Lesser General Public License for more details.\r |
13 | * \r |
14 | * You should have received a copy of the GNU Lesser General Public\r |
15 | * License along with this library; if not, write to the Free Software\r |
16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\r |
17 | * \r |
18 | */\r |
19 | package de.lohndirekt.print.attribute;\r |
20 | \r |
21 | import java.io.IOException;\r |
22 | import java.io.InputStream;\r |
23 | import java.lang.reflect.Constructor;\r |
24 | import java.lang.reflect.Field;\r |
25 | import java.lang.reflect.InvocationTargetException;\r |
26 | import java.net.URI;\r |
27 | import java.net.URISyntaxException;\r |
28 | import java.text.DateFormat;\r |
29 | import java.text.DecimalFormat;\r |
30 | import java.text.ParseException;\r |
31 | import java.text.SimpleDateFormat;\r |
32 | import java.util.Date;\r |
33 | import java.util.HashMap;\r |
34 | import java.util.HashSet;\r |
35 | import java.util.Locale;\r |
36 | import java.util.Map;\r |
37 | import java.util.Set;\r |
38 | import java.util.logging.Level;\r |
39 | import java.util.logging.Logger;\r |
40 | \r |
41 | import javax.print.attribute.Attribute;\r |
42 | import javax.print.attribute.EnumSyntax;\r |
43 | import javax.print.attribute.standard.JobStateReason;\r |
44 | import javax.print.attribute.standard.JobStateReasons;\r |
45 | import javax.print.attribute.standard.PrinterStateReason;\r |
46 | import javax.print.attribute.standard.PrinterStateReasons;\r |
47 | import javax.print.attribute.standard.Severity;\r |
48 | \r |
49 | import de.lohndirekt.print.attribute.ipp.Charset;\r |
50 | import de.lohndirekt.print.attribute.ipp.jobdesc.LdJobStateReason;\r |
51 | import de.lohndirekt.print.exception.EndOfAttributesException;\r |
52 | \r |
53 | /**\r |
54 | * @author bpusch\r |
55 | *\r |
56 | */\r |
57 | public final class AttributeParser {\r |
58 | \r |
59 | private final static EndOfAttributesException END_OF_ATTRIBUTES_EXCEPTION = new EndOfAttributesException();\r |
60 | \r |
61 | private final static Logger log = Logger.getLogger(AttributeParser.class.getName());\r |
62 | \r |
63 | \r |
64 | \r |
65 | /**\r |
66 | * @param name\r |
67 | * @param values\r |
68 | * @return\r |
69 | */\r |
70 | private static Attribute getAttribute(String name, Object[] values) {\r |
71 | Attribute attribute = null;\r |
72 | IppAttributeName attrName = IppAttributeName.get(name);\r |
73 | Class attrClass = attrName.getAttributeClass();\r |
74 | Class superClass = attrClass.getSuperclass();\r |
75 | if (superClass != null) {\r |
76 | if (superClass.equals(EnumSyntax.class)) {\r |
77 | try {\r |
78 | Field[] fields = attrClass.getDeclaredFields();\r |
79 | for (int i = 0; i < fields.length; i++) {\r |
80 | Field field = fields[i];\r |
81 | if (field.getType().equals(attrClass)) {\r |
82 | EnumSyntax attr = (EnumSyntax) field.get(null);\r |
83 | if (values[0] instanceof String) {\r |
84 | if (attr.toString().equals(values[0])) {\r |
85 | attribute = (Attribute) attr;\r |
86 | break;\r |
87 | }\r |
88 | } else {\r |
89 | if (attr.getValue() == ((Integer) values[0]).intValue()) {\r |
90 | attribute = (Attribute) attr;\r |
91 | break;\r |
92 | }\r |
93 | }\r |
94 | }\r |
95 | }\r |
96 | } catch (SecurityException e) {\r |
97 | log.log(Level.SEVERE, e.getMessage(), e);\r |
98 | } catch (IllegalArgumentException e) {\r |
99 | log.log(Level.SEVERE, e.getMessage(), e);\r |
100 | } catch (IllegalAccessException e) {\r |
101 | log.log(Level.SEVERE, e.getMessage(), e);\r |
102 | }\r |
103 | \r |
104 | } else {\r |
105 | Class[] parameters = toClassArray(values);\r |
106 | try {\r |
107 | Constructor constructor = attrClass.getDeclaredConstructor(parameters);\r |
108 | attribute = (Attribute) constructor.newInstance(values);\r |
109 | } catch (SecurityException e) {\r |
110 | log.log(Level.SEVERE, e.getMessage(), e);\r |
111 | } catch (NoSuchMethodException e) {\r |
112 | log.log(Level.SEVERE, e.getMessage(), e);\r |
113 | } catch (IllegalArgumentException e) {\r |
114 | log.log(Level.SEVERE, e.getMessage(), e);\r |
115 | } catch (InstantiationException e) {\r |
116 | log.log(Level.SEVERE, e.getMessage(), e);\r |
117 | } catch (IllegalAccessException e) {\r |
118 | log.log(Level.SEVERE, e.getMessage(), e);\r |
119 | } catch (InvocationTargetException e) {\r |
120 | log.log(Level.SEVERE, e.getMessage(), e);\r |
121 | }\r |
122 | }\r |
123 | }\r |
124 | \r |
125 | return attribute;\r |
126 | }\r |
127 | \r |
128 | /**\r |
129 | * @param byteArray\r |
130 | * @param byteCount\r |
131 | * @param lastAttribute\r |
132 | * @return\r |
133 | */\r |
134 | private static Attribute parseAttribute(InputStream in, Attribute lastAttribute)\r |
135 | throws IOException, EndOfAttributesException {\r |
136 | \r |
137 | int valueTag;\r |
138 | while ((valueTag = in.read()) < IppValueTag.UNSUPPORTED_VALUE.getValue()) {\r |
139 | if (valueTag == IppDelimiterTag.END_ATTRIBUTES.getValue()) {\r |
140 | throw END_OF_ATTRIBUTES_EXCEPTION;\r |
141 | }\r |
142 | }\r |
143 | \r |
144 | int nameLength = parseInt2(in);\r |
145 | // parse the Attribute-Name\r |
146 | String name;\r |
147 | if (nameLength == 0) {\r |
148 | name = lastAttribute.getName();\r |
149 | } else {\r |
150 | name = parseString(in, nameLength);\r |
151 | }\r |
152 | \r |
153 | \r |
154 | Object[] values = parseValues(valueTag, in);\r |
155 | \r |
156 | if (name.equals(IppAttributeName.PRINTER_STATE_REASONS.getName())) {\r |
157 | return parsePrinterStateReasons((String) values[0], lastAttribute);\r |
158 | } else if (name.equals(IppAttributeName.JOB_STATE_REASONS.getName())) {\r |
159 | return parseJobStateReasons(values, lastAttribute);\r |
160 | } else {\r |
161 | return getAttribute(name, values);\r |
162 | }\r |
163 | }\r |
164 | \r |
165 | private static String parseString(InputStream in, int nameLength) throws IOException{\r |
166 | return parseString(in, nameLength, Charset.US_ASCII.getValue());\r |
167 | }\r |
168 | \r |
169 | private static String parseNameAndTextString(InputStream in, int nameLength) throws IOException{\r |
170 | return parseString(in, nameLength, AttributeWriter.DEFAULT_CHARSET.getValue());\r |
171 | }\r |
172 | \r |
173 | /**\r |
174 | * @param in\r |
175 | * @param nameLength\r |
176 | */\r |
177 | private static String parseString(InputStream in, int nameLength, String charsetName) throws IOException {\r |
178 | byte[] bytes = new byte[nameLength];\r |
179 | in.read(bytes);\r |
180 | return new String(bytes, charsetName);\r |
181 | \r |
182 | }\r |
183 | \r |
184 | \r |
185 | \r |
186 | \r |
187 | /**\r |
188 | * @param byteArray\r |
189 | * @param valueOffset\r |
190 | * @return\r |
191 | */\r |
192 | private static Date parseDate(InputStream in) throws IOException {\r |
193 | DecimalFormat twoDigits = new DecimalFormat("00");\r |
194 | DecimalFormat threeDigits = new DecimalFormat("000");\r |
195 | DecimalFormat fourDigits = new DecimalFormat("0000");\r |
196 | //year is encoded in network-byte order\r |
197 | int year = parseInt2(in);\r |
198 | int month = in.read();\r |
199 | int day = in.read();\r |
200 | int hour = in.read();\r |
201 | int minute = in.read();\r |
202 | int second = in.read();\r |
203 | int deci = in.read();\r |
204 | int mili = deci * 100;\r |
205 | char direction = (char) in.read();\r |
206 | int hoursFromUtc = (int) in.read();\r |
207 | int minutesFromUtc = (int) in.read();\r |
208 | \r |
209 | String yearString = fourDigits.format((long) year);\r |
210 | String monthString = twoDigits.format((long) month);\r |
211 | String dayString = twoDigits.format((long) day);\r |
212 | String hourString = twoDigits.format((long) hour);\r |
213 | String minuteString = twoDigits.format((long) minute);\r |
214 | String secondString = twoDigits.format((long) second);\r |
215 | String miliString = threeDigits.format((long) mili);\r |
216 | String timeZone = direction + twoDigits.format((long) hoursFromUtc) + twoDigits.format((long) minutesFromUtc);\r |
217 | String dateString =\r |
218 | yearString\r |
219 | + "-"\r |
220 | + monthString\r |
221 | + "-"\r |
222 | + dayString\r |
223 | + " "\r |
224 | + hourString\r |
225 | + ":"\r |
226 | + minuteString\r |
227 | + ":"\r |
228 | + secondString\r |
229 | + ":"\r |
230 | + miliString\r |
231 | + timeZone;\r |
232 | DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSSZ");\r |
233 | Date date = null;\r |
234 | try {\r |
235 | date = dateFormat.parse(dateString);\r |
236 | } catch (ParseException e) {\r |
237 | log.log(Level.SEVERE, e.getMessage(), e);\r |
238 | }\r |
239 | return date;\r |
240 | }\r |
241 | \r |
242 | private static int parseInt4(InputStream in) throws IOException {\r |
243 | \r |
244 | //Same parsing as in java.io.DataInput readInt()\r |
245 | int a = in.read();\r |
246 | int b = in.read();\r |
247 | int c = in.read();\r |
248 | int d = in.read();\r |
249 | int value = (((a & 0xff) << 24) | ((b & 0xff) << 16) | ((c & 0xff) << 8) | (d & 0xff));\r |
250 | return value;\r |
251 | }\r |
252 | \r |
253 | /**\r |
254 | * @param bytes\r |
255 | * @param offset\r |
256 | * @return\r |
257 | */\r |
258 | private static int parseInt4(byte[] bytes, int offset) {\r |
259 | \r |
260 | //Same parsing as in java.io.DataInput readInt()\r |
261 | byte a = bytes[offset++];\r |
262 | byte b = bytes[offset++];\r |
263 | byte c = bytes[offset++];\r |
264 | byte d = bytes[offset++];\r |
265 | int value = (((a & 0xff) << 24) | ((b & 0xff) << 16) | ((c & 0xff) << 8) | (d & 0xff));\r |
266 | return value;\r |
267 | }\r |
268 | \r |
269 | private static int parseInt2(InputStream in) throws IOException {\r |
270 | \r |
271 | //Same parsing as in java.io.DataInput readInt()\r |
272 | int a = in.read();\r |
273 | int b = in.read();\r |
274 | int value = ((a & 0xff) << 8) | ((b & 0xff));\r |
275 | return value;\r |
276 | }\r |
277 | \r |
278 | \r |
279 | \r |
280 | /**\r |
281 | * @param string\r |
282 | * @param lastAttribute\r |
283 | * @return\r |
284 | */\r |
285 | private static Attribute parseJobStateReasons(Object[] values, Attribute lastAttribute) {\r |
286 | JobStateReasons reasons;\r |
287 | if (lastAttribute instanceof JobStateReasons) {\r |
288 | reasons = (JobStateReasons) lastAttribute;\r |
289 | } else {\r |
290 | reasons = new JobStateReasons();\r |
291 | }\r |
292 | JobStateReason reason = null;\r |
293 | if (values[0].equals(LdJobStateReason.NONE.toString())) {\r |
294 | reason = LdJobStateReason.NONE;\r |
295 | } else {\r |
296 | reason = (JobStateReason) getAttribute(IppAttributeName.JOB_STATE_REASON.getName(), values);\r |
297 | }\r |
298 | reasons.add(reason);\r |
299 | return reasons;\r |
300 | }\r |
301 | \r |
302 | /**\r |
303 | * @param reasonAndSeverity\r |
304 | * @param lastAttribute\r |
305 | * @return\r |
306 | */\r |
307 | private static PrinterStateReasons parsePrinterStateReasons(String reasonAndSeverity, Attribute lastAttribute) {\r |
308 | Severity severity = null;\r |
309 | int severityOffset = 0;\r |
310 | if ((severityOffset = reasonAndSeverity.indexOf(Severity.ERROR.toString())) > 0) {\r |
311 | severity = Severity.ERROR;\r |
312 | } else if ((severityOffset = reasonAndSeverity.indexOf(Severity.REPORT.toString())) > 0) {\r |
313 | severity = Severity.REPORT;\r |
314 | } else if ((severityOffset = reasonAndSeverity.indexOf(Severity.WARNING.toString())) > 0) {\r |
315 | severity = Severity.WARNING;\r |
316 | }\r |
317 | String reasonString;\r |
318 | if (severityOffset != -1) {\r |
319 | //subtract 1 for the hyphen\r |
320 | severityOffset--;\r |
321 | reasonString = reasonAndSeverity.substring(0, severityOffset - 1);\r |
322 | } else {\r |
323 | reasonString = reasonAndSeverity;\r |
324 | }\r |
325 | Object[] values = new Object[] { reasonString };\r |
326 | PrinterStateReason reason =\r |
327 | (PrinterStateReason) getAttribute(IppAttributeName.PRINTER_STATE_REASON.getName(), values);\r |
328 | PrinterStateReasons reasons;\r |
329 | if (lastAttribute instanceof PrinterStateReasons) {\r |
330 | reasons = (PrinterStateReasons) lastAttribute;\r |
331 | } else {\r |
332 | reasons = new PrinterStateReasons();\r |
333 | }\r |
334 | if (reason != null) {\r |
335 | if (severity == null) {\r |
336 | severity = Severity.ERROR;\r |
337 | }\r |
338 | reasons.put(reason, severity);\r |
339 | }\r |
340 | return reasons;\r |
341 | }\r |
342 | \r |
343 | /**\r |
344 | * @param bytes\r |
345 | * @return map of attributes (key -> category, value -> Set with attributes)\r |
346 | * \r |
347 | *\r |
348 | */\r |
349 | public static Map parseResponse(InputStream response) throws IOException {\r |
350 | Map attributes = new HashMap();\r |
351 | long start = System.currentTimeMillis();\r |
352 | Attribute lastAttribute = null;\r |
353 | boolean finished = false;\r |
354 | response.read();\r |
355 | \r |
356 | while (!finished) {\r |
357 | Attribute attribute = null;\r |
358 | try {\r |
359 | attribute = parseAttribute(response, lastAttribute);\r |
360 | if (attribute != null) {\r |
361 | lastAttribute = attribute;\r |
362 | attributes = put(attributes, attribute);\r |
363 | if (log.isLoggable(Level.FINEST)) {\r |
364 | log.finest("parsed attribute(" + attribute.getName() + "): " + attribute.toString());\r |
365 | }\r |
366 | } else {\r |
367 | if (log.isLoggable(Level.FINEST)) {\r |
368 | \r |
369 | log.finest("Attribute was null");\r |
370 | }\r |
371 | }\r |
372 | } catch (EndOfAttributesException e) {\r |
373 | \r |
374 | finished = true;\r |
375 | if (log.isLoggable(Level.INFO)) {\r |
376 | log.info("--- Attribute parsing finished ---");\r |
377 | }\r |
378 | }\r |
379 | }\r |
380 | long end = System.currentTimeMillis();\r |
381 | if (log.isLoggable(Level.INFO)) {\r |
382 | log.info("Parsing took " + (end - start) + "ms.");\r |
383 | }\r |
384 | return attributes;\r |
385 | }\r |
386 | \r |
387 | \r |
388 | /**\r |
389 | * @param byteArray\r |
390 | * @param valueOffset\r |
391 | * @param valueLength\r |
392 | * @return\r |
393 | */\r |
394 | private static URI parseUri(InputStream in, int valueLength) throws IOException {\r |
395 | String uriString = parseString(in, valueLength);\r |
396 | URI uri = null;\r |
397 | try {\r |
398 | uri = new URI(uriString);\r |
399 | } catch (URISyntaxException e) {\r |
400 | throw new RuntimeException(e);\r |
401 | }\r |
402 | return uri;\r |
403 | }\r |
404 | \r |
405 | \r |
406 | /**\r |
407 | * @param valueTag\r |
408 | * @param byteArray\r |
409 | * @param valueOffset\r |
410 | * @param valueLength\r |
411 | * @return\r |
412 | */\r |
413 | private static Object[] parseValues(int valueTag, InputStream in) throws IOException {\r |
414 | int valueLength = parseInt2(in);\r |
415 | Object[] values = null;\r |
416 | if (valueTag == IppValueTag.INTEGER.getValue() || valueTag == IppValueTag.ENUM.getValue()) {\r |
417 | Integer number = new Integer(parseInt4(in));\r |
418 | values = new Object[] { number };\r |
419 | } else if (\r |
420 | valueTag == IppValueTag.STRING.getValue()\r |
421 | || valueTag == IppValueTag.TEXT.getValue()\r |
422 | || valueTag == IppValueTag.NAME.getValue()){\r |
423 | String word = parseNameAndTextString(in, valueLength);\r |
424 | values = new Object[] { word, Locale.getDefault()}; \r |
425 | } else if (\r |
426 | valueTag == IppValueTag.CHARSET.getValue()\r |
427 | || valueTag == IppValueTag.LANGUAGE.getValue()\r |
428 | || valueTag == IppValueTag.MIMETYPE.getValue()) {\r |
429 | String word = parseString(in, valueLength);\r |
430 | values = new Object[] { word, Locale.getDefault()};\r |
431 | } else if (valueTag == IppValueTag.URI.getValue()) {\r |
432 | URI uri = parseUri(in, valueLength);\r |
433 | values = new Object[] { uri };\r |
434 | } else if (valueTag == IppValueTag.KEYWORD.getValue()) {\r |
435 | String word = parseString(in, valueLength);\r |
436 | values = new Object[] { word, Locale.getDefault()};\r |
437 | } else if (valueTag == IppValueTag.BOOLEAN.getValue()) {\r |
438 | Integer bool = new Integer(in.read());\r |
439 | values = new Object[] { bool };\r |
440 | } else if (valueTag == IppValueTag.RANGE.getValue()) {\r |
441 | Integer lowerBound = new Integer(parseInt4(in));\r |
442 | Integer upperBound = new Integer(parseInt4(in));\r |
443 | values = new Object[] { lowerBound, upperBound };\r |
444 | } else if (valueTag == IppValueTag.DATE.getValue()) {\r |
445 | \r |
446 | Date date = parseDate(in);\r |
447 | values = new Object[] { date };\r |
448 | } else if (valueTag == IppValueTag.NOVALUE.getValue()) {\r |
449 | values = new Object[] {\r |
450 | };\r |
451 | } else {\r |
452 | throw new IllegalArgumentException("\"" + Integer.toHexString(valueTag) + "\" is not a valid value-tag");\r |
453 | }\r |
454 | return values;\r |
455 | }\r |
456 | \r |
457 | \r |
458 | /**\r |
459 | * @param attributes\r |
460 | * @param attribute\r |
461 | */\r |
462 | private static Map put(Map attributes, Attribute attribute) {\r |
463 | Set values = (Set) attributes.get(attribute.getCategory());\r |
464 | if (values == null) {\r |
465 | values = new HashSet();\r |
466 | attributes.put(attribute.getCategory(), values);\r |
467 | }\r |
468 | values.add(attribute);\r |
469 | return attributes;\r |
470 | }\r |
471 | \r |
472 | /**\r |
473 | * @param values\r |
474 | * @return\r |
475 | */\r |
476 | private static Class[] toClassArray(Object[] values) {\r |
477 | Class[] classes = new Class[values.length];\r |
478 | for (int i = 0; i < values.length; i++) {\r |
479 | Class clazz = values[i].getClass();\r |
480 | if (clazz.equals(Integer.class)) {\r |
481 | clazz = int.class;\r |
482 | }\r |
483 | classes[i] = clazz;\r |
484 | }\r |
485 | return classes;\r |
486 | }\r |
487 | \r |
488 | }\r |