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