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 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();
 204:               tag = parseAttributes(tmp, stream);
 205:               operationAttributes.add(tmp);
 206:               break;
 207:             case IppDelimiterTag.JOB_ATTRIBUTES_TAG:
 208:               tmp = new HashMap();
 209:               tag = parseAttributes(tmp, stream);
 210:               jobAttributes.add(tmp);
 211:               break;
 212:             case IppDelimiterTag.PRINTER_ATTRIBUTES_TAG:
 213:               tmp = new HashMap();
 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();
 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 attributes, DataInputStream stream)
 251:         throws IppException, IOException
 252:     {
 253:       Attribute lastAttribute = null;
 254:       Attribute attribute = null;
 255: 
 256:       // declaration of variables
 257:       short nameLength;
 258:       String name;
 259:       short valueLength;
 260:       byte[] value;
 261: 
 262:       // tmp variables for parsing
 263:       // declared here so no name duplication occurs
 264:       URI uri;
 265:       String str;
 266: 
 267:       while (true)
 268:         {
 269:           byte tag = stream.readByte();
 270: 
 271:           if (IppDelimiterTag.isDelimiterTag(tag))
 272:             return tag;
 273: 
 274:           // it must be a value tag now
 275:           // so we have either a attribute-with-one-value
 276:           // or (if setOf is possible) an additional-value
 277: 
 278:           // (1) Length of the name
 279:           nameLength = stream.readShort();
 280: 
 281:           // (2) The name itself
 282:           // may be an additional-value
 283:           if (nameLength == 0x0000)
 284:             name = lastAttribute.getName();
 285:           else
 286:             {
 287:               byte[] nameBytes = new byte[nameLength];
 288:               stream.read(nameBytes);
 289:               name = new String(nameBytes);
 290:             }
 291: 
 292:           // (3) Length of the value
 293:           valueLength = stream.readShort();
 294: 
 295:           // (4) The value itself
 296:           value = new byte[valueLength];
 297:           stream.read(value);       
 298: 
 299:           // the value itself
 300:           switch (tag)
 301:             {
 302:             // out-of-band values
 303:             case IppValueTag.UNSUPPORTED:
 304:             case IppValueTag.UNKNOWN:
 305:               // TODO implement out-of-band handling
 306:               // We currently throw an exception to see when it occurs - not yet :-)
 307:           throw new IppException(
 308:                     "Unexpected name value for out-of-band value tag " + tag);
 309:             case IppValueTag.NO_VALUE:
 310:           attribute = null;
 311: 
 312:           break;
 313:             case IppValueTag.INTEGER:
 314:               int intValue = IppUtilities.convertToInt(value);
 315:               attribute = IppUtilities.getIntegerAttribute(name, intValue);
 316: 
 317:               break;
 318:             case IppValueTag.BOOLEAN:
 319:               // JPS API models boolean syntax type as enums
 320:               // 0x01 = true, 0x00 = false - all are enums
 321:               attribute = IppUtilities.getEnumAttribute(name, new Integer(value[0]));
 322:               
 323:               break;
 324:             case IppValueTag.ENUM:
 325:               int intVal = IppUtilities.convertToInt(value);            
 326:               attribute = IppUtilities.getEnumAttribute(name, new Integer(intVal));    
 327: 
 328:               break;
 329:             case IppValueTag.OCTECTSTRING_UNSPECIFIED:
 330:               // none exists according to spec
 331:               // so lets report as exception to see when it occurs
 332:               throw new IppException("Unspecified octet string occured.");
 333: 
 334:             case IppValueTag.DATETIME:
 335:               Date date = parseDate(value);
 336:               if (name.equals("printer-current-time"))
 337:                 attribute = new PrinterCurrentTime(date);
 338:               else if (name.equals("date-time-at-creation"))
 339:                 attribute = new DateTimeAtCreation(date);
 340:               else if (name.equals("date-time-at-processing"))
 341:                 attribute = new DateTimeAtProcessing(date);
 342:               else if (name.equals("date-time-at-completed"))
 343:                 attribute = new DateTimeAtCompleted(date);
 344: 
 345:               break;
 346:             case IppValueTag.RESOLUTION:
 347:               int crossFeed = IppUtilities.convertToInt(value[0], value[1], value[2], value[3]);
 348:               int feed = IppUtilities.convertToInt(value[4], value[5], value[6], value[7]);
 349:               int units = value[8];
 350:               
 351:               if (name.equals("printer-resolution-default"))
 352:                 attribute = new PrinterResolutionDefault(crossFeed, feed, units);
 353:               else if (name.equals("printer-resolution-supported")) // may be here also
 354:                 attribute = new PrinterResolutionSupported(crossFeed, feed, units);
 355: 
 356:               break;
 357:             case IppValueTag.RANGEOFINTEGER:
 358:               int lower = IppUtilities.convertToInt(value[0], value[1], value[2], value[3]);
 359:               int upper = IppUtilities.convertToInt(value[4], value[5], value[6], value[7]);
 360: 
 361:               if (name.equals("copies-supported"))
 362:                 attribute = new CopiesSupported(lower, upper);
 363:               else if (name.equals("number-up-supported"))
 364:                 attribute = new NumberUpSupported(lower, upper);
 365:               else if (name.equals("job-k-octets-supported")) 
 366:                 attribute = new JobKOctetsSupported(lower, upper);
 367:               else if (name.equals("job-impressions-supported"))
 368:                 attribute = new JobImpressionsSupported(lower, upper);
 369:               else if (name.equals("job-media-sheets-supported"))
 370:                 attribute = new JobMediaSheetsSupported(lower, upper);
 371: 
 372:               break;
 373:             case IppValueTag.TEXT_WITH_LANGUAGE:
 374:             case IppValueTag.TEXT_WITHOUT_LANGUAGE:
 375:             case IppValueTag.NAME_WITH_LANGUAGE:
 376:             case IppValueTag.NAME_WITHOUT_LANGUAGE:
 377:               attribute = IppUtilities.getTextAttribute(name, tag, value);
 378:              
 379:               break;
 380:             case IppValueTag.KEYWORD:
 381:               str = new String(value);
 382:               if (name.equals("job-hold-until-supported")) // may also be name type
 383:                 attribute = new JobHoldUntilSupported(str, null);
 384:               else if (name.equals("job-hold-until-default"))
 385:                 attribute = new JobHoldUntilDefault(str, null);             
 386:               else if (name.equals("media-supported"))
 387:                 attribute = new MediaSupported(str, null);
 388:               else if (name.equals("media-default"))
 389:                   attribute = new MediaDefault(str, null);
 390:               else if (name.equals("job-sheets-default")) 
 391:                 attribute = new JobSheetsDefault(str, null);
 392:               else if (name.equals("job-sheets-supported"))
 393:                 attribute = new JobSheetsSupported(str, null);
 394:               else if (name.equals("job-state-reasons")) // setOf
 395:                 attribute = parseJobStateReasons(value, lastAttribute);
 396:               else if (name.equals("printer-state-reasons")) // setOf
 397:                 attribute = parsePrinterStateReasons(value, lastAttribute);
 398:               else
 399:                 attribute = IppUtilities.getEnumAttribute(name, str);
 400:               
 401:               // all other stuff is either an enum or needs to be mapped to an 
 402:               // UnknownAttribute instance. Enums catched here are: 
 403:               // ipp-versions-supported, pdl-override-supported, compression-supported
 404:               // uri-authentication-supported, uri-security-supported, sides-supported
 405:               // sides-default, multiple-document-handling-supported, multiple-document-handling-default
 406:                             
 407:               break;
 408:             case IppValueTag.URI:
 409:               try
 410:                 {
 411:                   uri = new URI(new String(value));
 412:                 }
 413:               catch (URISyntaxException e)
 414:                 {
 415:                   throw new IppException("Wrong URI syntax encountered.", e);
 416:                 }
 417: 
 418:               if (name.equals("job-uri"))
 419:                 attribute = new JobUri(uri);
 420:               else if (name.equals("job-printer-uri"))
 421:                 attribute = new JobPrinterUri(uri);
 422:               else if (name.equals("job-more-info"))
 423:                 attribute = new JobMoreInfo(uri);
 424:               else if (name.equals("printer-uri-supported")) // setOf
 425:                 attribute = new PrinterUriSupported(uri);
 426:               else if (name.equals("printer-more-info"))
 427:                 attribute = new PrinterMoreInfo(uri);
 428:               else if (name.equals("printer-driver-installer"))
 429:                 attribute = new PrinterDriverInstaller(uri);
 430:               else if (name.equals("printer-more-info-manufacturer"))
 431:                 attribute = new PrinterMoreInfoManufacturer(uri);
 432:               
 433:               break;
 434:             case IppValueTag.URI_SCHEME:
 435:               // only one uri-scheme exists - and its an enum
 436:               if (name.equals("reference-uri-schemes-supported"))
 437:                 attribute = IppUtilities.getEnumAttribute(name, new String(value));
 438:               
 439:               break;
 440:             case IppValueTag.CHARSET:
 441:               str = new String(value);
 442:               if (name.equals("attributes-charset"))
 443:                 attribute = new AttributesCharset(str);
 444:               else if (name.equals("charset-configured"))
 445:                 attribute = new CharsetConfigured(str);
 446:               else if (name.equals("charset-supported")) // setOf
 447:                 attribute = new CharsetSupported(str);
 448:               
 449:               break;
 450:             case IppValueTag.NATURAL_LANGUAGE:
 451:               str = new String(value);
 452:               if (name.equals("attributes-natural-language"))
 453:                 attribute = new AttributesNaturalLanguage(str);
 454:               else if (name.equals("natural-language-configured"))
 455:                 attribute = new NaturalLanguageConfigured(str);
 456:               else if (name.equals("generated-natural-language-supported")) // setOf
 457:                 attribute = new GeneratedNaturalLanguageSupported(str);
 458:               
 459:               break;
 460:             case IppValueTag.MIME_MEDIA_TYPE:
 461:               str = new String(value);
 462:               if (name.equals("document-format-default"))
 463:                 attribute = new DocumentFormatDefault(str, null);
 464:               else if (name.equals("document-format-supported")) // setOf
 465:                 attribute = new DocumentFormatSupported(str, null);
 466:               else if (name.equals("document-format")) // setOf
 467:                 attribute = new DocumentFormat(str, null);
 468:               
 469:               break;
 470:             default:
 471:               throw new IppException("Unknown tag with value "
 472:                                      + Integer.toHexString(tag) + " found.");
 473:             }
 474: 
 475:           if (attribute == null)
 476:             attribute =  new UnknownAttribute(tag, name, value);
 477:           
 478:           addAttribute(attributes, attribute);
 479:           lastAttribute = attribute;
 480:           
 481:           logger.log(Component.IPP, "Attribute: " + name
 482:                      + " Value: " + attribute.toString());
 483:         }
 484:     }
 485: 
 486:     /**
 487:      * Adds a new attribute to the given attribute group. If this is the fist
 488:      * occurence of this attribute category a new set is created and associated
 489:      * with its category as key.
 490:      * @param attributeGroup
 491:      *          the attribute group
 492:      * @param attribute
 493:      *          the attribute to add
 494:      */
 495:     private void addAttribute(Map attributeGroup, Attribute attribute)
 496:     {
 497:       Class clazz = attribute.getCategory();
 498:       Set attributeValues = (Set) attributeGroup.get(clazz);
 499: 
 500:       if (attributeValues == null) // first attribute of this category
 501:         {
 502:           attributeValues = new HashSet();
 503:           attributeGroup.put(clazz, attributeValues);
 504:         }
 505: 
 506:       attributeValues.add(attribute);
 507:     }
 508:     
 509:     /**
 510:      * Parses a name with or without language attribute value from the byte[]
 511:      * and returns the result as an object[].
 512:      * @param value the byte[]
 513:      * @param lastAttr the last attribute
 514:      * @return The attribute.
 515:      */
 516:     private PrinterStateReasons parsePrinterStateReasons(byte[] value, Attribute lastAttr)
 517:     {
 518:       String str = new String(value);
 519:       PrinterStateReasons attribute; 
 520:       
 521:       if (lastAttr instanceof PrinterStateReasons)
 522:         attribute = (PrinterStateReasons) lastAttr;
 523:       else
 524:         attribute = new PrinterStateReasons();
 525:       
 526:       // special case indicating no reasons
 527:       if (str.equals("none")) 
 528:         return attribute;
 529:       
 530:       Severity severity = null;
 531:       PrinterStateReason reason = null;
 532:       
 533:       if (str.endsWith(Severity.WARNING.toString()))
 534:         severity = Severity.WARNING;
 535:       else if (str.endsWith(Severity.REPORT.toString()))
 536:         severity = Severity.REPORT;
 537:       else if (str.endsWith(Severity.ERROR.toString()))
 538:         severity = Severity.ERROR;
 539:       
 540:       if (severity != null)
 541:         str = str.substring(0, str.lastIndexOf('-'));    
 542:       else // we must associate a severity 
 543:         severity = Severity.REPORT;
 544:       
 545:       reason = (PrinterStateReason) 
 546:         IppUtilities.getEnumAttribute("printer-state-reason", str);
 547:       
 548:       attribute.put(reason , severity);
 549:       return attribute;
 550:     }
 551:     
 552:     /**
 553:      * Parses a name with or without language attribute value from the byte[]
 554:      * and returns the result as an object[].
 555:      * @param value the byte[]
 556:      * @param lastAttr the last attribute
 557:      * @return The attribute.
 558:      */
 559:     private JobStateReasons parseJobStateReasons(byte[] value, Attribute lastAttr)
 560:     {
 561:       String str = new String(value);
 562:       JobStateReasons attribute; 
 563:       
 564:       if (lastAttr instanceof JobStateReasons)
 565:         attribute = (JobStateReasons) lastAttr;
 566:       else
 567:         attribute = new JobStateReasons();
 568:       
 569:       // special case indicating no reasons
 570:       if (str.equals("none")) 
 571:         return attribute;
 572:       
 573:       JobStateReason reason = (JobStateReason) 
 574:         IppUtilities.getEnumAttribute("job-state-reason", str);
 575:       
 576:       attribute.add(reason);
 577:       return attribute;
 578:     }
 579:     
 580:     /**
 581:      * Parses a DateTime syntax attribute and returns the constructed Date
 582:      * object.
 583:      * <p>
 584:      * The syntax value is defined as 11 octets follwing the DateAndTime format
 585:      * of RFC 1903:
 586:      * <ul>
 587:      * <li>field | octets | contents | range</li>
 588:      * <li>1 | 1-2 | year | 0..65536</li>
 589:      * <li>2 | 3 | month | 1..12</li>
 590:      * <li>3 | 4 | day | 1..31</li>
 591:      * <li>4 | 5 | hour | 0..23</li>
 592:      * <li>5 | 6 | minutes | 0..59</li>
 593:      * <li>6 | 7 | seconds | 0..60 (use 60 for leap-second)</li>
 594:      * <li>7 | 8 | deci-seconds | 0..9</li>
 595:      * <li>8 | 9 | direction from UTC | '+' / '-'</li>
 596:      * <li>9 | 10 | hours from UTC | 0..11</li>
 597:      * <li>10 | 11 | minutes from UTC | 0..59</li>
 598:      * </ul>
 599:      * </p>
 600:      * 
 601:      * @param value the byte[]
 602:      * @return The date object.
 603:      */
 604:     private Date parseDate(byte[] value)
 605:     {
 606:       short year = IppUtilities.convertToShort(value[0], value[1]);
 607: 
 608:       Calendar cal = Calendar.getInstance();
 609:       cal.set(Calendar.YEAR, year);
 610:       cal.set(Calendar.MONTH, value[2]);
 611:       cal.set(Calendar.DAY_OF_MONTH, value[3]);
 612:       cal.set(Calendar.HOUR_OF_DAY, value[4]);
 613:       cal.set(Calendar.MINUTE, value[5]);
 614:       cal.set(Calendar.SECOND, value[6]);
 615:       cal.set(Calendar.MILLISECOND, value[7] * 100); // deci-seconds
 616: 
 617:       // offset from timezone
 618:       int offsetMilli = value[9] * 3600000; // hours to millis
 619:       offsetMilli = offsetMilli + value[10] * 60000; // minutes to millis
 620: 
 621:       if (((char) value[8]) == '-')
 622:         offsetMilli = offsetMilli * (-1);
 623: 
 624:       cal.set(Calendar.ZONE_OFFSET, offsetMilli);
 625:       return cal.getTime();
 626:     }
 627:   }
 628:   
 629:   /**
 630:    * Logger for tracing - enable by passing
 631:    * -Dgnu.classpath.debug.components=ipp to the vm.
 632:    */
 633:   static final Logger logger = SystemLogger.SYSTEM;
 634:   
 635:   URI uri;
 636:   short operation_id;
 637:   short status_code;
 638:   int request_id;
 639: 
 640:   List operationAttributes;
 641:   List printerAttributes;
 642:   List jobAttributes;
 643:   List unsupportedAttributes;
 644: 
 645:   byte[] data;
 646: 
 647:   /**
 648:    * Creates an <code>IppResponse</code> instance.
 649:    * 
 650:    * @param uri the uri the request was directy to.
 651:    * @param operation_id the operation id of the request.
 652:    */
 653:   public IppResponse(URI uri, short operation_id)
 654:   {
 655:     this.uri = uri;
 656:     this.operation_id = operation_id;
 657:     operationAttributes = new ArrayList();
 658:     jobAttributes = new ArrayList();
 659:     printerAttributes = new ArrayList();
 660:     unsupportedAttributes = new ArrayList();
 661:   }
 662: 
 663:   /**
 664:    * Sets the data received from the request sent.
 665:    * 
 666:    * @param input the input stream received.
 667:    * @throws IppException if parsing fails.
 668:    */
 669:   protected void setResponseData(InputStream input) throws IppException
 670:   {
 671:     ResponseReader reader = new ResponseReader();
 672: 
 673:     try
 674:       {
 675:         reader.parseResponse(input);
 676:       }
 677:     catch (IOException e)
 678:       {
 679:         throw new IppException(
 680:             "Exception during response parsing caused by IOException", e);
 681:       }
 682:   }
 683: 
 684:   /**
 685:    * Returns the uri of the original request.
 686:    * @return The URI of the request.
 687:    */
 688:   public URI getURI()
 689:   {
 690:     return uri;
 691:   }
 692: 
 693:   /**
 694:    * Returns the operation id of the original request.
 695:    * @return The operation id of the request.
 696:    */
 697:   public int getOperationID()
 698:   {
 699:     return operation_id;
 700:   }
 701: 
 702:   /**
 703:    * Returns the set of job attributes group maps.
 704:    * There may occur more than one group of type job attribute in a response
 705:    * because of e.g. multiple job or print service informations requested.
 706:    *  
 707:    * @return The list of job attribute grou maps.
 708:    */
 709:   public List getJobAttributes()
 710:   {
 711:     return jobAttributes;
 712:   }
 713: 
 714:   /**
 715:    * Returns the set of operation attributes group maps.
 716:    * There may occur more than one group of type job attribute in a response
 717:    * because of e.g. multiple job or print service informations requested.
 718:    *  
 719:    * @return The list of operation attribute grou maps.
 720:    */
 721:   public List getOperationAttributes()
 722:   {
 723:     return operationAttributes;
 724:   }
 725: 
 726:   /**
 727:    * Returns the set of printer attributes group maps.
 728:    * There may occur more than one group of type job attribute in a response
 729:    * because of e.g. multiple job or print service informations requested.
 730:    *  
 731:    * @return The list of printer attribute grou maps.
 732:    */
 733:   public List getPrinterAttributes()
 734:   {
 735:     return printerAttributes;
 736:   }
 737: 
 738:   /**
 739:    * Returns the ID of the initial request.
 740:    * 
 741:    * @return The request ID.
 742:    */
 743:   public int getRequestID()
 744:   {
 745:     return request_id;
 746:   }
 747: 
 748:   /**
 749:    * Returns the status code of the response.
 750:    * Defined in {@link IppStatusCode}.
 751:    * 
 752:    * @return The status code.
 753:    */
 754:   public short getStatusCode()
 755:   {
 756:     return status_code;
 757:   }
 758: 
 759:   /**
 760:    * Returns the set of unsupported attributes group maps.
 761:    * There may occur more than one group of type job attribute in a response
 762:    * because of e.g. multiple job or print service informations requested.
 763:    *  
 764:    * @return The list of unsupported attribute grou maps.
 765:    */
 766:   public List getUnsupportedAttributes()
 767:   {
 768:     return unsupportedAttributes;
 769:   }
 770: 
 771:   /**
 772:    * Returns the data of the response.
 773:    * 
 774:    * @return The data as byte[].
 775:    */
 776:   public byte[] getData()
 777:   {
 778:     return data;
 779:   }
 780: 
 781: }