| /* | |
| * Copyright (c) 2003, 2024, 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 java.awt.GraphicsEnvironment; | |
| import java.awt.Toolkit; | |
| import java.io.BufferedReader; | |
| import java.io.ByteArrayOutputStream; | |
| import java.io.DataInputStream; | |
| import java.io.File; | |
| import java.io.InputStream; | |
| import java.io.InputStreamReader; | |
| import java.io.OutputStream; | |
| import java.io.OutputStreamWriter; | |
| import java.net.HttpURLConnection; | |
| import java.net.MalformedURLException; | |
| import java.net.URI; | |
| import java.net.URISyntaxException; | |
| import java.net.URL; | |
| import java.net.URLConnection; | |
| import java.util.ArrayList; | |
| import java.util.Arrays; | |
| import java.util.HashMap; | |
| import java.util.HashSet; | |
| import java.util.Locale; | |
| import java.util.Map; | |
| import javax.print.DocFlavor; | |
| import javax.print.DocPrintJob; | |
| import javax.print.PrintService; | |
| import javax.print.ServiceUIFactory; | |
| import javax.print.attribute.Attribute; | |
| import javax.print.attribute.AttributeSet; | |
| import javax.print.attribute.AttributeSetUtilities; | |
| import javax.print.attribute.EnumSyntax; | |
| import javax.print.attribute.HashAttributeSet; | |
| import javax.print.attribute.HashPrintServiceAttributeSet; | |
| import javax.print.attribute.PrintRequestAttribute; | |
| import javax.print.attribute.PrintServiceAttribute; | |
| import javax.print.attribute.PrintServiceAttributeSet; | |
| import javax.print.attribute.Size2DSyntax; | |
| import javax.print.attribute.standard.Chromaticity; | |
| import javax.print.attribute.standard.ColorSupported; | |
| import javax.print.attribute.standard.Copies; | |
| import javax.print.attribute.standard.CopiesSupported; | |
| import javax.print.attribute.standard.Destination; | |
| import javax.print.attribute.standard.DialogOwner; | |
| import javax.print.attribute.standard.DialogTypeSelection; | |
| import javax.print.attribute.standard.Fidelity; | |
| import javax.print.attribute.standard.Finishings; | |
| import javax.print.attribute.standard.JobName; | |
| import javax.print.attribute.standard.JobSheets; | |
| import javax.print.attribute.standard.Media; | |
| import javax.print.attribute.standard.MediaPrintableArea; | |
| import javax.print.attribute.standard.MediaSize; | |
| import javax.print.attribute.standard.MediaSizeName; | |
| import javax.print.attribute.standard.MediaTray; | |
| import javax.print.attribute.standard.NumberUp; | |
| import javax.print.attribute.standard.OrientationRequested; | |
| import javax.print.attribute.standard.OutputBin; | |
| import javax.print.attribute.standard.PDLOverrideSupported; | |
| import javax.print.attribute.standard.PageRanges; | |
| import javax.print.attribute.standard.PagesPerMinute; | |
| import javax.print.attribute.standard.PagesPerMinuteColor; | |
| import javax.print.attribute.standard.PrinterInfo; | |
| import javax.print.attribute.standard.PrinterIsAcceptingJobs; | |
| import javax.print.attribute.standard.PrinterLocation; | |
| import javax.print.attribute.standard.PrinterMakeAndModel; | |
| import javax.print.attribute.standard.PrinterMessageFromOperator; | |
| import javax.print.attribute.standard.PrinterMoreInfo; | |
| import javax.print.attribute.standard.PrinterMoreInfoManufacturer; | |
| import javax.print.attribute.standard.PrinterName; | |
| import javax.print.attribute.standard.PrinterResolution; | |
| import javax.print.attribute.standard.PrinterState; | |
| import javax.print.attribute.standard.PrinterStateReasons; | |
| import javax.print.attribute.standard.PrinterURI; | |
| import javax.print.attribute.standard.QueuedJobCount; | |
| import javax.print.attribute.standard.RequestingUserName; | |
| import javax.print.attribute.standard.SheetCollate; | |
| import javax.print.attribute.standard.Sides; | |
| import javax.print.event.PrintServiceAttributeListener; | |
| import static java.nio.charset.StandardCharsets.ISO_8859_1; | |
| import static java.nio.charset.StandardCharsets.UTF_8; | |
| public final 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 = System.getProperty(FORCE_PIPE_PROP); | |
| debugPrint = "true".equalsIgnoreCase(debugStr); | |
| } | |
| private String printer; | |
| private URI myURI; | |
| private URL myURL; | |
| private transient 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 OutputBin[] outputBins; | |
| 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, | |
| OutputBin.TOP, | |
| }; | |
| /** | |
| * 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 = java.net.URLDecoder.decode(name, UTF_8); | |
| 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 = java.net.URLDecoder.decode(name, UTF_8); | |
| supportedDocFlavors = null; | |
| supportedCats = null; | |
| mediaSizeNames = null; | |
| customMediaSizeNames = null; | |
| mediaTrays = null; | |
| cps = null; | |
| init = false; | |
| defaultMediaIndex = -1; | |
| try { | |
| myURL = | |
| newURL(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]; | |
| outputBins = new OutputBin[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 { | |
| if (cps == null) { | |
| cps = new CUPSPrinter(printer); | |
| mediaSizeNames = cps.getMediaSizeNames(); | |
| mediaTrays = cps.getMediaTrays(); | |
| outputBins = PrintServiceLookupProvider.isMac() | |
| ? cps.getOutputBins() | |
| : getSupportedOutputBins(); | |
| 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); | |
| } | |
| if (outputBins == null) { | |
| outputBins = getSupportedOutputBins(); | |
| } | |
| urlConnection.disconnect(); | |
| init = true; | |
| } | |
| } | |
| @Override | |
| public DocPrintJob createPrintJob() { | |
| // REMIND: create IPPPrintJob | |
| return new UnixPrintJob(this); | |
| } | |
| @Override | |
| 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; | |
| if (PrintServiceLookupProvider.isMac()) { | |
| arr = new Chromaticity[2]; | |
| arr[0] = Chromaticity.COLOR; | |
| arr[1] = Chromaticity.MONOCHROME; | |
| } else { | |
| 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)) { | |
| return new Destination((new File("out.ps")).toURI()); | |
| } | |
| 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 (fAll[j] == null) { | |
| continue; | |
| } | |
| 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 instanceof MediaSizeName msn) { | |
| // 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 = System.getProperty("user.name", ""); | |
| 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; | |
| } else if (category == OutputBin.class) { | |
| return Arrays.copyOf(outputBins, outputBins.length); | |
| } | |
| return null; | |
| } | |
| //This class is for getting all pre-defined Finishings | |
| @SuppressWarnings("serial") // JDK implementation class | |
| private static final 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; | |
| } | |
| } | |
| @Override | |
| 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; | |
| } | |
| } | |
| @Override | |
| 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.contains("postscript")) { | |
| 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; | |
| } | |
| } | |
| DocFlavor[] flavor = new DocFlavor[2]; | |
| flavor[0] = DocFlavor.SERVICE_FORMATTED.PAGEABLE; | |
| flavor[1] = DocFlavor.SERVICE_FORMATTED.PRINTABLE; | |
| supportedDocFlavors = flavor; | |
| return flavor; | |
| } | |
| @Override | |
| 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 = | |
| CustomMediaSizeName.create("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 = CustomMediaTray.create("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]; | |
| } | |
| private OutputBin[] getSupportedOutputBins() { | |
| if ((getAttMap != null) && getAttMap.containsKey("output-bin-supported")) { | |
| AttributeClass attribClass = getAttMap.get("output-bin-supported"); | |
| if (attribClass != null) { | |
| String[] values = attribClass.getArrayOfStringValues(); | |
| if (values == null || values.length == 0) { | |
| return null; | |
| } | |
| OutputBin[] outputBinNames = new OutputBin[values.length]; | |
| for (int i = 0; i < values.length; i++) { | |
| outputBinNames[i] = CustomOutputBin.createOutputBin(values[i], values[i]); | |
| } | |
| return outputBinNames; | |
| } | |
| } | |
| return null; | |
| } | |
| @Override | |
| public synchronized Class<?>[] getSupportedAttributeCategories() { | |
| if (supportedCats != null) { | |
| Class<?> [] copyCats = new Class<?>[supportedCats.length]; | |
| System.arraycopy(supportedCats, 0, copyCats, 0, copyCats.length); | |
| return copyCats; | |
| } | |
| 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")) { | |
| if (pra == OutputBin.TOP && (outputBins == null || outputBins.length == 0)) { | |
| continue; | |
| } | |
| 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. | |
| 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); | |
| } | |
| if (GraphicsEnvironment.isHeadless() == false) { | |
| catList.add(DialogOwner.class); | |
| catList.add(DialogTypeSelection.class); | |
| } | |
| supportedCats = new Class<?>[catList.size()]; | |
| catList.toArray(supportedCats); | |
| Class<?>[] copyCats = new Class<?>[supportedCats.length]; | |
| System.arraycopy(supportedCats, 0, copyCats, 0, copyCats.length); | |
| return copyCats; | |
| } | |
| @Override | |
| 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; | |
| } | |
| if (category == OutputBin.class | |
| && (outputBins == null || outputBins.length == 0)) { | |
| return false; | |
| } | |
| for (int i=0;i<supportedCats.length;i++) { | |
| if (category == supportedCats[i]) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| @Override | |
| @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; | |
| } | |
| } | |
| @Override | |
| public synchronized PrintServiceAttributeSet getAttributes() { | |
| if (!init) { | |
| // get all attributes for the first time. | |
| initAttributes(); | |
| } else { | |
| // only need service attributes updated. | |
| // update getAttMap by sending again get-attributes IPP request | |
| if ((urlConnection = getIPPConnection(myURL)) != null) { | |
| opGetAttributes(); | |
| urlConnection.disconnect(); | |
| } | |
| } | |
| 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; | |
| } | |
| @Override | |
| 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 PrintServiceLookupProvider.isMac() || 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().isEmpty()) { | |
| 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; | |
| } else if (attr.getCategory() == PrinterResolution.class) { | |
| if (attr instanceof PrinterResolution) { | |
| return isSupportedResolution((PrinterResolution)attr); | |
| } | |
| } else if (attr.getCategory() == DialogOwner.class) { | |
| DialogOwner owner = (DialogOwner)attr; | |
| // ID not supported on any dialog type on Unix platforms. | |
| if (DialogOwnerAccessor.getID(owner) != 0) { | |
| return false; | |
| } | |
| // On Mac we have no control over the native dialog. | |
| DialogTypeSelection dst = (attributes == null) ? null : | |
| (DialogTypeSelection)attributes.get(DialogTypeSelection.class); | |
| if (PrintServiceLookupProvider.isMac() && | |
| dst == DialogTypeSelection.NATIVE) { | |
| return false; | |
| } | |
| // The other case is always a Swing dialog on all Unix platforms. | |
| // So we only need to check that the toolkit supports | |
| // always on top. | |
| if (owner.getOwner() != null) { | |
| return true; | |
| } else { | |
| return Toolkit.getDefaultToolkit().isAlwaysOnTopSupported(); | |
| } | |
| } else if (attr.getCategory() == DialogTypeSelection.class) { | |
| if (PrintServiceLookupProvider.isMac()) { | |
| return true; | |
| } else { | |
| DialogTypeSelection dst = (DialogTypeSelection)attr; | |
| return attr == DialogTypeSelection.COMMON; | |
| } | |
| } else if (attr.getCategory() == OutputBin.class) { | |
| if (attr instanceof CustomOutputBin) { | |
| return true; | |
| } | |
| String name = attr.toString(); | |
| for (OutputBin outputBin : outputBins) { | |
| String choice = ((CustomOutputBin) outputBin).getChoiceName(); | |
| if (name.equalsIgnoreCase(choice) || name.replaceAll("-", "").equalsIgnoreCase(choice)) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| return true; | |
| } | |
| @Override | |
| 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) { | |
| return new Destination((new File("out.ps")).toURI()); | |
| } 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.isEmpty() || | |
| 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().contains(name)) { | |
| 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.isEmpty() || | |
| 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 = System.getProperty("user.name", ""); | |
| 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); | |
| } | |
| } else if (category == OutputBin.class) { | |
| if (attribClass != null) { | |
| return CustomOutputBin.createOutputBin(attribClass.getStringValue(), attribClass.getStringValue()); | |
| } | |
| } | |
| 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; | |
| } | |
| @Override | |
| public ServiceUIFactory getServiceUIFactory() { | |
| return null; | |
| } | |
| public void wakeNotifier() { | |
| synchronized (this) { | |
| if (notifier != null) { | |
| notifier.wake(); | |
| } | |
| } | |
| } | |
| @Override | |
| public void addPrintServiceAttributeListener( | |
| PrintServiceAttributeListener listener) { | |
| synchronized (this) { | |
| if (listener == null) { | |
| return; | |
| } | |
| if (notifier == null) { | |
| notifier = new ServiceNotifier(this); | |
| } | |
| notifier.addListener(listener); | |
| } | |
| } | |
| @Override | |
| 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; | |
| } | |
| @Override | |
| 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 (PrintServiceLookupProvider.isMac()) { | |
| PrintServiceAttributeSet psaSet = this.getAttributes(); | |
| if (psaSet != null) { | |
| PrinterInfo pName = (PrinterInfo)psaSet.get(PrinterInfo.class); | |
| if (pName != null) { | |
| return pName.toString(); | |
| } | |
| } | |
| } | |
| return printer; | |
| } | |
| @Override | |
| 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.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( | |
| newURL(myURL+".ppd")); | |
| InputStream is = urlConnection.getInputStream(); | |
| if (is != null) { | |
| BufferedReader d = new BufferedReader( | |
| new InputStreamReader(is, 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 = null; | |
| try { | |
| os = urlConnection.getOutputStream(); | |
| } catch (Exception e) { | |
| } | |
| 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]; | |
| // If there is extra hashmap created due to duplicate | |
| // key/attribute present in IPPresponse, then use that | |
| // map too by appending to getAttMap after removing the | |
| // duplicate key/value | |
| if (responseMap.length > 1) { | |
| for (int i = 1; i < responseMap.length; i++) { | |
| for (Map.Entry<String, AttributeClass> entry : responseMap[i].entrySet()) { | |
| if (!getAttMap.containsKey(entry.getValue())) { | |
| getAttMap.put(entry.getKey(), entry.getValue()); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } 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 = new OutputStreamWriter(os, UTF_8); | |
| 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; | |
| } | |
| @Override | |
| public String toString() { | |
| return "IPP Printer : " + getName(); | |
| } | |
| @Override | |
| public boolean equals(Object obj) { | |
| return (obj == this || | |
| (obj instanceof IPPPrintService && | |
| ((IPPPrintService)obj).getName().equals(getName()))); | |
| } | |
| @Override | |
| public int hashCode() { | |
| return this.getClass().hashCode()+getName().hashCode(); | |
| } | |
| @SuppressWarnings("deprecation") | |
| private static URL newURL(String spec) throws MalformedURLException { | |
| return new URL(spec); | |
| } | |
| } |