jdk/src/java.desktop/unix/classes/sun/print/IPPPrintService.java
changeset 25859 3317bb8137f4
parent 25774 21b78da4b2df
child 27082 7ab1aa5e8713
child 27269 1ef2879dc7ad
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/unix/classes/sun/print/IPPPrintService.java	Sun Aug 17 15:54:13 2014 +0100
@@ -0,0 +1,1992 @@
+/*
+ * Copyright (c) 2003, 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.print;
+
+import javax.print.attribute.*;
+import javax.print.attribute.standard.*;
+import javax.print.DocFlavor;
+import javax.print.DocPrintJob;
+import javax.print.PrintService;
+import javax.print.ServiceUIFactory;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Date;
+import java.util.Arrays;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import javax.print.event.PrintServiceAttributeListener;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.HttpURLConnection;
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.DataInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.nio.charset.Charset;
+
+import java.util.Iterator;
+import java.util.HashSet;
+
+
+public class IPPPrintService implements PrintService, SunPrinterJobService {
+
+    public static final boolean debugPrint;
+    private static final String debugPrefix = "IPPPrintService>> ";
+    protected static void debug_println(String str) {
+        if (debugPrint) {
+            System.out.println(str);
+        }
+    }
+
+    private static final String FORCE_PIPE_PROP = "sun.print.ippdebug";
+
+    static {
+        String debugStr = java.security.AccessController.doPrivileged(
+                  new sun.security.action.GetPropertyAction(FORCE_PIPE_PROP));
+
+        debugPrint = "true".equalsIgnoreCase(debugStr);
+    }
+
+    private String printer;
+    private URI    myURI;
+    private URL    myURL;
+    transient private ServiceNotifier notifier = null;
+
+    private static int MAXCOPIES = 1000;
+    private static short MAX_ATTRIBUTE_LENGTH = 255;
+
+    private CUPSPrinter cps;
+    private HttpURLConnection urlConnection = null;
+    private DocFlavor[] supportedDocFlavors;
+    private Class<?>[] supportedCats;
+    private MediaTray[] mediaTrays;
+    private MediaSizeName[] mediaSizeNames;
+    private CustomMediaSizeName[] customMediaSizeNames;
+    private int defaultMediaIndex;
+    private int[] rawResolutions = null;
+    private PrinterResolution[] printerResolutions = null;
+    private boolean isCupsPrinter;
+    private boolean init;
+    private Boolean isPS;
+    private HashMap<String, AttributeClass> getAttMap;
+    private boolean pngImagesAdded = false;
+    private boolean gifImagesAdded = false;
+    private boolean jpgImagesAdded = false;
+
+
+    /**
+     * IPP Status Codes
+     */
+    private static final byte STATUSCODE_SUCCESS = 0x00;
+
+    /**
+     * IPP Group Tags.  Each tag is used once before the first attribute
+     * of that group.
+     */
+    // operation attributes group
+    private static final byte GRPTAG_OP_ATTRIBUTES = 0x01;
+    // job attributes group
+    private static final byte GRPTAG_JOB_ATTRIBUTES = 0x02;
+    // printer attributes group
+    private static final byte GRPTAG_PRINTER_ATTRIBUTES = 0x04;
+    // used as the last tag in an IPP message.
+    private static final byte GRPTAG_END_ATTRIBUTES = 0x03;
+
+    /**
+     * IPP Operation codes
+     */
+    // gets the attributes for a printer
+    public static final String OP_GET_ATTRIBUTES = "000B";
+    // gets the default printer
+    public static final String OP_CUPS_GET_DEFAULT = "4001";
+    // gets the list of printers
+    public static final String OP_CUPS_GET_PRINTERS = "4002";
+
+
+    /**
+     * List of all PrintRequestAttributes.  This is used
+     * for looping through all the IPP attribute name.
+     */
+    private static Object[] printReqAttribDefault = {
+        Chromaticity.COLOR,
+        new Copies(1),
+        Fidelity.FIDELITY_FALSE,
+        Finishings.NONE,
+        //new JobHoldUntil(new Date()),
+        //new JobImpressions(0),
+        //JobImpressions,
+        //JobKOctets,
+        //JobMediaSheets,
+        new JobName("", Locale.getDefault()),
+        //JobPriority,
+        JobSheets.NONE,
+        (Media)MediaSizeName.NA_LETTER,
+        //MediaPrintableArea.class, // not an IPP attribute
+        //MultipleDocumentHandling.SINGLE_DOCUMENT,
+        new NumberUp(1),
+        OrientationRequested.PORTRAIT,
+        new PageRanges(1),
+        //PresentationDirection,
+                 // CUPS does not supply printer-resolution attribute
+        //new PrinterResolution(300, 300, PrinterResolution.DPI),
+        //PrintQuality.NORMAL,
+        new RequestingUserName("", Locale.getDefault()),
+        //SheetCollate.UNCOLLATED, //CUPS has no sheet collate?
+        Sides.ONE_SIDED,
+    };
+
+
+    /**
+     * List of all PrintServiceAttributes.  This is used
+     * for looping through all the IPP attribute name.
+     */
+    private static Object[][] serviceAttributes = {
+        {ColorSupported.class, "color-supported"},
+        {PagesPerMinute.class,  "pages-per-minute"},
+        {PagesPerMinuteColor.class, "pages-per-minute-color"},
+        {PDLOverrideSupported.class, "pdl-override-supported"},
+        {PrinterInfo.class, "printer-info"},
+        {PrinterIsAcceptingJobs.class, "printer-is-accepting-jobs"},
+        {PrinterLocation.class, "printer-location"},
+        {PrinterMakeAndModel.class, "printer-make-and-model"},
+        {PrinterMessageFromOperator.class, "printer-message-from-operator"},
+        {PrinterMoreInfo.class, "printer-more-info"},
+        {PrinterMoreInfoManufacturer.class, "printer-more-info-manufacturer"},
+        {PrinterName.class, "printer-name"},
+        {PrinterState.class, "printer-state"},
+        {PrinterStateReasons.class, "printer-state-reasons"},
+        {PrinterURI.class, "printer-uri"},
+        {QueuedJobCount.class, "queued-job-count"}
+    };
+
+
+    /**
+     * List of DocFlavors, grouped based on matching mime-type.
+     * NOTE: For any change in the predefined DocFlavors, it must be reflected
+     * here also.
+     */
+    // PDF DocFlavors
+    private static DocFlavor[] appPDF = {
+        DocFlavor.BYTE_ARRAY.PDF,
+        DocFlavor.INPUT_STREAM.PDF,
+        DocFlavor.URL.PDF
+    };
+
+    // Postscript DocFlavors
+    private static DocFlavor[] appPostScript = {
+        DocFlavor.BYTE_ARRAY.POSTSCRIPT,
+        DocFlavor.INPUT_STREAM.POSTSCRIPT,
+        DocFlavor.URL.POSTSCRIPT
+    };
+
+    // Autosense DocFlavors
+    private static DocFlavor[] appOctetStream = {
+        DocFlavor.BYTE_ARRAY.AUTOSENSE,
+        DocFlavor.INPUT_STREAM.AUTOSENSE,
+        DocFlavor.URL.AUTOSENSE
+    };
+
+    // Text DocFlavors
+    private static DocFlavor[] textPlain = {
+        DocFlavor.BYTE_ARRAY.TEXT_PLAIN_UTF_8,
+        DocFlavor.BYTE_ARRAY.TEXT_PLAIN_UTF_16,
+        DocFlavor.BYTE_ARRAY.TEXT_PLAIN_UTF_16BE,
+        DocFlavor.BYTE_ARRAY.TEXT_PLAIN_UTF_16LE,
+        DocFlavor.BYTE_ARRAY.TEXT_PLAIN_US_ASCII,
+        DocFlavor.INPUT_STREAM.TEXT_PLAIN_UTF_8,
+        DocFlavor.INPUT_STREAM.TEXT_PLAIN_UTF_16,
+        DocFlavor.INPUT_STREAM.TEXT_PLAIN_UTF_16BE,
+        DocFlavor.INPUT_STREAM.TEXT_PLAIN_UTF_16LE,
+        DocFlavor.INPUT_STREAM.TEXT_PLAIN_US_ASCII,
+        DocFlavor.URL.TEXT_PLAIN_UTF_8,
+        DocFlavor.URL.TEXT_PLAIN_UTF_16,
+        DocFlavor.URL.TEXT_PLAIN_UTF_16BE,
+        DocFlavor.URL.TEXT_PLAIN_UTF_16LE,
+        DocFlavor.URL.TEXT_PLAIN_US_ASCII,
+        DocFlavor.CHAR_ARRAY.TEXT_PLAIN,
+        DocFlavor.STRING.TEXT_PLAIN,
+        DocFlavor.READER.TEXT_PLAIN
+    };
+
+    private static DocFlavor[] textPlainHost = {
+        DocFlavor.BYTE_ARRAY.TEXT_PLAIN_HOST,
+        DocFlavor.INPUT_STREAM.TEXT_PLAIN_HOST,
+        DocFlavor.URL.TEXT_PLAIN_HOST
+    };
+
+    // JPG DocFlavors
+    private static DocFlavor[] imageJPG = {
+        DocFlavor.BYTE_ARRAY.JPEG,
+        DocFlavor.INPUT_STREAM.JPEG,
+        DocFlavor.URL.JPEG
+    };
+
+    // GIF DocFlavors
+    private static DocFlavor[] imageGIF = {
+        DocFlavor.BYTE_ARRAY.GIF,
+        DocFlavor.INPUT_STREAM.GIF,
+        DocFlavor.URL.GIF
+    };
+
+    // PNG DocFlavors
+    private static DocFlavor[] imagePNG = {
+        DocFlavor.BYTE_ARRAY.PNG,
+        DocFlavor.INPUT_STREAM.PNG,
+        DocFlavor.URL.PNG
+    };
+
+    // HTML DocFlavors
+    private  static DocFlavor[] textHtml = {
+        DocFlavor.BYTE_ARRAY.TEXT_HTML_UTF_8,
+        DocFlavor.BYTE_ARRAY.TEXT_HTML_UTF_16,
+        DocFlavor.BYTE_ARRAY.TEXT_HTML_UTF_16BE,
+        DocFlavor.BYTE_ARRAY.TEXT_HTML_UTF_16LE,
+        DocFlavor.BYTE_ARRAY.TEXT_HTML_US_ASCII,
+        DocFlavor.INPUT_STREAM.TEXT_HTML_UTF_8,
+        DocFlavor.INPUT_STREAM.TEXT_HTML_UTF_16,
+        DocFlavor.INPUT_STREAM.TEXT_HTML_UTF_16BE,
+        DocFlavor.INPUT_STREAM.TEXT_HTML_UTF_16LE,
+        DocFlavor.INPUT_STREAM.TEXT_HTML_US_ASCII,
+        DocFlavor.URL.TEXT_HTML_UTF_8,
+        DocFlavor.URL.TEXT_HTML_UTF_16,
+        DocFlavor.URL.TEXT_HTML_UTF_16BE,
+        DocFlavor.URL.TEXT_HTML_UTF_16LE,
+        DocFlavor.URL.TEXT_HTML_US_ASCII,
+        // These are not handled in UnixPrintJob so commenting these
+        // for now.
+        /*
+        DocFlavor.CHAR_ARRAY.TEXT_HTML,
+        DocFlavor.STRING.TEXT_HTML,
+        DocFlavor.READER.TEXT_HTML,
+        */
+    };
+
+    private  static DocFlavor[] textHtmlHost = {
+        DocFlavor.BYTE_ARRAY.TEXT_HTML_HOST,
+        DocFlavor.INPUT_STREAM.TEXT_HTML_HOST,
+        DocFlavor.URL.TEXT_HTML_HOST,
+    };
+
+
+    // PCL DocFlavors
+    private static DocFlavor[] appPCL = {
+        DocFlavor.BYTE_ARRAY.PCL,
+        DocFlavor.INPUT_STREAM.PCL,
+        DocFlavor.URL.PCL
+    };
+
+    // List of all DocFlavors, used in looping
+    // through all supported mime-types
+    private static Object[] allDocFlavors = {
+        appPDF, appPostScript, appOctetStream,
+        textPlain, imageJPG, imageGIF, imagePNG,
+        textHtml, appPCL,
+    };
+
+
+    IPPPrintService(String name, URL url) {
+        if ((name == null) || (url == null)){
+            throw new IllegalArgumentException("null uri or printer name");
+        }
+        printer = name;
+        supportedDocFlavors = null;
+        supportedCats = null;
+        mediaSizeNames = null;
+        customMediaSizeNames = null;
+        mediaTrays = null;
+        myURL = url;
+        cps = null;
+        isCupsPrinter = false;
+        init = false;
+        defaultMediaIndex = -1;
+
+        String host = myURL.getHost();
+        if (host!=null && host.equals(CUPSPrinter.getServer())) {
+            isCupsPrinter = true;
+            try {
+                myURI =  new URI("ipp://"+host+
+                                 "/printers/"+printer);
+                debug_println(debugPrefix+"IPPPrintService myURI : "+myURI);
+            } catch (java.net.URISyntaxException e) {
+                throw new IllegalArgumentException("invalid url");
+            }
+        }
+    }
+
+
+    IPPPrintService(String name, String uriStr, boolean isCups) {
+        if ((name == null) || (uriStr == null)){
+            throw new IllegalArgumentException("null uri or printer name");
+        }
+        printer = name;
+        supportedDocFlavors = null;
+        supportedCats = null;
+        mediaSizeNames = null;
+        customMediaSizeNames = null;
+        mediaTrays = null;
+        cps = null;
+        init = false;
+        defaultMediaIndex = -1;
+        try {
+            myURL =
+                new URL(uriStr.replaceFirst("ipp", "http"));
+        } catch (Exception e) {
+            IPPPrintService.debug_println(debugPrefix+
+                                          " IPPPrintService, myURL="+
+                                          myURL+" Exception= "+
+                                          e);
+            throw new IllegalArgumentException("invalid url");
+        }
+
+        isCupsPrinter = isCups;
+        try {
+            myURI =  new URI(uriStr);
+            debug_println(debugPrefix+"IPPPrintService myURI : "+myURI);
+        } catch (java.net.URISyntaxException e) {
+            throw new IllegalArgumentException("invalid uri");
+        }
+    }
+
+
+    /*
+     * Initialize mediaSizeNames, mediaTrays and other attributes.
+     * Media size/trays are initialized to non-null values, may be 0-length
+     * array.
+     * NOTE: Must be called from a synchronized block only.
+     */
+    private void initAttributes() {
+        if (!init) {
+            // init customMediaSizeNames
+            customMediaSizeNames = new CustomMediaSizeName[0];
+
+            if ((urlConnection = getIPPConnection(myURL)) == null) {
+                mediaSizeNames = new MediaSizeName[0];
+                mediaTrays = new MediaTray[0];
+                debug_println(debugPrefix+"initAttributes, NULL urlConnection ");
+                init = true;
+                return;
+            }
+
+            // get all supported attributes through IPP
+            opGetAttributes();
+
+            if (isCupsPrinter) {
+                // note, it is possible to query media in CUPS using IPP
+                // right now we always get it from PPD.
+                // maybe use "&& (usePPD)" later?
+                // Another reason why we use PPD is because
+                // IPP currently does not support it but PPD does.
+
+                try {
+                    cps = new CUPSPrinter(printer);
+                    mediaSizeNames = cps.getMediaSizeNames();
+                    mediaTrays = cps.getMediaTrays();
+                    customMediaSizeNames = cps.getCustomMediaSizeNames();
+                    defaultMediaIndex = cps.getDefaultMediaIndex();
+                    rawResolutions = cps.getRawResolutions();
+                    urlConnection.disconnect();
+                    init = true;
+                    return;
+                } catch (Exception e) {
+                    IPPPrintService.debug_println(debugPrefix+
+                                       "initAttributes, error creating CUPSPrinter e="+e);
+                }
+            }
+
+            // use IPP to get all media,
+            Media[] allMedia = getSupportedMedia();
+            ArrayList<Media> sizeList = new ArrayList<>();
+            ArrayList<Media> trayList = new ArrayList<>();
+            for (int i=0; i<allMedia.length; i++) {
+                if (allMedia[i] instanceof MediaSizeName) {
+                    sizeList.add(allMedia[i]);
+                } else if (allMedia[i] instanceof MediaTray) {
+                    trayList.add(allMedia[i]);
+                }
+            }
+
+            if (sizeList != null) {
+                mediaSizeNames = new MediaSizeName[sizeList.size()];
+                mediaSizeNames = sizeList.toArray(mediaSizeNames);
+            }
+            if (trayList != null) {
+                mediaTrays = new MediaTray[trayList.size()];
+                mediaTrays = trayList.toArray(mediaTrays);
+            }
+            urlConnection.disconnect();
+
+            init = true;
+        }
+    }
+
+
+    public DocPrintJob createPrintJob() {
+        SecurityManager security = System.getSecurityManager();
+        if (security != null) {
+            security.checkPrintJobAccess();
+        }
+        // REMIND: create IPPPrintJob
+        return new UnixPrintJob(this);
+    }
+
+
+    public synchronized Object
+        getSupportedAttributeValues(Class<? extends Attribute> category,
+                                    DocFlavor flavor,
+                                    AttributeSet attributes)
+    {
+        if (category == null) {
+            throw new NullPointerException("null category");
+        }
+        if (!Attribute.class.isAssignableFrom(category)) {
+            throw new IllegalArgumentException(category +
+                                 " does not implement Attribute");
+        }
+        if (flavor != null) {
+            if (!isDocFlavorSupported(flavor)) {
+                throw new IllegalArgumentException(flavor +
+                                               " is an unsupported flavor");
+            } else if (isAutoSense(flavor)) {
+                return null;
+            }
+
+        }
+
+        if (!isAttributeCategorySupported(category)) {
+            return null;
+        }
+
+        /* Test if the flavor is compatible with the attributes */
+        if (!isDestinationSupported(flavor, attributes)) {
+            return null;
+        }
+
+        initAttributes();
+
+        /* Test if the flavor is compatible with the category */
+        if ((category == Copies.class) ||
+            (category == CopiesSupported.class)) {
+            if (flavor == null ||
+                !(flavor.equals(DocFlavor.INPUT_STREAM.POSTSCRIPT) ||
+                  flavor.equals(DocFlavor.URL.POSTSCRIPT) ||
+                  flavor.equals(DocFlavor.BYTE_ARRAY.POSTSCRIPT))) {
+                CopiesSupported cs = new CopiesSupported(1, MAXCOPIES);
+                AttributeClass attribClass = (getAttMap != null) ?
+                    getAttMap.get(cs.getName()) : null;
+                if (attribClass != null) {
+                    int[] range = attribClass.getIntRangeValue();
+                    cs = new CopiesSupported(range[0], range[1]);
+                }
+                return cs;
+            } else {
+                return null;
+            }
+        } else  if (category == Chromaticity.class) {
+            if (flavor == null ||
+                flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
+                flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE) ||
+                !isIPPSupportedImages(flavor.getMimeType())) {
+                Chromaticity[]arr = new Chromaticity[1];
+                arr[0] = Chromaticity.COLOR;
+                return (arr);
+            } else {
+                return null;
+            }
+        } else if (category == Destination.class) {
+            if (flavor == null ||
+                flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
+                flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE)) {
+                try {
+                    return new Destination((new File("out.ps")).toURI());
+                } catch (SecurityException se) {
+                    try {
+                        return new Destination(new URI("file:out.ps"));
+                    } catch (URISyntaxException e) {
+                        return null;
+                    }
+                }
+            }
+            return null;
+        } else if (category == Fidelity.class) {
+            Fidelity []arr = new Fidelity[2];
+            arr[0] = Fidelity.FIDELITY_FALSE;
+            arr[1] = Fidelity.FIDELITY_TRUE;
+            return arr;
+        } else if (category == Finishings.class) {
+            AttributeClass attribClass = (getAttMap != null) ?
+                getAttMap.get("finishings-supported")
+                : null;
+            if (attribClass != null) {
+                int[] finArray = attribClass.getArrayOfIntValues();
+                if ((finArray != null) && (finArray.length > 0)) {
+                    Finishings[] finSup = new Finishings[finArray.length];
+                    for (int i=0; i<finArray.length; i++) {
+                        finSup[i] = Finishings.NONE;
+                        Finishings[] fAll = (Finishings[])
+                            (new ExtFinishing(100)).getAll();
+                        for (int j=0; j<fAll.length; j++) {
+                            if (finArray[i] == fAll[j].getValue()) {
+                                finSup[i] = fAll[j];
+                                break;
+                            }
+                        }
+                    }
+                    return finSup;
+                }
+            }
+        } else if (category == JobName.class) {
+            return new JobName("Java Printing", null);
+        } else if (category == JobSheets.class) {
+            JobSheets arr[] = new JobSheets[2];
+            arr[0] = JobSheets.NONE;
+            arr[1] = JobSheets.STANDARD;
+            return arr;
+
+        } else if (category == Media.class) {
+            Media[] allMedia = new Media[mediaSizeNames.length+
+                                        mediaTrays.length];
+
+            for (int i=0; i<mediaSizeNames.length; i++) {
+                allMedia[i] = mediaSizeNames[i];
+            }
+
+            for (int i=0; i<mediaTrays.length; i++) {
+                allMedia[i+mediaSizeNames.length] = mediaTrays[i];
+            }
+
+            if (allMedia.length == 0) {
+                allMedia = new Media[1];
+                allMedia[0] = (Media)getDefaultAttributeValue(Media.class);
+            }
+
+            return allMedia;
+        } else if (category == MediaPrintableArea.class) {
+            MediaPrintableArea[] mpas = null;
+            if (cps != null) {
+                mpas = cps.getMediaPrintableArea();
+            }
+
+            if (mpas == null) {
+                mpas = new MediaPrintableArea[1];
+                mpas[0] = (MediaPrintableArea)
+                    getDefaultAttributeValue(MediaPrintableArea.class);
+            }
+
+            if ((attributes == null) || (attributes.size() == 0)) {
+                ArrayList<MediaPrintableArea> printableList =
+                                       new ArrayList<MediaPrintableArea>();
+
+                for (int i=0; i<mpas.length; i++) {
+                    if (mpas[i] != null) {
+                        printableList.add(mpas[i]);
+                    }
+                }
+                if (printableList.size() > 0) {
+                    mpas  = new MediaPrintableArea[printableList.size()];
+                    printableList.toArray(mpas);
+                }
+                return mpas;
+            }
+
+            int match = -1;
+            Media media = (Media)attributes.get(Media.class);
+            if (media != null && media instanceof MediaSizeName) {
+                MediaSizeName msn = (MediaSizeName)media;
+
+                // case when no supported mediasizenames are reported
+                // check given media against the default
+                if (mediaSizeNames.length == 0 &&
+                    msn.equals(getDefaultAttributeValue(Media.class))) {
+                    //default printable area is that of default mediasize
+                    return mpas;
+                }
+
+                for (int i=0; i<mediaSizeNames.length; i++) {
+                    if (msn.equals(mediaSizeNames[i])) {
+                        match = i;
+                    }
+                }
+            }
+
+            if (match == -1) {
+                return null;
+            } else {
+                MediaPrintableArea []arr = new MediaPrintableArea[1];
+                arr[0] = mpas[match];
+                return arr;
+            }
+        } else if (category == NumberUp.class) {
+            AttributeClass attribClass = (getAttMap != null) ?
+                getAttMap.get("number-up-supported") : null;
+            if (attribClass != null) {
+                int[] values = attribClass.getArrayOfIntValues();
+                if (values != null) {
+                    NumberUp[] nUp = new NumberUp[values.length];
+                    for (int i=0; i<values.length; i++) {
+                        nUp[i] = new NumberUp(values[i]);
+                    }
+                    return nUp;
+                } else {
+                    return null;
+                }
+            }
+        } else if (category == OrientationRequested.class) {
+            if ((flavor != null) &&
+                (flavor.equals(DocFlavor.INPUT_STREAM.POSTSCRIPT) ||
+                 flavor.equals(DocFlavor.URL.POSTSCRIPT) ||
+                 flavor.equals(DocFlavor.BYTE_ARRAY.POSTSCRIPT))) {
+                return null;
+            }
+
+            boolean revPort = false;
+            OrientationRequested[] orientSup = null;
+
+            AttributeClass attribClass = (getAttMap != null) ?
+              getAttMap.get("orientation-requested-supported")
+                : null;
+            if (attribClass != null) {
+                int[] orientArray = attribClass.getArrayOfIntValues();
+                if ((orientArray != null) && (orientArray.length > 0)) {
+                    orientSup =
+                        new OrientationRequested[orientArray.length];
+                    for (int i=0; i<orientArray.length; i++) {
+                        switch (orientArray[i]) {
+                        default:
+                        case 3 :
+                            orientSup[i] = OrientationRequested.PORTRAIT;
+                            break;
+                        case 4:
+                            orientSup[i] = OrientationRequested.LANDSCAPE;
+                            break;
+                        case 5:
+                            orientSup[i] =
+                                OrientationRequested.REVERSE_LANDSCAPE;
+                            break;
+                        case 6:
+                            orientSup[i] =
+                                OrientationRequested.REVERSE_PORTRAIT;
+                            revPort = true;
+                            break;
+                        }
+                    }
+                }
+            }
+            if (flavor == null ||
+                flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
+                flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE)) {
+
+                if (revPort && flavor == null) {
+                    OrientationRequested []orSup = new OrientationRequested[4];
+                    orSup[0] = OrientationRequested.PORTRAIT;
+                    orSup[1] = OrientationRequested.LANDSCAPE;
+                    orSup[2] = OrientationRequested.REVERSE_LANDSCAPE;
+                    orSup[3] = OrientationRequested.REVERSE_PORTRAIT;
+                    return orSup;
+                } else {
+                    OrientationRequested []orSup = new OrientationRequested[3];
+                    orSup[0] = OrientationRequested.PORTRAIT;
+                    orSup[1] = OrientationRequested.LANDSCAPE;
+                    orSup[2] = OrientationRequested.REVERSE_LANDSCAPE;
+                    return orSup;
+                }
+            } else {
+                return orientSup;
+            }
+        } else if (category == PageRanges.class) {
+           if (flavor == null ||
+                flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
+                flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE)) {
+                PageRanges []arr = new PageRanges[1];
+                arr[0] = new PageRanges(1, Integer.MAX_VALUE);
+                return arr;
+            } else {
+                // Returning null as this is not yet supported in UnixPrintJob.
+                return null;
+            }
+        } else if (category == RequestingUserName.class) {
+            String userName = "";
+            try {
+              userName = System.getProperty("user.name", "");
+            } catch (SecurityException se) {
+            }
+            return new RequestingUserName(userName, null);
+        } else if (category == Sides.class) {
+            // The printer takes care of Sides so if short-edge
+            // is chosen in a job, the rotation is done by the printer.
+            // Orientation is rotated by emulation if pageable
+            // or printable so if the document is in Landscape, this may
+            // result in double rotation.
+            AttributeClass attribClass = (getAttMap != null) ?
+                getAttMap.get("sides-supported")
+                : null;
+            if (attribClass != null) {
+                String[] sidesArray = attribClass.getArrayOfStringValues();
+                if ((sidesArray != null) && (sidesArray.length > 0)) {
+                    Sides[] sidesSup = new Sides[sidesArray.length];
+                    for (int i=0; i<sidesArray.length; i++) {
+                        if (sidesArray[i].endsWith("long-edge")) {
+                            sidesSup[i] = Sides.TWO_SIDED_LONG_EDGE;
+                        } else if (sidesArray[i].endsWith("short-edge")) {
+                            sidesSup[i] = Sides.TWO_SIDED_SHORT_EDGE;
+                        } else {
+                            sidesSup[i] = Sides.ONE_SIDED;
+                        }
+                    }
+                    return sidesSup;
+                }
+            }
+        } else if (category == PrinterResolution.class) {
+            PrinterResolution[] supportedRes = getPrintResolutions();
+            if (supportedRes == null) {
+                return null;
+            }
+            PrinterResolution []arr =
+                new PrinterResolution[supportedRes.length];
+            System.arraycopy(supportedRes, 0, arr, 0, supportedRes.length);
+            return arr;
+        }
+
+        return null;
+    }
+
+    //This class is for getting all pre-defined Finishings
+    @SuppressWarnings("serial") // JDK implementation class
+    private class ExtFinishing extends Finishings {
+        ExtFinishing(int value) {
+            super(100); // 100 to avoid any conflicts with predefined values.
+        }
+
+        EnumSyntax[] getAll() {
+            EnumSyntax[] es = super.getEnumValueTable();
+            return es;
+        }
+    }
+
+
+    public AttributeSet getUnsupportedAttributes(DocFlavor flavor,
+                                                 AttributeSet attributes) {
+        if (flavor != null && !isDocFlavorSupported(flavor)) {
+            throw new IllegalArgumentException("flavor " + flavor +
+                                               "is not supported");
+        }
+
+        if (attributes == null) {
+            return null;
+        }
+
+        Attribute attr;
+        AttributeSet unsupp = new HashAttributeSet();
+        Attribute []attrs = attributes.toArray();
+        for (int i=0; i<attrs.length; i++) {
+            try {
+                attr = attrs[i];
+                if (!isAttributeCategorySupported(attr.getCategory())) {
+                    unsupp.add(attr);
+                } else if (!isAttributeValueSupported(attr, flavor,
+                                                      attributes)) {
+                    unsupp.add(attr);
+                }
+            } catch (ClassCastException e) {
+            }
+        }
+        if (unsupp.isEmpty()) {
+            return null;
+        } else {
+            return unsupp;
+        }
+    }
+
+
+    public synchronized DocFlavor[] getSupportedDocFlavors() {
+
+        if (supportedDocFlavors != null) {
+            int len = supportedDocFlavors.length;
+                DocFlavor[] copyflavors = new DocFlavor[len];
+                System.arraycopy(supportedDocFlavors, 0, copyflavors, 0, len);
+                return copyflavors;
+        }
+        initAttributes();
+
+        if ((getAttMap != null) &&
+            getAttMap.containsKey("document-format-supported")) {
+
+            AttributeClass attribClass =
+                getAttMap.get("document-format-supported");
+            if (attribClass != null) {
+                String mimeType;
+                boolean psSupported = false;
+                String[] docFlavors = attribClass.getArrayOfStringValues();
+                DocFlavor[] flavors;
+                HashSet<Object> docList = new HashSet<>();
+                int j;
+                String hostEnc = DocFlavor.hostEncoding.
+                    toLowerCase(Locale.ENGLISH);
+                boolean addHostEncoding = !hostEnc.equals("utf-8") &&
+                    !hostEnc.equals("utf-16") && !hostEnc.equals("utf-16be") &&
+                    !hostEnc.equals("utf-16le") && !hostEnc.equals("us-ascii");
+
+                for (int i = 0; i < docFlavors.length; i++) {
+                    for (j=0; j<allDocFlavors.length; j++) {
+                        flavors = (DocFlavor[])allDocFlavors[j];
+
+                        mimeType = flavors[0].getMimeType();
+                        if (mimeType.startsWith(docFlavors[i])) {
+
+                            docList.addAll(Arrays.asList(flavors));
+
+                            if (mimeType.equals("text/plain") &&
+                                addHostEncoding) {
+                                docList.add(Arrays.asList(textPlainHost));
+                            } else if (mimeType.equals("text/html") &&
+                                       addHostEncoding) {
+                                docList.add(Arrays.asList(textHtmlHost));
+                            } else if (mimeType.equals("image/png")) {
+                                pngImagesAdded = true;
+                            } else if (mimeType.equals("image/gif")) {
+                                gifImagesAdded = true;
+                            } else if (mimeType.equals("image/jpeg")) {
+                                jpgImagesAdded = true;
+                            } else if (mimeType.indexOf("postscript") != -1) {
+                                psSupported = true;
+                            }
+                            break;
+                        }
+                    }
+
+                    // Not added? Create new DocFlavors
+                    if (j == allDocFlavors.length) {
+                        //  make new DocFlavors
+                        docList.add(new DocFlavor.BYTE_ARRAY(docFlavors[i]));
+                        docList.add(new DocFlavor.INPUT_STREAM(docFlavors[i]));
+                        docList.add(new DocFlavor.URL(docFlavors[i]));
+                    }
+                }
+
+                // check if we need to add image DocFlavors
+                // and Pageable/Printable flavors
+                if (psSupported || isCupsPrinter) {
+                    /*
+                     Always add Pageable and Printable for CUPS
+                     since it uses Filters to convert from Postscript
+                     to device printer language.
+                    */
+                    docList.add(DocFlavor.SERVICE_FORMATTED.PAGEABLE);
+                    docList.add(DocFlavor.SERVICE_FORMATTED.PRINTABLE);
+
+                    docList.addAll(Arrays.asList(imageJPG));
+                    docList.addAll(Arrays.asList(imagePNG));
+                    docList.addAll(Arrays.asList(imageGIF));
+                }
+                supportedDocFlavors = new DocFlavor[docList.size()];
+                docList.toArray(supportedDocFlavors);
+                int len = supportedDocFlavors.length;
+                DocFlavor[] copyflavors = new DocFlavor[len];
+                System.arraycopy(supportedDocFlavors, 0, copyflavors, 0, len);
+                return copyflavors;
+            }
+        }
+        return null;
+    }
+
+
+    public boolean isDocFlavorSupported(DocFlavor flavor) {
+        if (supportedDocFlavors == null) {
+            getSupportedDocFlavors();
+        }
+        if (supportedDocFlavors != null) {
+            for (int f=0; f<supportedDocFlavors.length; f++) {
+                if (flavor.equals(supportedDocFlavors[f])) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+
+    /**
+     * Finds matching CustomMediaSizeName of given media.
+     */
+    public CustomMediaSizeName findCustomMedia(MediaSizeName media) {
+        if (customMediaSizeNames == null) {
+            return null;
+        }
+        for (int i=0; i< customMediaSizeNames.length; i++) {
+            CustomMediaSizeName custom = customMediaSizeNames[i];
+            MediaSizeName msn = custom.getStandardMedia();
+            if (media.equals(msn)) {
+                return customMediaSizeNames[i];
+            }
+        }
+        return null;
+    }
+
+
+    /**
+     * Returns the matching standard Media using string comparison of names.
+     */
+    private Media getIPPMedia(String mediaName) {
+        CustomMediaSizeName sampleSize = new CustomMediaSizeName("sample", "",
+                                                                 0, 0);
+        Media[] sizes = sampleSize.getSuperEnumTable();
+        for (int i=0; i<sizes.length; i++) {
+            if (mediaName.equals(""+sizes[i])) {
+                return sizes[i];
+            }
+        }
+        CustomMediaTray sampleTray = new CustomMediaTray("sample", "");
+        Media[] trays = sampleTray.getSuperEnumTable();
+        for (int i=0; i<trays.length; i++) {
+            if (mediaName.equals(""+trays[i])) {
+                return trays[i];
+            }
+        }
+        return null;
+    }
+
+    private Media[] getSupportedMedia() {
+        if ((getAttMap != null) &&
+            getAttMap.containsKey("media-supported")) {
+
+            AttributeClass attribClass = getAttMap.get("media-supported");
+
+            if (attribClass != null) {
+                String[] mediaVals = attribClass.getArrayOfStringValues();
+                Media msn;
+                Media[] mediaNames =
+                    new Media[mediaVals.length];
+                for (int i=0; i<mediaVals.length; i++) {
+                    msn = getIPPMedia(mediaVals[i]);
+                    //REMIND: if null, create custom?
+                    mediaNames[i] = msn;
+                }
+                return mediaNames;
+            }
+        }
+        return new Media[0];
+    }
+
+
+    public synchronized Class<?>[] getSupportedAttributeCategories() {
+        if (supportedCats != null) {
+            return supportedCats;
+        }
+
+        initAttributes();
+
+        ArrayList<Class<?>> catList = new ArrayList<>();
+
+        for (int i=0; i < printReqAttribDefault.length; i++) {
+            PrintRequestAttribute pra =
+                (PrintRequestAttribute)printReqAttribDefault[i];
+            if (getAttMap != null &&
+                getAttMap.containsKey(pra.getName()+"-supported")) {
+                catList.add(pra.getCategory());
+            }
+        }
+
+        // Some IPP printers like lexc710 do not have list of supported media
+        // but CUPS can get the media from PPD, so we still report as
+        // supported category.
+        if (isCupsPrinter) {
+            if (!catList.contains(Media.class)) {
+                catList.add(Media.class);
+            }
+
+            // Always add MediaPrintable for cups,
+            // because we can get it from PPD.
+            catList.add(MediaPrintableArea.class);
+
+            // this is already supported in UnixPrintJob
+            catList.add(Destination.class);
+
+            // It is unfortunate that CUPS doesn't provide a way to query
+            // if printer supports collation but since most printers
+            // now supports collation and that most OS has a way
+            // of setting it, it is a safe assumption to just always
+            // include SheetCollate as supported attribute.
+
+            /*
+               In Linux, we use Postscript for rendering but Linux still
+               has issues in propagating Postscript-embedded setpagedevice
+               setting like collation.  Therefore, we temporarily exclude
+               Linux.
+            */
+            if (!UnixPrintServiceLookup.isLinux()) {
+                catList.add(SheetCollate.class);
+            }
+        }
+
+        // With the assumption that  Chromaticity is equivalent to
+        // ColorSupported.
+        if (getAttMap != null && getAttMap.containsKey("color-supported")) {
+            catList.add(Chromaticity.class);
+        }
+
+        // CUPS does not report printer resolution via IPP but it
+        // may be gleaned from the PPD.
+        PrinterResolution[] supportedRes = getPrintResolutions();
+        if (supportedRes != null && (supportedRes.length > 0)) {
+            catList.add(PrinterResolution.class);
+        }
+
+        supportedCats = new Class<?>[catList.size()];
+        catList.toArray(supportedCats);
+        return supportedCats;
+    }
+
+
+    public boolean
+        isAttributeCategorySupported(Class<? extends Attribute> category)
+    {
+        if (category == null) {
+            throw new NullPointerException("null category");
+        }
+        if (!(Attribute.class.isAssignableFrom(category))) {
+            throw new IllegalArgumentException(category +
+                                             " is not an Attribute");
+        }
+
+        if (supportedCats == null) {
+            getSupportedAttributeCategories();
+        }
+
+        // It is safe to assume that Orientation is always supported
+        // and even if CUPS or an IPP device reports it as not,
+        // our renderer can do portrait, landscape and
+        // reverse landscape.
+        if (category == OrientationRequested.class) {
+            return true;
+        }
+
+        for (int i=0;i<supportedCats.length;i++) {
+            if (category == supportedCats[i]) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    @SuppressWarnings("unchecked")
+    public synchronized <T extends PrintServiceAttribute>
+        T getAttribute(Class<T> category)
+    {
+        if (category == null) {
+            throw new NullPointerException("category");
+        }
+        if (!(PrintServiceAttribute.class.isAssignableFrom(category))) {
+            throw new IllegalArgumentException("Not a PrintServiceAttribute");
+        }
+
+        initAttributes();
+
+        if (category == PrinterName.class) {
+            return (T)(new PrinterName(printer, null));
+        } else if (category == PrinterInfo.class) {
+            PrinterInfo pInfo = new PrinterInfo(printer, null);
+            AttributeClass ac = (getAttMap != null) ?
+                getAttMap.get(pInfo.getName())
+                : null;
+            if (ac != null) {
+                return (T)(new PrinterInfo(ac.getStringValue(), null));
+            }
+            return (T)pInfo;
+        } else if (category == QueuedJobCount.class) {
+            QueuedJobCount qjc = new QueuedJobCount(0);
+            AttributeClass ac = (getAttMap != null) ?
+                getAttMap.get(qjc.getName())
+                : null;
+            if (ac != null) {
+                qjc = new QueuedJobCount(ac.getIntValue());
+            }
+            return (T)qjc;
+        } else if (category == PrinterIsAcceptingJobs.class) {
+            PrinterIsAcceptingJobs accJob =
+                PrinterIsAcceptingJobs.ACCEPTING_JOBS;
+            AttributeClass ac = (getAttMap != null) ?
+                getAttMap.get(accJob.getName())
+                : null;
+            if ((ac != null) && (ac.getByteValue() == 0)) {
+                accJob = PrinterIsAcceptingJobs.NOT_ACCEPTING_JOBS;
+            }
+            return (T)accJob;
+        } else if (category == ColorSupported.class) {
+            ColorSupported cs = ColorSupported.SUPPORTED;
+            AttributeClass ac = (getAttMap != null) ?
+                getAttMap.get(cs.getName())
+                : null;
+            if ((ac != null) && (ac.getByteValue() == 0)) {
+                cs = ColorSupported.NOT_SUPPORTED;
+            }
+            return (T)cs;
+        } else if (category == PDLOverrideSupported.class) {
+
+            if (isCupsPrinter) {
+                // Documented: For CUPS this will always be false
+                return (T)PDLOverrideSupported.NOT_ATTEMPTED;
+            } else {
+                // REMIND: check attribute values
+                return (T)PDLOverrideSupported.NOT_ATTEMPTED;
+            }
+        } else if (category == PrinterURI.class) {
+            return (T)(new PrinterURI(myURI));
+        } else {
+            return null;
+        }
+    }
+
+
+    public synchronized PrintServiceAttributeSet getAttributes() {
+        // update getAttMap by sending again get-attributes IPP request
+        init = false;
+        initAttributes();
+
+        HashPrintServiceAttributeSet attrs =
+            new HashPrintServiceAttributeSet();
+
+        for (int i=0; i < serviceAttributes.length; i++) {
+            String name = (String)serviceAttributes[i][1];
+            if (getAttMap != null && getAttMap.containsKey(name)) {
+                @SuppressWarnings("unchecked")
+                Class<PrintServiceAttribute> c = (Class<PrintServiceAttribute>)serviceAttributes[i][0];
+                PrintServiceAttribute psa = getAttribute(c);
+                if (psa != null) {
+                    attrs.add(psa);
+                }
+            }
+        }
+        return AttributeSetUtilities.unmodifiableView(attrs);
+    }
+
+    public boolean isIPPSupportedImages(String mimeType) {
+        if (supportedDocFlavors == null) {
+            getSupportedDocFlavors();
+        }
+
+        if (mimeType.equals("image/png") && pngImagesAdded) {
+            return true;
+        } else if (mimeType.equals("image/gif") && gifImagesAdded) {
+            return true;
+        } else if (mimeType.equals("image/jpeg") && jpgImagesAdded) {
+            return true;
+        }
+
+        return false;
+    }
+
+
+    private boolean isSupportedCopies(Copies copies) {
+        CopiesSupported cs = (CopiesSupported)
+            getSupportedAttributeValues(Copies.class, null, null);
+        int[][] members = cs.getMembers();
+        int min, max;
+        if ((members.length > 0) && (members[0].length > 0)) {
+            min = members[0][0];
+            max = members[0][1];
+        } else {
+            min = 1;
+            max = MAXCOPIES;
+        }
+
+        int value = copies.getValue();
+        return (value >= min && value <= max);
+    }
+
+    private boolean isAutoSense(DocFlavor flavor) {
+        if (flavor.equals(DocFlavor.BYTE_ARRAY.AUTOSENSE) ||
+            flavor.equals(DocFlavor.INPUT_STREAM.AUTOSENSE) ||
+            flavor.equals(DocFlavor.URL.AUTOSENSE)) {
+            return true;
+        }
+        else {
+            return false;
+        }
+    }
+
+    private synchronized boolean isSupportedMediaTray(MediaTray msn) {
+        initAttributes();
+
+        if (mediaTrays != null) {
+            for (int i=0; i<mediaTrays.length; i++) {
+               if (msn.equals(mediaTrays[i])) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private synchronized boolean isSupportedMedia(MediaSizeName msn) {
+        initAttributes();
+
+        if (msn.equals((Media)getDefaultAttributeValue(Media.class))) {
+            return true;
+        }
+        for (int i=0; i<mediaSizeNames.length; i++) {
+            debug_println(debugPrefix+"isSupportedMedia, mediaSizeNames[i] "+mediaSizeNames[i]);
+            if (msn.equals(mediaSizeNames[i])) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /* Return false if flavor is not null, pageable, nor printable and
+     * Destination is part of attributes.
+     */
+    private boolean
+        isDestinationSupported(DocFlavor flavor, AttributeSet attributes) {
+
+            if ((attributes != null) &&
+                    (attributes.get(Destination.class) != null) &&
+                    !(flavor == null ||
+                      flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
+                      flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE))) {
+                return false;
+            }
+            return true;
+    }
+
+
+    public boolean isAttributeValueSupported(Attribute attr,
+                                             DocFlavor flavor,
+                                             AttributeSet attributes) {
+        if (attr == null) {
+            throw new NullPointerException("null attribute");
+        }
+        if (flavor != null) {
+            if (!isDocFlavorSupported(flavor)) {
+                throw new IllegalArgumentException(flavor +
+                                               " is an unsupported flavor");
+            } else if (isAutoSense(flavor)) {
+                return false;
+            }
+        }
+        Class<? extends Attribute> category = attr.getCategory();
+        if (!isAttributeCategorySupported(category)) {
+            return false;
+        }
+
+        /* Test if the flavor is compatible with the attributes */
+        if (!isDestinationSupported(flavor, attributes)) {
+            return false;
+        }
+
+        /* Test if the flavor is compatible with the category */
+        if (attr.getCategory() == Chromaticity.class) {
+            if ((flavor == null) ||
+                flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
+                flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE) ||
+                !isIPPSupportedImages(flavor.getMimeType())) {
+                return attr == Chromaticity.COLOR;
+            } else {
+                return false;
+            }
+        } else if (attr.getCategory() == Copies.class) {
+            return (flavor == null ||
+                   !(flavor.equals(DocFlavor.INPUT_STREAM.POSTSCRIPT) ||
+                   flavor.equals(DocFlavor.URL.POSTSCRIPT) ||
+                   flavor.equals(DocFlavor.BYTE_ARRAY.POSTSCRIPT))) &&
+                isSupportedCopies((Copies)attr);
+
+        } else if (attr.getCategory() == Destination.class) {
+            if (flavor == null ||
+                flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
+                flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE)) {
+                URI uri = ((Destination)attr).getURI();
+                if ("file".equals(uri.getScheme()) &&
+                    !(uri.getSchemeSpecificPart().equals(""))) {
+                    return true;
+                }
+            }
+            return false;
+        } else if (attr.getCategory() == Media.class) {
+            if (attr instanceof MediaSizeName) {
+                return isSupportedMedia((MediaSizeName)attr);
+            }
+            if (attr instanceof MediaTray) {
+                return isSupportedMediaTray((MediaTray)attr);
+            }
+        } else if (attr.getCategory() == PageRanges.class) {
+            if (flavor != null &&
+                !(flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
+                flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE))) {
+                return false;
+            }
+        } else if (attr.getCategory() == SheetCollate.class) {
+            if (flavor != null &&
+                !(flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
+                flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE))) {
+                return false;
+            }
+        } else if (attr.getCategory() == Sides.class) {
+            Sides[] sidesArray = (Sides[])getSupportedAttributeValues(
+                                          Sides.class,
+                                          flavor,
+                                          attributes);
+
+            if (sidesArray != null) {
+                for (int i=0; i<sidesArray.length; i++) {
+                    if (sidesArray[i] == (Sides)attr) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        } else if (attr.getCategory() == OrientationRequested.class) {
+            OrientationRequested[] orientArray =
+                (OrientationRequested[])getSupportedAttributeValues(
+                                          OrientationRequested.class,
+                                          flavor,
+                                          attributes);
+
+            if (orientArray != null) {
+                for (int i=0; i<orientArray.length; i++) {
+                    if (orientArray[i] == (OrientationRequested)attr) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        } if (attr.getCategory() == PrinterResolution.class) {
+            if (attr instanceof PrinterResolution) {
+                return isSupportedResolution((PrinterResolution)attr);
+            }
+        }
+        return true;
+    }
+
+
+    public synchronized Object
+        getDefaultAttributeValue(Class<? extends Attribute> category)
+    {
+        if (category == null) {
+            throw new NullPointerException("null category");
+        }
+        if (!Attribute.class.isAssignableFrom(category)) {
+            throw new IllegalArgumentException(category +
+                                             " is not an Attribute");
+        }
+        if (!isAttributeCategorySupported(category)) {
+            return null;
+        }
+
+        initAttributes();
+
+        String catName = null;
+        for (int i=0; i < printReqAttribDefault.length; i++) {
+            PrintRequestAttribute pra =
+                (PrintRequestAttribute)printReqAttribDefault[i];
+            if (pra.getCategory() == category) {
+                catName = pra.getName();
+                break;
+            }
+        }
+        String attribName = catName+"-default";
+        AttributeClass attribClass = (getAttMap != null) ?
+                getAttMap.get(attribName) : null;
+
+        if (category == Copies.class) {
+            if (attribClass != null) {
+                return new Copies(attribClass.getIntValue());
+            } else {
+                return new Copies(1);
+            }
+        } else if (category == Chromaticity.class) {
+            return Chromaticity.COLOR;
+        } else if (category == Destination.class) {
+            try {
+                return new Destination((new File("out.ps")).toURI());
+            } catch (SecurityException se) {
+                try {
+                    return new Destination(new URI("file:out.ps"));
+                } catch (URISyntaxException e) {
+                    return null;
+                }
+            }
+        } else if (category == Fidelity.class) {
+            return Fidelity.FIDELITY_FALSE;
+        } else if (category == Finishings.class) {
+            return Finishings.NONE;
+        } else if (category == JobName.class) {
+            return new JobName("Java Printing", null);
+        } else if (category == JobSheets.class) {
+            if (attribClass != null &&
+                attribClass.getStringValue().equals("none")) {
+                return JobSheets.NONE;
+            } else {
+                return JobSheets.STANDARD;
+            }
+        } else if (category == Media.class) {
+            if (defaultMediaIndex == -1) {
+                defaultMediaIndex = 0;
+            }
+            if (mediaSizeNames.length == 0) {
+                String defaultCountry = Locale.getDefault().getCountry();
+                if (defaultCountry != null &&
+                    (defaultCountry.equals("") ||
+                     defaultCountry.equals(Locale.US.getCountry()) ||
+                     defaultCountry.equals(Locale.CANADA.getCountry()))) {
+                    return MediaSizeName.NA_LETTER;
+                } else {
+                    return MediaSizeName.ISO_A4;
+                }
+            }
+
+            if (attribClass != null) {
+                String name = attribClass.getStringValue();
+                if (isCupsPrinter) {
+                    return mediaSizeNames[defaultMediaIndex];
+                } else {
+                    for (int i=0; i< mediaSizeNames.length; i++) {
+                        if (mediaSizeNames[i].toString().indexOf(name) != -1) {
+                            defaultMediaIndex = i;
+                            return mediaSizeNames[defaultMediaIndex];
+                        }
+                    }
+                }
+            }
+            return mediaSizeNames[defaultMediaIndex];
+
+        } else if (category == MediaPrintableArea.class) {
+            MediaPrintableArea[] mpas;
+             if ((cps != null)  &&
+                 ((mpas = cps.getMediaPrintableArea()) != null)) {
+                 if (defaultMediaIndex == -1) {
+                     // initializes value of defaultMediaIndex
+                     getDefaultAttributeValue(Media.class);
+                 }
+                 return mpas[defaultMediaIndex];
+             } else {
+                 String defaultCountry = Locale.getDefault().getCountry();
+                 float iw, ih;
+                 if (defaultCountry != null &&
+                     (defaultCountry.equals("") ||
+                      defaultCountry.equals(Locale.US.getCountry()) ||
+                      defaultCountry.equals(Locale.CANADA.getCountry()))) {
+                     iw = MediaSize.NA.LETTER.getX(Size2DSyntax.INCH) - 0.5f;
+                     ih = MediaSize.NA.LETTER.getY(Size2DSyntax.INCH) - 0.5f;
+                 } else {
+                     iw = MediaSize.ISO.A4.getX(Size2DSyntax.INCH) - 0.5f;
+                     ih = MediaSize.ISO.A4.getY(Size2DSyntax.INCH) - 0.5f;
+                 }
+                 return new MediaPrintableArea(0.25f, 0.25f, iw, ih,
+                                               MediaPrintableArea.INCH);
+             }
+        } else if (category == NumberUp.class) {
+            return new NumberUp(1); // for CUPS this is always 1
+        } else if (category == OrientationRequested.class) {
+            if (attribClass != null) {
+                switch (attribClass.getIntValue()) {
+                default:
+                case 3: return OrientationRequested.PORTRAIT;
+                case 4: return OrientationRequested.LANDSCAPE;
+                case 5: return OrientationRequested.REVERSE_LANDSCAPE;
+                case 6: return OrientationRequested.REVERSE_PORTRAIT;
+                }
+            } else {
+                return OrientationRequested.PORTRAIT;
+            }
+        } else if (category == PageRanges.class) {
+            if (attribClass != null) {
+                int[] range = attribClass.getIntRangeValue();
+                return new PageRanges(range[0], range[1]);
+            } else {
+                return new PageRanges(1, Integer.MAX_VALUE);
+            }
+        } else if (category == RequestingUserName.class) {
+            String userName = "";
+            try {
+              userName = System.getProperty("user.name", "");
+            } catch (SecurityException se) {
+            }
+            return new RequestingUserName(userName, null);
+        } else if (category == SheetCollate.class) {
+            return SheetCollate.UNCOLLATED;
+        } else if (category == Sides.class) {
+            if (attribClass != null) {
+                if (attribClass.getStringValue().endsWith("long-edge")) {
+                    return Sides.TWO_SIDED_LONG_EDGE;
+                } else if (attribClass.getStringValue().endsWith(
+                                                           "short-edge")) {
+                    return Sides.TWO_SIDED_SHORT_EDGE;
+                }
+            }
+            return Sides.ONE_SIDED;
+        } else if (category == PrinterResolution.class) {
+             PrinterResolution[] supportedRes = getPrintResolutions();
+             if ((supportedRes != null) && (supportedRes.length > 0)) {
+                return supportedRes[0];
+             } else {
+                 return new PrinterResolution(300, 300, PrinterResolution.DPI);
+             }
+        }
+
+        return null;
+    }
+
+    private PrinterResolution[] getPrintResolutions() {
+        if (printerResolutions == null) {
+            if (rawResolutions == null) {
+              printerResolutions = new PrinterResolution[0];
+            } else {
+                int numRes = rawResolutions.length / 2;
+                PrinterResolution[] pres = new PrinterResolution[numRes];
+                for (int i=0; i < numRes; i++) {
+                    pres[i] =  new PrinterResolution(rawResolutions[i*2],
+                                                     rawResolutions[i*2+1],
+                                                     PrinterResolution.DPI);
+                }
+                printerResolutions = pres;
+            }
+        }
+        return printerResolutions;
+    }
+
+    private boolean isSupportedResolution(PrinterResolution res) {
+        PrinterResolution[] supportedRes = getPrintResolutions();
+        if (supportedRes != null) {
+            for (int i=0; i<supportedRes.length; i++) {
+                if (res.equals(supportedRes[i])) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public ServiceUIFactory getServiceUIFactory() {
+        return null;
+    }
+
+    public void wakeNotifier() {
+        synchronized (this) {
+            if (notifier != null) {
+                notifier.wake();
+            }
+        }
+    }
+
+    public void addPrintServiceAttributeListener(
+                                 PrintServiceAttributeListener listener) {
+        synchronized (this) {
+            if (listener == null) {
+                return;
+            }
+            if (notifier == null) {
+                notifier = new ServiceNotifier(this);
+            }
+            notifier.addListener(listener);
+        }
+    }
+
+    public void removePrintServiceAttributeListener(
+                                  PrintServiceAttributeListener listener) {
+        synchronized (this) {
+            if (listener == null || notifier == null ) {
+                return;
+            }
+            notifier.removeListener(listener);
+            if (notifier.isEmpty()) {
+                notifier.stopNotifier();
+                notifier = null;
+            }
+        }
+    }
+
+    String getDest() {
+        return printer;
+    }
+
+    public String getName() {
+        /*
+         * Mac is using printer-info IPP attribute for its human-readable printer
+         * name and is also the identifier used in NSPrintInfo:setPrinter.
+         */
+        if (UnixPrintServiceLookup.isMac()) {
+            PrintServiceAttributeSet psaSet = this.getAttributes();
+            if (psaSet != null) {
+                PrinterInfo pName = (PrinterInfo)psaSet.get(PrinterInfo.class);
+                if (pName != null) {
+                    return pName.toString();
+                }
+            }
+        }
+        return printer;
+    }
+
+
+    public boolean usesClass(Class<?> c) {
+        return (c == sun.print.PSPrinterJob.class);
+    }
+
+
+    public static HttpURLConnection getIPPConnection(URL url) {
+        HttpURLConnection connection;
+        URLConnection urlc;
+        try {
+            urlc = url.openConnection();
+        } catch (java.io.IOException ioe) {
+            return null;
+        }
+        if (!(urlc instanceof HttpURLConnection)) {
+            return null;
+        }
+        connection = (HttpURLConnection)urlc;
+        connection.setUseCaches(false);
+        connection.setDefaultUseCaches(false);
+        connection.setDoInput(true);
+        connection.setDoOutput(true);
+        connection.setRequestProperty("Content-type", "application/ipp");
+        return connection;
+    }
+
+
+    public synchronized boolean isPostscript() {
+        if (isPS == null) {
+           isPS = Boolean.TRUE;
+            if (isCupsPrinter) {
+                try {
+                    urlConnection = getIPPConnection(
+                                             new URL(myURL+".ppd"));
+
+                   InputStream is = urlConnection.getInputStream();
+                   if (is != null) {
+                       BufferedReader d =
+                           new BufferedReader(new InputStreamReader(is,
+                                                          Charset.forName("ISO-8859-1")));
+                       String lineStr;
+                       while ((lineStr = d.readLine()) != null) {
+                           if (lineStr.startsWith("*cupsFilter:")) {
+                               isPS = Boolean.FALSE;
+                               break;
+                           }
+                       }
+                    }
+                } catch (java.io.IOException e) {
+                    debug_println(" isPostscript, e= "+e);
+                    /* if PPD is not found, this may be a raw printer
+                       and in this case it is assumed that it is a
+                       Postscript printer */
+                    // do nothing
+                }
+            }
+        }
+        return isPS.booleanValue();
+    }
+
+
+    private void opGetAttributes() {
+        try {
+            debug_println(debugPrefix+"opGetAttributes myURI "+myURI+" myURL "+myURL);
+
+            AttributeClass attClNoUri[] = {
+                AttributeClass.ATTRIBUTES_CHARSET,
+                AttributeClass.ATTRIBUTES_NATURAL_LANGUAGE};
+
+            AttributeClass attCl[] = {
+                AttributeClass.ATTRIBUTES_CHARSET,
+                AttributeClass.ATTRIBUTES_NATURAL_LANGUAGE,
+                new AttributeClass("printer-uri",
+                                   AttributeClass.TAG_URI,
+                                   ""+myURI)};
+
+            OutputStream os = java.security.AccessController.
+                doPrivileged(new java.security.PrivilegedAction<OutputStream>() {
+                    public OutputStream run() {
+                        try {
+                            return urlConnection.getOutputStream();
+                        } catch (Exception e) {
+                        }
+                        return null;
+                    }
+                });
+
+            if (os == null) {
+                return;
+            }
+
+            boolean success = (myURI == null) ?
+                writeIPPRequest(os, OP_GET_ATTRIBUTES, attClNoUri) :
+                writeIPPRequest(os, OP_GET_ATTRIBUTES, attCl);
+            if (success) {
+                InputStream is = null;
+                if ((is = urlConnection.getInputStream())!=null) {
+                    HashMap<String, AttributeClass>[] responseMap = readIPPResponse(is);
+
+                    if (responseMap != null && responseMap.length > 0) {
+                        getAttMap = responseMap[0];
+                    }
+                } else {
+                    debug_println(debugPrefix+"opGetAttributes - null input stream");
+                }
+                is.close();
+            }
+            os.close();
+        } catch (java.io.IOException e) {
+            debug_println(debugPrefix+"opGetAttributes - input/output stream: "+e);
+        }
+    }
+
+
+    public static boolean writeIPPRequest(OutputStream os,
+                                           String operCode,
+                                           AttributeClass[] attCl) {
+        OutputStreamWriter osw;
+        try {
+            osw = new OutputStreamWriter(os, "UTF-8");
+        } catch (java.io.UnsupportedEncodingException exc) {
+            debug_println(debugPrefix+"writeIPPRequest, UTF-8 not supported? Exception: "+exc);
+            return false;
+        }
+        debug_println(debugPrefix+"writeIPPRequest, op code= "+operCode);
+        char[] opCode =  new char[2];
+        opCode[0] =  (char)Byte.parseByte(operCode.substring(0,2), 16);
+        opCode[1] =  (char)Byte.parseByte(operCode.substring(2,4), 16);
+        char[] bytes = {0x01, 0x01, 0x00, 0x01};
+        try {
+            osw.write(bytes, 0, 2); // version number
+            osw.write(opCode, 0, 2); // operation code
+            bytes[0] = 0x00; bytes[1] = 0x00;
+            osw.write(bytes, 0, 4); // request ID #1
+
+            bytes[0] = 0x01; // operation-group-tag
+            osw.write(bytes[0]);
+
+            String valStr;
+            char[] lenStr;
+
+            AttributeClass ac;
+            for (int i=0; i < attCl.length; i++) {
+                ac = attCl[i];
+                osw.write(ac.getType()); // value tag
+
+                lenStr = ac.getLenChars();
+                osw.write(lenStr, 0, 2); // length
+                osw.write(""+ac, 0, ac.getName().length());
+
+                // check if string range (0x35 -> 0x49)
+                if (ac.getType() >= AttributeClass.TAG_TEXT_LANGUAGE &&
+                    ac.getType() <= AttributeClass.TAG_MIME_MEDIATYPE){
+                    valStr = (String)ac.getObjectValue();
+                    bytes[0] = 0; bytes[1] = (char)valStr.length();
+                    osw.write(bytes, 0, 2);
+                    osw.write(valStr, 0, valStr.length());
+                } // REMIND: need to support other value tags but for CUPS
+                // string is all we need.
+            }
+
+            osw.write(GRPTAG_END_ATTRIBUTES);
+            osw.flush();
+            osw.close();
+        } catch (java.io.IOException ioe) {
+            debug_println(debugPrefix+"writeIPPRequest, IPPPrintService Exception in writeIPPRequest: "+ioe);
+            return false;
+        }
+        return true;
+    }
+
+
+    public static HashMap<String, AttributeClass>[] readIPPResponse(InputStream inputStream) {
+
+        if (inputStream == null) {
+            return null;
+        }
+
+        byte response[] = new byte[MAX_ATTRIBUTE_LENGTH];
+        try {
+
+            DataInputStream ois = new DataInputStream(inputStream);
+
+            // read status and ID
+            if ((ois.read(response, 0, 8) > -1) &&
+                (response[2] == STATUSCODE_SUCCESS)) {
+
+                ByteArrayOutputStream outObj;
+                int counter=0;
+                short len = 0;
+                String attribStr = null;
+                // assign default value
+                byte valTagByte = AttributeClass.TAG_KEYWORD;
+                ArrayList<HashMap<String, AttributeClass>> respList = new ArrayList<>();
+                HashMap<String, AttributeClass> responseMap = new HashMap<>();
+
+                response[0] = ois.readByte();
+
+                // check for group tags
+                while ((response[0] >= GRPTAG_OP_ATTRIBUTES) &&
+                       (response[0] <= GRPTAG_PRINTER_ATTRIBUTES)
+                          && (response[0] != GRPTAG_END_ATTRIBUTES)) {
+                    debug_println(debugPrefix+"readIPPResponse, checking group tag,  response[0]= "+
+                                  response[0]);
+
+                    outObj = new ByteArrayOutputStream();
+                    //make sure counter and attribStr are re-initialized
+                    counter = 0;
+                    attribStr = null;
+
+                    // read value tag
+                    response[0] = ois.readByte();
+                    while (response[0] >= AttributeClass.TAG_UNSUPPORTED_VALUE &&
+                           response[0] <= AttributeClass.TAG_MEMBER_ATTRNAME) {
+                        // read name length
+                        len  = ois.readShort();
+
+                        // If current value is not part of previous attribute
+                        // then close stream and add it to HashMap.
+                        // It is part of previous attribute if name length=0.
+                        if ((len != 0) && (attribStr != null)) {
+                            //last byte is the total # of values
+                            outObj.write(counter);
+                            outObj.flush();
+                            outObj.close();
+                            byte outArray[] = outObj.toByteArray();
+
+                            // if key exists, new HashMap
+                            if (responseMap.containsKey(attribStr)) {
+                                respList.add(responseMap);
+                                responseMap = new HashMap<>();
+                            }
+
+                            // exclude those that are unknown
+                            if (valTagByte >= AttributeClass.TAG_INT) {
+                                AttributeClass ac =
+                                    new AttributeClass(attribStr,
+                                                       valTagByte,
+                                                       outArray);
+
+                                responseMap.put(ac.getName(), ac);
+                                debug_println(debugPrefix+ "readIPPResponse "+ac);
+                            }
+
+                            outObj = new ByteArrayOutputStream();
+                            counter = 0; //reset counter
+                        }
+                        //check if this is new value tag
+                        if (counter == 0) {
+                            valTagByte = response[0];
+                        }
+                        // read attribute name
+                        if (len != 0) {
+                            // read "len" characters
+                            // make sure it doesn't exceed the maximum
+                            if (len > MAX_ATTRIBUTE_LENGTH) {
+                                response = new byte[len]; // expand as needed
+                            }
+                            ois.read(response, 0, len);
+                            attribStr = new String(response, 0, len);
+                        }
+                        // read value length
+                        len  = ois.readShort();
+                        // write name length
+                        outObj.write(len);
+                        // read value, make sure it doesn't exceed the maximum
+                        if (len > MAX_ATTRIBUTE_LENGTH) {
+                            response = new byte[len]; // expand as needed
+                        }
+                        ois.read(response, 0, len);
+                        // write value of "len" length
+                        outObj.write(response, 0, len);
+                        counter++;
+                        // read next byte
+                        response[0] = ois.readByte();
+                    }
+
+                    if (attribStr != null) {
+                        outObj.write(counter);
+                        outObj.flush();
+                        outObj.close();
+
+                        // if key exists in old HashMap, new HashMap
+                        if ((counter != 0) &&
+                            responseMap.containsKey(attribStr)) {
+                            respList.add(responseMap);
+                            responseMap = new HashMap<>();
+                        }
+
+                        byte outArray[] = outObj.toByteArray();
+
+                        AttributeClass ac =
+                            new AttributeClass(attribStr,
+                                               valTagByte,
+                                               outArray);
+                        responseMap.put(ac.getName(), ac);
+                    }
+                }
+                ois.close();
+                if ((responseMap != null) && (responseMap.size() > 0)) {
+                    respList.add(responseMap);
+                }
+                @SuppressWarnings({"unchecked", "rawtypes"})
+                HashMap<String, AttributeClass>[] tmp  =
+                    respList.toArray((HashMap<String, AttributeClass>[])new HashMap[respList.size()]);
+                return tmp;
+            } else {
+                debug_println(debugPrefix+
+                          "readIPPResponse client error, IPP status code: 0x"+
+                          toHex(response[2]) + toHex(response[3]));
+                return null;
+            }
+
+        } catch (java.io.IOException e) {
+            debug_println(debugPrefix+"readIPPResponse: "+e);
+            if (debugPrint) {
+                e.printStackTrace();
+            }
+            return null;
+        }
+    }
+
+    private static String toHex(byte v) {
+        String s = Integer.toHexString(v&0xff);
+        return (s.length() == 2) ? s :  "0"+s;
+    }
+
+    public String toString() {
+        return "IPP Printer : " + getName();
+    }
+
+    public boolean equals(Object obj) {
+        return  (obj == this ||
+                 (obj instanceof IPPPrintService &&
+                  ((IPPPrintService)obj).getName().equals(getName())));
+    }
+
+    public int hashCode() {
+        return this.getClass().hashCode()+getName().hashCode();
+    }
+}