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