Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
8288137: The set of available printers is not updated without applica…
…tion restart

Reviewed-by: kcr, arapte
  • Loading branch information
prrace committed Jul 13, 2022
1 parent 0132ac8 commit b9a1ec5
Show file tree
Hide file tree
Showing 3 changed files with 213 additions and 17 deletions.
Expand Up @@ -32,6 +32,7 @@
import javax.print.PrintService;
import javax.print.PrintServiceLookup;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Set;
import java.util.TreeSet;
import java.awt.Graphics;
Expand Down Expand Up @@ -62,8 +63,6 @@ public PrinterJobImpl createPrinterJob(PrinterJob job) {

private static Printer defaultPrinter = null;
public synchronized Printer getDefaultPrinter() {
// Eventually this needs to be updated to reflect when
// the default has changed.
if (defaultPrinter == null) {
PrintService defPrt =
PrintServiceLookup.lookupDefaultPrintService();
Expand Down Expand Up @@ -96,13 +95,16 @@ public int compare(Printer p1, Printer p2) {

private static final NameComparator nameComparator = new NameComparator();

// This is static. Eventually I want it to be dynamic, but first
// it needs to be enhanced to only create new instances where
// there really has been a change, which will be rare.
// The map is useful when updating
private static HashMap<PrintService, Printer> pMap = new HashMap<>();

private static long lastTime = 0L;
private static ObservableSet<Printer> printerSet = null;
private static ObservableSet<Printer> returnedPrinterSet = null;

public synchronized ObservableSet<Printer> getAllPrinters() {
if (printerSet == null) {
Set printers = new TreeSet<Printer>(nameComparator);
if (returnedPrinterSet == null) {
TreeSet<Printer> printers = new TreeSet<Printer>(nameComparator);
// Trigger getting default first, so we don't recreate that.
Printer defPrinter = getDefaultPrinter();
PrintService defService = null;
Expand All @@ -116,17 +118,94 @@ public synchronized ObservableSet<Printer> getAllPrinters() {
for (int i=0; i<allServices.length;i++) {
if (defService != null && defService.equals(allServices[i])) {
printers.add(defPrinter);
pMap.put(defService, defPrinter);
} else {
PrinterImpl impl = new J2DPrinter(allServices[i]);
Printer printer = PrintHelper.createPrinter(impl);
impl.setPrinter(printer);
printers.add(printer);
addNew(allServices[i], printers);
}
}
printerSet = FXCollections.observableSet(printers);
returnedPrinterSet =
FXCollections.unmodifiableObservableSet(printerSet);
lastTime = System.currentTimeMillis();
} else {
PrintService[] newServices =
PrintServiceLookup.lookupPrintServices(null, null);
if ((newServices.length != printerSet.size()) ||
(lastTime + 120000) < System.currentTimeMillis()) {
updatePrinters(newServices);
lastTime = System.currentTimeMillis();
}
}
return returnedPrinterSet;
}

private void addNew(PrintService s, Set<Printer> printers) {
PrinterImpl impl = new J2DPrinter(s);
Printer printer = PrintHelper.createPrinter(impl);
impl.setPrinter(printer);
printers.add(printer);
pMap.put(s, printer);
}

/* Only change a Printer instance if Java 2D changed it.
* Otherwise re-map back to the existing Printer instance.
* This relies on Java 2D not re-creating a printer too.
* Update the existing set so that an app that has cached the printer list
* automatically gets the updates. They can also observe changes .. but
* if doing so they could see the work in progress, but that's probably
* a good thing for an app that is interested.
* Two passes -
* First pass remove any printers that no longer exist.
* Second pass add any new printers.
* Finally update the default printer - if needed.
*/
private void updatePrinters(PrintService[] newServices) {

Set<PrintService> oldServiceSet = pMap.keySet();
PrintService[] oldServices = oldServiceSet.toArray(new PrintService[0]);

for (PrintService os : oldServices) {
boolean present = false;
for (PrintService ns : newServices) {
if (os.equals(ns)) {
present = true;
break;
}
}
printerSet =
FXCollections.unmodifiableObservableSet
(FXCollections.observableSet(printers));
if (!present) {
Printer printer = pMap.get(os);
pMap.remove(os);
printerSet.remove(printer);
}
}
for (PrintService s : newServices) {
if (!pMap.containsKey(s)) {
addNew(s, printerSet);
}
}
PrintService oldDefaultService =
(defaultPrinter == null) ? null :
((J2DPrinter)PrintHelper.getPrinterImpl(defaultPrinter)).getService();
PrintService newDefaultService = PrintServiceLookup.lookupDefaultPrintService();
if (newDefaultService != null) {
if (oldDefaultService == null ||
(!oldDefaultService.equals(newDefaultService)))
{
defaultPrinter = findDefaultPrinter(printerSet, newDefaultService);
}
} else {
defaultPrinter = null;
}
}

private static Printer findDefaultPrinter(Set<Printer> printers,
PrintService defaultService) {
for (Printer p : printers) {
PrintService s = ((J2DPrinter)PrintHelper.getPrinterImpl(p)).getService();
if (s.getName().equals(defaultService.getName())) {
return p;
}
}
return printerSet;
return null;
}
}
Expand Up @@ -58,7 +58,7 @@ public final class Printer {
/**
* Retrieve the installed printers.
* The set of printers may be dynamic.
* Consequently there is no guarantee that the result will be
* Consequently, there is no guarantee that the result will be
* the same from call to call, but should change only as
* a result of the default changing in the environment of the
* application.
Expand Down Expand Up @@ -86,10 +86,12 @@ private static ReadOnlyObjectWrapper<Printer> defaultPrinterImpl() {
if (security != null) {
security.checkPrintJobAccess();
}
Printer p = PrintPipeline.getPrintPipeline().getDefaultPrinter();
if (defaultPrinter == null) {
Printer p = PrintPipeline.getPrintPipeline().getDefaultPrinter();
defaultPrinter =
new ReadOnlyObjectWrapper<Printer>(null, "defaultPrinter", p);
} else {
defaultPrinter.setValue(p);
}
return defaultPrinter;
}
Expand Down
115 changes: 115 additions & 0 deletions tests/manual/printing/PrinterListenerTest.java
@@ -0,0 +1,115 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
import javafx.collections.SetChangeListener;

import javafx.application.Application;
import javafx.print.PrinterJob;
import javafx.print.Printer;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;

public class PrinterListenerTest extends Application {

public static void main(String[] args) {
launch(args);
}

ObservableSet<Printer> printers;
Printer defaultPrinter;
Stage window;

public void start(Stage stage) {
window = stage;
printPrinters();
printers.addListener(new SetChangeListener<Printer>() {
public void onChanged(SetChangeListener.Change<? extends Printer> change) {
printChanged(change);
}
});

VBox root = new VBox();
Scene scene = new Scene(root);
Button b = new Button("List Printers");
b.setOnAction(e -> printPrinters());
root.getChildren().add(b);
Button p = new Button("Show Print Dialog");
p.setOnAction(e -> showPrintDialog());
root.getChildren().add(p);
Text t = new Text();
t.setWrappingWidth(400);
t.setText(
"This is a very manual test which to be useful " +
"requires you to be adding and removing printers and changing " +
"the default from System Settings or whatever is the norm for " +
"the platform being tested and then pressing 'List Printers'. \n" +
"Updates happen only when you call the API - no background thread. " +
"The Added or Removed printers will be reported by the change listener " +
"demonstrating that the ObservableList works.\n" +
"The Print Dialog can be used to verify what is listed matches the dialog.");

root.getChildren().add(t);
stage.setScene(scene);
stage.show();
}

public void showPrintDialog() {
PrinterJob job = PrinterJob.createPrinterJob();
job.showPrintDialog(window);
}

public void printPrinters() {
if (printers != null) {
System.out.println("Current default printer="+defaultPrinter);
System.out.println("Current Printers :");
for (Printer p : printers) System.out.println(p);
System.out.println();
}

printers = Printer.getAllPrinters();
defaultPrinter = Printer.getDefaultPrinter();

System.out.println("New Default Printer ="+defaultPrinter);
System.out.println("New Printers :");
for (Printer p : printers) System.out.println(p);
System.out.println();
}

static void printChanged(SetChangeListener.Change<? extends Printer> c) {
if (c.wasAdded()) {
System.out.println("Added : " + c.getElementAdded());
} else if (c.wasRemoved()) {
System.out.println("Removed : " + c.getElementRemoved());
} else {
System.out.println("Other change");
}

}
}

1 comment on commit b9a1ec5

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.