Source for gnu.javax.print.ipp.IppResponse

   1: /* IppResponse.java --
   2:  Copyright (C) 2006 Free Software Foundation, Inc.
   3: 
   4:  This file is part of GNU Classpath.
   5: 
   6:  GNU Classpath is free software; you can redistribute it and/or modify
   7:  it under the terms of the GNU General Public License as published by
   8:  the Free Software Foundation; either version 2, or (at your option)
   9:  any later version.
  10: 
  11:  GNU Classpath is distributed in the hope that it will be useful, but
  12:  WITHOUT ANY WARRANTY; without even the implied warranty of
  13:  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14:  General Public License for more details.
  15: 
  16:  You should have received a copy of the GNU General Public License
  17:  along with GNU Classpath; see the file COPYING.  If not, write to the
  18:  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19:  02110-1301 USA.
  20: 
  21:  Linking this library statically or dynamically with other modules is
  22:  making a combined work based on this library.  Thus, the terms and
  23:  conditions of the GNU General Public License cover the whole
  24:  combination.
  25: 
  26:  As a special exception, the copyright holders of this library give you
  27:  permission to link this library with independent modules to produce an
  28:  executable, regardless of the license terms of these independent
  29:  modules, and to copy and distribute the resulting executable under
  30:  terms of your choice, provided that you also meet, for each linked
  31:  independent module, the terms and conditions of the license of that
  32:  module.  An independent module is a module which is not derived from
  33:  or based on this library.  If you modify this library, you may extend
  34:  this exception to your version of the library, but you are not
  35:  obligated to do so.  If you do not wish to do so, delete this
  36:  exception statement from your version. */
  37: 
  38: 
  39: package gnu.javax.print.ipp;
  40: 
  41: import gnu.classpath.debug.Component;
  42: import gnu.classpath.debug.SystemLogger;
  43: import gnu.javax.print.ipp.attribute.UnknownAttribute;
  44: import gnu.javax.print.ipp.attribute.defaults.DocumentFormatDefault;
  45: import gnu.javax.print.ipp.attribute.defaults.JobHoldUntilDefault;
  46: import gnu.javax.print.ipp.attribute.defaults.JobSheetsDefault;
  47: import gnu.javax.print.ipp.attribute.defaults.MediaDefault;
  48: import gnu.javax.print.ipp.attribute.defaults.PrinterResolutionDefault;
  49: import gnu.javax.print.ipp.attribute.job.AttributesCharset;
  50: import gnu.javax.print.ipp.attribute.job.AttributesNaturalLanguage;
  51: import gnu.javax.print.ipp.attribute.job.JobMoreInfo;
  52: import gnu.javax.print.ipp.attribute.job.JobPrinterUri;
  53: import gnu.javax.print.ipp.attribute.job.JobUri;
  54: import gnu.javax.print.ipp.attribute.printer.CharsetConfigured;
  55: import gnu.javax.print.ipp.attribute.printer.DocumentFormat;
  56: import gnu.javax.print.ipp.attribute.printer.NaturalLanguageConfigured;
  57: import gnu.javax.print.ipp.attribute.printer.PrinterCurrentTime;
  58: import gnu.javax.print.ipp.attribute.printer.PrinterDriverInstaller;
  59: import gnu.javax.print.ipp.attribute.supported.CharsetSupported;
  60: import gnu.javax.print.ipp.attribute.supported.DocumentFormatSupported;
  61: import gnu.javax.print.ipp.attribute.supported.GeneratedNaturalLanguageSupported;
  62: import gnu.javax.print.ipp.attribute.supported.JobHoldUntilSupported;
  63: import gnu.javax.print.ipp.attribute.supported.JobSheetsSupported;
  64: import gnu.javax.print.ipp.attribute.supported.MediaSupported;
  65: import gnu.javax.print.ipp.attribute.supported.PrinterResolutionSupported;
  66: import gnu.javax.print.ipp.attribute.supported.PrinterUriSupported;
  67: 
  68: import java.io.ByteArrayOutputStream;
  69: import java.io.DataInputStream;
  70: import java.io.IOException;
  71: import java.io.InputStream;
  72: import java.net.URI;
  73: import java.net.URISyntaxException;
  74: import java.util.ArrayList;
  75: import java.util.Calendar;
  76: import java.util.Date;
  77: import java.util.HashMap;
  78: import java.util.HashSet;
  79: import java.util.List;
  80: import java.util.Map;
  81: import java.util.Set;
  82: import java.util.logging.Logger;
  83: 
  84: import javax.print.attribute.Attribute;
  85: import javax.print.attribute.standard.CopiesSupported;
  86: import javax.print.attribute.standard.DateTimeAtCompleted;
  87: import javax.print.attribute.standard.DateTimeAtCreation;
  88: import javax.print.attribute.standard.DateTimeAtProcessing;
  89: import javax.print.attribute.standard.JobImpressionsSupported;
  90: import javax.print.attribute.standard.JobKOctetsSupported;
  91: import javax.print.attribute.standard.JobMediaSheetsSupported;
  92: import javax.print.attribute.standard.JobStateReason;
  93: import javax.print.attribute.standard.JobStateReasons;
  94: import javax.print.attribute.standard.NumberUpSupported;
  95: import javax.print.attribute.standard.PrinterMoreInfo;
  96: import javax.print.attribute.standard.PrinterMoreInfoManufacturer;
  97: import javax.print.attribute.standard.PrinterStateReason;
  98: import javax.print.attribute.standard.PrinterStateReasons;
  99: import javax.print.attribute.standard.Severity;
 100: 
 101: /**
 102:  * <code>IppResponse</code> models a response received from an IPP
 103:  * compatible server as described in RFC 2910 IPP 1.1 Encoding and Transport.
 104:  *
 105:  * @author Wolfgang Baer (WBaer@gmx.de)
 106:  */
 107: public class IppResponse
 108: {
 109: 
 110:   /**
 111:    * <code>ResponseReader</code> is responsible for parsing an IPP 1.1
 112:    * response stream. It provides access to the attribute groups after parsing
 113:    * via getter methods.
 114:    * <p>
 115:    * The enconding of a response is structured as follows (for an official
 116:    * description please have a look at the RFC document mentioned above):
 117:    * <ul>
 118:    * <li>version-number            - 2 bytes - required</li>
 119:    * <li>status-code               - 2 bytes - required</li>
 120:    * <li>request-id                - 4 bytes - required</li>
 121:    * <li>attribute-group           - n bytes - 0 or more</li>
 122:    * <li>end-of-attributes-tag     - 1 byte  - required</li>
 123:    * <li>data                      - q bytes - optional</li>
 124:    * </ul>
 125:    * </p><p>
 126:    * Where each attribute-group (if any) is encoded as follows:
 127:    * <ul>
 128:    * <li>begin-attribute-group-tag - 1 byte</li>
 129:    * <li>attribute                 - p bytes - 0 or more</li>
 130:    * </ul>
 131:    * </p><p>
 132:    * Encoding of attributes:
 133:    * <ul>
 134:    * <li>attribute-with-one-value - q bytes</li>
 135:    * <li>additional-value         - r bytes  - 0 or more</li>
 136:    * </ul>
 137:    * </p><p>
 138:    * Encoding of attribute-with-one-value:
 139:    * <ul>
 140:    * <li>value-tag                  - 1 byte</li>
 141:    * <li>name-length  (value is u)  - 2 bytes</li>
 142:    * <li>name                       - u bytes</li>
 143:    * <li>value-length  (value is v) - 2 bytes</li>
 144:    * <li>value                      - v bytes</li>
 145:    * </ul>
 146:    * </p><p>
 147:    * Encoding of additional value:
 148:    * <ul>
 149:    * <li>value-tag                       - 1 byte</li>
 150:    * <li>name-length  (value is 0x0000)  - 2 bytes</li>
 151:    * <li>value-length (value is w)       - 2 bytes</li>
 152:    * <li>value                           - w bytes</li>
 153:    * </ul>
 154:    * </p>
 155:    *
 156:    * @author Wolfgang Baer (WBaer@gmx.de)
 157:    */
 158:   class ResponseReader
 159:   {
 160:     /** The IPP version defaults to 1.1 */
 161:     private static final short VERSION = 0x0101;
 162: 
 163:     /**
 164:      * Parses the inputstream containing the response of the IPP request.
 165:      * @param input the inputstream
 166:      * @throws IppException if unexpected exceptions occur.
 167:      * @throws IOException if IO problems with the underlying inputstream occur.
 168:      */
 169:     public void parseResponse(InputStream input)
 170:         throws IppException, IOException
 171:     {
 172:       DataInputStream stream = new DataInputStream(input);
 173: 
 174:       short version = stream.readShort();
 175:       status_code = stream.readShort();
 176:       request_id = stream.readInt();
 177: 
 178:       if (VERSION != version)
 179:         throw new IppException("Version mismatch - "
 180:           + "implementation does not support other versions than IPP 1.1");
 181: 
 182:       logger.log(Component.IPP, "Statuscode: "
 183:         + Integer.toHexString(status_code) + " Request-ID: " + request_id);
 184: 
 185:       byte tag = 0;
 186:       boolean proceed = true;
 187:       HashMap<Class<? extends Attribute>, Set<Attribute>> tmp;
 188:       // iterate over attribute-groups until end-of-attributes-tag is found
 189:       while (proceed)
 190:         {
 191:           if (tag == 0) // only at start time
 192:             tag = stream.readByte();
 193: 
 194:           logger.log(Component.IPP, "DelimiterTag: " + Integer.toHexString(tag));
 195: 
 196:           // check if end of attributes
 197:           switch (tag)
 198:             {
 199:             case IppDelimiterTag.END_OF_ATTRIBUTES_TAG:
 200:               proceed = false;
 201:               break;
 202:             case IppDelimiterTag.OPERATION_ATTRIBUTES_TAG:
 203:               tmp = new HashMap<Class<? extends Attribute>, Set<Attribute>>();
 204:               tag = parseAttributes(tmp, stream);
 205:               operationAttributes.add(tmp);
 206:               break;
 207:             case IppDelimiterTag.JOB_ATTRIBUTES_TAG:
 208:               tmp = new HashMap<Class<? extends Attribute>, Set<Attribute>>();
 209:               tag = parseAttributes(tmp, stream);
 210:               jobAttributes.add(tmp);
 211:               break;
 212:             case IppDelimiterTag.PRINTER_ATTRIBUTES_TAG:
 213:               tmp = new HashMap<Class<? extends Attribute>, Set<Attribute>>();
 214:               tag = parseAttributes(tmp, stream);
 215:               printerAttributes.add(tmp);
 216:               break;
 217:             case IppDelimiterTag.UNSUPPORTED_ATTRIBUTES_TAG:
 218:               System.out.println("Called");
 219:               tmp = new HashMap<Class<? extends Attribute>, Set<Attribute>>();
 220:               tag = parseAttributes(tmp, stream);
 221:               unsupportedAttributes.add(tmp);
 222:               break;
 223:             default:
 224:               throw new IppException("Unknown tag with value "
 225:                                      + Integer.toHexString(tag) + " occured.");
 226:             }
 227:         }
 228: 
 229:       // if there are more bytes that has to be data.
 230:       ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
 231:       byte[] readbuf = new byte[2048];
 232:       int len = 0;
 233: 
 234:       while ((len = stream.read(readbuf)) > 0)
 235:         byteStream.write(readbuf, 0, len);
 236: 
 237:       byteStream.flush();
 238:       data = byteStream.toByteArray();
 239:     }
 240: 
 241:     /**
 242:      * The actual parsing of the attributes and further putting into the
 243:      * provided group maps.
 244:      * @param attributes the provided attribute group map.
 245:      * @param stream the provided stream to read from.
 246:      * @return The last read tag byte (normally a DelimiterTag)
 247:      * @throws IppException if unexpected exceptions occur.
 248:      * @throws IOException if IO problems with the underlying inputstream occur.
 249:      */
 250:     private byte parseAttributes(Map<Class<? extends Attribute>, Set<Attribute>> attributes,
 251:                                  DataInputStream stream)
 252:         throws IppException, IOException
 253:     {
 254:       Attribute lastAttribute = null;
 255:       Attribute attribute = null;
 256: 
 257:       // declaration of variables
 258:       short nameLength;
 259:       String name;
 260:       short valueLength;
 261:       byte[] value;
 262: 
 263:       // tmp variables for parsing
 264:       // declared here so no name duplication occurs
 265:       URI uri;
 266:       String str;
 267: 
 268:       while (true)
 269:         {
 270:           byte tag = stream.readByte();
 271: 
 272:           if (IppDelimiterTag.isDelimiterTag(tag))
 273:             return tag;
 274: 
 275:           // it must be a value tag now
 276:           // so we have either a attribute-with-one-value
 277:           // or (if setOf is possible) an additional-value
 278: 
 279:           // (1) Length of the name
 280:           nameLength = stream.readShort();
 281: 
 282:           // (2) The name itself
 283:           // may be an additional-value
 284:           if (nameLength == 0x0000)
 285:             name = lastAttribute.getName();
 286:           else
 287:             {
 288:               byte[] nameBytes = new byte[nameLength];
 289:               stream.read(nameBytes);
 290:               name = new String(nameBytes);
 291:             }
 292: 
 293:           // (3) Length of the value
 294:           valueLength = stream.readShort();
 295: 
 296:           // (4) The value itself
 297:           value = new byte[valueLength];
 298:           stream.read(value);
 299: 
 300:           // the value itself
 301:           switch (tag)
 302:             {
 303:             // out-of-band values
 304:             case IppValueTag.UNSUPPORTED:
 305:             case IppValueTag.UNKNOWN:
 306:               // TODO implement out-of-band handling
 307:               // We currently throw an exception to see when it occurs - not yet :-)
 308:               throw new IppException(
 309:                     "Unexpected name value for out-of-band value tag " + tag);
 310:             case IppValueTag.NO_VALUE:
 311:               attribute = null;
 312: 
 313:               break;
 314:             case IppValueTag.INTEGER:
 315:               int intValue = IppUtilities.convertToInt(value);
 316:               attribute = IppUtilities.getIntegerAttribute(name, intValue);
 317: 
 318:               break;
 319:             case IppValueTag.BOOLEAN:
 320:               // JPS API models boolean syntax type as enums
 321:               // 0x01 = true, 0x00 = false - all are enums
 322:               attribute = IppUtilities.getEnumAttribute(name, new Integer(value[0]));
 323: 
 324:               break;
 325:             case IppValueTag.ENUM:
 326:               int intVal = IppUtilities.convertToInt(value);
 327:               attribute = IppUtilities.getEnumAttribute(name, new Integer(intVal));
 328: 
 329:               break;
 330:             case IppValueTag.OCTECTSTRING_UNSPECIFIED:
 331:               // none exists according to spec
 332:               // so lets report as exception to see when it occurs
 333:               throw new IppException("Unspecified octet string occured.");
 334: 
 335:             case IppValueTag.DATETIME:
 336:               Date date = parseDate(value);
 337:               if (name.equals("printer-current-time"))
 338:                 attribute = new PrinterCurrentTime(date);
 339:               else if (name.equals("date-time-at-creation"))
 340:                 attribute = new DateTimeAtCreation(date);
 341:               else if (name.equals("date-time-at-processing"))
 342:                 attribute = new DateTimeAtProcessing(date);
 343:               else if (name.equals("date-time-at-completed"))
 344:                 attribute = new DateTimeAtCompleted(date);
 345: 
 346:               break;
 347:             case IppValueTag.RESOLUTION:
 348:               int crossFeed = IppUtilities.convertToInt(value[0], value[1], value[2], value[3]);
 349:               int feed = IppUtilities.convertToInt(value[4], value[5], value[6], value[7]);
 350:               int units = value[8];
 351: 
 352:               if (name.equals("printer-resolution-default"))
 353:                 attribute = new PrinterResolutionDefault(crossFeed, feed, units);
 354:               else if (name.equals("printer-resolution-supported")) // may be here also
 355:                 attribute = new PrinterResolutionSupported(crossFeed, feed, units);
 356: 
 357:               break;
 358:             case IppValueTag.RANGEOFINTEGER:
 359:               int lower = IppUtilities.convertToInt(value[0], value[1], value[2], value[3]);
 360:               int upper = IppUtilities.convertToInt(value[4], value[5], value[6], value[7]);
 361: 
 362:               if (name.equals("copies-supported"))
 363:                 attribute = new CopiesSupported(lower, upper);
 364:               else if (name.equals("number-up-supported"))
 365:                 attribute = new NumberUpSupported(lower, upper);
 366:               else if (name.equals("job-k-octets-supported"))
 367:                 attribute = new JobKOctetsSupported(lower, upper);
 368:               else if (name.equals("job-impressions-supported"))
 369:                 attribute = new JobImpressionsSupported(lower, upper);
 370:               else if (name.equals("job-media-sheets-supported"))
 371:                 attribute = new JobMediaSheetsSupported(lower, upper);
 372: 
 373:               break;
 374:             case IppValueTag.TEXT_WITH_LANGUAGE:
 375:             case IppValueTag.TEXT_WITHOUT_LANGUAGE:
 376:             case IppValueTag.NAME_WITH_LANGUAGE:
 377:             case IppValueTag.NAME_WITHOUT_LANGUAGE:
 378:               attribute = IppUtilities.getTextAttribute(name, tag, value);
 379: 
 380:               break;
 381:             case IppValueTag.KEYWORD:
 382:               str = new String(value);
 383:               if (name.equals("job-hold-until-supported")) // may also be name type
 384:                 attribute = new JobHoldUntilSupported(str, null);
 385:               else if (name.equals("job-hold-until-default"))
 386:                 attribute = new JobHoldUntilDefault(str, null);
 387:               else if (name.equals("media-supported"))
 388:                 attribute = new MediaSupported(str, null);
 389:               else if (name.equals("media-default"))
 390:                 attribute = new MediaDefault(str, null);
 391:               else if (name.equals("job-sheets-default"))
 392:                 attribute = new JobSheetsDefault(str, null);
 393:               else if (name.equals("job-sheets-supported"))
 394:                 attribute = new JobSheetsSupported(str, null);
 395:               else if (name.equals("job-state-reasons")) // setOf
 396:                 attribute = parseJobStateReasons(value, lastAttribute);
 397:               else if (name.equals("printer-state-reasons")) // setOf
 398:                 attribute = parsePrinterStateReasons(value, lastAttribute);
 399:               else
 400:                 attribute = IppUtilities.getEnumAttribute(name, str);
 401: 
 402:               // all other stuff is either an enum or needs to be mapped to an
 403:               // UnknownAttribute instance. Enums catched here are:
 404:               // ipp-versions-supported, pdl-override-supported, compression-supported
 405:               // uri-authentication-supported, uri-security-supported, sides-supported
 406:               // sides-default, multiple-document-handling-supported, multiple-document-handling-default
 407: 
 408:               break;
 409:             case IppValueTag.URI:
 410:               try
 411:                 {
 412:                   uri = new URI(new String(value));
 413:                 }
 414:               catch (URISyntaxException e)
 415:                 {
 416:                   throw new IppException("Wrong URI syntax encountered.", e);
 417:                 }
 418: 
 419:               if (name.equals("job-uri"))
 420:                 attribute = new JobUri(uri);
 421:               else if (name.equals("job-printer-uri"))
 422:                 attribute = new JobPrinterUri(uri);
 423:               else if (name.equals("job-more-info"))
 424:                 attribute = new JobMoreInfo(uri);
 425:               else if (name.equals("printer-uri-supported")) // setOf
 426:                 attribute = new PrinterUriSupported(uri);
 427:               else if (name.equals("printer-more-info"))
 428:                 attribute = new PrinterMoreInfo(uri);
 429:               else if (name.equals("printer-driver-installer"))
 430:                 attribute = new PrinterDriverInstaller(uri);
 431:               else if (name.equals("printer-more-info-manufacturer"))
 432:                 attribute = new PrinterMoreInfoManufacturer(uri);
 433: 
 434:               break;
 435:             case IppValueTag.URI_SCHEME:
 436:               // only one uri-scheme exists - and its an enum
 437:               if (name.equals("reference-uri-schemes-supported"))
 438:                 attribute = IppUtilities.getEnumAttribute(name, new String(value));
 439: 
 440:               break;
 441:             case IppValueTag.CHARSET:
 442:               str = new String(value);
 443:               if (name.equals("attributes-charset"))
 444:                 attribute = new AttributesCharset(str);
 445:               else if (name.equals("charset-configured"))
 446:                 attribute = new CharsetConfigured(str);
 447:               else if (name.equals("charset-supported")) // setOf
 448:                 attribute = new CharsetSupported(str);
 449: 
 450:               break;
 451:             case IppValueTag.NATURAL_LANGUAGE:
 452:               str = new String(value);
 453:               if (name.equals("attributes-natural-language"))
 454:                 attribute = new AttributesNaturalLanguage(str);
 455:               else if (name.equals("natural-language-configured"))
 456:                 attribute = new NaturalLanguageConfigured(str);
 457:               else if (name.equals("generated-natural-language-supported")) // setOf
 458:                 attribute = new GeneratedNaturalLanguageSupported(str);
 459: 
 460:               break;
 461:             case IppValueTag.MIME_MEDIA_TYPE:
 462:               str = new String(value);
 463:               if (name.equals("document-format-default"))
 464:                 attribute = new DocumentFormatDefault(str, null);
 465:               else if (name.equals("document-format-supported")) // setOf
 466:                 attribute = new DocumentFormatSupported(str, null);
 467:               else if (name.equals("document-format")) // setOf
 468:                 attribute = new DocumentFormat(str, null);
 469: 
 470:               break;
 471:             default:
 472:               throw new IppException("Unknown tag with value "
 473:                                      + Integer.toHexString(tag) + " found.");
 474:             }
 475: 
 476:           if (attribute == null)
 477:             attribute = new UnknownAttribute(tag, name, value);
 478: 
 479:           addAttribute(attributes, attribute);
 480:           lastAttribute = attribute;
 481: 
 482:           logger.log(Component.IPP, "Attribute: " + name
 483:                      + " Value: " + attribute.toString());
 484:         }
 485:     }
 486: 
 487:     /**
 488:      * Adds a new attribute to the given attribute group. If this is the fist
 489:      * occurence of this attribute category a new set is created and associated
 490:      * with its category as key.
 491:      * @param attributeGroup
 492:      *          the attribute group
 493:      * @param attribute
 494:      *          the attribute to add
 495:      */
 496:     private void addAttribute(Map<Class<? extends Attribute>, Set<Attribute>> attributeGroup,
 497:                               Attribute attribute)
 498:     {
 499:       Class<? extends Attribute> clazz = attribute.getCategory();
 500:       Set<Attribute> attributeValues = attributeGroup.get(clazz);
 501: 
 502:       if (attributeValues == null) // first attribute of this category
 503:         {
 504:           attributeValues = new HashSet<Attribute>();
 505:           attributeGroup.put(clazz, attributeValues);
 506:         }
 507: 
 508:       attributeValues.add(attribute);
 509:     }
 510: 
 511:     /**
 512:      * Parses a name with or without language attribute value from the byte[]
 513:      * and returns the result as an object[].
 514:      * @param value the byte[]
 515:      * @param lastAttr the last attribute
 516:      * @return The attribute.
 517:      */
 518:     private PrinterStateReasons parsePrinterStateReasons(byte[] value, Attribute lastAttr)
 519:     {
 520:       String str = new String(value);
 521:       PrinterStateReasons attribute;
 522: 
 523:       if (lastAttr instanceof PrinterStateReasons)
 524:         attribute = (PrinterStateReasons) lastAttr;
 525:       else
 526:         attribute = new PrinterStateReasons();
 527: 
 528:       // special case indicating no reasons
 529:       if (str.equals("none"))
 530:         return attribute;
 531: 
 532:       Severity severity = null;
 533:       PrinterStateReason reason = null;
 534: 
 535:       if (str.endsWith(Severity.WARNING.toString()))
 536:         severity = Severity.WARNING;
 537:       else if (str.endsWith(Severity.REPORT.toString()))
 538:         severity = Severity.REPORT;
 539:       else if (str.endsWith(Severity.ERROR.toString()))
 540:         severity = Severity.ERROR;
 541: 
 542:       if (severity != null)
 543:         str = str.substring(0, str.lastIndexOf('-'));
 544:       else // we must associate a severity
 545:         severity = Severity.REPORT;
 546: 
 547:       reason = (PrinterStateReason)
 548:         IppUtilities.getEnumAttribute("printer-state-reason", str);
 549: 
 550:       attribute.put(reason , severity);
 551:       return attribute;
 552:     }
 553: 
 554:     /**
 555:      * Parses a name with or without language attribute value from the byte[]
 556:      * and returns the result as an object[].
 557:      * @param value the byte[]
 558:      * @param lastAttr the last attribute
 559:      * @return The attribute.
 560:      */
 561:     private JobStateReasons parseJobStateReasons(byte[] value, Attribute lastAttr)
 562:     {
 563:       String str = new String(value);
 564:       JobStateReasons attribute;
 565: 
 566:       if (lastAttr instanceof JobStateReasons)
 567:         attribute = (JobStateReasons) lastAttr;
 568:       else
 569:         attribute = new JobStateReasons();
 570: 
 571:       // special case indicating no reasons
 572:       if (str.equals("none"))
 573:         return attribute;
 574: 
 575:       JobStateReason reason = (JobStateReason)
 576:         IppUtilities.getEnumAttribute("job-state-reason", str);
 577: 
 578:       attribute.add(reason);
 579:       return attribute;
 580:     }
 581: 
 582:     /**
 583:      * Parses a DateTime syntax attribute and returns the constructed Date
 584:      * object.
 585:      * <p>
 586:      * The syntax value is defined as 11 octets follwing the DateAndTime format
 587:      * of RFC 1903:
 588:      * <ul>
 589:      * <li>field | octets | contents | range</li>
 590:      * <li>1 | 1-2 | year | 0..65536</li>
 591:      * <li>2 | 3 | month | 1..12</li>
 592:      * <li>3 | 4 | day | 1..31</li>
 593:      * <li>4 | 5 | hour | 0..23</li>
 594:      * <li>5 | 6 | minutes | 0..59</li>
 595:      * <li>6 | 7 | seconds | 0..60 (use 60 for leap-second)</li>
 596:      * <li>7 | 8 | deci-seconds | 0..9</li>
 597:      * <li>8 | 9 | direction from UTC | '+' / '-'</li>
 598:      * <li>9 | 10 | hours from UTC | 0..11</li>
 599:      * <li>10 | 11 | minutes from UTC | 0..59</li>
 600:      * </ul>
 601:      * </p>
 602:      *
 603:      * @param value the byte[]
 604:      * @return The date object.
 605:      */
 606:     private Date parseDate(byte[] value)
 607:     {
 608:       short year = IppUtilities.convertToShort(value[0], value[1]);
 609: 
 610:       Calendar cal = Calendar.getInstance();
 611:       cal.set(Calendar.YEAR, year);
 612:       cal.set(Calendar.MONTH, value[2]);
 613:       cal.set(Calendar.DAY_OF_MONTH, value[3]);
 614:       cal.set(Calendar.HOUR_OF_DAY, value[4]);
 615:       cal.set(Calendar.MINUTE, value[5]);
 616:       cal.set(Calendar.SECOND, value[6]);
 617:       cal.set(Calendar.MILLISECOND, value[7] * 100); // deci-seconds
 618: 
 619:       // offset from timezone
 620:       int offsetMilli = value[9] * 3600000; // hours to millis
 621:       offsetMilli = offsetMilli + value[10] * 60000; // minutes to millis
 622: 
 623:       if (((char) value[8]) == '-')
 624:         offsetMilli = offsetMilli * (-1);
 625: 
 626:       cal.set(Calendar.ZONE_OFFSET, offsetMilli);
 627:       return cal.getTime();
 628:     }
 629:   }
 630: 
 631:   /**
 632:    * Logger for tracing - enable by passing
 633:    * -Dgnu.classpath.debug.components=ipp to the vm.
 634:    */
 635:   static final Logger logger = SystemLogger.SYSTEM;
 636: 
 637:   URI uri;
 638:   short operation_id;
 639:   short status_code;
 640:   int request_id;
 641: 
 642:   List<Map<Class<? extends Attribute>, Set<Attribute>>> operationAttributes;
 643:   List<Map<Class<? extends Attribute>, Set<Attribute>>> printerAttributes;
 644:   List<Map<Class<? extends Attribute>, Set<Attribute>>> jobAttributes;
 645:   List<Map<Class<? extends Attribute>, Set<Attribute>>> unsupportedAttributes;
 646: 
 647:   byte[] data;
 648: 
 649:   /**
 650:    * Creates an <code>IppResponse</code> instance.
 651:    *
 652:    * @param uri the uri the request was directy to.
 653:    * @param operation_id the operation id of the request.
 654:    */
 655:   public IppResponse(URI uri, short operation_id)
 656:   {
 657:     this.uri = uri;
 658:     this.operation_id = operation_id;
 659:     operationAttributes =
 660:       new ArrayList<Map<Class<? extends Attribute>, Set<Attribute>>>();
 661:     jobAttributes =
 662:       new ArrayList<Map<Class<? extends Attribute>, Set<Attribute>>>();
 663:     printerAttributes =
 664:       new ArrayList<Map<Class<? extends Attribute>, Set<Attribute>>>();
 665:     unsupportedAttributes =
 666:       new ArrayList<Map<Class<? extends Attribute>, Set<Attribute>>>();
 667:   }
 668: 
 669:   /**
 670:    * Sets the data received from the request sent.
 671:    *
 672:    * @param input the input stream received.
 673:    * @throws IppException if parsing fails.
 674:    */
 675:   protected void setResponseData(InputStream input) throws IppException
 676:   {
 677:     ResponseReader reader = new ResponseReader();
 678: 
 679:     try
 680:       {
 681:         reader.parseResponse(input);
 682:       }
 683:     catch (IOException e)
 684:       {
 685:         throw new IppException(
 686:             "Exception during response parsing caused by IOException", e);
 687:       }
 688:   }
 689: 
 690:   /**
 691:    * Returns the uri of the original request.
 692:    * @return The URI of the request.
 693:    */
 694:   public URI getURI()
 695:   {
 696:     return uri;
 697:   }
 698: 
 699:   /**
 700:    * Returns the operation id of the original request.
 701:    * @return The operation id of the request.
 702:    */
 703:   public int getOperationID()
 704:   {
 705:     return operation_id;
 706:   }
 707: 
 708:   /**
 709:    * Returns the set of job attributes group maps.
 710:    * There may occur more than one group of type job attribute in a response
 711:    * because of e.g. multiple job or print service informations requested.
 712:    *
 713:    * @return The list of job attribute group maps.
 714:    */
 715:   public List<Map<Class<? extends Attribute>, Set<Attribute>>> getJobAttributes()
 716:   {
 717:     return jobAttributes;
 718:   }
 719: 
 720:   /**
 721:    * Returns the set of operation attributes group maps.
 722:    * There may occur more than one group of type job attribute in a response
 723:    * because of e.g. multiple job or print service informations requested.
 724:    *
 725:    * @return The list of operation attribute group maps.
 726:    */
 727:   public List<Map<Class<? extends Attribute>, Set<Attribute>>> getOperationAttributes()
 728:   {
 729:     return operationAttributes;
 730:   }
 731: 
 732:   /**
 733:    * Returns the set of printer attributes group maps.
 734:    * There may occur more than one group of type job attribute in a response
 735:    * because of e.g. multiple job or print service informations requested.
 736:    *
 737:    * @return The list of printer attribute group maps.
 738:    */
 739:   public List<Map<Class<? extends Attribute>, Set<Attribute>>> getPrinterAttributes()
 740:   {
 741:     return printerAttributes;
 742:   }
 743: 
 744:   /**
 745:    * Returns the ID of the initial request.
 746:    *
 747:    * @return The request ID.
 748:    */
 749:   public int getRequestID()
 750:   {
 751:     return request_id;
 752:   }
 753: 
 754:   /**
 755:    * Returns the status code of the response.
 756:    * Defined in {@link IppStatusCode}.
 757:    *
 758:    * @return The status code.
 759:    */
 760:   public short getStatusCode()
 761:   {
 762:     return status_code;
 763:   }
 764: 
 765:   /**
 766:    * Returns the set of unsupported attributes group maps.
 767:    * There may occur more than one group of type job attribute in a response
 768:    * because of e.g. multiple job or print service informations requested.
 769:    *
 770:    * @return The list of unsupported attribute group maps.
 771:    */
 772:   public List<Map<Class<? extends Attribute>, Set<Attribute>>> getUnsupportedAttributes()
 773:   {
 774:     return unsupportedAttributes;
 775:   }
 776: 
 777:   /**
 778:    * Returns the data of the response.
 779:    *
 780:    * @return The data as byte[].
 781:    */
 782:   public byte[] getData()
 783:   {
 784:     return data;
 785:   }
 786: 
 787: }