1 /*
   2  * Copyright (c) 2000, 2007, 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 java.io.File;
  29 import java.net.URI;
  30 import java.net.URISyntaxException;
  31 import java.util.ArrayList;
  32 import java.util.Locale;
  33 
  34 import java.awt.GraphicsEnvironment;
  35 import java.awt.Toolkit;
  36 import javax.print.DocFlavor;
  37 import javax.print.DocPrintJob;
  38 import javax.print.PrintService;
  39 import javax.print.ServiceUIFactory;
  40 import javax.print.attribute.Attribute;
  41 import javax.print.attribute.AttributeSet;
  42 import javax.print.attribute.AttributeSetUtilities;
  43 import javax.print.attribute.HashAttributeSet;
  44 import javax.print.attribute.PrintServiceAttribute;
  45 import javax.print.attribute.PrintServiceAttributeSet;
  46 import javax.print.attribute.HashPrintServiceAttributeSet;
  47 import javax.print.attribute.Size2DSyntax;
  48 import javax.print.attribute.standard.PrinterName;
  49 import javax.print.attribute.standard.PrinterIsAcceptingJobs;
  50 import javax.print.attribute.standard.QueuedJobCount;
  51 import javax.print.attribute.standard.JobName;
  52 import javax.print.attribute.standard.JobSheets;
  53 import javax.print.attribute.standard.RequestingUserName;
  54 import javax.print.attribute.standard.Chromaticity;
  55 import javax.print.attribute.standard.ColorSupported;
  56 import javax.print.attribute.standard.Copies;
  57 import javax.print.attribute.standard.CopiesSupported;
  58 import javax.print.attribute.standard.Destination;
  59 import javax.print.attribute.standard.DialogOwner;
  60 import javax.print.attribute.standard.DialogTypeSelection;
  61 import javax.print.attribute.standard.Fidelity;
  62 import javax.print.attribute.standard.Media;
  63 import javax.print.attribute.standard.MediaPrintableArea;
  64 import javax.print.attribute.standard.MediaSize;
  65 import javax.print.attribute.standard.MediaSizeName;
  66 import javax.print.attribute.standard.OrientationRequested;
  67 import javax.print.attribute.standard.PageRanges;
  68 import javax.print.attribute.standard.PrinterState;
  69 import javax.print.attribute.standard.PrinterStateReason;
  70 import javax.print.attribute.standard.PrinterStateReasons;
  71 import javax.print.attribute.standard.Severity;
  72 import javax.print.attribute.standard.SheetCollate;
  73 import javax.print.attribute.standard.Sides;
  74 import javax.print.event.PrintServiceAttributeListener;
  75 
  76 
  77 public class UnixPrintService implements PrintService, AttributeUpdater,
  78                                          SunPrinterJobService {
  79 
  80     /* define doc flavors for text types in the default encoding of
  81      * this platform since we can always read those.
  82      */
  83     private static String encoding = "ISO8859_1";
  84     private static DocFlavor textByteFlavor;
  85 
  86     private static DocFlavor[] supportedDocFlavors = null;
  87     private static final DocFlavor[] supportedDocFlavorsInit = {
  88          DocFlavor.BYTE_ARRAY.POSTSCRIPT,
  89          DocFlavor.INPUT_STREAM.POSTSCRIPT,
  90          DocFlavor.URL.POSTSCRIPT,
  91          DocFlavor.BYTE_ARRAY.GIF,
  92          DocFlavor.INPUT_STREAM.GIF,
  93          DocFlavor.URL.GIF,
  94          DocFlavor.BYTE_ARRAY.JPEG,
  95          DocFlavor.INPUT_STREAM.JPEG,
  96          DocFlavor.URL.JPEG,
  97          DocFlavor.BYTE_ARRAY.PNG,
  98          DocFlavor.INPUT_STREAM.PNG,
  99          DocFlavor.URL.PNG,
 100 
 101          DocFlavor.CHAR_ARRAY.TEXT_PLAIN,
 102          DocFlavor.READER.TEXT_PLAIN,
 103          DocFlavor.STRING.TEXT_PLAIN,
 104 
 105          DocFlavor.BYTE_ARRAY.TEXT_PLAIN_UTF_8,
 106          DocFlavor.BYTE_ARRAY.TEXT_PLAIN_UTF_16,
 107          DocFlavor.BYTE_ARRAY.TEXT_PLAIN_UTF_16BE,
 108          DocFlavor.BYTE_ARRAY.TEXT_PLAIN_UTF_16LE,
 109          DocFlavor.BYTE_ARRAY.TEXT_PLAIN_US_ASCII,
 110 
 111 
 112          DocFlavor.INPUT_STREAM.TEXT_PLAIN_UTF_8,
 113          DocFlavor.INPUT_STREAM.TEXT_PLAIN_UTF_16,
 114          DocFlavor.INPUT_STREAM.TEXT_PLAIN_UTF_16BE,
 115          DocFlavor.INPUT_STREAM.TEXT_PLAIN_UTF_16LE,
 116          DocFlavor.INPUT_STREAM.TEXT_PLAIN_US_ASCII,
 117 
 118 
 119          DocFlavor.URL.TEXT_PLAIN_UTF_8,
 120          DocFlavor.URL.TEXT_PLAIN_UTF_16,
 121          DocFlavor.URL.TEXT_PLAIN_UTF_16BE,
 122          DocFlavor.URL.TEXT_PLAIN_UTF_16LE,
 123          DocFlavor.URL.TEXT_PLAIN_US_ASCII,
 124 
 125          DocFlavor.SERVICE_FORMATTED.PAGEABLE,
 126          DocFlavor.SERVICE_FORMATTED.PRINTABLE,
 127 
 128          DocFlavor.BYTE_ARRAY.AUTOSENSE,
 129          DocFlavor.URL.AUTOSENSE,
 130          DocFlavor.INPUT_STREAM.AUTOSENSE
 131     };
 132 
 133     private static final DocFlavor[] supportedHostDocFlavors = {
 134         DocFlavor.BYTE_ARRAY.TEXT_PLAIN_HOST,
 135         DocFlavor.INPUT_STREAM.TEXT_PLAIN_HOST,
 136         DocFlavor.URL.TEXT_PLAIN_HOST
 137     };
 138 
 139     String[] lpcStatusCom = {
 140       "",
 141       "| grep -E '^[ 0-9a-zA-Z_-]*@' | awk '{print $2, $3}'"
 142     };
 143 
 144     String[] lpcQueueCom = {
 145       "",
 146       "| grep -E '^[ 0-9a-zA-Z_-]*@' | awk '{print $4}'"
 147     };
 148 
 149     static {
 150         encoding = java.security.AccessController.doPrivileged(
 151             new sun.security.action.GetPropertyAction("file.encoding"));
 152     }
 153 
 154     /* let's try to support a few of these */
 155     private static final Class<?>[] serviceAttrCats = {
 156         PrinterName.class,
 157         PrinterIsAcceptingJobs.class,
 158         QueuedJobCount.class,
 159     };
 160 
 161     /*  it turns out to be inconvenient to store the other categories
 162      *  separately because many attributes are in multiple categories.
 163      */
 164     private static final Class<?>[] otherAttrCats = {
 165         Chromaticity.class,
 166         Copies.class,
 167         Destination.class,
 168         Fidelity.class,
 169         JobName.class,
 170         JobSheets.class,
 171         Media.class, /* have to support this somehow ... */
 172         MediaPrintableArea.class,
 173         OrientationRequested.class,
 174         PageRanges.class,
 175         RequestingUserName.class,
 176         SheetCollate.class,
 177         Sides.class,
 178     };
 179 
 180     private static int MAXCOPIES = 1000;
 181 
 182     private static final MediaSizeName mediaSizes[] = {
 183         MediaSizeName.NA_LETTER,
 184         MediaSizeName.TABLOID,
 185         MediaSizeName.LEDGER,
 186         MediaSizeName.NA_LEGAL,
 187         MediaSizeName.EXECUTIVE,
 188         MediaSizeName.ISO_A3,
 189         MediaSizeName.ISO_A4,
 190         MediaSizeName.ISO_A5,
 191         MediaSizeName.ISO_B4,
 192         MediaSizeName.ISO_B5,
 193     };
 194 
 195     private String printer;
 196     private PrinterName name;
 197     private boolean isInvalid;
 198 
 199     private transient PrintServiceAttributeSet lastSet;
 200     private transient ServiceNotifier notifier = null;
 201 
 202     UnixPrintService(String name) {
 203         if (name == null) {
 204             throw new IllegalArgumentException("null printer name");
 205         }
 206         printer = name;
 207         isInvalid = false;
 208     }
 209 
 210     public void invalidateService() {
 211         isInvalid = true;
 212     }
 213 
 214     public String getName() {
 215         return printer;
 216     }
 217 
 218     private PrinterName getPrinterName() {
 219         if (name == null) {
 220             name = new PrinterName(printer, null);
 221         }
 222         return name;
 223     }
 224 
 225     private PrinterIsAcceptingJobs getPrinterIsAcceptingJobsSysV() {
 226         String command = "/usr/bin/lpstat -a " + printer;
 227         String results[]= PrintServiceLookupProvider.execCmd(command);
 228 
 229         if (results != null && results.length > 0) {
 230             if (results[0].startsWith(printer + " accepting requests")) {
 231                 return PrinterIsAcceptingJobs.ACCEPTING_JOBS;
 232             }
 233             else if (results[0].startsWith(printer)) {
 234                 /* As well as "myprinter accepting requests", look for
 235                  * "myprinter@somehost accepting requests".
 236                  */
 237                 int index = printer.length();
 238                 String str = results[0];
 239                 if (str.length() > index &&
 240                     str.charAt(index) == '@' &&
 241                     str.indexOf(" accepting requests", index) > 0 &&
 242                     str.indexOf(" not accepting requests", index) == -1) {
 243                    return PrinterIsAcceptingJobs.ACCEPTING_JOBS;
 244                 }
 245             }
 246         }
 247         return PrinterIsAcceptingJobs.NOT_ACCEPTING_JOBS ;
 248     }
 249 
 250     private PrinterIsAcceptingJobs getPrinterIsAcceptingJobsBSD() {
 251         if (PrintServiceLookupProvider.cmdIndex ==
 252             PrintServiceLookupProvider.UNINITIALIZED) {
 253 
 254             PrintServiceLookupProvider.cmdIndex =
 255                 PrintServiceLookupProvider.getBSDCommandIndex();
 256         }
 257 
 258         String command = "/usr/sbin/lpc status " + printer
 259             + lpcStatusCom[PrintServiceLookupProvider.cmdIndex];
 260         String results[]= PrintServiceLookupProvider.execCmd(command);
 261 
 262         if (results != null && results.length > 0) {
 263             if (PrintServiceLookupProvider.cmdIndex ==
 264                 PrintServiceLookupProvider.BSD_LPD_NG) {
 265                 if (results[0].startsWith("enabled enabled")) {
 266                     return PrinterIsAcceptingJobs.ACCEPTING_JOBS ;
 267                 }
 268             } else {
 269                 if ((results[1].trim().startsWith("queuing is enabled") &&
 270                     results[2].trim().startsWith("printing is enabled")) ||
 271                     (results.length >= 4 &&
 272                      results[2].trim().startsWith("queuing is enabled") &&
 273                      results[3].trim().startsWith("printing is enabled"))) {
 274                     return PrinterIsAcceptingJobs.ACCEPTING_JOBS ;
 275                 }
 276             }
 277         }
 278         return PrinterIsAcceptingJobs.NOT_ACCEPTING_JOBS ;
 279     }
 280 
 281     // Filter the list of possible AIX Printers and remove header lines
 282     // and extra lines which have been added for remote printers.
 283     // 'protected' because this method is also used from PrintServiceLookupProvider.
 284     protected static String[] filterPrinterNamesAIX(String[] posPrinters) {
 285         ArrayList<String> printers = new ArrayList<>();
 286         String [] splitPart;
 287 
 288         for(int i = 0; i < posPrinters.length; i++) {
 289             // Remove the header lines
 290             if (posPrinters[i].startsWith("---") ||
 291                 posPrinters[i].startsWith("Queue") ||
 292                 posPrinters[i].equals("")) continue;
 293 
 294             // Check if there is a ":" in the end of the first colomn.
 295             // This means that it is not a valid printer definition.
 296             splitPart = posPrinters[i].split(" ");
 297             if(splitPart.length >= 1 && !splitPart[0].trim().endsWith(":")) {
 298                 printers.add(posPrinters[i]);
 299             }
 300         }
 301 
 302         return printers.toArray(new String[printers.size()]);
 303     }
 304 
 305     private PrinterIsAcceptingJobs getPrinterIsAcceptingJobsAIX() {
 306         // On AIX there should not be a blank after '-a'.
 307         String command = "/usr/bin/lpstat -a" + printer;
 308         String results[]= PrintServiceLookupProvider.execCmd(command);
 309 
 310         // Remove headers and bogus entries added by remote printers.
 311         results = filterPrinterNamesAIX(results);
 312 
 313         if (results != null && results.length > 0) {
 314             for (int i = 0; i < results.length; i++) {
 315                 if (results[i].contains("READY") ||
 316                     results[i].contains("RUNNING")) {
 317                     return PrinterIsAcceptingJobs.ACCEPTING_JOBS;
 318                 }
 319             }
 320         }
 321 
 322         return PrinterIsAcceptingJobs.NOT_ACCEPTING_JOBS;
 323 
 324     }
 325 
 326     private PrinterIsAcceptingJobs getPrinterIsAcceptingJobs() {
 327         if (PrintServiceLookupProvider.isSysV()) {
 328             return getPrinterIsAcceptingJobsSysV();
 329         } else if (PrintServiceLookupProvider.isBSD()) {
 330             return getPrinterIsAcceptingJobsBSD();
 331         } else if (PrintServiceLookupProvider.isAIX()) {
 332             return getPrinterIsAcceptingJobsAIX();
 333         } else {
 334             return PrinterIsAcceptingJobs.ACCEPTING_JOBS;
 335         }
 336     }
 337 
 338     private PrinterState getPrinterState() {
 339         if (isInvalid) {
 340             return PrinterState.STOPPED;
 341         } else {
 342             return null;
 343         }
 344     }
 345 
 346     private PrinterStateReasons getPrinterStateReasons() {
 347         if (isInvalid) {
 348             PrinterStateReasons psr = new PrinterStateReasons();
 349             psr.put(PrinterStateReason.SHUTDOWN, Severity.ERROR);
 350             return psr;
 351         } else {
 352             return null;
 353         }
 354     }
 355 
 356     private QueuedJobCount getQueuedJobCountSysV() {
 357         String command = "/usr/bin/lpstat -R " + printer;
 358         String results[]= PrintServiceLookupProvider.execCmd(command);
 359         int qlen = (results == null) ? 0 : results.length;
 360 
 361         return new QueuedJobCount(qlen);
 362     }
 363 
 364     private QueuedJobCount getQueuedJobCountBSD() {
 365         if (PrintServiceLookupProvider.cmdIndex ==
 366             PrintServiceLookupProvider.UNINITIALIZED) {
 367 
 368             PrintServiceLookupProvider.cmdIndex =
 369                 PrintServiceLookupProvider.getBSDCommandIndex();
 370         }
 371 
 372         int qlen = 0;
 373         String command = "/usr/sbin/lpc status " + printer
 374             + lpcQueueCom[PrintServiceLookupProvider.cmdIndex];
 375         String results[] = PrintServiceLookupProvider.execCmd(command);
 376 
 377         if (results != null && results.length > 0) {
 378             String queued;
 379             if (PrintServiceLookupProvider.cmdIndex ==
 380                 PrintServiceLookupProvider.BSD_LPD_NG) {
 381                 queued = results[0];
 382             } else {
 383                 queued = results[3].trim();
 384                 if (queued.startsWith("no")) {
 385                     return new QueuedJobCount(0);
 386                 } else {
 387                     queued = queued.substring(0, queued.indexOf(' '));
 388                 }
 389             }
 390 
 391             try {
 392                 qlen = Integer.parseInt(queued);
 393             } catch (NumberFormatException e) {
 394             }
 395         }
 396 
 397         return new QueuedJobCount(qlen);
 398     }
 399 
 400     private QueuedJobCount getQueuedJobCountAIX() {
 401         // On AIX there should not be a blank after '-a'.
 402         String command = "/usr/bin/lpstat -a" + printer;
 403         String results[]=  PrintServiceLookupProvider.execCmd(command);
 404 
 405         // Remove headers and bogus entries added by remote printers.
 406         results = filterPrinterNamesAIX(results);
 407 
 408         int qlen = 0;
 409         if (results != null && results.length > 0){
 410             for (int i = 0; i < results.length; i++) {
 411                 if (results[i].contains("QUEUED")){
 412                     qlen ++;
 413                 }
 414             }
 415         }
 416         return new QueuedJobCount(qlen);
 417     }
 418 
 419     private QueuedJobCount getQueuedJobCount() {
 420         if (PrintServiceLookupProvider.isSysV()) {
 421             return getQueuedJobCountSysV();
 422         } else if (PrintServiceLookupProvider.isBSD()) {
 423             return getQueuedJobCountBSD();
 424         } else if (PrintServiceLookupProvider.isAIX()) {
 425             return getQueuedJobCountAIX();
 426         } else {
 427             return new QueuedJobCount(0);
 428         }
 429     }
 430 
 431     private PrintServiceAttributeSet getSysVServiceAttributes() {
 432         PrintServiceAttributeSet attrs = new HashPrintServiceAttributeSet();
 433         attrs.add(getQueuedJobCountSysV());
 434         attrs.add(getPrinterIsAcceptingJobsSysV());
 435         return attrs;
 436     }
 437 
 438     private PrintServiceAttributeSet getBSDServiceAttributes() {
 439         PrintServiceAttributeSet attrs = new HashPrintServiceAttributeSet();
 440         attrs.add(getQueuedJobCountBSD());
 441         attrs.add(getPrinterIsAcceptingJobsBSD());
 442         return attrs;
 443     }
 444 
 445     private PrintServiceAttributeSet getAIXServiceAttributes() {
 446         PrintServiceAttributeSet attrs = new HashPrintServiceAttributeSet();
 447         attrs.add(getQueuedJobCountAIX());
 448         attrs.add(getPrinterIsAcceptingJobsAIX());
 449         return attrs;
 450     }
 451 
 452     private boolean isSupportedCopies(Copies copies) {
 453         int numCopies = copies.getValue();
 454         return (numCopies > 0 && numCopies < MAXCOPIES);
 455     }
 456 
 457     private boolean isSupportedMedia(MediaSizeName msn) {
 458         for (int i=0; i<mediaSizes.length; i++) {
 459             if (msn.equals(mediaSizes[i])) {
 460                 return true;
 461             }
 462         }
 463         return false;
 464     }
 465 
 466     public DocPrintJob createPrintJob() {
 467       SecurityManager security = System.getSecurityManager();
 468       if (security != null) {
 469         security.checkPrintJobAccess();
 470       }
 471         return new UnixPrintJob(this);
 472     }
 473 
 474     private PrintServiceAttributeSet getDynamicAttributes() {
 475         if (PrintServiceLookupProvider.isSysV()) {
 476             return getSysVServiceAttributes();
 477         } else if (PrintServiceLookupProvider.isAIX()) {
 478             return getAIXServiceAttributes();
 479         } else {
 480             return getBSDServiceAttributes();
 481         }
 482     }
 483 
 484     public PrintServiceAttributeSet getUpdatedAttributes() {
 485         PrintServiceAttributeSet currSet = getDynamicAttributes();
 486         if (lastSet == null) {
 487             lastSet = currSet;
 488             return AttributeSetUtilities.unmodifiableView(currSet);
 489         } else {
 490             PrintServiceAttributeSet updates =
 491                 new HashPrintServiceAttributeSet();
 492             Attribute []attrs = currSet.toArray();
 493             Attribute attr;
 494             for (int i=0; i<attrs.length; i++) {
 495                 attr = attrs[i];
 496                 if (!lastSet.containsValue(attr)) {
 497                     updates.add(attr);
 498                 }
 499             }
 500             lastSet = currSet;
 501             return AttributeSetUtilities.unmodifiableView(updates);
 502         }
 503     }
 504 
 505     public void wakeNotifier() {
 506         synchronized (this) {
 507             if (notifier != null) {
 508                 notifier.wake();
 509             }
 510         }
 511     }
 512 
 513     public void addPrintServiceAttributeListener(
 514                                  PrintServiceAttributeListener listener) {
 515         synchronized (this) {
 516             if (listener == null) {
 517                 return;
 518             }
 519             if (notifier == null) {
 520                 notifier = new ServiceNotifier(this);
 521             }
 522             notifier.addListener(listener);
 523         }
 524     }
 525 
 526     public void removePrintServiceAttributeListener(
 527                                   PrintServiceAttributeListener listener) {
 528         synchronized (this) {
 529             if (listener == null || notifier == null ) {
 530                 return;
 531             }
 532             notifier.removeListener(listener);
 533             if (notifier.isEmpty()) {
 534                 notifier.stopNotifier();
 535                 notifier = null;
 536             }
 537         }
 538     }
 539 
 540     @SuppressWarnings("unchecked")
 541     public <T extends PrintServiceAttribute>
 542         T getAttribute(Class<T> category)
 543     {
 544         if (category == null) {
 545             throw new NullPointerException("category");
 546         }
 547         if (!(PrintServiceAttribute.class.isAssignableFrom(category))) {
 548             throw new IllegalArgumentException("Not a PrintServiceAttribute");
 549         }
 550 
 551         if (category == PrinterName.class) {
 552             return (T)getPrinterName();
 553         } else if (category == PrinterState.class) {
 554             return (T)getPrinterState();
 555         } else if (category == PrinterStateReasons.class) {
 556             return (T)getPrinterStateReasons();
 557         } else if (category == QueuedJobCount.class) {
 558             return (T)getQueuedJobCount();
 559         } else if (category == PrinterIsAcceptingJobs.class) {
 560             return (T)getPrinterIsAcceptingJobs();
 561         } else {
 562             return null;
 563         }
 564     }
 565 
 566     public PrintServiceAttributeSet getAttributes() {
 567         PrintServiceAttributeSet attrs = new HashPrintServiceAttributeSet();
 568         attrs.add(getPrinterName());
 569         attrs.add(getPrinterIsAcceptingJobs());
 570         PrinterState prnState = getPrinterState();
 571         if (prnState != null) {
 572             attrs.add(prnState);
 573         }
 574         PrinterStateReasons prnStateReasons = getPrinterStateReasons();
 575         if (prnStateReasons != null) {
 576             attrs.add(prnStateReasons);
 577         }
 578         attrs.add(getQueuedJobCount());
 579         return AttributeSetUtilities.unmodifiableView(attrs);
 580     }
 581 
 582     private void initSupportedDocFlavors() {
 583         String hostEnc = DocFlavor.hostEncoding.toLowerCase(Locale.ENGLISH);
 584         if (!hostEnc.equals("utf-8") && !hostEnc.equals("utf-16") &&
 585             !hostEnc.equals("utf-16be") && !hostEnc.equals("utf-16le") &&
 586             !hostEnc.equals("us-ascii")) {
 587 
 588             int len = supportedDocFlavorsInit.length;
 589             DocFlavor[] flavors =
 590                 new DocFlavor[len + supportedHostDocFlavors.length];
 591             // copy host encoding flavors
 592             System.arraycopy(supportedHostDocFlavors, 0, flavors,
 593                              len, supportedHostDocFlavors.length);
 594             System.arraycopy(supportedDocFlavorsInit, 0, flavors, 0, len);
 595 
 596             supportedDocFlavors = flavors;
 597         } else {
 598             supportedDocFlavors = supportedDocFlavorsInit;
 599         }
 600     }
 601 
 602     public DocFlavor[] getSupportedDocFlavors() {
 603         if (supportedDocFlavors == null) {
 604             initSupportedDocFlavors();
 605         }
 606         int len = supportedDocFlavors.length;
 607         DocFlavor[] flavors = new DocFlavor[len];
 608         System.arraycopy(supportedDocFlavors, 0, flavors, 0, len);
 609 
 610         return flavors;
 611     }
 612 
 613     public boolean isDocFlavorSupported(DocFlavor flavor) {
 614         if (supportedDocFlavors == null) {
 615             initSupportedDocFlavors();
 616         }
 617         for (int f=0; f<supportedDocFlavors.length; f++) {
 618             if (flavor.equals(supportedDocFlavors[f])) {
 619                 return true;
 620             }
 621         }
 622         return false;
 623     }
 624 
 625     public Class<?>[] getSupportedAttributeCategories() {
 626         ArrayList<Class<?>> categList = new ArrayList<>(otherAttrCats.length);
 627         for (Class<?> c : otherAttrCats) {
 628             categList.add(c);
 629         }
 630         if (GraphicsEnvironment.isHeadless() == false) {
 631             categList.add(DialogOwner.class);
 632             categList.add(DialogTypeSelection.class);
 633         }
 634         return categList.toArray(new Class<?>[categList.size()]);
 635     }
 636 
 637     public boolean
 638         isAttributeCategorySupported(Class<? extends Attribute> category)
 639     {
 640         if (category == null) {
 641             throw new NullPointerException("null category");
 642         }
 643         if (!(Attribute.class.isAssignableFrom(category))) {
 644             throw new IllegalArgumentException(category +
 645                                              " is not an Attribute");
 646         }
 647 
 648         for (int i=0;i<otherAttrCats.length;i++) {
 649             if (category == otherAttrCats[i]) {
 650                 return true;
 651             }
 652         }
 653         return false;
 654     }
 655 
 656     /* return defaults for all attributes for which there is a default
 657      * value
 658      */
 659     public Object
 660         getDefaultAttributeValue(Class<? extends Attribute> category)
 661     {
 662         if (category == null) {
 663             throw new NullPointerException("null category");
 664         }
 665         if (!Attribute.class.isAssignableFrom(category)) {
 666             throw new IllegalArgumentException(category +
 667                                              " is not an Attribute");
 668         }
 669 
 670         if (!isAttributeCategorySupported(category)) {
 671             return null;
 672         }
 673 
 674         if (category == Copies.class) {
 675             return new Copies(1);
 676         } else if (category == Chromaticity.class) {
 677             return Chromaticity.COLOR;
 678         } else if (category == Destination.class) {
 679             try {
 680                 return new Destination((new File("out.ps")).toURI());
 681             } catch (SecurityException se) {
 682                 try {
 683                     return new Destination(new URI("file:out.ps"));
 684                 } catch (URISyntaxException e) {
 685                     return null;
 686                 }
 687             }
 688         } else if (category == Fidelity.class) {
 689             return Fidelity.FIDELITY_FALSE;
 690         } else if (category == JobName.class) {
 691             return new JobName("Java Printing", null);
 692         } else if (category == JobSheets.class) {
 693             return JobSheets.STANDARD;
 694         } else if (category == Media.class) {
 695             String defaultCountry = Locale.getDefault().getCountry();
 696             if (defaultCountry != null &&
 697                 (defaultCountry.equals("") ||
 698                  defaultCountry.equals(Locale.US.getCountry()) ||
 699                  defaultCountry.equals(Locale.CANADA.getCountry()))) {
 700                 return MediaSizeName.NA_LETTER;
 701             } else {
 702                  return MediaSizeName.ISO_A4;
 703             }
 704         } else if (category == MediaPrintableArea.class) {
 705             String defaultCountry = Locale.getDefault().getCountry();
 706             float iw, ih;
 707             if (defaultCountry != null &&
 708                 (defaultCountry.equals("") ||
 709                  defaultCountry.equals(Locale.US.getCountry()) ||
 710                  defaultCountry.equals(Locale.CANADA.getCountry()))) {
 711                 iw = MediaSize.NA.LETTER.getX(Size2DSyntax.INCH) - 0.5f;
 712                 ih = MediaSize.NA.LETTER.getY(Size2DSyntax.INCH) - 0.5f;
 713             } else {
 714                 iw = MediaSize.ISO.A4.getX(Size2DSyntax.INCH) - 0.5f;
 715                 ih = MediaSize.ISO.A4.getY(Size2DSyntax.INCH) - 0.5f;
 716             }
 717             return new MediaPrintableArea(0.25f, 0.25f, iw, ih,
 718                                           MediaPrintableArea.INCH);
 719         } else if (category == OrientationRequested.class) {
 720             return OrientationRequested.PORTRAIT;
 721         } else if (category == PageRanges.class) {
 722             return new PageRanges(1, Integer.MAX_VALUE);
 723         } else if (category == RequestingUserName.class) {
 724             String userName = "";
 725             try {
 726               userName = System.getProperty("user.name", "");
 727             } catch (SecurityException se) {
 728             }
 729             return new RequestingUserName(userName, null);
 730         } else if (category == SheetCollate.class) {
 731             return SheetCollate.UNCOLLATED;
 732         } else if (category == Sides.class) {
 733             return Sides.ONE_SIDED;
 734         } else
 735             return null;
 736     }
 737 
 738 
 739     private boolean isAutoSense(DocFlavor flavor) {
 740         if (flavor.equals(DocFlavor.BYTE_ARRAY.AUTOSENSE) ||
 741             flavor.equals(DocFlavor.INPUT_STREAM.AUTOSENSE) ||
 742             flavor.equals(DocFlavor.URL.AUTOSENSE)) {
 743             return true;
 744         }
 745         else {
 746             return false;
 747         }
 748     }
 749 
 750     public Object
 751         getSupportedAttributeValues(Class<? extends Attribute> category,
 752                                     DocFlavor flavor,
 753                                     AttributeSet attributes)
 754     {
 755 
 756         if (category == null) {
 757             throw new NullPointerException("null category");
 758         }
 759         if (!Attribute.class.isAssignableFrom(category)) {
 760             throw new IllegalArgumentException(category +
 761                                              " does not implement Attribute");
 762         }
 763         if (flavor != null) {
 764             if (!isDocFlavorSupported(flavor)) {
 765                 throw new IllegalArgumentException(flavor +
 766                                                " is an unsupported flavor");
 767             } else if (isAutoSense(flavor)) {
 768                 return null;
 769             }
 770         }
 771 
 772         if (!isAttributeCategorySupported(category)) {
 773             return null;
 774         }
 775 
 776         if (category == Chromaticity.class) {
 777             if (flavor == null || isServiceFormattedFlavor(flavor)) {
 778                 Chromaticity[]arr = new Chromaticity[1];
 779                 arr[0] = Chromaticity.COLOR;
 780                 return (arr);
 781             } else {
 782                 return null;
 783             }
 784         } else if (category == Destination.class) {
 785             try {
 786                 return new Destination((new File("out.ps")).toURI());
 787             } catch (SecurityException se) {
 788                 try {
 789                     return new Destination(new URI("file:out.ps"));
 790                 } catch (URISyntaxException e) {
 791                     return null;
 792                 }
 793             }
 794         } else if (category == JobName.class) {
 795             return new JobName("Java Printing", null);
 796         } else if (category == JobSheets.class) {
 797             JobSheets arr[] = new JobSheets[2];
 798             arr[0] = JobSheets.NONE;
 799             arr[1] = JobSheets.STANDARD;
 800             return arr;
 801         } else if (category == RequestingUserName.class) {
 802             String userName = "";
 803             try {
 804               userName = System.getProperty("user.name", "");
 805             } catch (SecurityException se) {
 806             }
 807             return new RequestingUserName(userName, null);
 808         } else if (category == OrientationRequested.class) {
 809             if (flavor == null || isServiceFormattedFlavor(flavor)) {
 810                 OrientationRequested []arr = new OrientationRequested[3];
 811                 arr[0] = OrientationRequested.PORTRAIT;
 812                 arr[1] = OrientationRequested.LANDSCAPE;
 813                 arr[2] = OrientationRequested.REVERSE_LANDSCAPE;
 814                 return arr;
 815             } else {
 816                 return null;
 817             }
 818         } else if ((category == Copies.class) ||
 819                    (category == CopiesSupported.class)) {
 820             if (flavor == null ||
 821                 !(flavor.equals(DocFlavor.INPUT_STREAM.POSTSCRIPT) ||
 822                   flavor.equals(DocFlavor.URL.POSTSCRIPT) ||
 823                   flavor.equals(DocFlavor.BYTE_ARRAY.POSTSCRIPT))) {
 824                 return new CopiesSupported(1, MAXCOPIES);
 825             } else {
 826                 return null;
 827             }
 828         } else if (category == Media.class) {
 829             Media []arr = new Media[mediaSizes.length];
 830             System.arraycopy(mediaSizes, 0, arr, 0, mediaSizes.length);
 831             return arr;
 832         } else if (category == Fidelity.class) {
 833             Fidelity []arr = new Fidelity[2];
 834             arr[0] = Fidelity.FIDELITY_FALSE;
 835             arr[1] = Fidelity.FIDELITY_TRUE;
 836             return arr;
 837         } else if (category == MediaPrintableArea.class) {
 838             /* The code below implements the behaviour that if no Media or
 839              * MediaSize attribute is specified, return an array of
 840              * MediaPrintableArea, one for each supported Media.
 841              * If a MediaSize is specified, return a MPA consistent for that,
 842              * and if a Media is specified locate its MediaSize and return
 843              * its MPA, and if none is found, return an MPA for the default
 844              * Media for this service.
 845              */
 846             if (attributes == null) {
 847                 return getAllPrintableAreas();
 848             }
 849             MediaSize mediaSize = (MediaSize)attributes.get(MediaSize.class);
 850             Media media = (Media)attributes.get(Media.class);
 851             MediaPrintableArea []arr = new MediaPrintableArea[1];
 852             if (mediaSize == null) {
 853                 if (media instanceof MediaSizeName) {
 854                     MediaSizeName msn = (MediaSizeName)media;
 855                     mediaSize = MediaSize.getMediaSizeForName(msn);
 856                     if (mediaSize == null) {
 857                         /* try to get a size from the default media */
 858                         media = (Media)getDefaultAttributeValue(Media.class);
 859                         if (media instanceof MediaSizeName) {
 860                             msn = (MediaSizeName)media;
 861                             mediaSize = MediaSize.getMediaSizeForName(msn);
 862                         }
 863                         if (mediaSize == null) {
 864                             /* shouldn't happen, return a default */
 865                             arr[0] = new MediaPrintableArea(0.25f, 0.25f,
 866                                                             8f, 10.5f,
 867                                                             MediaSize.INCH);
 868                             return arr;
 869                         }
 870                     }
 871                 } else {
 872                     return getAllPrintableAreas();
 873                 }
 874             }
 875             /* If reach here MediaSize is non-null */
 876             assert mediaSize != null;
 877             arr[0] = new MediaPrintableArea(0.25f, 0.25f,
 878                                 mediaSize.getX(MediaSize.INCH)-0.5f,
 879                                 mediaSize.getY(MediaSize.INCH)-0.5f,
 880                                 MediaSize.INCH);
 881             return arr;
 882         } else if (category == PageRanges.class) {
 883             if (flavor == null ||
 884                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
 885                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE)) {
 886                 PageRanges []arr = new PageRanges[1];
 887                 arr[0] = new PageRanges(1, Integer.MAX_VALUE);
 888                 return arr;
 889             } else {
 890                 return null;
 891             }
 892         } else if (category == SheetCollate.class) {
 893             if (flavor == null ||
 894                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
 895                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE)) {
 896                 SheetCollate []arr = new SheetCollate[2];
 897                 arr[0] = SheetCollate.UNCOLLATED;
 898                 arr[1] = SheetCollate.COLLATED;
 899                 return arr;
 900             } else {
 901                 return null;
 902             }
 903         } else if (category == Sides.class) {
 904             if (flavor == null ||
 905                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
 906                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE)) {
 907                 Sides []arr = new Sides[3];
 908                 arr[0] = Sides.ONE_SIDED;
 909                 arr[1] = Sides.TWO_SIDED_LONG_EDGE;
 910                 arr[2] = Sides.TWO_SIDED_SHORT_EDGE;
 911                 return arr;
 912             } else {
 913                 return null;
 914             }
 915         } else {
 916             return null;
 917         }
 918     }
 919 
 920     private static MediaPrintableArea[] mpas = null;
 921     private MediaPrintableArea[] getAllPrintableAreas() {
 922 
 923         if (mpas == null) {
 924             Media[] media = (Media[])getSupportedAttributeValues(Media.class,
 925                                                                  null, null);
 926             mpas = new MediaPrintableArea[media.length];
 927             for (int i=0; i< mpas.length; i++) {
 928                 if (media[i] instanceof MediaSizeName) {
 929                     MediaSizeName msn = (MediaSizeName)media[i];
 930                     MediaSize mediaSize = MediaSize.getMediaSizeForName(msn);
 931                     if (mediaSize == null) {
 932                         mpas[i] = (MediaPrintableArea)
 933                             getDefaultAttributeValue(MediaPrintableArea.class);
 934                     } else {
 935                         mpas[i] = new MediaPrintableArea(0.25f, 0.25f,
 936                                         mediaSize.getX(MediaSize.INCH)-0.5f,
 937                                         mediaSize.getY(MediaSize.INCH)-0.5f,
 938                                         MediaSize.INCH);
 939                     }
 940                 }
 941             }
 942         }
 943         MediaPrintableArea[] mpasCopy = new MediaPrintableArea[mpas.length];
 944         System.arraycopy(mpas, 0, mpasCopy, 0, mpas.length);
 945         return mpasCopy;
 946     }
 947 
 948     /* Is this one of the flavors that this service explicitly
 949      * generates postscript for, and so can control how it is rendered?
 950      */
 951     private boolean isServiceFormattedFlavor(DocFlavor flavor) {
 952         return
 953             flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
 954             flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE) ||
 955             flavor.equals(DocFlavor.BYTE_ARRAY.GIF) ||
 956             flavor.equals(DocFlavor.INPUT_STREAM.GIF) ||
 957             flavor.equals(DocFlavor.URL.GIF) ||
 958             flavor.equals(DocFlavor.BYTE_ARRAY.JPEG) ||
 959             flavor.equals(DocFlavor.INPUT_STREAM.JPEG) ||
 960             flavor.equals(DocFlavor.URL.JPEG) ||
 961             flavor.equals(DocFlavor.BYTE_ARRAY.PNG) ||
 962             flavor.equals(DocFlavor.INPUT_STREAM.PNG) ||
 963             flavor.equals(DocFlavor.URL.PNG);
 964     }
 965 
 966     public boolean isAttributeValueSupported(Attribute attr,
 967                                              DocFlavor flavor,
 968                                              AttributeSet attributes) {
 969         if (attr == null) {
 970             throw new NullPointerException("null attribute");
 971         }
 972         if (flavor != null) {
 973             if (!isDocFlavorSupported(flavor)) {
 974                 throw new IllegalArgumentException(flavor +
 975                                                " is an unsupported flavor");
 976             } else if (isAutoSense(flavor)) {
 977                 return false;
 978             }
 979         }
 980         Class<? extends Attribute> category = attr.getCategory();
 981         if (!isAttributeCategorySupported(category)) {
 982             return false;
 983         }
 984         else if (attr.getCategory() == Chromaticity.class) {
 985             if (flavor == null || isServiceFormattedFlavor(flavor)) {
 986                 return attr == Chromaticity.COLOR;
 987             } else {
 988                 return false;
 989             }
 990         }
 991         else if (attr.getCategory() == Copies.class) {
 992             return (flavor == null ||
 993                    !(flavor.equals(DocFlavor.INPUT_STREAM.POSTSCRIPT) ||
 994                      flavor.equals(DocFlavor.URL.POSTSCRIPT) ||
 995                      flavor.equals(DocFlavor.BYTE_ARRAY.POSTSCRIPT))) &&
 996                 isSupportedCopies((Copies)attr);
 997         } else if (attr.getCategory() == Destination.class) {
 998             URI uri = ((Destination)attr).getURI();
 999                 if ("file".equals(uri.getScheme()) &&
1000                     !(uri.getSchemeSpecificPart().equals(""))) {
1001                 return true;
1002             } else {
1003             return false;
1004             }
1005         } else if (attr.getCategory() == Media.class) {
1006             if (attr instanceof MediaSizeName) {
1007                 return isSupportedMedia((MediaSizeName)attr);
1008             } else {
1009                 return false;
1010             }
1011         } else if (attr.getCategory() == OrientationRequested.class) {
1012             if (attr == OrientationRequested.REVERSE_PORTRAIT ||
1013                 (flavor != null) &&
1014                 !isServiceFormattedFlavor(flavor)) {
1015                 return false;
1016             }
1017         } else if (attr.getCategory() == PageRanges.class) {
1018             if (flavor != null &&
1019                 !(flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
1020                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE))) {
1021                 return false;
1022             }
1023         } else if (attr.getCategory() == SheetCollate.class) {
1024             if (flavor != null &&
1025                 !(flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
1026                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE))) {
1027                 return false;
1028             }
1029         } else if (attr.getCategory() == Sides.class) {
1030             if (flavor != null &&
1031                 !(flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
1032                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE))) {
1033                 return false;
1034             }
1035         } else if (attr.getCategory() == DialogOwner.class) {
1036             DialogOwner owner = (DialogOwner)attr;
1037             // ID not supported on any dialog type on Unix platforms.
1038             if (DialogOwnerAccessor.getID(owner) != 0) {
1039                 return false;
1040             }
1041             // UnixPrintService is not used on Mac, so this is
1042             // always some Unix system that does not have CUPS/IPP
1043             // Which means we always use a Swing dialog and we need
1044             // only check if alwaysOnTop is supported by the toolkit.
1045             if (owner.getOwner() != null) {
1046                 return true;
1047             } else {
1048                 return Toolkit.getDefaultToolkit().isAlwaysOnTopSupported();
1049             }
1050         } else if (attr.getCategory() == DialogTypeSelection.class) {
1051             DialogTypeSelection dts = (DialogTypeSelection)attr;
1052             return dts == DialogTypeSelection.COMMON;
1053         }
1054         return true;
1055     }
1056 
1057     public AttributeSet getUnsupportedAttributes(DocFlavor flavor,
1058                                                  AttributeSet attributes) {
1059 
1060         if (flavor != null && !isDocFlavorSupported(flavor)) {
1061             throw new IllegalArgumentException("flavor " + flavor +
1062                                                "is not supported");
1063         }
1064 
1065         if (attributes == null) {
1066             return null;
1067         }
1068 
1069         Attribute attr;
1070         AttributeSet unsupp = new HashAttributeSet();
1071         Attribute []attrs = attributes.toArray();
1072         for (int i=0; i<attrs.length; i++) {
1073             try {
1074                 attr = attrs[i];
1075                 if (!isAttributeCategorySupported(attr.getCategory())) {
1076                     unsupp.add(attr);
1077                 } else if (!isAttributeValueSupported(attr, flavor,
1078                                                       attributes)) {
1079                     unsupp.add(attr);
1080                 }
1081             } catch (ClassCastException e) {
1082             }
1083         }
1084         if (unsupp.isEmpty()) {
1085             return null;
1086         } else {
1087             return unsupp;
1088         }
1089     }
1090 
1091     public ServiceUIFactory getServiceUIFactory() {
1092         return null;
1093     }
1094 
1095     public String toString() {
1096         return "Unix Printer : " + getName();
1097     }
1098 
1099     public boolean equals(Object obj) {
1100         return  (obj == this ||
1101                  (obj instanceof UnixPrintService &&
1102                   ((UnixPrintService)obj).getName().equals(getName())));
1103     }
1104 
1105     public int hashCode() {
1106         return this.getClass().hashCode()+getName().hashCode();
1107     }
1108 
1109     public boolean usesClass(Class<?> c) {
1110         return (c == sun.print.PSPrinterJob.class);
1111     }
1112 
1113 }