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