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