1 /*
   2  * Copyright (c) 2000, 2020, 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 getPrinterIsAcceptingJobsBSD() {
 226         if (PrintServiceLookupProvider.cmdIndex ==
 227             PrintServiceLookupProvider.UNINITIALIZED) {
 228 
 229             PrintServiceLookupProvider.cmdIndex =
 230                 PrintServiceLookupProvider.getBSDCommandIndex();
 231         }
 232 
 233         String command = "/usr/sbin/lpc status " + printer
 234             + lpcStatusCom[PrintServiceLookupProvider.cmdIndex];
 235         String[] results= PrintServiceLookupProvider.execCmd(command);
 236 
 237         if (results != null && results.length > 0) {
 238             if (PrintServiceLookupProvider.cmdIndex ==
 239                 PrintServiceLookupProvider.BSD_LPD_NG) {
 240                 if (results[0].startsWith("enabled enabled")) {
 241                     return PrinterIsAcceptingJobs.ACCEPTING_JOBS ;
 242                 }
 243             } else {
 244                 if ((results[1].trim().startsWith("queuing is enabled") &&
 245                     results[2].trim().startsWith("printing is enabled")) ||
 246                     (results.length >= 4 &&
 247                      results[2].trim().startsWith("queuing is enabled") &&
 248                      results[3].trim().startsWith("printing is enabled"))) {
 249                     return PrinterIsAcceptingJobs.ACCEPTING_JOBS ;
 250                 }
 251             }
 252         }
 253         return PrinterIsAcceptingJobs.NOT_ACCEPTING_JOBS ;
 254     }
 255 
 256     // Filter the list of possible AIX Printers and remove header lines
 257     // and extra lines which have been added for remote printers.
 258     // 'protected' because this method is also used from PrintServiceLookupProvider.
 259     protected static String[] filterPrinterNamesAIX(String[] posPrinters) {
 260         ArrayList<String> printers = new ArrayList<>();
 261         String [] splitPart;
 262 
 263         for(int i = 0; i < posPrinters.length; i++) {
 264             // Remove the header lines
 265             if (posPrinters[i].startsWith("---") ||
 266                 posPrinters[i].startsWith("Queue") ||
 267                 posPrinters[i].isEmpty()) continue;
 268 
 269             // Check if there is a ":" in the end of the first colomn.
 270             // This means that it is not a valid printer definition.
 271             splitPart = posPrinters[i].split(" ");
 272             if(splitPart.length >= 1 && !splitPart[0].trim().endsWith(":")) {
 273                 printers.add(posPrinters[i]);
 274             }
 275         }
 276 
 277         return printers.toArray(new String[printers.size()]);
 278     }
 279 
 280     private PrinterIsAcceptingJobs getPrinterIsAcceptingJobsAIX() {
 281         // On AIX there should not be a blank after '-a'.
 282         String command = "/usr/bin/lpstat -a" + printer;
 283         String[] results= PrintServiceLookupProvider.execCmd(command);
 284 
 285         // Remove headers and bogus entries added by remote printers.
 286         results = filterPrinterNamesAIX(results);
 287 
 288         if (results != null && results.length > 0) {
 289             for (int i = 0; i < results.length; i++) {
 290                 if (results[i].contains("READY") ||
 291                     results[i].contains("RUNNING")) {
 292                     return PrinterIsAcceptingJobs.ACCEPTING_JOBS;
 293                 }
 294             }
 295         }
 296 
 297         return PrinterIsAcceptingJobs.NOT_ACCEPTING_JOBS;
 298 
 299     }
 300 
 301     private PrinterIsAcceptingJobs getPrinterIsAcceptingJobs() {
 302         if (PrintServiceLookupProvider.isBSD()) {
 303             return getPrinterIsAcceptingJobsBSD();
 304         } else if (PrintServiceLookupProvider.isAIX()) {
 305             return getPrinterIsAcceptingJobsAIX();
 306         } else {
 307             return PrinterIsAcceptingJobs.ACCEPTING_JOBS;
 308         }
 309     }
 310 
 311     private PrinterState getPrinterState() {
 312         if (isInvalid) {
 313             return PrinterState.STOPPED;
 314         } else {
 315             return null;
 316         }
 317     }
 318 
 319     private PrinterStateReasons getPrinterStateReasons() {
 320         if (isInvalid) {
 321             PrinterStateReasons psr = new PrinterStateReasons();
 322             psr.put(PrinterStateReason.SHUTDOWN, Severity.ERROR);
 323             return psr;
 324         } else {
 325             return null;
 326         }
 327     }
 328 
 329     private QueuedJobCount getQueuedJobCountBSD() {
 330         if (PrintServiceLookupProvider.cmdIndex ==
 331             PrintServiceLookupProvider.UNINITIALIZED) {
 332 
 333             PrintServiceLookupProvider.cmdIndex =
 334                 PrintServiceLookupProvider.getBSDCommandIndex();
 335         }
 336 
 337         int qlen = 0;
 338         String command = "/usr/sbin/lpc status " + printer
 339             + lpcQueueCom[PrintServiceLookupProvider.cmdIndex];
 340         String[] results = PrintServiceLookupProvider.execCmd(command);
 341 
 342         if (results != null && results.length > 0) {
 343             String queued;
 344             if (PrintServiceLookupProvider.cmdIndex ==
 345                 PrintServiceLookupProvider.BSD_LPD_NG) {
 346                 queued = results[0];
 347             } else {
 348                 queued = results[3].trim();
 349                 if (queued.startsWith("no")) {
 350                     return new QueuedJobCount(0);
 351                 } else {
 352                     queued = queued.substring(0, queued.indexOf(' '));
 353                 }
 354             }
 355 
 356             try {
 357                 qlen = Integer.parseInt(queued);
 358             } catch (NumberFormatException e) {
 359             }
 360         }
 361 
 362         return new QueuedJobCount(qlen);
 363     }
 364 
 365     private QueuedJobCount getQueuedJobCountAIX() {
 366         // On AIX there should not be a blank after '-a'.
 367         String command = "/usr/bin/lpstat -a" + printer;
 368         String[] results=  PrintServiceLookupProvider.execCmd(command);
 369 
 370         // Remove headers and bogus entries added by remote printers.
 371         results = filterPrinterNamesAIX(results);
 372 
 373         int qlen = 0;
 374         if (results != null && results.length > 0){
 375             for (int i = 0; i < results.length; i++) {
 376                 if (results[i].contains("QUEUED")){
 377                     qlen ++;
 378                 }
 379             }
 380         }
 381         return new QueuedJobCount(qlen);
 382     }
 383 
 384     private QueuedJobCount getQueuedJobCount() {
 385         if (PrintServiceLookupProvider.isBSD()) {
 386             return getQueuedJobCountBSD();
 387         } else if (PrintServiceLookupProvider.isAIX()) {
 388             return getQueuedJobCountAIX();
 389         } else {
 390             return new QueuedJobCount(0);
 391         }
 392     }
 393 
 394     private PrintServiceAttributeSet getBSDServiceAttributes() {
 395         PrintServiceAttributeSet attrs = new HashPrintServiceAttributeSet();
 396         attrs.add(getQueuedJobCountBSD());
 397         attrs.add(getPrinterIsAcceptingJobsBSD());
 398         return attrs;
 399     }
 400 
 401     private PrintServiceAttributeSet getAIXServiceAttributes() {
 402         PrintServiceAttributeSet attrs = new HashPrintServiceAttributeSet();
 403         attrs.add(getQueuedJobCountAIX());
 404         attrs.add(getPrinterIsAcceptingJobsAIX());
 405         return attrs;
 406     }
 407 
 408     private boolean isSupportedCopies(Copies copies) {
 409         int numCopies = copies.getValue();
 410         return (numCopies > 0 && numCopies < MAXCOPIES);
 411     }
 412 
 413     private boolean isSupportedMedia(MediaSizeName msn) {
 414         for (int i=0; i<mediaSizes.length; i++) {
 415             if (msn.equals(mediaSizes[i])) {
 416                 return true;
 417             }
 418         }
 419         return false;
 420     }
 421 
 422     public DocPrintJob createPrintJob() {
 423       SecurityManager security = System.getSecurityManager();
 424       if (security != null) {
 425         security.checkPrintJobAccess();
 426       }
 427         return new UnixPrintJob(this);
 428     }
 429 
 430     private PrintServiceAttributeSet getDynamicAttributes() {
 431         if (PrintServiceLookupProvider.isAIX()) {
 432             return getAIXServiceAttributes();
 433         } else {
 434             return getBSDServiceAttributes();
 435         }
 436     }
 437 
 438     public PrintServiceAttributeSet getUpdatedAttributes() {
 439         PrintServiceAttributeSet currSet = getDynamicAttributes();
 440         if (lastSet == null) {
 441             lastSet = currSet;
 442             return AttributeSetUtilities.unmodifiableView(currSet);
 443         } else {
 444             PrintServiceAttributeSet updates =
 445                 new HashPrintServiceAttributeSet();
 446             Attribute []attrs = currSet.toArray();
 447             Attribute attr;
 448             for (int i=0; i<attrs.length; i++) {
 449                 attr = attrs[i];
 450                 if (!lastSet.containsValue(attr)) {
 451                     updates.add(attr);
 452                 }
 453             }
 454             lastSet = currSet;
 455             return AttributeSetUtilities.unmodifiableView(updates);
 456         }
 457     }
 458 
 459     public void wakeNotifier() {
 460         synchronized (this) {
 461             if (notifier != null) {
 462                 notifier.wake();
 463             }
 464         }
 465     }
 466 
 467     public void addPrintServiceAttributeListener(
 468                                  PrintServiceAttributeListener listener) {
 469         synchronized (this) {
 470             if (listener == null) {
 471                 return;
 472             }
 473             if (notifier == null) {
 474                 notifier = new ServiceNotifier(this);
 475             }
 476             notifier.addListener(listener);
 477         }
 478     }
 479 
 480     public void removePrintServiceAttributeListener(
 481                                   PrintServiceAttributeListener listener) {
 482         synchronized (this) {
 483             if (listener == null || notifier == null ) {
 484                 return;
 485             }
 486             notifier.removeListener(listener);
 487             if (notifier.isEmpty()) {
 488                 notifier.stopNotifier();
 489                 notifier = null;
 490             }
 491         }
 492     }
 493 
 494     @SuppressWarnings("unchecked")
 495     public <T extends PrintServiceAttribute>
 496         T getAttribute(Class<T> category)
 497     {
 498         if (category == null) {
 499             throw new NullPointerException("category");
 500         }
 501         if (!(PrintServiceAttribute.class.isAssignableFrom(category))) {
 502             throw new IllegalArgumentException("Not a PrintServiceAttribute");
 503         }
 504 
 505         if (category == PrinterName.class) {
 506             return (T)getPrinterName();
 507         } else if (category == PrinterState.class) {
 508             return (T)getPrinterState();
 509         } else if (category == PrinterStateReasons.class) {
 510             return (T)getPrinterStateReasons();
 511         } else if (category == QueuedJobCount.class) {
 512             return (T)getQueuedJobCount();
 513         } else if (category == PrinterIsAcceptingJobs.class) {
 514             return (T)getPrinterIsAcceptingJobs();
 515         } else {
 516             return null;
 517         }
 518     }
 519 
 520     public PrintServiceAttributeSet getAttributes() {
 521         PrintServiceAttributeSet attrs = new HashPrintServiceAttributeSet();
 522         attrs.add(getPrinterName());
 523         attrs.add(getPrinterIsAcceptingJobs());
 524         PrinterState prnState = getPrinterState();
 525         if (prnState != null) {
 526             attrs.add(prnState);
 527         }
 528         PrinterStateReasons prnStateReasons = getPrinterStateReasons();
 529         if (prnStateReasons != null) {
 530             attrs.add(prnStateReasons);
 531         }
 532         attrs.add(getQueuedJobCount());
 533         return AttributeSetUtilities.unmodifiableView(attrs);
 534     }
 535 
 536     private void initSupportedDocFlavors() {
 537         String hostEnc = DocFlavor.hostEncoding.toLowerCase(Locale.ENGLISH);
 538         if (!hostEnc.equals("utf-8") && !hostEnc.equals("utf-16") &&
 539             !hostEnc.equals("utf-16be") && !hostEnc.equals("utf-16le") &&
 540             !hostEnc.equals("us-ascii")) {
 541 
 542             int len = supportedDocFlavorsInit.length;
 543             DocFlavor[] flavors =
 544                 new DocFlavor[len + supportedHostDocFlavors.length];
 545             // copy host encoding flavors
 546             System.arraycopy(supportedHostDocFlavors, 0, flavors,
 547                              len, supportedHostDocFlavors.length);
 548             System.arraycopy(supportedDocFlavorsInit, 0, flavors, 0, len);
 549 
 550             supportedDocFlavors = flavors;
 551         } else {
 552             supportedDocFlavors = supportedDocFlavorsInit;
 553         }
 554     }
 555 
 556     public DocFlavor[] getSupportedDocFlavors() {
 557         if (supportedDocFlavors == null) {
 558             initSupportedDocFlavors();
 559         }
 560         int len = supportedDocFlavors.length;
 561         DocFlavor[] flavors = new DocFlavor[len];
 562         System.arraycopy(supportedDocFlavors, 0, flavors, 0, len);
 563 
 564         return flavors;
 565     }
 566 
 567     public boolean isDocFlavorSupported(DocFlavor flavor) {
 568         if (supportedDocFlavors == null) {
 569             initSupportedDocFlavors();
 570         }
 571         for (int f=0; f<supportedDocFlavors.length; f++) {
 572             if (flavor.equals(supportedDocFlavors[f])) {
 573                 return true;
 574             }
 575         }
 576         return false;
 577     }
 578 
 579     public Class<?>[] getSupportedAttributeCategories() {
 580         ArrayList<Class<?>> categList = new ArrayList<>(otherAttrCats.length);
 581         for (Class<?> c : otherAttrCats) {
 582             categList.add(c);
 583         }
 584         if (GraphicsEnvironment.isHeadless() == false) {
 585             categList.add(DialogOwner.class);
 586             categList.add(DialogTypeSelection.class);
 587         }
 588         return categList.toArray(new Class<?>[categList.size()]);
 589     }
 590 
 591     public boolean
 592         isAttributeCategorySupported(Class<? extends Attribute> category)
 593     {
 594         if (category == null) {
 595             throw new NullPointerException("null category");
 596         }
 597         if (!(Attribute.class.isAssignableFrom(category))) {
 598             throw new IllegalArgumentException(category +
 599                                              " is not an Attribute");
 600         }
 601 
 602         for (int i=0;i<otherAttrCats.length;i++) {
 603             if (category == otherAttrCats[i]) {
 604                 return true;
 605             }
 606         }
 607         return false;
 608     }
 609 
 610     /* return defaults for all attributes for which there is a default
 611      * value
 612      */
 613     public Object
 614         getDefaultAttributeValue(Class<? extends Attribute> category)
 615     {
 616         if (category == null) {
 617             throw new NullPointerException("null category");
 618         }
 619         if (!Attribute.class.isAssignableFrom(category)) {
 620             throw new IllegalArgumentException(category +
 621                                              " is not an Attribute");
 622         }
 623 
 624         if (!isAttributeCategorySupported(category)) {
 625             return null;
 626         }
 627 
 628         if (category == Copies.class) {
 629             return new Copies(1);
 630         } else if (category == Chromaticity.class) {
 631             return Chromaticity.COLOR;
 632         } else if (category == Destination.class) {
 633             try {
 634                 return new Destination((new File("out.ps")).toURI());
 635             } catch (SecurityException se) {
 636                 try {
 637                     return new Destination(new URI("file:out.ps"));
 638                 } catch (URISyntaxException e) {
 639                     return null;
 640                 }
 641             }
 642         } else if (category == Fidelity.class) {
 643             return Fidelity.FIDELITY_FALSE;
 644         } else if (category == JobName.class) {
 645             return new JobName("Java Printing", null);
 646         } else if (category == JobSheets.class) {
 647             return JobSheets.STANDARD;
 648         } else if (category == Media.class) {
 649             String defaultCountry = Locale.getDefault().getCountry();
 650             if (defaultCountry != null &&
 651                 (defaultCountry.isEmpty() ||
 652                  defaultCountry.equals(Locale.US.getCountry()) ||
 653                  defaultCountry.equals(Locale.CANADA.getCountry()))) {
 654                 return MediaSizeName.NA_LETTER;
 655             } else {
 656                  return MediaSizeName.ISO_A4;
 657             }
 658         } else if (category == MediaPrintableArea.class) {
 659             String defaultCountry = Locale.getDefault().getCountry();
 660             float iw, ih;
 661             if (defaultCountry != null &&
 662                 (defaultCountry.isEmpty() ||
 663                  defaultCountry.equals(Locale.US.getCountry()) ||
 664                  defaultCountry.equals(Locale.CANADA.getCountry()))) {
 665                 iw = MediaSize.NA.LETTER.getX(Size2DSyntax.INCH) - 0.5f;
 666                 ih = MediaSize.NA.LETTER.getY(Size2DSyntax.INCH) - 0.5f;
 667             } else {
 668                 iw = MediaSize.ISO.A4.getX(Size2DSyntax.INCH) - 0.5f;
 669                 ih = MediaSize.ISO.A4.getY(Size2DSyntax.INCH) - 0.5f;
 670             }
 671             return new MediaPrintableArea(0.25f, 0.25f, iw, ih,
 672                                           MediaPrintableArea.INCH);
 673         } else if (category == OrientationRequested.class) {
 674             return OrientationRequested.PORTRAIT;
 675         } else if (category == PageRanges.class) {
 676             return new PageRanges(1, Integer.MAX_VALUE);
 677         } else if (category == RequestingUserName.class) {
 678             String userName = "";
 679             try {
 680               userName = System.getProperty("user.name", "");
 681             } catch (SecurityException se) {
 682             }
 683             return new RequestingUserName(userName, null);
 684         } else if (category == SheetCollate.class) {
 685             return SheetCollate.UNCOLLATED;
 686         } else if (category == Sides.class) {
 687             return Sides.ONE_SIDED;
 688         } else
 689             return null;
 690     }
 691 
 692 
 693     private boolean isAutoSense(DocFlavor flavor) {
 694         if (flavor.equals(DocFlavor.BYTE_ARRAY.AUTOSENSE) ||
 695             flavor.equals(DocFlavor.INPUT_STREAM.AUTOSENSE) ||
 696             flavor.equals(DocFlavor.URL.AUTOSENSE)) {
 697             return true;
 698         }
 699         else {
 700             return false;
 701         }
 702     }
 703 
 704     public Object
 705         getSupportedAttributeValues(Class<? extends Attribute> category,
 706                                     DocFlavor flavor,
 707                                     AttributeSet attributes)
 708     {
 709 
 710         if (category == null) {
 711             throw new NullPointerException("null category");
 712         }
 713         if (!Attribute.class.isAssignableFrom(category)) {
 714             throw new IllegalArgumentException(category +
 715                                              " does not implement Attribute");
 716         }
 717         if (flavor != null) {
 718             if (!isDocFlavorSupported(flavor)) {
 719                 throw new IllegalArgumentException(flavor +
 720                                                " is an unsupported flavor");
 721             } else if (isAutoSense(flavor)) {
 722                 return null;
 723             }
 724         }
 725 
 726         if (!isAttributeCategorySupported(category)) {
 727             return null;
 728         }
 729 
 730         if (category == Chromaticity.class) {
 731             if (flavor == null || isServiceFormattedFlavor(flavor)) {
 732                 Chromaticity[]arr = new Chromaticity[1];
 733                 arr[0] = Chromaticity.COLOR;
 734                 return (arr);
 735             } else {
 736                 return null;
 737             }
 738         } else if (category == Destination.class) {
 739             try {
 740                 return new Destination((new File("out.ps")).toURI());
 741             } catch (SecurityException se) {
 742                 try {
 743                     return new Destination(new URI("file:out.ps"));
 744                 } catch (URISyntaxException e) {
 745                     return null;
 746                 }
 747             }
 748         } else if (category == JobName.class) {
 749             return new JobName("Java Printing", null);
 750         } else if (category == JobSheets.class) {
 751             JobSheets[] arr = new JobSheets[2];
 752             arr[0] = JobSheets.NONE;
 753             arr[1] = JobSheets.STANDARD;
 754             return arr;
 755         } else if (category == RequestingUserName.class) {
 756             String userName = "";
 757             try {
 758               userName = System.getProperty("user.name", "");
 759             } catch (SecurityException se) {
 760             }
 761             return new RequestingUserName(userName, null);
 762         } else if (category == OrientationRequested.class) {
 763             if (flavor == null || isServiceFormattedFlavor(flavor)) {
 764                 OrientationRequested []arr = new OrientationRequested[3];
 765                 arr[0] = OrientationRequested.PORTRAIT;
 766                 arr[1] = OrientationRequested.LANDSCAPE;
 767                 arr[2] = OrientationRequested.REVERSE_LANDSCAPE;
 768                 return arr;
 769             } else {
 770                 return null;
 771             }
 772         } else if ((category == Copies.class) ||
 773                    (category == CopiesSupported.class)) {
 774             if (flavor == null ||
 775                 !(flavor.equals(DocFlavor.INPUT_STREAM.POSTSCRIPT) ||
 776                   flavor.equals(DocFlavor.URL.POSTSCRIPT) ||
 777                   flavor.equals(DocFlavor.BYTE_ARRAY.POSTSCRIPT))) {
 778                 return new CopiesSupported(1, MAXCOPIES);
 779             } else {
 780                 return null;
 781             }
 782         } else if (category == Media.class) {
 783             Media []arr = new Media[mediaSizes.length];
 784             System.arraycopy(mediaSizes, 0, arr, 0, mediaSizes.length);
 785             return arr;
 786         } else if (category == Fidelity.class) {
 787             Fidelity []arr = new Fidelity[2];
 788             arr[0] = Fidelity.FIDELITY_FALSE;
 789             arr[1] = Fidelity.FIDELITY_TRUE;
 790             return arr;
 791         } else if (category == MediaPrintableArea.class) {
 792             /* The code below implements the behaviour that if no Media or
 793              * MediaSize attribute is specified, return an array of
 794              * MediaPrintableArea, one for each supported Media.
 795              * If a MediaSize is specified, return a MPA consistent for that,
 796              * and if a Media is specified locate its MediaSize and return
 797              * its MPA, and if none is found, return an MPA for the default
 798              * Media for this service.
 799              */
 800             if (attributes == null) {
 801                 return getAllPrintableAreas();
 802             }
 803             MediaSize mediaSize = (MediaSize)attributes.get(MediaSize.class);
 804             Media media = (Media)attributes.get(Media.class);
 805             MediaPrintableArea []arr = new MediaPrintableArea[1];
 806             if (mediaSize == null) {
 807                 if (media instanceof MediaSizeName) {
 808                     MediaSizeName msn = (MediaSizeName)media;
 809                     mediaSize = MediaSize.getMediaSizeForName(msn);
 810                     if (mediaSize == null) {
 811                         /* try to get a size from the default media */
 812                         media = (Media)getDefaultAttributeValue(Media.class);
 813                         if (media instanceof MediaSizeName) {
 814                             msn = (MediaSizeName)media;
 815                             mediaSize = MediaSize.getMediaSizeForName(msn);
 816                         }
 817                         if (mediaSize == null) {
 818                             /* shouldn't happen, return a default */
 819                             arr[0] = new MediaPrintableArea(0.25f, 0.25f,
 820                                                             8f, 10.5f,
 821                                                             MediaSize.INCH);
 822                             return arr;
 823                         }
 824                     }
 825                 } else {
 826                     return getAllPrintableAreas();
 827                 }
 828             }
 829             /* If reach here MediaSize is non-null */
 830             assert mediaSize != null;
 831             arr[0] = new MediaPrintableArea(0.25f, 0.25f,
 832                                 mediaSize.getX(MediaSize.INCH)-0.5f,
 833                                 mediaSize.getY(MediaSize.INCH)-0.5f,
 834                                 MediaSize.INCH);
 835             return arr;
 836         } else if (category == PageRanges.class) {
 837             if (flavor == null ||
 838                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
 839                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE)) {
 840                 PageRanges []arr = new PageRanges[1];
 841                 arr[0] = new PageRanges(1, Integer.MAX_VALUE);
 842                 return arr;
 843             } else {
 844                 return null;
 845             }
 846         } else if (category == SheetCollate.class) {
 847             if (flavor == null ||
 848                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
 849                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE)) {
 850                 SheetCollate []arr = new SheetCollate[2];
 851                 arr[0] = SheetCollate.UNCOLLATED;
 852                 arr[1] = SheetCollate.COLLATED;
 853                 return arr;
 854             } else {
 855                 return null;
 856             }
 857         } else if (category == Sides.class) {
 858             if (flavor == null ||
 859                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
 860                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE)) {
 861                 Sides []arr = new Sides[3];
 862                 arr[0] = Sides.ONE_SIDED;
 863                 arr[1] = Sides.TWO_SIDED_LONG_EDGE;
 864                 arr[2] = Sides.TWO_SIDED_SHORT_EDGE;
 865                 return arr;
 866             } else {
 867                 return null;
 868             }
 869         } else {
 870             return null;
 871         }
 872     }
 873 
 874     private static MediaPrintableArea[] mpas = null;
 875     private MediaPrintableArea[] getAllPrintableAreas() {
 876 
 877         if (mpas == null) {
 878             Media[] media = (Media[])getSupportedAttributeValues(Media.class,
 879                                                                  null, null);
 880             mpas = new MediaPrintableArea[media.length];
 881             for (int i=0; i< mpas.length; i++) {
 882                 if (media[i] instanceof MediaSizeName) {
 883                     MediaSizeName msn = (MediaSizeName)media[i];
 884                     MediaSize mediaSize = MediaSize.getMediaSizeForName(msn);
 885                     if (mediaSize == null) {
 886                         mpas[i] = (MediaPrintableArea)
 887                             getDefaultAttributeValue(MediaPrintableArea.class);
 888                     } else {
 889                         mpas[i] = new MediaPrintableArea(0.25f, 0.25f,
 890                                         mediaSize.getX(MediaSize.INCH)-0.5f,
 891                                         mediaSize.getY(MediaSize.INCH)-0.5f,
 892                                         MediaSize.INCH);
 893                     }
 894                 }
 895             }
 896         }
 897         MediaPrintableArea[] mpasCopy = new MediaPrintableArea[mpas.length];
 898         System.arraycopy(mpas, 0, mpasCopy, 0, mpas.length);
 899         return mpasCopy;
 900     }
 901 
 902     /* Is this one of the flavors that this service explicitly
 903      * generates postscript for, and so can control how it is rendered?
 904      */
 905     private boolean isServiceFormattedFlavor(DocFlavor flavor) {
 906         return
 907             flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
 908             flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE) ||
 909             flavor.equals(DocFlavor.BYTE_ARRAY.GIF) ||
 910             flavor.equals(DocFlavor.INPUT_STREAM.GIF) ||
 911             flavor.equals(DocFlavor.URL.GIF) ||
 912             flavor.equals(DocFlavor.BYTE_ARRAY.JPEG) ||
 913             flavor.equals(DocFlavor.INPUT_STREAM.JPEG) ||
 914             flavor.equals(DocFlavor.URL.JPEG) ||
 915             flavor.equals(DocFlavor.BYTE_ARRAY.PNG) ||
 916             flavor.equals(DocFlavor.INPUT_STREAM.PNG) ||
 917             flavor.equals(DocFlavor.URL.PNG);
 918     }
 919 
 920     public boolean isAttributeValueSupported(Attribute attr,
 921                                              DocFlavor flavor,
 922                                              AttributeSet attributes) {
 923         if (attr == null) {
 924             throw new NullPointerException("null attribute");
 925         }
 926         if (flavor != null) {
 927             if (!isDocFlavorSupported(flavor)) {
 928                 throw new IllegalArgumentException(flavor +
 929                                                " is an unsupported flavor");
 930             } else if (isAutoSense(flavor)) {
 931                 return false;
 932             }
 933         }
 934         Class<? extends Attribute> category = attr.getCategory();
 935         if (!isAttributeCategorySupported(category)) {
 936             return false;
 937         }
 938         else if (attr.getCategory() == Chromaticity.class) {
 939             if (flavor == null || isServiceFormattedFlavor(flavor)) {
 940                 return attr == Chromaticity.COLOR;
 941             } else {
 942                 return false;
 943             }
 944         }
 945         else if (attr.getCategory() == Copies.class) {
 946             return (flavor == null ||
 947                    !(flavor.equals(DocFlavor.INPUT_STREAM.POSTSCRIPT) ||
 948                      flavor.equals(DocFlavor.URL.POSTSCRIPT) ||
 949                      flavor.equals(DocFlavor.BYTE_ARRAY.POSTSCRIPT))) &&
 950                 isSupportedCopies((Copies)attr);
 951         } else if (attr.getCategory() == Destination.class) {
 952             URI uri = ((Destination)attr).getURI();
 953                 if ("file".equals(uri.getScheme()) &&
 954                     !uri.getSchemeSpecificPart().isEmpty()) {
 955                 return true;
 956             } else {
 957             return false;
 958             }
 959         } else if (attr.getCategory() == Media.class) {
 960             if (attr instanceof MediaSizeName) {
 961                 return isSupportedMedia((MediaSizeName)attr);
 962             } else {
 963                 return false;
 964             }
 965         } else if (attr.getCategory() == OrientationRequested.class) {
 966             if (attr == OrientationRequested.REVERSE_PORTRAIT ||
 967                 (flavor != null) &&
 968                 !isServiceFormattedFlavor(flavor)) {
 969                 return false;
 970             }
 971         } else if (attr.getCategory() == PageRanges.class) {
 972             if (flavor != null &&
 973                 !(flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
 974                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE))) {
 975                 return false;
 976             }
 977         } else if (attr.getCategory() == SheetCollate.class) {
 978             if (flavor != null &&
 979                 !(flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
 980                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE))) {
 981                 return false;
 982             }
 983         } else if (attr.getCategory() == Sides.class) {
 984             if (flavor != null &&
 985                 !(flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
 986                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE))) {
 987                 return false;
 988             }
 989         } else if (attr.getCategory() == DialogOwner.class) {
 990             DialogOwner owner = (DialogOwner)attr;
 991             // ID not supported on any dialog type on Unix platforms.
 992             if (DialogOwnerAccessor.getID(owner) != 0) {
 993                 return false;
 994             }
 995             // UnixPrintService is not used on Mac, so this is
 996             // always some Unix system that does not have CUPS/IPP
 997             // Which means we always use a Swing dialog and we need
 998             // only check if alwaysOnTop is supported by the toolkit.
 999             if (owner.getOwner() != null) {
1000                 return true;
1001             } else {
1002                 return Toolkit.getDefaultToolkit().isAlwaysOnTopSupported();
1003             }
1004         } else if (attr.getCategory() == DialogTypeSelection.class) {
1005             DialogTypeSelection dts = (DialogTypeSelection)attr;
1006             return dts == DialogTypeSelection.COMMON;
1007         }
1008         return true;
1009     }
1010 
1011     public AttributeSet getUnsupportedAttributes(DocFlavor flavor,
1012                                                  AttributeSet attributes) {
1013 
1014         if (flavor != null && !isDocFlavorSupported(flavor)) {
1015             throw new IllegalArgumentException("flavor " + flavor +
1016                                                "is not supported");
1017         }
1018 
1019         if (attributes == null) {
1020             return null;
1021         }
1022 
1023         Attribute attr;
1024         AttributeSet unsupp = new HashAttributeSet();
1025         Attribute []attrs = attributes.toArray();
1026         for (int i=0; i<attrs.length; i++) {
1027             try {
1028                 attr = attrs[i];
1029                 if (!isAttributeCategorySupported(attr.getCategory())) {
1030                     unsupp.add(attr);
1031                 } else if (!isAttributeValueSupported(attr, flavor,
1032                                                       attributes)) {
1033                     unsupp.add(attr);
1034                 }
1035             } catch (ClassCastException e) {
1036             }
1037         }
1038         if (unsupp.isEmpty()) {
1039             return null;
1040         } else {
1041             return unsupp;
1042         }
1043     }
1044 
1045     public ServiceUIFactory getServiceUIFactory() {
1046         return null;
1047     }
1048 
1049     public String toString() {
1050         return "Unix Printer : " + getName();
1051     }
1052 
1053     public boolean equals(Object obj) {
1054         return  (obj == this ||
1055                  (obj instanceof UnixPrintService &&
1056                   ((UnixPrintService)obj).getName().equals(getName())));
1057     }
1058 
1059     public int hashCode() {
1060         return this.getClass().hashCode()+getName().hashCode();
1061     }
1062 
1063     public boolean usesClass(Class<?> c) {
1064         return (c == sun.print.PSPrinterJob.class);
1065     }
1066 
1067 }