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