1 /*
   2  * Copyright (c) 2003, 2007, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.print;
  27 
  28 import javax.print.attribute.*;
  29 import javax.print.attribute.standard.*;
  30 import javax.print.DocFlavor;
  31 import javax.print.DocPrintJob;
  32 import javax.print.PrintService;
  33 import javax.print.ServiceUIFactory;
  34 import java.util.ArrayList;
  35 import java.util.HashMap;
  36 import java.util.Locale;
  37 import java.util.Date;
  38 import java.util.Arrays;
  39 import java.security.AccessController;
  40 import java.security.PrivilegedActionException;
  41 import java.security.PrivilegedExceptionAction;
  42 import javax.print.event.PrintServiceAttributeListener;
  43 
  44 import java.net.URI;
  45 import java.net.URISyntaxException;
  46 import java.net.URL;
  47 import java.net.URLConnection;
  48 import java.net.HttpURLConnection;
  49 import java.io.File;
  50 import java.io.InputStream;
  51 import java.io.OutputStream;
  52 import java.io.OutputStreamWriter;
  53 import java.io.DataInputStream;
  54 import java.io.ByteArrayOutputStream;
  55 import java.io.ByteArrayInputStream;
  56 import java.io.BufferedReader;
  57 import java.io.InputStreamReader;
  58 import java.nio.charset.Charset;
  59 
  60 import java.util.Iterator;
  61 import java.util.HashSet;
  62 
  63 
  64 public class IPPPrintService implements PrintService, SunPrinterJobService {
  65 
  66     public static final boolean debugPrint;
  67     private static final String debugPrefix = "IPPPrintService>> ";
  68     protected static void debug_println(String str) {
  69         if (debugPrint) {
  70             System.out.println(str);
  71         }
  72     }
  73 
  74     private static final String FORCE_PIPE_PROP = "sun.print.ippdebug";
  75 
  76     static {
  77         String debugStr =
  78                 (String)java.security.AccessController.doPrivileged(
  79                   new sun.security.action.GetPropertyAction(FORCE_PIPE_PROP));
  80 
  81         debugPrint = "true".equalsIgnoreCase(debugStr);
  82     }
  83 
  84     private String printer;
  85     private URI    myURI;
  86     private URL    myURL;
  87     transient private ServiceNotifier notifier = null;
  88 
  89     private static int MAXCOPIES = 1000;
  90     private static short MAX_ATTRIBUTE_LENGTH = 255;
  91 
  92     private CUPSPrinter cps;
  93     private HttpURLConnection urlConnection = null;
  94     private DocFlavor[] supportedDocFlavors;
  95     private Class[] supportedCats;
  96     private MediaTray[] mediaTrays;
  97     private MediaSizeName[] mediaSizeNames;
  98     private CustomMediaSizeName[] customMediaSizeNames;
  99     private int defaultMediaIndex;
 100     private boolean isCupsPrinter;
 101     private boolean init;
 102     private Boolean isPS;
 103     private HashMap getAttMap;
 104     private boolean pngImagesAdded = false;
 105     private boolean gifImagesAdded = false;
 106     private boolean jpgImagesAdded = false;
 107 
 108 
 109     /**
 110      * IPP Status Codes
 111      */
 112     private static final byte STATUSCODE_SUCCESS = 0x00;
 113 
 114     /**
 115      * IPP Group Tags.  Each tag is used once before the first attribute
 116      * of that group.
 117      */
 118     // operation attributes group
 119     private static final byte GRPTAG_OP_ATTRIBUTES = 0x01;
 120     // job attributes group
 121     private static final byte GRPTAG_JOB_ATTRIBUTES = 0x02;
 122     // printer attributes group
 123     private static final byte GRPTAG_PRINTER_ATTRIBUTES = 0x04;
 124     // used as the last tag in an IPP message.
 125     private static final byte GRPTAG_END_ATTRIBUTES = 0x03;
 126 
 127     /**
 128      * IPP Operation codes
 129      */
 130     // gets the attributes for a printer
 131     public static final String OP_GET_ATTRIBUTES = "000B";
 132     // gets the default printer
 133     public static final String OP_CUPS_GET_DEFAULT = "4001";
 134     // gets the list of printers
 135     public static final String OP_CUPS_GET_PRINTERS = "4002";
 136 
 137 
 138     /**
 139      * List of all PrintRequestAttributes.  This is used
 140      * for looping through all the IPP attribute name.
 141      */
 142     private static Object[] printReqAttribDefault = {
 143         Chromaticity.COLOR,
 144         new Copies(1),
 145         Fidelity.FIDELITY_FALSE,
 146         Finishings.NONE,
 147         //new JobHoldUntil(new Date()),
 148         //new JobImpressions(0),
 149         //JobImpressions,
 150         //JobKOctets,
 151         //JobMediaSheets,
 152         new JobName("", Locale.getDefault()),
 153         //JobPriority,
 154         JobSheets.NONE,
 155         (Media)MediaSizeName.NA_LETTER,
 156         //MediaPrintableArea.class, // not an IPP attribute
 157         //MultipleDocumentHandling.SINGLE_DOCUMENT,
 158         new NumberUp(1),
 159         OrientationRequested.PORTRAIT,
 160         new PageRanges(1),
 161         //PresentationDirection,
 162                  // CUPS does not supply printer-resolution attribute
 163         //new PrinterResolution(300, 300, PrinterResolution.DPI),
 164         //PrintQuality.NORMAL,
 165         new RequestingUserName("", Locale.getDefault()),
 166         //SheetCollate.UNCOLLATED, //CUPS has no sheet collate?
 167         Sides.ONE_SIDED,
 168     };
 169 
 170 
 171     /**
 172      * List of all PrintServiceAttributes.  This is used
 173      * for looping through all the IPP attribute name.
 174      */
 175     private static Object[][] serviceAttributes = {
 176         {ColorSupported.class, "color-supported"},
 177         {PagesPerMinute.class,  "pages-per-minute"},
 178         {PagesPerMinuteColor.class, "pages-per-minute-color"},
 179         {PDLOverrideSupported.class, "pdl-override-supported"},
 180         {PrinterInfo.class, "printer-info"},
 181         {PrinterIsAcceptingJobs.class, "printer-is-accepting-jobs"},
 182         {PrinterLocation.class, "printer-location"},
 183         {PrinterMakeAndModel.class, "printer-make-and-model"},
 184         {PrinterMessageFromOperator.class, "printer-message-from-operator"},
 185         {PrinterMoreInfo.class, "printer-more-info"},
 186         {PrinterMoreInfoManufacturer.class, "printer-more-info-manufacturer"},
 187         {PrinterName.class, "printer-name"},
 188         {PrinterState.class, "printer-state"},
 189         {PrinterStateReasons.class, "printer-state-reasons"},
 190         {PrinterURI.class, "printer-uri"},
 191         {QueuedJobCount.class, "queued-job-count"}
 192     };
 193 
 194 
 195     /**
 196      * List of DocFlavors, grouped based on matching mime-type.
 197      * NOTE: For any change in the predefined DocFlavors, it must be reflected
 198      * here also.
 199      */
 200     // PDF DocFlavors
 201     private static DocFlavor[] appPDF = {
 202         DocFlavor.BYTE_ARRAY.PDF,
 203         DocFlavor.INPUT_STREAM.PDF,
 204         DocFlavor.URL.PDF
 205     };
 206 
 207     // Postscript DocFlavors
 208     private static DocFlavor[] appPostScript = {
 209         DocFlavor.BYTE_ARRAY.POSTSCRIPT,
 210         DocFlavor.INPUT_STREAM.POSTSCRIPT,
 211         DocFlavor.URL.POSTSCRIPT
 212     };
 213 
 214     // Autosense DocFlavors
 215     private static DocFlavor[] appOctetStream = {
 216         DocFlavor.BYTE_ARRAY.AUTOSENSE,
 217         DocFlavor.INPUT_STREAM.AUTOSENSE,
 218         DocFlavor.URL.AUTOSENSE
 219     };
 220 
 221     // Text DocFlavors
 222     private static DocFlavor[] textPlain = {
 223         DocFlavor.BYTE_ARRAY.TEXT_PLAIN_UTF_8,
 224         DocFlavor.BYTE_ARRAY.TEXT_PLAIN_UTF_16,
 225         DocFlavor.BYTE_ARRAY.TEXT_PLAIN_UTF_16BE,
 226         DocFlavor.BYTE_ARRAY.TEXT_PLAIN_UTF_16LE,
 227         DocFlavor.BYTE_ARRAY.TEXT_PLAIN_US_ASCII,
 228         DocFlavor.INPUT_STREAM.TEXT_PLAIN_UTF_8,
 229         DocFlavor.INPUT_STREAM.TEXT_PLAIN_UTF_16,
 230         DocFlavor.INPUT_STREAM.TEXT_PLAIN_UTF_16BE,
 231         DocFlavor.INPUT_STREAM.TEXT_PLAIN_UTF_16LE,
 232         DocFlavor.INPUT_STREAM.TEXT_PLAIN_US_ASCII,
 233         DocFlavor.URL.TEXT_PLAIN_UTF_8,
 234         DocFlavor.URL.TEXT_PLAIN_UTF_16,
 235         DocFlavor.URL.TEXT_PLAIN_UTF_16BE,
 236         DocFlavor.URL.TEXT_PLAIN_UTF_16LE,
 237         DocFlavor.URL.TEXT_PLAIN_US_ASCII,
 238         DocFlavor.CHAR_ARRAY.TEXT_PLAIN,
 239         DocFlavor.STRING.TEXT_PLAIN,
 240         DocFlavor.READER.TEXT_PLAIN
 241     };
 242 
 243     private static DocFlavor[] textPlainHost = {
 244         DocFlavor.BYTE_ARRAY.TEXT_PLAIN_HOST,
 245         DocFlavor.INPUT_STREAM.TEXT_PLAIN_HOST,
 246         DocFlavor.URL.TEXT_PLAIN_HOST
 247     };
 248 
 249     // JPG DocFlavors
 250     private static DocFlavor[] imageJPG = {
 251         DocFlavor.BYTE_ARRAY.JPEG,
 252         DocFlavor.INPUT_STREAM.JPEG,
 253         DocFlavor.URL.JPEG
 254     };
 255 
 256     // GIF DocFlavors
 257     private static DocFlavor[] imageGIF = {
 258         DocFlavor.BYTE_ARRAY.GIF,
 259         DocFlavor.INPUT_STREAM.GIF,
 260         DocFlavor.URL.GIF
 261     };
 262 
 263     // PNG DocFlavors
 264     private static DocFlavor[] imagePNG = {
 265         DocFlavor.BYTE_ARRAY.PNG,
 266         DocFlavor.INPUT_STREAM.PNG,
 267         DocFlavor.URL.PNG
 268     };
 269 
 270     // HTML DocFlavors
 271     private  static DocFlavor[] textHtml = {
 272         DocFlavor.BYTE_ARRAY.TEXT_HTML_UTF_8,
 273         DocFlavor.BYTE_ARRAY.TEXT_HTML_UTF_16,
 274         DocFlavor.BYTE_ARRAY.TEXT_HTML_UTF_16BE,
 275         DocFlavor.BYTE_ARRAY.TEXT_HTML_UTF_16LE,
 276         DocFlavor.BYTE_ARRAY.TEXT_HTML_US_ASCII,
 277         DocFlavor.INPUT_STREAM.TEXT_HTML_UTF_8,
 278         DocFlavor.INPUT_STREAM.TEXT_HTML_UTF_16,
 279         DocFlavor.INPUT_STREAM.TEXT_HTML_UTF_16BE,
 280         DocFlavor.INPUT_STREAM.TEXT_HTML_UTF_16LE,
 281         DocFlavor.INPUT_STREAM.TEXT_HTML_US_ASCII,
 282         DocFlavor.URL.TEXT_HTML_UTF_8,
 283         DocFlavor.URL.TEXT_HTML_UTF_16,
 284         DocFlavor.URL.TEXT_HTML_UTF_16BE,
 285         DocFlavor.URL.TEXT_HTML_UTF_16LE,
 286         DocFlavor.URL.TEXT_HTML_US_ASCII,
 287         // These are not handled in UnixPrintJob so commenting these
 288         // for now.
 289         /*
 290         DocFlavor.CHAR_ARRAY.TEXT_HTML,
 291         DocFlavor.STRING.TEXT_HTML,
 292         DocFlavor.READER.TEXT_HTML,
 293         */
 294     };
 295 
 296     private  static DocFlavor[] textHtmlHost = {
 297         DocFlavor.BYTE_ARRAY.TEXT_HTML_HOST,
 298         DocFlavor.INPUT_STREAM.TEXT_HTML_HOST,
 299         DocFlavor.URL.TEXT_HTML_HOST,
 300     };
 301 
 302 
 303     // PCL DocFlavors
 304     private static DocFlavor[] appPCL = {
 305         DocFlavor.BYTE_ARRAY.PCL,
 306         DocFlavor.INPUT_STREAM.PCL,
 307         DocFlavor.URL.PCL
 308     };
 309 
 310     // List of all DocFlavors, used in looping
 311     // through all supported mime-types
 312     private static Object[] allDocFlavors = {
 313         appPDF, appPostScript, appOctetStream,
 314         textPlain, imageJPG, imageGIF, imagePNG,
 315         textHtml, appPCL,
 316     };
 317 
 318 
 319     IPPPrintService(String name, URL url) {
 320         if ((name == null) || (url == null)){
 321             throw new IllegalArgumentException("null uri or printer name");
 322         }
 323         printer = name;
 324         supportedDocFlavors = null;
 325         supportedCats = null;
 326         mediaSizeNames = null;
 327         customMediaSizeNames = null;
 328         mediaTrays = null;
 329         myURL = url;
 330         cps = null;
 331         isCupsPrinter = false;
 332         init = false;
 333         defaultMediaIndex = -1;
 334 
 335         String host = myURL.getHost();
 336         if (host!=null && host.equals(CUPSPrinter.getServer())) {
 337             isCupsPrinter = true;
 338             try {
 339                 myURI =  new URI("ipp://"+host+
 340                                  "/printers/"+printer);
 341                 debug_println(debugPrefix+"IPPPrintService myURI : "+myURI);
 342             } catch (java.net.URISyntaxException e) {
 343                 throw new IllegalArgumentException("invalid url");
 344             }
 345         }
 346     }
 347 
 348 
 349     IPPPrintService(String name, String uriStr, boolean isCups) {
 350         if ((name == null) || (uriStr == null)){
 351             throw new IllegalArgumentException("null uri or printer name");
 352         }
 353         printer = name;
 354         supportedDocFlavors = null;
 355         supportedCats = null;
 356         mediaSizeNames = null;
 357         customMediaSizeNames = null;
 358         mediaTrays = null;
 359         cps = null;
 360         init = false;
 361         defaultMediaIndex = -1;
 362         try {
 363             myURL =
 364                 new URL(uriStr.replaceFirst("ipp", "http"));
 365         } catch (Exception e) {
 366             IPPPrintService.debug_println(debugPrefix+
 367                                           " IPPPrintService, myURL="+
 368                                           myURL+" Exception= "+
 369                                           e);
 370             throw new IllegalArgumentException("invalid url");
 371         }
 372 
 373         isCupsPrinter = isCups;
 374         try {
 375             myURI =  new URI(uriStr);
 376             debug_println(debugPrefix+"IPPPrintService myURI : "+myURI);
 377         } catch (java.net.URISyntaxException e) {
 378             throw new IllegalArgumentException("invalid uri");
 379         }
 380     }
 381 
 382 
 383     /*
 384      * Initialize mediaSizeNames, mediaTrays and other attributes.
 385      * Media size/trays are initialized to non-null values, may be 0-length
 386      * array.
 387      * NOTE: Must be called from a synchronized block only.
 388      */
 389     private void initAttributes() {
 390         if (!init) {
 391             // init customMediaSizeNames
 392             customMediaSizeNames = new CustomMediaSizeName[0];
 393 
 394             if ((urlConnection = getIPPConnection(myURL)) == null) {
 395                 mediaSizeNames = new MediaSizeName[0];
 396                 mediaTrays = new MediaTray[0];
 397                 debug_println(debugPrefix+"initAttributes, NULL urlConnection ");
 398                 init = true;
 399                 return;
 400             }
 401 
 402             // get all supported attributes through IPP
 403             opGetAttributes();
 404 
 405             if (isCupsPrinter) {
 406                 // note, it is possible to query media in CUPS using IPP
 407                 // right now we always get it from PPD.
 408                 // maybe use "&& (usePPD)" later?
 409                 // Another reason why we use PPD is because
 410                 // IPP currently does not support it but PPD does.
 411 
 412                 try {
 413                     cps = new CUPSPrinter(printer);
 414                     mediaSizeNames = cps.getMediaSizeNames();
 415                     mediaTrays = cps.getMediaTrays();
 416                     customMediaSizeNames = cps.getCustomMediaSizeNames();
 417                     urlConnection.disconnect();
 418                     init = true;
 419                     return;
 420                 } catch (Exception e) {
 421                     IPPPrintService.debug_println(debugPrefix+
 422                                        "initAttributes, error creating CUPSPrinter e="+e);
 423                 }
 424             }
 425 
 426             // use IPP to get all media,
 427             Media[] allMedia = (Media[])getSupportedMedia();
 428             ArrayList sizeList = new ArrayList();
 429             ArrayList trayList = new ArrayList();
 430             for (int i=0; i<allMedia.length; i++) {
 431                 if (allMedia[i] instanceof MediaSizeName) {
 432                     sizeList.add(allMedia[i]);
 433                 } else if (allMedia[i] instanceof MediaTray) {
 434                     trayList.add(allMedia[i]);
 435                 }
 436             }
 437 
 438             if (sizeList != null) {
 439                 mediaSizeNames = new MediaSizeName[sizeList.size()];
 440                 mediaSizeNames = (MediaSizeName[])sizeList.toArray(
 441                                                        mediaSizeNames);
 442             }
 443             if (trayList != null) {
 444                 mediaTrays = new MediaTray[trayList.size()];
 445                 mediaTrays = (MediaTray[])trayList.toArray(
 446                                                            mediaTrays);
 447             }
 448             urlConnection.disconnect();
 449 
 450             init = true;
 451         }
 452     }
 453 
 454 
 455     public DocPrintJob createPrintJob() {
 456         SecurityManager security = System.getSecurityManager();
 457         if (security != null) {
 458             security.checkPrintJobAccess();
 459         }
 460         // REMIND: create IPPPrintJob
 461         return new UnixPrintJob(this);
 462     }
 463 
 464 
 465     public synchronized Object
 466         getSupportedAttributeValues(Class<? extends Attribute> category,
 467                                     DocFlavor flavor,
 468                                     AttributeSet attributes)
 469     {
 470         if (category == null) {
 471             throw new NullPointerException("null category");
 472         }
 473         if (!Attribute.class.isAssignableFrom(category)) {
 474             throw new IllegalArgumentException(category +
 475                                  " does not implement Attribute");
 476         }
 477         if (flavor != null) {
 478             if (!isDocFlavorSupported(flavor)) {
 479                 throw new IllegalArgumentException(flavor +
 480                                                " is an unsupported flavor");
 481             } else if (isAutoSense(flavor)) {
 482                 return null;
 483             }
 484 
 485         }
 486 
 487         if (!isAttributeCategorySupported(category)) {
 488             return null;
 489         }
 490 
 491         /* Test if the flavor is compatible with the attributes */
 492         if (!isDestinationSupported(flavor, attributes)) {
 493             return null;
 494         }
 495 
 496         initAttributes();
 497 
 498         /* Test if the flavor is compatible with the category */
 499         if ((category == Copies.class) ||
 500             (category == CopiesSupported.class)) {
 501             if (flavor == null ||
 502                 !(flavor.equals(DocFlavor.INPUT_STREAM.POSTSCRIPT) ||
 503                   flavor.equals(DocFlavor.URL.POSTSCRIPT) ||
 504                   flavor.equals(DocFlavor.BYTE_ARRAY.POSTSCRIPT))) {
 505                 CopiesSupported cs = new CopiesSupported(1, MAXCOPIES);
 506                 AttributeClass attribClass = (getAttMap != null) ?
 507                     (AttributeClass)getAttMap.get(cs.getName()) : null;
 508                 if (attribClass != null) {
 509                     int[] range = attribClass.getIntRangeValue();
 510                     cs = new CopiesSupported(range[0], range[1]);
 511                 }
 512                 return cs;
 513             } else {
 514                 return null;
 515             }
 516         } else  if (category == Chromaticity.class) {
 517             if (flavor == null ||
 518                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
 519                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE) ||
 520                 !isIPPSupportedImages(flavor.getMimeType())) {
 521                 Chromaticity[]arr = new Chromaticity[1];
 522                 arr[0] = Chromaticity.COLOR;
 523                 return (arr);
 524             } else {
 525                 return null;
 526             }
 527         } else if (category == Destination.class) {
 528             if (flavor == null ||
 529                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
 530                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE)) {
 531                 try {
 532                     return new Destination((new File("out.ps")).toURI());
 533                 } catch (SecurityException se) {
 534                     try {
 535                         return new Destination(new URI("file:out.ps"));
 536                     } catch (URISyntaxException e) {
 537                         return null;
 538                     }
 539                 }
 540             }
 541             return null;
 542         } else if (category == Fidelity.class) {
 543             Fidelity []arr = new Fidelity[2];
 544             arr[0] = Fidelity.FIDELITY_FALSE;
 545             arr[1] = Fidelity.FIDELITY_TRUE;
 546             return arr;
 547         } else if (category == Finishings.class) {
 548             AttributeClass attribClass = (getAttMap != null) ?
 549                 (AttributeClass)getAttMap.get("finishings-supported")
 550                 : null;
 551             if (attribClass != null) {
 552                 int[] finArray = attribClass.getArrayOfIntValues();
 553                 if ((finArray != null) && (finArray.length > 0)) {
 554                     Finishings[] finSup = new Finishings[finArray.length];
 555                     for (int i=0; i<finArray.length; i++) {
 556                         finSup[i] = Finishings.NONE;
 557                         Finishings[] fAll = (Finishings[])
 558                             (new ExtFinishing(100)).getAll();
 559                         for (int j=0; j<fAll.length; j++) {
 560                             if (finArray[i] == fAll[j].getValue()) {
 561                                 finSup[i] = fAll[j];
 562                                 break;
 563                             }
 564                         }
 565                     }
 566                     return finSup;
 567                 }
 568             }
 569         } else if (category == JobName.class) {
 570             return new JobName("Java Printing", null);
 571         } else if (category == JobSheets.class) {
 572             JobSheets arr[] = new JobSheets[2];
 573             arr[0] = JobSheets.NONE;
 574             arr[1] = JobSheets.STANDARD;
 575             return arr;
 576 
 577         } else if (category == Media.class) {
 578             Media[] allMedia = new Media[mediaSizeNames.length+
 579                                         mediaTrays.length];
 580 
 581             for (int i=0; i<mediaSizeNames.length; i++) {
 582                 allMedia[i] = mediaSizeNames[i];
 583             }
 584 
 585             for (int i=0; i<mediaTrays.length; i++) {
 586                 allMedia[i+mediaSizeNames.length] = mediaTrays[i];
 587             }
 588 
 589             if (allMedia.length == 0) {
 590                 allMedia = new Media[1];
 591                 allMedia[0] = (Media)getDefaultAttributeValue(Media.class);
 592             }
 593 
 594             return allMedia;
 595         } else if (category == MediaPrintableArea.class) {
 596             MediaPrintableArea[] mpas = null;
 597             if (cps != null) {
 598                 mpas = cps.getMediaPrintableArea();
 599             }
 600 
 601             if (mpas == null) {
 602                 mpas = new MediaPrintableArea[1];
 603                 mpas[0] = (MediaPrintableArea)
 604                     getDefaultAttributeValue(MediaPrintableArea.class);
 605             }
 606 
 607             if ((attributes == null) || (attributes.size() == 0)) {
 608                 ArrayList<MediaPrintableArea> printableList =
 609                                        new ArrayList<MediaPrintableArea>();
 610 
 611                 for (int i=0; i<mpas.length; i++) {
 612                     if (mpas[i] != null) {
 613                         printableList.add(mpas[i]);
 614                     }
 615                 }
 616                 if (printableList.size() > 0) {
 617                     mpas  = new MediaPrintableArea[printableList.size()];
 618                     printableList.toArray(mpas);
 619                 }
 620                 return mpas;
 621             }
 622 
 623             int match = -1;
 624             Media media = (Media)attributes.get(Media.class);
 625             if (media != null && media instanceof MediaSizeName) {
 626                 MediaSizeName msn = (MediaSizeName)media;
 627 
 628                 // case when no supported mediasizenames are reported
 629                 // check given media against the default
 630                 if (mediaSizeNames.length == 0 &&
 631                     msn.equals(getDefaultAttributeValue(Media.class))) {
 632                     //default printable area is that of default mediasize
 633                     return mpas;
 634                 }
 635 
 636                 for (int i=0; i<mediaSizeNames.length; i++) {
 637                     if (msn.equals(mediaSizeNames[i])) {
 638                         match = i;
 639                     }
 640                 }
 641             }
 642 
 643             if (match == -1) {
 644                 return null;
 645             } else {
 646                 MediaPrintableArea []arr = new MediaPrintableArea[1];
 647                 arr[0] = mpas[match];
 648                 return arr;
 649             }
 650         } else if (category == NumberUp.class) {
 651             AttributeClass attribClass = (getAttMap != null) ?
 652                 (AttributeClass)getAttMap.get("number-up-supported") : null;
 653             if (attribClass != null) {
 654                 int[] values = attribClass.getArrayOfIntValues();
 655                 if (values != null) {
 656                     NumberUp[] nUp = new NumberUp[values.length];
 657                     for (int i=0; i<values.length; i++) {
 658                         nUp[i] = new NumberUp(values[i]);
 659                     }
 660                     return nUp;
 661                 } else {
 662                     return null;
 663                 }
 664             }
 665         } else if (category == OrientationRequested.class) {
 666             if ((flavor != null) &&
 667                 (flavor.equals(DocFlavor.INPUT_STREAM.POSTSCRIPT) ||
 668                  flavor.equals(DocFlavor.URL.POSTSCRIPT) ||
 669                  flavor.equals(DocFlavor.BYTE_ARRAY.POSTSCRIPT))) {
 670                 return null;
 671             }
 672 
 673             boolean revPort = false;
 674             OrientationRequested[] orientSup = null;
 675 
 676             AttributeClass attribClass = (getAttMap != null) ?
 677               (AttributeClass)getAttMap.get("orientation-requested-supported")
 678                 : null;
 679             if (attribClass != null) {
 680                 int[] orientArray = attribClass.getArrayOfIntValues();
 681                 if ((orientArray != null) && (orientArray.length > 0)) {
 682                     orientSup =
 683                         new OrientationRequested[orientArray.length];
 684                     for (int i=0; i<orientArray.length; i++) {
 685                         switch (orientArray[i]) {
 686                         default:
 687                         case 3 :
 688                             orientSup[i] = OrientationRequested.PORTRAIT;
 689                             break;
 690                         case 4:
 691                             orientSup[i] = OrientationRequested.LANDSCAPE;
 692                             break;
 693                         case 5:
 694                             orientSup[i] =
 695                                 OrientationRequested.REVERSE_LANDSCAPE;
 696                             break;
 697                         case 6:
 698                             orientSup[i] =
 699                                 OrientationRequested.REVERSE_PORTRAIT;
 700                             revPort = true;
 701                             break;
 702                         }
 703                     }
 704                 }
 705             }
 706             if (flavor == null ||
 707                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
 708                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE)) {
 709 
 710                 if (revPort && flavor == null) {
 711                     OrientationRequested []orSup = new OrientationRequested[4];
 712                     orSup[0] = OrientationRequested.PORTRAIT;
 713                     orSup[1] = OrientationRequested.LANDSCAPE;
 714                     orSup[2] = OrientationRequested.REVERSE_LANDSCAPE;
 715                     orSup[3] = OrientationRequested.REVERSE_PORTRAIT;
 716                     return orSup;
 717                 } else {
 718                     OrientationRequested []orSup = new OrientationRequested[3];
 719                     orSup[0] = OrientationRequested.PORTRAIT;
 720                     orSup[1] = OrientationRequested.LANDSCAPE;
 721                     orSup[2] = OrientationRequested.REVERSE_LANDSCAPE;
 722                     return orSup;
 723                 }
 724             } else {
 725                 return orientSup;
 726             }
 727         } else if (category == PageRanges.class) {
 728            if (flavor == null ||
 729                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
 730                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE)) {
 731                 PageRanges []arr = new PageRanges[1];
 732                 arr[0] = new PageRanges(1, Integer.MAX_VALUE);
 733                 return arr;
 734             } else {
 735                 // Returning null as this is not yet supported in UnixPrintJob.
 736                 return null;
 737             }
 738         } else if (category == RequestingUserName.class) {
 739             String userName = "";
 740             try {
 741               userName = System.getProperty("user.name", "");
 742             } catch (SecurityException se) {
 743             }
 744             return new RequestingUserName(userName, null);
 745         } else if (category == Sides.class) {
 746             // The printer takes care of Sides so if short-edge
 747             // is chosen in a job, the rotation is done by the printer.
 748             // Orientation is rotated by emulation if pageable
 749             // or printable so if the document is in Landscape, this may
 750             // result in double rotation.
 751             AttributeClass attribClass = (getAttMap != null) ?
 752                 (AttributeClass)getAttMap.get("sides-supported")
 753                 : null;
 754             if (attribClass != null) {
 755                 String[] sidesArray = attribClass.getArrayOfStringValues();
 756                 if ((sidesArray != null) && (sidesArray.length > 0)) {
 757                     Sides[] sidesSup = new Sides[sidesArray.length];
 758                     for (int i=0; i<sidesArray.length; i++) {
 759                         if (sidesArray[i].endsWith("long-edge")) {
 760                             sidesSup[i] = Sides.TWO_SIDED_LONG_EDGE;
 761                         } else if (sidesArray[i].endsWith("short-edge")) {
 762                             sidesSup[i] = Sides.TWO_SIDED_SHORT_EDGE;
 763                         } else {
 764                             sidesSup[i] = Sides.ONE_SIDED;
 765                         }
 766                     }
 767                     return sidesSup;
 768                 }
 769             }
 770         }
 771 
 772         return null;
 773     }
 774 
 775     //This class is for getting all pre-defined Finishings
 776     private class ExtFinishing extends Finishings {
 777         ExtFinishing(int value) {
 778             super(100); // 100 to avoid any conflicts with predefined values.
 779         }
 780 
 781         EnumSyntax[] getAll() {
 782             EnumSyntax[] es = super.getEnumValueTable();
 783             return es;
 784         }
 785     }
 786 
 787 
 788     public AttributeSet getUnsupportedAttributes(DocFlavor flavor,
 789                                                  AttributeSet attributes) {
 790         if (flavor != null && !isDocFlavorSupported(flavor)) {
 791             throw new IllegalArgumentException("flavor " + flavor +
 792                                                "is not supported");
 793         }
 794 
 795         if (attributes == null) {
 796             return null;
 797         }
 798 
 799         Attribute attr;
 800         AttributeSet unsupp = new HashAttributeSet();
 801         Attribute []attrs = attributes.toArray();
 802         for (int i=0; i<attrs.length; i++) {
 803             try {
 804                 attr = attrs[i];
 805                 if (!isAttributeCategorySupported(attr.getCategory())) {
 806                     unsupp.add(attr);
 807                 } else if (!isAttributeValueSupported(attr, flavor,
 808                                                       attributes)) {
 809                     unsupp.add(attr);
 810                 }
 811             } catch (ClassCastException e) {
 812             }
 813         }
 814         if (unsupp.isEmpty()) {
 815             return null;
 816         } else {
 817             return unsupp;
 818         }
 819     }
 820 
 821 
 822     public synchronized DocFlavor[] getSupportedDocFlavors() {
 823 
 824         if (supportedDocFlavors != null) {
 825             int len = supportedDocFlavors.length;
 826                 DocFlavor[] copyflavors = new DocFlavor[len];
 827                 System.arraycopy(supportedDocFlavors, 0, copyflavors, 0, len);
 828                 return copyflavors;
 829         }
 830         initAttributes();
 831 
 832         if ((getAttMap != null) &&
 833             getAttMap.containsKey("document-format-supported")) {
 834 
 835             AttributeClass attribClass =
 836                 (AttributeClass)getAttMap.get("document-format-supported");
 837             if (attribClass != null) {
 838                 String mimeType;
 839                 boolean psSupported = false;
 840                 String[] docFlavors = attribClass.getArrayOfStringValues();
 841                 DocFlavor[] flavors;
 842                 HashSet docList = new HashSet();
 843                 int j;
 844                 String hostEnc = DocFlavor.hostEncoding.
 845                     toLowerCase(Locale.ENGLISH);
 846                 boolean addHostEncoding = !hostEnc.equals("utf-8") &&
 847                     !hostEnc.equals("utf-16") && !hostEnc.equals("utf-16be") &&
 848                     !hostEnc.equals("utf-16le") && !hostEnc.equals("us-ascii");
 849 
 850                 for (int i = 0; i < docFlavors.length; i++) {
 851                     for (j=0; j<allDocFlavors.length; j++) {
 852                         flavors = (DocFlavor[])allDocFlavors[j];
 853 
 854                         mimeType = flavors[0].getMimeType();
 855                         if (mimeType.startsWith(docFlavors[i])) {
 856 
 857                             docList.addAll(Arrays.asList(flavors));
 858 
 859                             if (mimeType.equals("text/plain") &&
 860                                 addHostEncoding) {
 861                                 docList.add(Arrays.asList(textPlainHost));
 862                             } else if (mimeType.equals("text/html") &&
 863                                        addHostEncoding) {
 864                                 docList.add(Arrays.asList(textHtmlHost));
 865                             } else if (mimeType.equals("image/png")) {
 866                                 pngImagesAdded = true;
 867                             } else if (mimeType.equals("image/gif")) {
 868                                 gifImagesAdded = true;
 869                             } else if (mimeType.equals("image/jpeg")) {
 870                                 jpgImagesAdded = true;
 871                             } else if (mimeType.indexOf("postscript") != -1) {
 872                                 psSupported = true;
 873                             }
 874                             break;
 875                         }
 876                     }
 877 
 878                     // Not added? Create new DocFlavors
 879                     if (j == allDocFlavors.length) {
 880                         //  make new DocFlavors
 881                         docList.add(new DocFlavor.BYTE_ARRAY(docFlavors[i]));
 882                         docList.add(new DocFlavor.INPUT_STREAM(docFlavors[i]));
 883                         docList.add(new DocFlavor.URL(docFlavors[i]));
 884                     }
 885                 }
 886 
 887                 // check if we need to add image DocFlavors
 888                 // and Pageable/Printable flavors
 889                 if (psSupported || isCupsPrinter) {
 890                     /*
 891                      Always add Pageable and Printable for CUPS
 892                      since it uses Filters to convert from Postscript
 893                      to device printer language.
 894                     */
 895                     docList.add(DocFlavor.SERVICE_FORMATTED.PAGEABLE);
 896                     docList.add(DocFlavor.SERVICE_FORMATTED.PRINTABLE);
 897 
 898                     docList.addAll(Arrays.asList(imageJPG));
 899                     docList.addAll(Arrays.asList(imagePNG));
 900                     docList.addAll(Arrays.asList(imageGIF));
 901                 }
 902                 supportedDocFlavors = new DocFlavor[docList.size()];
 903                 docList.toArray(supportedDocFlavors);
 904                 int len = supportedDocFlavors.length;
 905                 DocFlavor[] copyflavors = new DocFlavor[len];
 906                 System.arraycopy(supportedDocFlavors, 0, copyflavors, 0, len);
 907                 return copyflavors;
 908             }
 909         }
 910         return null;
 911     }
 912 
 913 
 914     public boolean isDocFlavorSupported(DocFlavor flavor) {
 915         if (supportedDocFlavors == null) {
 916             getSupportedDocFlavors();
 917         }
 918         if (supportedDocFlavors != null) {
 919             for (int f=0; f<supportedDocFlavors.length; f++) {
 920                 if (flavor.equals(supportedDocFlavors[f])) {
 921                     return true;
 922                 }
 923             }
 924         }
 925         return false;
 926     }
 927 
 928 
 929     /**
 930      * Finds matching CustomMediaSizeName of given media.
 931      */
 932     public CustomMediaSizeName findCustomMedia(MediaSizeName media) {
 933         if (customMediaSizeNames == null) {
 934             return null;
 935         }
 936         for (int i=0; i< customMediaSizeNames.length; i++) {
 937             CustomMediaSizeName custom =
 938                 (CustomMediaSizeName)customMediaSizeNames[i];
 939             MediaSizeName msn = custom.getStandardMedia();
 940             if (media.equals(msn)) {
 941                 return customMediaSizeNames[i];
 942             }
 943         }
 944         return null;
 945     }
 946 
 947 
 948     /**
 949      * Returns the matching standard Media using string comparison of names.
 950      */
 951     private Media getIPPMedia(String mediaName) {
 952         CustomMediaSizeName sampleSize = new CustomMediaSizeName("sample", "",
 953                                                                  0, 0);
 954         Media[] sizes = sampleSize.getSuperEnumTable();
 955         for (int i=0; i<sizes.length; i++) {
 956             if (mediaName.equals(""+sizes[i])) {
 957                 return sizes[i];
 958             }
 959         }
 960         CustomMediaTray sampleTray = new CustomMediaTray("sample", "");
 961         Media[] trays = sampleTray.getSuperEnumTable();
 962         for (int i=0; i<trays.length; i++) {
 963             if (mediaName.equals(""+trays[i])) {
 964                 return trays[i];
 965             }
 966         }
 967         return null;
 968     }
 969 
 970     private Media[] getSupportedMedia() {
 971         if ((getAttMap != null) &&
 972             getAttMap.containsKey("media-supported")) {
 973 
 974             AttributeClass attribClass =
 975                 (AttributeClass)getAttMap.get("media-supported");
 976 
 977             if (attribClass != null) {
 978                 String[] mediaVals = attribClass.getArrayOfStringValues();
 979                 Media msn;
 980                 Media[] mediaNames =
 981                     new Media[mediaVals.length];
 982                 for (int i=0; i<mediaVals.length; i++) {
 983                     msn = getIPPMedia(mediaVals[i]);
 984                     //REMIND: if null, create custom?
 985                     mediaNames[i] = msn;
 986                 }
 987                 return mediaNames;
 988             }
 989         }
 990         return new Media[0];
 991     }
 992 
 993 
 994     public synchronized Class[] getSupportedAttributeCategories() {
 995         if (supportedCats != null) {
 996             return supportedCats;
 997         }
 998 
 999         initAttributes();
1000 
1001         ArrayList catList = new ArrayList();
1002         Class cl;
1003 
1004         for (int i=0; i < printReqAttribDefault.length; i++) {
1005             PrintRequestAttribute pra =
1006                 (PrintRequestAttribute)printReqAttribDefault[i];
1007             if (getAttMap != null &&
1008                 getAttMap.containsKey(pra.getName()+"-supported")) {
1009                 cl = pra.getCategory();
1010                 catList.add(cl);
1011             }
1012         }
1013 
1014         // Some IPP printers like lexc710 do not have list of supported media
1015         // but CUPS can get the media from PPD, so we still report as
1016         // supported category.
1017         if (isCupsPrinter) {
1018             if (!catList.contains(Media.class)) {
1019                 catList.add(Media.class);
1020             }
1021 
1022             // Always add MediaPrintable for cups,
1023             // because we can get it from PPD.
1024             catList.add(MediaPrintableArea.class);
1025 
1026             // this is already supported in UnixPrintJob
1027             catList.add(Destination.class);
1028 
1029             // It is unfortunate that CUPS doesn't provide a way to query
1030             // if printer supports collation but since most printers
1031             // now supports collation and that most OS has a way
1032             // of setting it, it is a safe assumption to just always
1033             // include SheetCollate as supported attribute.
1034 
1035             /*
1036                In Linux, we use Postscript for rendering but Linux still
1037                has issues in propagating Postscript-embedded setpagedevice
1038                setting like collation.  Therefore, we temporarily exclude
1039                Linux.
1040             */
1041             if (!UnixPrintServiceLookup.isLinux()) {
1042                 catList.add(SheetCollate.class);
1043             }
1044         }
1045 
1046         // With the assumption that  Chromaticity is equivalent to
1047         // ColorSupported.
1048         if (getAttMap != null && getAttMap.containsKey("color-supported")) {
1049             catList.add(Chromaticity.class);
1050         }
1051         supportedCats = new Class[catList.size()];
1052         catList.toArray(supportedCats);
1053         return supportedCats;
1054     }
1055 
1056 
1057     public boolean
1058         isAttributeCategorySupported(Class<? extends Attribute> category)
1059     {
1060         if (category == null) {
1061             throw new NullPointerException("null category");
1062         }
1063         if (!(Attribute.class.isAssignableFrom(category))) {
1064             throw new IllegalArgumentException(category +
1065                                              " is not an Attribute");
1066         }
1067 
1068         if (supportedCats == null) {
1069             getSupportedAttributeCategories();
1070         }
1071 
1072         // It is safe to assume that Orientation is always supported
1073         // and even if CUPS or an IPP device reports it as not,
1074         // our renderer can do portrait, landscape and
1075         // reverse landscape.
1076         if (category == OrientationRequested.class) {
1077             return true;
1078         }
1079 
1080         for (int i=0;i<supportedCats.length;i++) {
1081             if (category == supportedCats[i]) {
1082                 return true;
1083             }
1084         }
1085 
1086         return false;
1087     }
1088 
1089 
1090     public synchronized <T extends PrintServiceAttribute>
1091         T getAttribute(Class<T> category)
1092     {
1093         if (category == null) {
1094             throw new NullPointerException("category");
1095         }
1096         if (!(PrintServiceAttribute.class.isAssignableFrom(category))) {
1097             throw new IllegalArgumentException("Not a PrintServiceAttribute");
1098         }
1099 
1100         initAttributes();
1101 
1102         if (category == PrinterName.class) {
1103             return (T)(new PrinterName(printer, null));
1104         } else if (category == PrinterInfo.class) {
1105             PrinterInfo pInfo = new PrinterInfo(printer, null);
1106             AttributeClass ac = (getAttMap != null) ?
1107                 (AttributeClass)getAttMap.get(pInfo.getName())
1108                 : null;
1109             if (ac != null) {
1110                 return (T)(new PrinterInfo(ac.getStringValue(), null));
1111             }
1112             return (T)pInfo;
1113         } else if (category == QueuedJobCount.class) {
1114             QueuedJobCount qjc = new QueuedJobCount(0);
1115             AttributeClass ac = (getAttMap != null) ?
1116                 (AttributeClass)getAttMap.get(qjc.getName())
1117                 : null;
1118             if (ac != null) {
1119                 qjc = new QueuedJobCount(ac.getIntValue());
1120             }
1121             return (T)qjc;
1122         } else if (category == PrinterIsAcceptingJobs.class) {
1123             PrinterIsAcceptingJobs accJob =
1124                 PrinterIsAcceptingJobs.ACCEPTING_JOBS;
1125             AttributeClass ac = (getAttMap != null) ?
1126                 (AttributeClass)getAttMap.get(accJob.getName())
1127                 : null;
1128             if ((ac != null) && (ac.getByteValue() == 0)) {
1129                 accJob = PrinterIsAcceptingJobs.NOT_ACCEPTING_JOBS;
1130             }
1131             return (T)accJob;
1132         } else if (category == ColorSupported.class) {
1133             ColorSupported cs = ColorSupported.SUPPORTED;
1134             AttributeClass ac = (getAttMap != null) ?
1135                 (AttributeClass)getAttMap.get(cs.getName())
1136                 : null;
1137             if ((ac != null) && (ac.getByteValue() == 0)) {
1138                 cs = ColorSupported.NOT_SUPPORTED;
1139             }
1140             return (T)cs;
1141         } else if (category == PDLOverrideSupported.class) {
1142 
1143             if (isCupsPrinter) {
1144                 // Documented: For CUPS this will always be false
1145                 return (T)PDLOverrideSupported.NOT_ATTEMPTED;
1146             } else {
1147                 // REMIND: check attribute values
1148                 return (T)PDLOverrideSupported.NOT_ATTEMPTED;
1149             }
1150         } else if (category == PrinterURI.class) {
1151             return (T)(new PrinterURI(myURI));
1152         } else {
1153             return null;
1154         }
1155     }
1156 
1157 
1158     public synchronized PrintServiceAttributeSet getAttributes() {
1159         // update getAttMap by sending again get-attributes IPP request
1160         init = false;
1161         initAttributes();
1162 
1163         HashPrintServiceAttributeSet attrs =
1164             new HashPrintServiceAttributeSet();
1165 
1166         for (int i=0; i < serviceAttributes.length; i++) {
1167             String name = (String)serviceAttributes[i][1];
1168             if (getAttMap != null && getAttMap.containsKey(name)) {
1169                 Class c = (Class)serviceAttributes[i][0];
1170                 PrintServiceAttribute psa = getAttribute(c);
1171                 if (psa != null) {
1172                     attrs.add(psa);
1173                 }
1174             }
1175         }
1176         return AttributeSetUtilities.unmodifiableView(attrs);
1177     }
1178 
1179     public boolean isIPPSupportedImages(String mimeType) {
1180         if (supportedDocFlavors == null) {
1181             getSupportedDocFlavors();
1182         }
1183 
1184         if (mimeType.equals("image/png") && pngImagesAdded) {
1185             return true;
1186         } else if (mimeType.equals("image/gif") && gifImagesAdded) {
1187             return true;
1188         } else if (mimeType.equals("image/jpeg") && jpgImagesAdded) {
1189             return true;
1190         }
1191 
1192         return false;
1193     }
1194 
1195 
1196     private boolean isSupportedCopies(Copies copies) {
1197         CopiesSupported cs = (CopiesSupported)
1198             getSupportedAttributeValues(Copies.class, null, null);
1199         int[][] members = cs.getMembers();
1200         int min, max;
1201         if ((members.length > 0) && (members[0].length > 0)) {
1202             min = members[0][0];
1203             max = members[0][1];
1204         } else {
1205             min = 1;
1206             max = MAXCOPIES;
1207         }
1208 
1209         int value = copies.getValue();
1210         return (value >= min && value <= max);
1211     }
1212 
1213     private boolean isAutoSense(DocFlavor flavor) {
1214         if (flavor.equals(DocFlavor.BYTE_ARRAY.AUTOSENSE) ||
1215             flavor.equals(DocFlavor.INPUT_STREAM.AUTOSENSE) ||
1216             flavor.equals(DocFlavor.URL.AUTOSENSE)) {
1217             return true;
1218         }
1219         else {
1220             return false;
1221         }
1222     }
1223 
1224     private synchronized boolean isSupportedMediaTray(MediaTray msn) {
1225         initAttributes();
1226 
1227         if (mediaTrays != null) {
1228             for (int i=0; i<mediaTrays.length; i++) {
1229                if (msn.equals(mediaTrays[i])) {
1230                     return true;
1231                 }
1232             }
1233         }
1234         return false;
1235     }
1236 
1237     private synchronized boolean isSupportedMedia(MediaSizeName msn) {
1238         initAttributes();
1239 
1240         if (msn.equals((Media)getDefaultAttributeValue(Media.class))) {
1241             return true;
1242         }
1243         for (int i=0; i<mediaSizeNames.length; i++) {
1244             debug_println(debugPrefix+"isSupportedMedia, mediaSizeNames[i] "+mediaSizeNames[i]);
1245             if (msn.equals(mediaSizeNames[i])) {
1246                 return true;
1247             }
1248         }
1249         return false;
1250     }
1251 
1252     /* Return false if flavor is not null, pageable, nor printable and
1253      * Destination is part of attributes.
1254      */
1255     private boolean
1256         isDestinationSupported(DocFlavor flavor, AttributeSet attributes) {
1257 
1258             if ((attributes != null) &&
1259                     (attributes.get(Destination.class) != null) &&
1260                     !(flavor == null ||
1261                       flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
1262                       flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE))) {
1263                 return false;
1264             }
1265             return true;
1266     }
1267 
1268 
1269     public boolean isAttributeValueSupported(Attribute attr,
1270                                              DocFlavor flavor,
1271                                              AttributeSet attributes) {
1272         if (attr == null) {
1273             throw new NullPointerException("null attribute");
1274         }
1275         if (flavor != null) {
1276             if (!isDocFlavorSupported(flavor)) {
1277                 throw new IllegalArgumentException(flavor +
1278                                                " is an unsupported flavor");
1279             } else if (isAutoSense(flavor)) {
1280                 return false;
1281             }
1282         }
1283         Class category = attr.getCategory();
1284         if (!isAttributeCategorySupported(category)) {
1285             return false;
1286         }
1287 
1288         /* Test if the flavor is compatible with the attributes */
1289         if (!isDestinationSupported(flavor, attributes)) {
1290             return false;
1291         }
1292 
1293         /* Test if the flavor is compatible with the category */
1294         if (attr.getCategory() == Chromaticity.class) {
1295             if ((flavor == null) ||
1296                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
1297                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE) ||
1298                 !isIPPSupportedImages(flavor.getMimeType())) {
1299                 return attr == Chromaticity.COLOR;
1300             } else {
1301                 return false;
1302             }
1303         } else if (attr.getCategory() == Copies.class) {
1304             return (flavor == null ||
1305                    !(flavor.equals(DocFlavor.INPUT_STREAM.POSTSCRIPT) ||
1306                    flavor.equals(DocFlavor.URL.POSTSCRIPT) ||
1307                    flavor.equals(DocFlavor.BYTE_ARRAY.POSTSCRIPT))) &&
1308                 isSupportedCopies((Copies)attr);
1309 
1310         } else if (attr.getCategory() == Destination.class) {
1311             if (flavor == null ||
1312                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
1313                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE)) {
1314                 URI uri = ((Destination)attr).getURI();
1315                 if ("file".equals(uri.getScheme()) &&
1316                     !(uri.getSchemeSpecificPart().equals(""))) {
1317                     return true;
1318                 }
1319             }
1320             return false;
1321         } else if (attr.getCategory() == Media.class) {
1322             if (attr instanceof MediaSizeName) {
1323                 return isSupportedMedia((MediaSizeName)attr);
1324             }
1325             if (attr instanceof MediaTray) {
1326                 return isSupportedMediaTray((MediaTray)attr);
1327             }
1328         } else if (attr.getCategory() == PageRanges.class) {
1329             if (flavor != null &&
1330                 !(flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
1331                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE))) {
1332                 return false;
1333             }
1334         } else if (attr.getCategory() == SheetCollate.class) {
1335             if (flavor != null &&
1336                 !(flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
1337                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE))) {
1338                 return false;
1339             }
1340         } else if (attr.getCategory() == Sides.class) {
1341             Sides[] sidesArray = (Sides[])getSupportedAttributeValues(
1342                                           Sides.class,
1343                                           flavor,
1344                                           attributes);
1345 
1346             if (sidesArray != null) {
1347                 for (int i=0; i<sidesArray.length; i++) {
1348                     if (sidesArray[i] == (Sides)attr) {
1349                         return true;
1350                     }
1351                 }
1352             }
1353             return false;
1354         } else if (attr.getCategory() == OrientationRequested.class) {
1355             OrientationRequested[] orientArray =
1356                 (OrientationRequested[])getSupportedAttributeValues(
1357                                           OrientationRequested.class,
1358                                           flavor,
1359                                           attributes);
1360 
1361             if (orientArray != null) {
1362                 for (int i=0; i<orientArray.length; i++) {
1363                     if (orientArray[i] == (OrientationRequested)attr) {
1364                         return true;
1365                     }
1366                 }
1367             }
1368             return false;
1369         }
1370         return true;
1371     }
1372 
1373 
1374     public synchronized Object
1375         getDefaultAttributeValue(Class<? extends Attribute> category)
1376     {
1377         if (category == null) {
1378             throw new NullPointerException("null category");
1379         }
1380         if (!Attribute.class.isAssignableFrom(category)) {
1381             throw new IllegalArgumentException(category +
1382                                              " is not an Attribute");
1383         }
1384         if (!isAttributeCategorySupported(category)) {
1385             return null;
1386         }
1387 
1388         initAttributes();
1389 
1390         String catName = null;
1391         for (int i=0; i < printReqAttribDefault.length; i++) {
1392             PrintRequestAttribute pra =
1393                 (PrintRequestAttribute)printReqAttribDefault[i];
1394             if (pra.getCategory() == category) {
1395                 catName = pra.getName();
1396                 break;
1397             }
1398         }
1399         String attribName = catName+"-default";
1400         AttributeClass attribClass = (getAttMap != null) ?
1401                 (AttributeClass)getAttMap.get(attribName) : null;
1402 
1403         if (category == Copies.class) {
1404             if (attribClass != null) {
1405                 return new Copies(attribClass.getIntValue());
1406             } else {
1407                 return new Copies(1);
1408             }
1409         } else if (category == Chromaticity.class) {
1410             return Chromaticity.COLOR;
1411         } else if (category == Destination.class) {
1412             try {
1413                 return new Destination((new File("out.ps")).toURI());
1414             } catch (SecurityException se) {
1415                 try {
1416                     return new Destination(new URI("file:out.ps"));
1417                 } catch (URISyntaxException e) {
1418                     return null;
1419                 }
1420             }
1421         } else if (category == Fidelity.class) {
1422             return Fidelity.FIDELITY_FALSE;
1423         } else if (category == Finishings.class) {
1424             return Finishings.NONE;
1425         } else if (category == JobName.class) {
1426             return new JobName("Java Printing", null);
1427         } else if (category == JobSheets.class) {
1428             if (attribClass != null &&
1429                 attribClass.getStringValue().equals("none")) {
1430                 return JobSheets.NONE;
1431             } else {
1432                 return JobSheets.STANDARD;
1433             }
1434         } else if (category == Media.class) {
1435             defaultMediaIndex = 0;
1436             if (mediaSizeNames.length == 0) {
1437                 String defaultCountry = Locale.getDefault().getCountry();
1438                 if (defaultCountry != null &&
1439                     (defaultCountry.equals("") ||
1440                      defaultCountry.equals(Locale.US.getCountry()) ||
1441                      defaultCountry.equals(Locale.CANADA.getCountry()))) {
1442                     return MediaSizeName.NA_LETTER;
1443                 } else {
1444                     return MediaSizeName.ISO_A4;
1445                 }
1446             }
1447 
1448             if (attribClass != null) {
1449                 String name = attribClass.getStringValue();
1450                 if (isCupsPrinter) {
1451                     for (int i=0; i< customMediaSizeNames.length; i++) {
1452                         //REMIND:  get default from PPD. In native _getMedia,
1453                         // move default (ppd_option_t->defchoice) to index 0.
1454                         // In the meantime, use indexOf because PPD name
1455                         // may be different from the IPP attribute name.
1456                         if (customMediaSizeNames[i].toString().indexOf(name)
1457                             != -1) {
1458                             defaultMediaIndex = i;
1459                             return mediaSizeNames[defaultMediaIndex];
1460                         }
1461                     }
1462                 } else {
1463                     for (int i=0; i< mediaSizeNames.length; i++) {
1464                         if (mediaSizeNames[i].toString().indexOf(name) != -1) {
1465                             defaultMediaIndex = i;
1466                             return mediaSizeNames[defaultMediaIndex];
1467                         }
1468                     }
1469                 }
1470             }
1471             return mediaSizeNames[defaultMediaIndex];
1472 
1473         } else if (category == MediaPrintableArea.class) {
1474             MediaPrintableArea[] mpas;
1475              if ((cps != null)  &&
1476                  ((mpas = cps.getMediaPrintableArea()) != null)) {
1477                  if (defaultMediaIndex == -1) {
1478                      // initializes value of defaultMediaIndex
1479                      getDefaultAttributeValue(Media.class);
1480                  }
1481                  return mpas[defaultMediaIndex];
1482              } else {
1483                  String defaultCountry = Locale.getDefault().getCountry();
1484                  float iw, ih;
1485                  if (defaultCountry != null &&
1486                      (defaultCountry.equals("") ||
1487                       defaultCountry.equals(Locale.US.getCountry()) ||
1488                       defaultCountry.equals(Locale.CANADA.getCountry()))) {
1489                      iw = MediaSize.NA.LETTER.getX(Size2DSyntax.INCH) - 0.5f;
1490                      ih = MediaSize.NA.LETTER.getY(Size2DSyntax.INCH) - 0.5f;
1491                  } else {
1492                      iw = MediaSize.ISO.A4.getX(Size2DSyntax.INCH) - 0.5f;
1493                      ih = MediaSize.ISO.A4.getY(Size2DSyntax.INCH) - 0.5f;
1494                  }
1495                  return new MediaPrintableArea(0.25f, 0.25f, iw, ih,
1496                                                MediaPrintableArea.INCH);
1497              }
1498         } else if (category == NumberUp.class) {
1499             return new NumberUp(1); // for CUPS this is always 1
1500         } else if (category == OrientationRequested.class) {
1501             if (attribClass != null) {
1502                 switch (attribClass.getIntValue()) {
1503                 default:
1504                 case 3: return OrientationRequested.PORTRAIT;
1505                 case 4: return OrientationRequested.LANDSCAPE;
1506                 case 5: return OrientationRequested.REVERSE_LANDSCAPE;
1507                 case 6: return OrientationRequested.REVERSE_PORTRAIT;
1508                 }
1509             } else {
1510                 return OrientationRequested.PORTRAIT;
1511             }
1512         } else if (category == PageRanges.class) {
1513             if (attribClass != null) {
1514                 int[] range = attribClass.getIntRangeValue();
1515                 return new PageRanges(range[0], range[1]);
1516             } else {
1517                 return new PageRanges(1, Integer.MAX_VALUE);
1518             }
1519         } else if (category == RequestingUserName.class) {
1520             String userName = "";
1521             try {
1522               userName = System.getProperty("user.name", "");
1523             } catch (SecurityException se) {
1524             }
1525             return new RequestingUserName(userName, null);
1526         } else if (category == SheetCollate.class) {
1527             return SheetCollate.UNCOLLATED;
1528         } else if (category == Sides.class) {
1529             if (attribClass != null) {
1530                 if (attribClass.getStringValue().endsWith("long-edge")) {
1531                     return Sides.TWO_SIDED_LONG_EDGE;
1532                 } else if (attribClass.getStringValue().endsWith(
1533                                                            "short-edge")) {
1534                     return Sides.TWO_SIDED_SHORT_EDGE;
1535                 }
1536             }
1537             return Sides.ONE_SIDED;
1538         }
1539 
1540         return null;
1541     }
1542 
1543     public ServiceUIFactory getServiceUIFactory() {
1544         return null;
1545     }
1546 
1547     public void wakeNotifier() {
1548         synchronized (this) {
1549             if (notifier != null) {
1550                 notifier.wake();
1551             }
1552         }
1553     }
1554 
1555     public void addPrintServiceAttributeListener(
1556                                  PrintServiceAttributeListener listener) {
1557         synchronized (this) {
1558             if (listener == null) {
1559                 return;
1560             }
1561             if (notifier == null) {
1562                 notifier = new ServiceNotifier(this);
1563             }
1564             notifier.addListener(listener);
1565         }
1566     }
1567 
1568     public void removePrintServiceAttributeListener(
1569                                   PrintServiceAttributeListener listener) {
1570         synchronized (this) {
1571             if (listener == null || notifier == null ) {
1572                 return;
1573             }
1574             notifier.removeListener(listener);
1575             if (notifier.isEmpty()) {
1576                 notifier.stopNotifier();
1577                 notifier = null;
1578             }
1579         }
1580     }
1581 
1582     String getDest() {
1583         return printer;
1584     }
1585 
1586     public String getName() {
1587         /*
1588          * Mac is using printer-info IPP attribute for its human-readable printer
1589          * name and is also the identifier used in NSPrintInfo:setPrinter.
1590          */
1591         if (UnixPrintServiceLookup.isMac()) {
1592             PrintServiceAttributeSet psaSet = this.getAttributes();
1593             if (psaSet != null) {
1594                 PrinterInfo pName = (PrinterInfo)psaSet.get(PrinterInfo.class);
1595                 if (pName != null) {
1596                     return pName.toString();
1597                 }
1598             }
1599         }
1600         return printer;
1601     }
1602 
1603 
1604     public boolean usesClass(Class c) {
1605         return (c == sun.print.PSPrinterJob.class);
1606     }
1607 
1608 
1609     public static HttpURLConnection getIPPConnection(URL url) {
1610         HttpURLConnection connection;
1611         URLConnection urlc;
1612         try {
1613             urlc = url.openConnection();
1614         } catch (java.io.IOException ioe) {
1615             return null;
1616         }
1617         if (!(urlc instanceof HttpURLConnection)) {
1618             return null;
1619         }
1620         connection = (HttpURLConnection)urlc;
1621         connection.setUseCaches(false);
1622         connection.setDefaultUseCaches(false);
1623         connection.setDoInput(true);
1624         connection.setDoOutput(true);
1625         connection.setRequestProperty("Content-type", "application/ipp");
1626         return connection;
1627     }
1628 
1629 
1630     public synchronized boolean isPostscript() {
1631         if (isPS == null) {
1632            isPS = Boolean.TRUE;
1633             if (isCupsPrinter) {
1634                 try {
1635                     urlConnection = getIPPConnection(
1636                                              new URL(myURL+".ppd"));
1637 
1638                    InputStream is = urlConnection.getInputStream();
1639                    if (is != null) {
1640                        BufferedReader d =
1641                            new BufferedReader(new InputStreamReader(is,
1642                                                           Charset.forName("ISO-8859-1")));
1643                        String lineStr;
1644                        while ((lineStr = d.readLine()) != null) {
1645                            if (lineStr.startsWith("*cupsFilter:")) {
1646                                isPS = Boolean.FALSE;
1647                                break;
1648                            }
1649                        }
1650                     }
1651                 } catch (java.io.IOException e) {
1652                     debug_println(" isPostscript, e= "+e);
1653                     /* if PPD is not found, this may be a raw printer
1654                        and in this case it is assumed that it is a
1655                        Postscript printer */
1656                     // do nothing
1657                 }
1658             }
1659         }
1660         return isPS.booleanValue();
1661     }
1662 
1663 
1664     private void opGetAttributes() {
1665         try {
1666             debug_println(debugPrefix+"opGetAttributes myURI "+myURI+" myURL "+myURL);
1667 
1668             AttributeClass attClNoUri[] = {
1669                 AttributeClass.ATTRIBUTES_CHARSET,
1670                 AttributeClass.ATTRIBUTES_NATURAL_LANGUAGE};
1671 
1672             AttributeClass attCl[] = {
1673                 AttributeClass.ATTRIBUTES_CHARSET,
1674                 AttributeClass.ATTRIBUTES_NATURAL_LANGUAGE,
1675                 new AttributeClass("printer-uri",
1676                                    AttributeClass.TAG_URI,
1677                                    ""+myURI)};
1678 
1679             OutputStream os = (OutputStream)java.security.AccessController.
1680                 doPrivileged(new java.security.PrivilegedAction() {
1681                     public Object run() {
1682                         try {
1683                             return urlConnection.getOutputStream();
1684                         } catch (Exception e) {
1685                         }
1686                         return null;
1687                     }
1688                 });
1689 
1690             if (os == null) {
1691                 return;
1692             }
1693 
1694             boolean success = (myURI == null) ?
1695                 writeIPPRequest(os, OP_GET_ATTRIBUTES, attClNoUri) :
1696                 writeIPPRequest(os, OP_GET_ATTRIBUTES, attCl);
1697             if (success) {
1698                 InputStream is = null;
1699                 if ((is = urlConnection.getInputStream())!=null) {
1700                     HashMap[] responseMap = readIPPResponse(is);
1701 
1702                     if (responseMap != null && responseMap.length > 0) {
1703                         getAttMap = responseMap[0];
1704                     }
1705                 } else {
1706                     debug_println(debugPrefix+"opGetAttributes - null input stream");
1707                 }
1708                 is.close();
1709             }
1710             os.close();
1711         } catch (java.io.IOException e) {
1712             debug_println(debugPrefix+"opGetAttributes - input/output stream: "+e);
1713         }
1714     }
1715 
1716 
1717     public static boolean writeIPPRequest(OutputStream os,
1718                                            String operCode,
1719                                            AttributeClass[] attCl) {
1720         OutputStreamWriter osw;
1721         try {
1722             osw = new OutputStreamWriter(os, "UTF-8");
1723         } catch (java.io.UnsupportedEncodingException exc) {
1724             debug_println(debugPrefix+"writeIPPRequest, UTF-8 not supported? Exception: "+exc);
1725             return false;
1726         }
1727         debug_println(debugPrefix+"writeIPPRequest, op code= "+operCode);
1728         char[] opCode =  new char[2];
1729         opCode[0] =  (char)Byte.parseByte(operCode.substring(0,2), 16);
1730         opCode[1] =  (char)Byte.parseByte(operCode.substring(2,4), 16);
1731         char[] bytes = {0x01, 0x01, 0x00, 0x01};
1732         try {
1733             osw.write(bytes, 0, 2); // version number
1734             osw.write(opCode, 0, 2); // operation code
1735             bytes[0] = 0x00; bytes[1] = 0x00;
1736             osw.write(bytes, 0, 4); // request ID #1
1737 
1738             bytes[0] = 0x01; // operation-group-tag
1739             osw.write(bytes[0]);
1740 
1741             String valStr;
1742             char[] lenStr;
1743 
1744             AttributeClass ac;
1745             for (int i=0; i < attCl.length; i++) {
1746                 ac = attCl[i];
1747                 osw.write(ac.getType()); // value tag
1748 
1749                 lenStr = ac.getLenChars();
1750                 osw.write(lenStr, 0, 2); // length
1751                 osw.write(""+ac, 0, ac.getName().length());
1752 
1753                 // check if string range (0x35 -> 0x49)
1754                 if (ac.getType() >= AttributeClass.TAG_TEXT_LANGUAGE &&
1755                     ac.getType() <= AttributeClass.TAG_MIME_MEDIATYPE){
1756                     valStr = (String)ac.getObjectValue();
1757                     bytes[0] = 0; bytes[1] = (char)valStr.length();
1758                     osw.write(bytes, 0, 2);
1759                     osw.write(valStr, 0, valStr.length());
1760                 } // REMIND: need to support other value tags but for CUPS
1761                 // string is all we need.
1762             }
1763 
1764             osw.write(GRPTAG_END_ATTRIBUTES);
1765             osw.flush();
1766             osw.close();
1767         } catch (java.io.IOException ioe) {
1768             debug_println(debugPrefix+"writeIPPRequest, IPPPrintService Exception in writeIPPRequest: "+ioe);
1769             return false;
1770         }
1771         return true;
1772     }
1773 
1774 
1775     public static HashMap[] readIPPResponse(InputStream inputStream) {
1776 
1777         if (inputStream == null) {
1778             return null;
1779         }
1780 
1781         byte response[] = new byte[MAX_ATTRIBUTE_LENGTH];
1782         try {
1783 
1784             DataInputStream ois = new DataInputStream(inputStream);
1785 
1786             // read status and ID
1787             if ((ois.read(response, 0, 8) > -1) &&
1788                 (response[2] == STATUSCODE_SUCCESS)) {
1789 
1790                 ByteArrayOutputStream outObj;
1791                 int counter=0;
1792                 short len = 0;
1793                 String attribStr = null;
1794                 // assign default value
1795                 byte valTagByte = AttributeClass.TAG_KEYWORD;
1796                 ArrayList respList = new ArrayList();
1797                 HashMap responseMap = new HashMap();
1798 
1799                 response[0] = ois.readByte();
1800 
1801                 // check for group tags
1802                 while ((response[0] >= GRPTAG_OP_ATTRIBUTES) &&
1803                        (response[0] <= GRPTAG_PRINTER_ATTRIBUTES)
1804                           && (response[0] != GRPTAG_END_ATTRIBUTES)) {
1805                     debug_println(debugPrefix+"readIPPResponse, checking group tag,  response[0]= "+
1806                                   response[0]);
1807 
1808                     outObj = new ByteArrayOutputStream();
1809                     //make sure counter and attribStr are re-initialized
1810                     counter = 0;
1811                     attribStr = null;
1812 
1813                     // read value tag
1814                     response[0] = ois.readByte();
1815                     while (response[0] >= AttributeClass.TAG_UNSUPPORTED_VALUE &&
1816                            response[0] <= AttributeClass.TAG_MEMBER_ATTRNAME) {
1817                         // read name length
1818                         len  = ois.readShort();
1819 
1820                         // If current value is not part of previous attribute
1821                         // then close stream and add it to HashMap.
1822                         // It is part of previous attribute if name length=0.
1823                         if ((len != 0) && (attribStr != null)) {
1824                             //last byte is the total # of values
1825                             outObj.write(counter);
1826                             outObj.flush();
1827                             outObj.close();
1828                             byte outArray[] = outObj.toByteArray();
1829 
1830                             // if key exists, new HashMap
1831                             if (responseMap.containsKey(attribStr)) {
1832                                 respList.add(responseMap);
1833                                 responseMap = new HashMap();
1834                             }
1835 
1836                             // exclude those that are unknown
1837                             if (valTagByte >= AttributeClass.TAG_INT) {
1838                                 AttributeClass ac =
1839                                     new AttributeClass(attribStr,
1840                                                        valTagByte,
1841                                                        outArray);
1842 
1843                                 responseMap.put(ac.getName(), ac);
1844                                 debug_println(debugPrefix+ "readIPPResponse "+ac);
1845                             }
1846 
1847                             outObj = new ByteArrayOutputStream();
1848                             counter = 0; //reset counter
1849                         }
1850                         //check if this is new value tag
1851                         if (counter == 0) {
1852                             valTagByte = response[0];
1853                         }
1854                         // read attribute name
1855                         if (len != 0) {
1856                             // read "len" characters
1857                             // make sure it doesn't exceed the maximum
1858                             if (len > MAX_ATTRIBUTE_LENGTH) {
1859                                 response = new byte[len]; // expand as needed
1860                             }
1861                             ois.read(response, 0, len);
1862                             attribStr = new String(response, 0, len);
1863                         }
1864                         // read value length
1865                         len  = ois.readShort();
1866                         // write name length
1867                         outObj.write(len);
1868                         // read value, make sure it doesn't exceed the maximum
1869                         if (len > MAX_ATTRIBUTE_LENGTH) {
1870                             response = new byte[len]; // expand as needed
1871                         }
1872                         ois.read(response, 0, len);
1873                         // write value of "len" length
1874                         outObj.write(response, 0, len);
1875                         counter++;
1876                         // read next byte
1877                         response[0] = ois.readByte();
1878                     }
1879 
1880                     if (attribStr != null) {
1881                         outObj.write(counter);
1882                         outObj.flush();
1883                         outObj.close();
1884 
1885                         // if key exists in old HashMap, new HashMap
1886                         if ((counter != 0) &&
1887                             responseMap.containsKey(attribStr)) {
1888                             respList.add(responseMap);
1889                             responseMap = new HashMap();
1890                         }
1891 
1892                         byte outArray[] = outObj.toByteArray();
1893 
1894                         AttributeClass ac =
1895                             new AttributeClass(attribStr,
1896                                                valTagByte,
1897                                                outArray);
1898                         responseMap.put(ac.getName(), ac);
1899                     }
1900                 }
1901                 ois.close();
1902                 if ((responseMap != null) && (responseMap.size() > 0)) {
1903                     respList.add(responseMap);
1904                 }
1905                 return (HashMap[])respList.toArray(
1906                                   new HashMap[respList.size()]);
1907             } else {
1908                 debug_println(debugPrefix+
1909                               "readIPPResponse client error, IPP status code-"
1910                                    +Integer.toHexString(response[2])+" & "
1911                                    +Integer.toHexString(response[3]));
1912                 return null;
1913             }
1914 
1915         } catch (java.io.IOException e) {
1916             debug_println(debugPrefix+"readIPPResponse: "+e);
1917             if (debugPrint) {
1918                 e.printStackTrace();
1919             }
1920             return null;
1921         }
1922     }
1923 
1924 
1925     public String toString() {
1926         return "IPP Printer : " + getName();
1927     }
1928 
1929     public boolean equals(Object obj) {
1930         return  (obj == this ||
1931                  (obj instanceof IPPPrintService &&
1932                   ((IPPPrintService)obj).getName().equals(getName())));
1933     }
1934 
1935     public int hashCode() {
1936         return this.getClass().hashCode()+getName().hashCode();
1937     }
1938 }