Navigation Menu

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8288137: The set of available printers is not updated without application restart #817

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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,17 @@ 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 TreeSet<Printer> printers = null;
kevinrushforth marked this conversation as resolved.
Show resolved Hide resolved

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 +119,93 @@ 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);
} else {
PrintService[] newServices =
PrintServiceLookup.lookupPrintServices(null, null);
if ((newServices.length != printerSet.size()) ||
(lastTime + 120000) > System.currentTimeMillis()) {
kevinrushforth marked this conversation as resolved.
Show resolved Hide resolved
updatePrinters(newServices);
}
}
lastTime = System.currentTimeMillis();
kevinrushforth marked this conversation as resolved.
Show resolved Hide resolved
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);
kevinrushforth marked this conversation as resolved.
Show resolved Hide resolved
}
} 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");
}

}
}