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
26package sun.print;
27
28import javax.print.attribute.*;
29import javax.print.attribute.standard.*;
30import javax.print.DocFlavor;
31import javax.print.DocPrintJob;
32import javax.print.PrintService;
33import javax.print.ServiceUIFactory;
34import java.util.ArrayList;
35import java.util.HashMap;
36import java.util.Locale;
37import java.util.Date;
38import java.util.Arrays;
39import java.security.AccessController;
40import java.security.PrivilegedActionException;
41import java.security.PrivilegedExceptionAction;
42import javax.print.event.PrintServiceAttributeListener;
43
44import java.net.URI;
45import java.net.URISyntaxException;
46import java.net.URL;
47import java.net.URLConnection;
48import java.net.HttpURLConnection;
49import java.io.File;
50import java.io.InputStream;
51import java.io.OutputStream;
52import java.io.OutputStreamWriter;
53import java.io.DataInputStream;
54import java.io.ByteArrayOutputStream;
55import java.io.ByteArrayInputStream;
56import java.io.BufferedReader;
57import java.io.InputStreamReader;
58import java.nio.charset.Charset;
59
60import java.util.Iterator;
61import java.util.HashSet;
62import java.util.Map;
63
64
65public 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            catList.add(SheetCollate.class);
1058
1059        }
1060
1061        // With the assumption that  Chromaticity is equivalent to
1062        // ColorSupported.
1063        if (getAttMap != null && getAttMap.containsKey("color-supported")) {
1064            catList.add(Chromaticity.class);
1065        }
1066
1067        // CUPS does not report printer resolution via IPP but it
1068        // may be gleaned from the PPD.
1069        PrinterResolution[] supportedRes = getPrintResolutions();
1070        if (supportedRes != null && (supportedRes.length > 0)) {
1071            catList.add(PrinterResolution.class);
1072        }
1073
1074        supportedCats = new Class<?>[catList.size()];
1075        catList.toArray(supportedCats);
1076        Class<?>[] copyCats = new Class<?>[supportedCats.length];
1077        System.arraycopy(supportedCats, 0, copyCats, 0, copyCats.length);
1078        return copyCats;
1079    }
1080
1081
1082    public boolean
1083        isAttributeCategorySupported(Class<? extends Attribute> category)
1084    {
1085        if (category == null) {
1086            throw new NullPointerException("null category");
1087        }
1088        if (!(Attribute.class.isAssignableFrom(category))) {
1089            throw new IllegalArgumentException(category +
1090                                             " is not an Attribute");
1091        }
1092
1093        if (supportedCats == null) {
1094            getSupportedAttributeCategories();
1095        }
1096
1097        // It is safe to assume that Orientation is always supported
1098        // and even if CUPS or an IPP device reports it as not,
1099        // our renderer can do portrait, landscape and
1100        // reverse landscape.
1101        if (category == OrientationRequested.class) {
1102            return true;
1103        }
1104
1105        for (int i=0;i<supportedCats.length;i++) {
1106            if (category == supportedCats[i]) {
1107                return true;
1108            }
1109        }
1110
1111        return false;
1112    }
1113
1114    @SuppressWarnings("unchecked")
1115    public synchronized <T extends PrintServiceAttribute>
1116        T getAttribute(Class<T> category)
1117    {
1118        if (category == null) {
1119            throw new NullPointerException("category");
1120        }
1121        if (!(PrintServiceAttribute.class.isAssignableFrom(category))) {
1122            throw new IllegalArgumentException("Not a PrintServiceAttribute");
1123        }
1124
1125        initAttributes();
1126
1127        if (category == PrinterName.class) {
1128            return (T)(new PrinterName(printer, null));
1129        } else if (category == PrinterInfo.class) {
1130            PrinterInfo pInfo = new PrinterInfo(printer, null);
1131            AttributeClass ac = (getAttMap != null) ?
1132                getAttMap.get(pInfo.getName())
1133                : null;
1134            if (ac != null) {
1135                return (T)(new PrinterInfo(ac.getStringValue(), null));
1136            }
1137            return (T)pInfo;
1138        } else if (category == QueuedJobCount.class) {
1139            QueuedJobCount qjc = new QueuedJobCount(0);
1140            AttributeClass ac = (getAttMap != null) ?
1141                getAttMap.get(qjc.getName())
1142                : null;
1143            if (ac != null) {
1144                qjc = new QueuedJobCount(ac.getIntValue());
1145            }
1146            return (T)qjc;
1147        } else if (category == PrinterIsAcceptingJobs.class) {
1148            PrinterIsAcceptingJobs accJob =
1149                PrinterIsAcceptingJobs.ACCEPTING_JOBS;
1150            AttributeClass ac = (getAttMap != null) ?
1151                getAttMap.get(accJob.getName())
1152                : null;
1153            if ((ac != null) && (ac.getByteValue() == 0)) {
1154                accJob = PrinterIsAcceptingJobs.NOT_ACCEPTING_JOBS;
1155            }
1156            return (T)accJob;
1157        } else if (category == ColorSupported.class) {
1158            ColorSupported cs = ColorSupported.SUPPORTED;
1159            AttributeClass ac = (getAttMap != null) ?
1160                getAttMap.get(cs.getName())
1161                : null;
1162            if ((ac != null) && (ac.getByteValue() == 0)) {
1163                cs = ColorSupported.NOT_SUPPORTED;
1164            }
1165            return (T)cs;
1166        } else if (category == PDLOverrideSupported.class) {
1167
1168            if (isCupsPrinter) {
1169                // Documented: For CUPS this will always be false
1170                return (T)PDLOverrideSupported.NOT_ATTEMPTED;
1171            } else {
1172                // REMIND: check attribute values
1173                return (T)PDLOverrideSupported.NOT_ATTEMPTED;
1174            }
1175        } else if (category == PrinterURI.class) {
1176            return (T)(new PrinterURI(myURI));
1177        } else {
1178            return null;
1179        }
1180    }
1181
1182
1183    public synchronized PrintServiceAttributeSet getAttributes() {
1184        // update getAttMap by sending again get-attributes IPP request
1185        init = false;
1186        initAttributes();
1187
1188        HashPrintServiceAttributeSet attrs =
1189            new HashPrintServiceAttributeSet();
1190
1191        for (int i=0; i < serviceAttributes.length; i++) {
1192            String name = (String)serviceAttributes[i][1];
1193            if (getAttMap != null && getAttMap.containsKey(name)) {
1194                @SuppressWarnings("unchecked")
1195                Class<PrintServiceAttribute> c = (Class<PrintServiceAttribute>)serviceAttributes[i][0];
1196                PrintServiceAttribute psa = getAttribute(c);
1197                if (psa != null) {
1198                    attrs.add(psa);
1199                }
1200            }
1201        }
1202        return AttributeSetUtilities.unmodifiableView(attrs);
1203    }
1204
1205    public boolean isIPPSupportedImages(String mimeType) {
1206        if (supportedDocFlavors == null) {
1207            getSupportedDocFlavors();
1208        }
1209
1210        if (mimeType.equals("image/png") && pngImagesAdded) {
1211            return true;
1212        } else if (mimeType.equals("image/gif") && gifImagesAdded) {
1213            return true;
1214        } else if (mimeType.equals("image/jpeg") && jpgImagesAdded) {
1215            return true;
1216        }
1217
1218        return false;
1219    }
1220
1221
1222    private boolean isSupportedCopies(Copies copies) {
1223        CopiesSupported cs = (CopiesSupported)
1224            getSupportedAttributeValues(Copies.class, null, null);
1225        int[][] members = cs.getMembers();
1226        int min, max;
1227        if ((members.length > 0) && (members[0].length > 0)) {
1228            min = members[0][0];
1229            max = members[0][1];
1230        } else {
1231            min = 1;
1232            max = MAXCOPIES;
1233        }
1234
1235        int value = copies.getValue();
1236        return (value >= min && value <= max);
1237    }
1238
1239    private boolean isAutoSense(DocFlavor flavor) {
1240        if (flavor.equals(DocFlavor.BYTE_ARRAY.AUTOSENSE) ||
1241            flavor.equals(DocFlavor.INPUT_STREAM.AUTOSENSE) ||
1242            flavor.equals(DocFlavor.URL.AUTOSENSE)) {
1243            return true;
1244        }
1245        else {
1246            return false;
1247        }
1248    }
1249
1250    private synchronized boolean isSupportedMediaTray(MediaTray msn) {
1251        initAttributes();
1252
1253        if (mediaTrays != null) {
1254            for (int i=0; i<mediaTrays.length; i++) {
1255               if (msn.equals(mediaTrays[i])) {
1256                    return true;
1257                }
1258            }
1259        }
1260        return false;
1261    }
1262
1263    private synchronized boolean isSupportedMedia(MediaSizeName msn) {
1264        initAttributes();
1265
1266        if (msn.equals((Media)getDefaultAttributeValue(Media.class))) {
1267            return true;
1268        }
1269        for (int i=0; i<mediaSizeNames.length; i++) {
1270            debug_println(debugPrefix+"isSupportedMedia, mediaSizeNames[i] "+mediaSizeNames[i]);
1271            if (msn.equals(mediaSizeNames[i])) {
1272                return true;
1273            }
1274        }
1275        return false;
1276    }
1277
1278    /* Return false if flavor is not null, pageable, nor printable and
1279     * Destination is part of attributes.
1280     */
1281    private boolean
1282        isDestinationSupported(DocFlavor flavor, AttributeSet attributes) {
1283
1284            if ((attributes != null) &&
1285                    (attributes.get(Destination.class) != null) &&
1286                    !(flavor == null ||
1287                      flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
1288                      flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE))) {
1289                return false;
1290            }
1291            return true;
1292    }
1293
1294
1295    public boolean isAttributeValueSupported(Attribute attr,
1296                                             DocFlavor flavor,
1297                                             AttributeSet attributes) {
1298        if (attr == null) {
1299            throw new NullPointerException("null attribute");
1300        }
1301        if (flavor != null) {
1302            if (!isDocFlavorSupported(flavor)) {
1303                throw new IllegalArgumentException(flavor +
1304                                               " is an unsupported flavor");
1305            } else if (isAutoSense(flavor)) {
1306                return false;
1307            }
1308        }
1309        Class<? extends Attribute> category = attr.getCategory();
1310        if (!isAttributeCategorySupported(category)) {
1311            return false;
1312        }
1313
1314        /* Test if the flavor is compatible with the attributes */
1315        if (!isDestinationSupported(flavor, attributes)) {
1316            return false;
1317        }
1318
1319        /* Test if the flavor is compatible with the category */
1320        if (attr.getCategory() == Chromaticity.class) {
1321            if ((flavor == null) ||
1322                flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
1323                flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE) ||
1324                !isIPPSupportedImages(flavor.getMimeType())) {
1325                return attr == Chromaticity.COLOR;
1326            } else {
1327                return false;
1328            }
1329        } else if (attr.getCategory() == Copies.class) {
1330            return (flavor == null ||
1331                   !(flavor.equals(DocFlavor.INPUT_STREAM.POSTSCRIPT) ||
1332                   flavor.equals(DocFlavor.URL.POSTSCRIPT) ||
1333                   flavor.equals(DocFlavor.BYTE_ARRAY.POSTSCRIPT))) &&
1334                isSupportedCopies((Copies)attr);
1335
1336        } else if (attr.getCategory() == Destination.class) {
1337            if (flavor == null ||
1338                flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
1339                flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE)) {
1340                URI uri = ((Destination)attr).getURI();
1341                if ("file".equals(uri.getScheme()) &&
1342                    !(uri.getSchemeSpecificPart().equals(""))) {
1343                    return true;
1344                }
1345            }
1346            return false;
1347        } else if (attr.getCategory() == Media.class) {
1348            if (attr instanceof MediaSizeName) {
1349                return isSupportedMedia((MediaSizeName)attr);
1350            }
1351            if (attr instanceof MediaTray) {
1352                return isSupportedMediaTray((MediaTray)attr);
1353            }
1354        } else if (attr.getCategory() == PageRanges.class) {
1355            if (flavor != null &&
1356                !(flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
1357                flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE))) {
1358                return false;
1359            }
1360        } else if (attr.getCategory() == SheetCollate.class) {
1361            if (flavor != null &&
1362                !(flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
1363                flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE))) {
1364                return false;
1365            }
1366        } else if (attr.getCategory() == Sides.class) {
1367            Sides[] sidesArray = (Sides[])getSupportedAttributeValues(
1368                                          Sides.class,
1369                                          flavor,
1370                                          attributes);
1371
1372            if (sidesArray != null) {
1373                for (int i=0; i<sidesArray.length; i++) {
1374                    if (sidesArray[i] == (Sides)attr) {
1375                        return true;
1376                    }
1377                }
1378            }
1379            return false;
1380        } else if (attr.getCategory() == OrientationRequested.class) {
1381            OrientationRequested[] orientArray =
1382                (OrientationRequested[])getSupportedAttributeValues(
1383                                          OrientationRequested.class,
1384                                          flavor,
1385                                          attributes);
1386
1387            if (orientArray != null) {
1388                for (int i=0; i<orientArray.length; i++) {
1389                    if (orientArray[i] == (OrientationRequested)attr) {
1390                        return true;
1391                    }
1392                }
1393            }
1394            return false;
1395        } if (attr.getCategory() == PrinterResolution.class) {
1396            if (attr instanceof PrinterResolution) {
1397                return isSupportedResolution((PrinterResolution)attr);
1398            }
1399        }
1400        return true;
1401    }
1402
1403
1404    public synchronized Object
1405        getDefaultAttributeValue(Class<? extends Attribute> category)
1406    {
1407        if (category == null) {
1408            throw new NullPointerException("null category");
1409        }
1410        if (!Attribute.class.isAssignableFrom(category)) {
1411            throw new IllegalArgumentException(category +
1412                                             " is not an Attribute");
1413        }
1414        if (!isAttributeCategorySupported(category)) {
1415            return null;
1416        }
1417
1418        initAttributes();
1419
1420        String catName = null;
1421        for (int i=0; i < printReqAttribDefault.length; i++) {
1422            PrintRequestAttribute pra =
1423                (PrintRequestAttribute)printReqAttribDefault[i];
1424            if (pra.getCategory() == category) {
1425                catName = pra.getName();
1426                break;
1427            }
1428        }
1429        String attribName = catName+"-default";
1430        AttributeClass attribClass = (getAttMap != null) ?
1431                getAttMap.get(attribName) : null;
1432
1433        if (category == Copies.class) {
1434            if (attribClass != null) {
1435                return new Copies(attribClass.getIntValue());
1436            } else {
1437                return new Copies(1);
1438            }
1439        } else if (category == Chromaticity.class) {
1440            return Chromaticity.COLOR;
1441        } else if (category == Destination.class) {
1442            try {
1443                return new Destination((new File("out.ps")).toURI());
1444            } catch (SecurityException se) {
1445                try {
1446                    return new Destination(new URI("file:out.ps"));
1447                } catch (URISyntaxException e) {
1448                    return null;
1449                }
1450            }
1451        } else if (category == Fidelity.class) {
1452            return Fidelity.FIDELITY_FALSE;
1453        } else if (category == Finishings.class) {
1454            return Finishings.NONE;
1455        } else if (category == JobName.class) {
1456            return new JobName("Java Printing", null);
1457        } else if (category == JobSheets.class) {
1458            if (attribClass != null &&
1459                attribClass.getStringValue().equals("none")) {
1460                return JobSheets.NONE;
1461            } else {
1462                return JobSheets.STANDARD;
1463            }
1464        } else if (category == Media.class) {
1465            if (defaultMediaIndex == -1) {
1466                defaultMediaIndex = 0;
1467            }
1468            if (mediaSizeNames.length == 0) {
1469                String defaultCountry = Locale.getDefault().getCountry();
1470                if (defaultCountry != null &&
1471                    (defaultCountry.equals("") ||
1472                     defaultCountry.equals(Locale.US.getCountry()) ||
1473                     defaultCountry.equals(Locale.CANADA.getCountry()))) {
1474                    return MediaSizeName.NA_LETTER;
1475                } else {
1476                    return MediaSizeName.ISO_A4;
1477                }
1478            }
1479
1480            if (attribClass != null) {
1481                String name = attribClass.getStringValue();
1482                if (isCupsPrinter) {
1483                    return mediaSizeNames[defaultMediaIndex];
1484                } else {
1485                    for (int i=0; i< mediaSizeNames.length; i++) {
1486                        if (mediaSizeNames[i].toString().indexOf(name) != -1) {
1487                            defaultMediaIndex = i;
1488                            return mediaSizeNames[defaultMediaIndex];
1489                        }
1490                    }
1491                }
1492            }
1493            return mediaSizeNames[defaultMediaIndex];
1494
1495        } else if (category == MediaPrintableArea.class) {
1496            MediaPrintableArea[] mpas;
1497             if ((cps != null)  &&
1498                 ((mpas = cps.getMediaPrintableArea()) != null)) {
1499                 if (defaultMediaIndex == -1) {
1500                     // initializes value of defaultMediaIndex
1501                     getDefaultAttributeValue(Media.class);
1502                 }
1503                 return mpas[defaultMediaIndex];
1504             } else {
1505                 String defaultCountry = Locale.getDefault().getCountry();
1506                 float iw, ih;
1507                 if (defaultCountry != null &&
1508                     (defaultCountry.equals("") ||
1509                      defaultCountry.equals(Locale.US.getCountry()) ||
1510                      defaultCountry.equals(Locale.CANADA.getCountry()))) {
1511                     iw = MediaSize.NA.LETTER.getX(Size2DSyntax.INCH) - 0.5f;
1512                     ih = MediaSize.NA.LETTER.getY(Size2DSyntax.INCH) - 0.5f;
1513                 } else {
1514                     iw = MediaSize.ISO.A4.getX(Size2DSyntax.INCH) - 0.5f;
1515                     ih = MediaSize.ISO.A4.getY(Size2DSyntax.INCH) - 0.5f;
1516                 }
1517                 return new MediaPrintableArea(0.25f, 0.25f, iw, ih,
1518                                               MediaPrintableArea.INCH);
1519             }
1520        } else if (category == NumberUp.class) {
1521            return new NumberUp(1); // for CUPS this is always 1
1522        } else if (category == OrientationRequested.class) {
1523            if (attribClass != null) {
1524                switch (attribClass.getIntValue()) {
1525                default:
1526                case 3: return OrientationRequested.PORTRAIT;
1527                case 4: return OrientationRequested.LANDSCAPE;
1528                case 5: return OrientationRequested.REVERSE_LANDSCAPE;
1529                case 6: return OrientationRequested.REVERSE_PORTRAIT;
1530                }
1531            } else {
1532                return OrientationRequested.PORTRAIT;
1533            }
1534        } else if (category == PageRanges.class) {
1535            if (attribClass != null) {
1536                int[] range = attribClass.getIntRangeValue();
1537                return new PageRanges(range[0], range[1]);
1538            } else {
1539                return new PageRanges(1, Integer.MAX_VALUE);
1540            }
1541        } else if (category == RequestingUserName.class) {
1542            String userName = "";
1543            try {
1544              userName = System.getProperty("user.name", "");
1545            } catch (SecurityException se) {
1546            }
1547            return new RequestingUserName(userName, null);
1548        } else if (category == SheetCollate.class) {
1549            return SheetCollate.UNCOLLATED;
1550        } else if (category == Sides.class) {
1551            if (attribClass != null) {
1552                if (attribClass.getStringValue().endsWith("long-edge")) {
1553                    return Sides.TWO_SIDED_LONG_EDGE;
1554                } else if (attribClass.getStringValue().endsWith(
1555                                                           "short-edge")) {
1556                    return Sides.TWO_SIDED_SHORT_EDGE;
1557                }
1558            }
1559            return Sides.ONE_SIDED;
1560        } else if (category == PrinterResolution.class) {
1561             PrinterResolution[] supportedRes = getPrintResolutions();
1562             if ((supportedRes != null) && (supportedRes.length > 0)) {
1563                return supportedRes[0];
1564             } else {
1565                 return new PrinterResolution(300, 300, PrinterResolution.DPI);
1566             }
1567        }
1568
1569        return null;
1570    }
1571
1572    private PrinterResolution[] getPrintResolutions() {
1573        if (printerResolutions == null) {
1574            if (rawResolutions == null) {
1575              printerResolutions = new PrinterResolution[0];
1576            } else {
1577                int numRes = rawResolutions.length / 2;
1578                PrinterResolution[] pres = new PrinterResolution[numRes];
1579                for (int i=0; i < numRes; i++) {
1580                    pres[i] =  new PrinterResolution(rawResolutions[i*2],
1581                                                     rawResolutions[i*2+1],
1582                                                     PrinterResolution.DPI);
1583                }
1584                printerResolutions = pres;
1585            }
1586        }
1587        return printerResolutions;
1588    }
1589
1590    private boolean isSupportedResolution(PrinterResolution res) {
1591        PrinterResolution[] supportedRes = getPrintResolutions();
1592        if (supportedRes != null) {
1593            for (int i=0; i<supportedRes.length; i++) {
1594                if (res.equals(supportedRes[i])) {
1595                    return true;
1596                }
1597            }
1598        }
1599        return false;
1600    }
1601
1602    public ServiceUIFactory getServiceUIFactory() {
1603        return null;
1604    }
1605
1606    public void wakeNotifier() {
1607        synchronized (this) {
1608            if (notifier != null) {
1609                notifier.wake();
1610            }
1611        }
1612    }
1613
1614    public void addPrintServiceAttributeListener(
1615                                 PrintServiceAttributeListener listener) {
1616        synchronized (this) {
1617            if (listener == null) {
1618                return;
1619            }
1620            if (notifier == null) {
1621                notifier = new ServiceNotifier(this);
1622            }
1623            notifier.addListener(listener);
1624        }
1625    }
1626
1627    public void removePrintServiceAttributeListener(
1628                                  PrintServiceAttributeListener listener) {
1629        synchronized (this) {
1630            if (listener == null || notifier == null ) {
1631                return;
1632            }
1633            notifier.removeListener(listener);
1634            if (notifier.isEmpty()) {
1635                notifier.stopNotifier();
1636                notifier = null;
1637            }
1638        }
1639    }
1640
1641    String getDest() {
1642        return printer;
1643    }
1644
1645    public String getName() {
1646        /*
1647         * Mac is using printer-info IPP attribute for its human-readable printer
1648         * name and is also the identifier used in NSPrintInfo:setPrinter.
1649         */
1650        if (PrintServiceLookupProvider.isMac()) {
1651            PrintServiceAttributeSet psaSet = this.getAttributes();
1652            if (psaSet != null) {
1653                PrinterInfo pName = (PrinterInfo)psaSet.get(PrinterInfo.class);
1654                if (pName != null) {
1655                    return pName.toString();
1656                }
1657            }
1658        }
1659        return printer;
1660    }
1661
1662
1663    public boolean usesClass(Class<?> c) {
1664        return (c == sun.print.PSPrinterJob.class);
1665    }
1666
1667
1668    public static HttpURLConnection getIPPConnection(URL url) {
1669        HttpURLConnection connection;
1670        URLConnection urlc;
1671        try {
1672            urlc = url.openConnection();
1673        } catch (java.io.IOException ioe) {
1674            return null;
1675        }
1676        if (!(urlc instanceof HttpURLConnection)) {
1677            return null;
1678        }
1679        connection = (HttpURLConnection)urlc;
1680        connection.setUseCaches(false);
1681        connection.setDoInput(true);
1682        connection.setDoOutput(true);
1683        connection.setRequestProperty("Content-type", "application/ipp");
1684        return connection;
1685    }
1686
1687
1688    public synchronized boolean isPostscript() {
1689        if (isPS == null) {
1690           isPS = Boolean.TRUE;
1691            if (isCupsPrinter) {
1692                try {
1693                    urlConnection = getIPPConnection(
1694                                             new URL(myURL+".ppd"));
1695
1696                   InputStream is = urlConnection.getInputStream();
1697                   if (is != null) {
1698                       BufferedReader d =
1699                           new BufferedReader(new InputStreamReader(is,
1700                                                          Charset.forName("ISO-8859-1")));
1701                       String lineStr;
1702                       while ((lineStr = d.readLine()) != null) {
1703                           if (lineStr.startsWith("*cupsFilter:")) {
1704                               isPS = Boolean.FALSE;
1705                               break;
1706                           }
1707                       }
1708                    }
1709                } catch (java.io.IOException e) {
1710                    debug_println(" isPostscript, e= "+e);
1711                    /* if PPD is not found, this may be a raw printer
1712                       and in this case it is assumed that it is a
1713                       Postscript printer */
1714                    // do nothing
1715                }
1716            }
1717        }
1718        return isPS.booleanValue();
1719    }
1720
1721
1722    private void opGetAttributes() {
1723        try {
1724            debug_println(debugPrefix+"opGetAttributes myURI "+myURI+" myURL "+myURL);
1725
1726            AttributeClass attClNoUri[] = {
1727                AttributeClass.ATTRIBUTES_CHARSET,
1728                AttributeClass.ATTRIBUTES_NATURAL_LANGUAGE};
1729
1730            AttributeClass attCl[] = {
1731                AttributeClass.ATTRIBUTES_CHARSET,
1732                AttributeClass.ATTRIBUTES_NATURAL_LANGUAGE,
1733                new AttributeClass("printer-uri",
1734                                   AttributeClass.TAG_URI,
1735                                   ""+myURI)};
1736
1737            OutputStream os = java.security.AccessController.
1738                doPrivileged(new java.security.PrivilegedAction<OutputStream>() {
1739                    public OutputStream run() {
1740                        try {
1741                            return urlConnection.getOutputStream();
1742                        } catch (Exception e) {
1743                        }
1744                        return null;
1745                    }
1746                });
1747
1748            if (os == null) {
1749                return;
1750            }
1751
1752            boolean success = (myURI == null) ?
1753                writeIPPRequest(os, OP_GET_ATTRIBUTES, attClNoUri) :
1754                writeIPPRequest(os, OP_GET_ATTRIBUTES, attCl);
1755            if (success) {
1756                InputStream is = null;
1757                if ((is = urlConnection.getInputStream())!=null) {
1758                    HashMap<String, AttributeClass>[] responseMap = readIPPResponse(is);
1759
1760                    if (responseMap != null && responseMap.length > 0) {
1761                        getAttMap = responseMap[0];
1762                        // If there is extra hashmap created due to duplicate
1763                        // key/attribute present in IPPresponse, then use that
1764                        // map too by appending to getAttMap after removing the
1765                        // duplicate key/value
1766                        if (responseMap.length > 1) {
1767                            for (int i = 1; i < responseMap.length; i++) {
1768                                for (Map.Entry<String, AttributeClass> entry : responseMap[i].entrySet()) {
1769                                    if (!getAttMap.containsKey(entry.getValue())) {
1770                                        getAttMap.put(entry.getKey(), entry.getValue());
1771                                    }
1772                                }
1773                            }
1774                        }
1775                    }
1776                } else {
1777                    debug_println(debugPrefix+"opGetAttributes - null input stream");
1778                }
1779                is.close();
1780            }
1781            os.close();
1782        } catch (java.io.IOException e) {
1783            debug_println(debugPrefix+"opGetAttributes - input/output stream: "+e);
1784        }
1785    }
1786
1787
1788    public static boolean writeIPPRequest(OutputStream os,
1789                                           String operCode,
1790                                           AttributeClass[] attCl) {
1791        OutputStreamWriter osw;
1792        try {
1793            osw = new OutputStreamWriter(os, "UTF-8");
1794        } catch (java.io.UnsupportedEncodingException exc) {
1795            debug_println(debugPrefix+"writeIPPRequest, UTF-8 not supported? Exception: "+exc);
1796            return false;
1797        }
1798        debug_println(debugPrefix+"writeIPPRequest, op code= "+operCode);
1799        char[] opCode =  new char[2];
1800        opCode[0] =  (char)Byte.parseByte(operCode.substring(0,2), 16);
1801        opCode[1] =  (char)Byte.parseByte(operCode.substring(2,4), 16);
1802        char[] bytes = {0x01, 0x01, 0x00, 0x01};
1803        try {
1804            osw.write(bytes, 0, 2); // version number
1805            osw.write(opCode, 0, 2); // operation code
1806            bytes[0] = 0x00; bytes[1] = 0x00;
1807            osw.write(bytes, 0, 4); // request ID #1
1808
1809            bytes[0] = 0x01; // operation-group-tag
1810            osw.write(bytes[0]);
1811
1812            String valStr;
1813            char[] lenStr;
1814
1815            AttributeClass ac;
1816            for (int i=0; i < attCl.length; i++) {
1817                ac = attCl[i];
1818                osw.write(ac.getType()); // value tag
1819
1820                lenStr = ac.getLenChars();
1821                osw.write(lenStr, 0, 2); // length
1822                osw.write(""+ac, 0, ac.getName().length());
1823
1824                // check if string range (0x35 -> 0x49)
1825                if (ac.getType() >= AttributeClass.TAG_TEXT_LANGUAGE &&
1826                    ac.getType() <= AttributeClass.TAG_MIME_MEDIATYPE){
1827                    valStr = (String)ac.getObjectValue();
1828                    bytes[0] = 0; bytes[1] = (char)valStr.length();
1829                    osw.write(bytes, 0, 2);
1830                    osw.write(valStr, 0, valStr.length());
1831                } // REMIND: need to support other value tags but for CUPS
1832                // string is all we need.
1833            }
1834
1835            osw.write(GRPTAG_END_ATTRIBUTES);
1836            osw.flush();
1837            osw.close();
1838        } catch (java.io.IOException ioe) {
1839            debug_println(debugPrefix+"writeIPPRequest, IPPPrintService Exception in writeIPPRequest: "+ioe);
1840            return false;
1841        }
1842        return true;
1843    }
1844
1845
1846    public static HashMap<String, AttributeClass>[] readIPPResponse(InputStream inputStream) {
1847
1848        if (inputStream == null) {
1849            return null;
1850        }
1851
1852        byte response[] = new byte[MAX_ATTRIBUTE_LENGTH];
1853        try {
1854
1855            DataInputStream ois = new DataInputStream(inputStream);
1856
1857            // read status and ID
1858            if ((ois.read(response, 0, 8) > -1) &&
1859                (response[2] == STATUSCODE_SUCCESS)) {
1860
1861                ByteArrayOutputStream outObj;
1862                int counter=0;
1863                short len = 0;
1864                String attribStr = null;
1865                // assign default value
1866                byte valTagByte = AttributeClass.TAG_KEYWORD;
1867                ArrayList<HashMap<String, AttributeClass>> respList = new ArrayList<>();
1868                HashMap<String, AttributeClass> responseMap = new HashMap<>();
1869
1870                response[0] = ois.readByte();
1871
1872                // check for group tags
1873                while ((response[0] >= GRPTAG_OP_ATTRIBUTES) &&
1874                       (response[0] <= GRPTAG_PRINTER_ATTRIBUTES)
1875                          && (response[0] != GRPTAG_END_ATTRIBUTES)) {
1876                    debug_println(debugPrefix+"readIPPResponse, checking group tag,  response[0]= "+
1877                                  response[0]);
1878
1879                    outObj = new ByteArrayOutputStream();
1880                    //make sure counter and attribStr are re-initialized
1881                    counter = 0;
1882                    attribStr = null;
1883
1884                    // read value tag
1885                    response[0] = ois.readByte();
1886                    while (response[0] >= AttributeClass.TAG_UNSUPPORTED_VALUE &&
1887                           response[0] <= AttributeClass.TAG_MEMBER_ATTRNAME) {
1888                        // read name length
1889                        len  = ois.readShort();
1890
1891                        // If current value is not part of previous attribute
1892                        // then close stream and add it to HashMap.
1893                        // It is part of previous attribute if name length=0.
1894                        if ((len != 0) && (attribStr != null)) {
1895                            //last byte is the total # of values
1896                            outObj.write(counter);
1897                            outObj.flush();
1898                            outObj.close();
1899                            byte outArray[] = outObj.toByteArray();
1900
1901                            // if key exists, new HashMap
1902                            if (responseMap.containsKey(attribStr)) {
1903                                respList.add(responseMap);
1904                                responseMap = new HashMap<>();
1905                            }
1906
1907                            // exclude those that are unknown
1908                            if (valTagByte >= AttributeClass.TAG_INT) {
1909                                AttributeClass ac =
1910                                    new AttributeClass(attribStr,
1911                                                       valTagByte,
1912                                                       outArray);
1913
1914                                responseMap.put(ac.getName(), ac);
1915                                debug_println(debugPrefix+ "readIPPResponse "+ac);
1916                            }
1917
1918                            outObj = new ByteArrayOutputStream();
1919                            counter = 0; //reset counter
1920                        }
1921                        //check if this is new value tag
1922                        if (counter == 0) {
1923                            valTagByte = response[0];
1924                        }
1925                        // read attribute name
1926                        if (len != 0) {
1927                            // read "len" characters
1928                            // make sure it doesn't exceed the maximum
1929                            if (len > MAX_ATTRIBUTE_LENGTH) {
1930                                response = new byte[len]; // expand as needed
1931                            }
1932                            ois.read(response, 0, len);
1933                            attribStr = new String(response, 0, len);
1934                        }
1935                        // read value length
1936                        len  = ois.readShort();
1937                        // write name length
1938                        outObj.write(len);
1939                        // read value, make sure it doesn't exceed the maximum
1940                        if (len > MAX_ATTRIBUTE_LENGTH) {
1941                            response = new byte[len]; // expand as needed
1942                        }
1943                        ois.read(response, 0, len);
1944                        // write value of "len" length
1945                        outObj.write(response, 0, len);
1946                        counter++;
1947                        // read next byte
1948                        response[0] = ois.readByte();
1949                    }
1950
1951                    if (attribStr != null) {
1952                        outObj.write(counter);
1953                        outObj.flush();
1954                        outObj.close();
1955
1956                        // if key exists in old HashMap, new HashMap
1957                        if ((counter != 0) &&
1958                            responseMap.containsKey(attribStr)) {
1959                            respList.add(responseMap);
1960                            responseMap = new HashMap<>();
1961                        }
1962
1963                        byte outArray[] = outObj.toByteArray();
1964
1965                        AttributeClass ac =
1966                            new AttributeClass(attribStr,
1967                                               valTagByte,
1968                                               outArray);
1969                        responseMap.put(ac.getName(), ac);
1970                    }
1971                }
1972                ois.close();
1973                if ((responseMap != null) && (responseMap.size() > 0)) {
1974                    respList.add(responseMap);
1975                }
1976                @SuppressWarnings({"unchecked", "rawtypes"})
1977                HashMap<String, AttributeClass>[] tmp  =
1978                    respList.toArray((HashMap<String, AttributeClass>[])new HashMap[respList.size()]);
1979                return tmp;
1980            } else {
1981                debug_println(debugPrefix+
1982                          "readIPPResponse client error, IPP status code: 0x"+
1983                          toHex(response[2]) + toHex(response[3]));
1984                return null;
1985            }
1986
1987        } catch (java.io.IOException e) {
1988            debug_println(debugPrefix+"readIPPResponse: "+e);
1989            if (debugPrint) {
1990                e.printStackTrace();
1991            }
1992            return null;
1993        }
1994    }
1995
1996    private static String toHex(byte v) {
1997        String s = Integer.toHexString(v&0xff);
1998        return (s.length() == 2) ? s :  "0"+s;
1999    }
2000
2001    public String toString() {
2002        return "IPP Printer : " + getName();
2003    }
2004
2005    public boolean equals(Object obj) {
2006        return  (obj == this ||
2007                 (obj instanceof IPPPrintService &&
2008                  ((IPPPrintService)obj).getName().equals(getName())));
2009    }
2010
2011    public int hashCode() {
2012        return this.getClass().hashCode()+getName().hashCode();
2013    }
2014}
2015