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