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