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