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