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