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