76fea80707
Reviewed-by: aivanov, serb
448 lines
15 KiB
Java
448 lines
15 KiB
Java
/*
|
|
* Copyright (c) 2018, 2021, 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.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
/*
|
|
* @test
|
|
* @key printer
|
|
* @bug 8153732 8212202 8221263 8221412 8222108 8263311
|
|
* @requires (os.family == "Windows")
|
|
* @summary Windows remote printer changes do not reflect in lookupPrintServices()
|
|
* @run main/manual RemotePrinterStatusRefresh
|
|
*/
|
|
|
|
import java.awt.BorderLayout;
|
|
import java.awt.Color;
|
|
import java.awt.Component;
|
|
import java.awt.GridLayout;
|
|
import java.awt.event.ActionEvent;
|
|
import java.awt.event.WindowAdapter;
|
|
import java.awt.event.WindowEvent;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.List;
|
|
import java.util.concurrent.CountDownLatch;
|
|
import javax.print.PrintService;
|
|
import javax.print.PrintServiceLookup;
|
|
import javax.swing.AbstractListModel;
|
|
import javax.swing.BorderFactory;
|
|
import javax.swing.Box;
|
|
import javax.swing.BoxLayout;
|
|
import javax.swing.DefaultListCellRenderer;
|
|
import javax.swing.GroupLayout;
|
|
import javax.swing.JButton;
|
|
import javax.swing.JFrame;
|
|
import javax.swing.JLabel;
|
|
import javax.swing.JList;
|
|
import javax.swing.JPanel;
|
|
import javax.swing.JScrollPane;
|
|
import javax.swing.JTextArea;
|
|
import javax.swing.JTextField;
|
|
import javax.swing.SwingUtilities;
|
|
import javax.swing.Timer;
|
|
|
|
import static javax.swing.BorderFactory.createTitledBorder;
|
|
|
|
public class RemotePrinterStatusRefresh extends WindowAdapter {
|
|
|
|
private static final long TIMEOUT = 15L * 60;
|
|
|
|
|
|
private static final CountDownLatch latch = new CountDownLatch(1);
|
|
private static volatile RemotePrinterStatusRefresh test;
|
|
|
|
private volatile boolean testResult;
|
|
private volatile boolean testTimedOut;
|
|
|
|
private final JFrame frame;
|
|
|
|
private JButton refreshButton;
|
|
private JButton passButton;
|
|
private JButton failButton;
|
|
|
|
private final ServiceItemListModel beforeList;
|
|
private final ServiceItemListModel afterList;
|
|
|
|
private JTextField timeLeft;
|
|
|
|
private final Timer timer;
|
|
private final long startTime;
|
|
|
|
|
|
private static class ServiceItem {
|
|
private enum State {
|
|
REMOVED, UNCHANGED, ADDED
|
|
}
|
|
|
|
final String name;
|
|
State state;
|
|
|
|
private ServiceItem(final String name) {
|
|
this.name = name;
|
|
state = State.UNCHANGED;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return name;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
return (obj instanceof ServiceItem)
|
|
&& ((ServiceItem) obj).name.equals(name);
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return name.hashCode();
|
|
}
|
|
}
|
|
|
|
private static class ServiceItemListModel extends AbstractListModel<ServiceItem> {
|
|
private final List<ServiceItem> list;
|
|
|
|
private ServiceItemListModel(List<ServiceItem> list) {
|
|
this.list = list;
|
|
}
|
|
|
|
@Override
|
|
public int getSize() {
|
|
return list.size();
|
|
}
|
|
|
|
@Override
|
|
public ServiceItem getElementAt(int index) {
|
|
return list.get(index);
|
|
}
|
|
|
|
private void refreshList(List<ServiceItem> newList) {
|
|
list.clear();
|
|
list.addAll(newList);
|
|
fireChanged();
|
|
}
|
|
|
|
private void fireChanged() {
|
|
fireContentsChanged(this, 0, list.size() - 1);
|
|
}
|
|
}
|
|
|
|
private static class ServiceItemListRenderer extends DefaultListCellRenderer {
|
|
@Override
|
|
public Component getListCellRendererComponent(JList<?> list,
|
|
Object value,
|
|
int index,
|
|
boolean isSelected,
|
|
boolean cellHasFocus) {
|
|
Component component =
|
|
super.getListCellRendererComponent(list, value, index,
|
|
isSelected, cellHasFocus);
|
|
switch (((ServiceItem) value).state) {
|
|
case REMOVED:
|
|
component.setBackground(Color.RED);
|
|
component.setForeground(Color.WHITE);
|
|
break;
|
|
case ADDED:
|
|
component.setBackground(Color.GREEN);
|
|
component.setForeground(Color.BLACK);
|
|
break;
|
|
case UNCHANGED:
|
|
default:
|
|
break;
|
|
}
|
|
return component;
|
|
}
|
|
}
|
|
|
|
private static final String INSTRUCTIONS_TEXT =
|
|
"Please follow the steps for this manual test:\n"
|
|
+ "Step 0: \"Before\" list is populated with currently "
|
|
+ "configured printers.\n"
|
|
+ "Step 1: Add or Remove a network printer using "
|
|
+ "Windows Control Panel.\n"
|
|
+ "Step 2: Click Refresh."
|
|
+ "\"After\" list is populated with updated list "
|
|
+ "of printers.\n"
|
|
+ "Step 3: Compare the list of printers in \"Before\" and "
|
|
+ "\"After\" lists.\n"
|
|
+ " Added printers are highlighted with "
|
|
+ "green color, removed ones \u2014 with "
|
|
+ "red color.\n"
|
|
+ "Step 4: Click Pass if the list of printers is correctly "
|
|
+ "updated.\n"
|
|
+ "Step 5: If the list is not updated, click Refresh again.\n"
|
|
+ "Step 6: If the list does not update, click Fail.\n"
|
|
+ "\n"
|
|
+ "You have to click Refresh to enable Pass and Fail buttons. "
|
|
+ "If no button is pressed,\n"
|
|
+ "the test will time out. "
|
|
+ "Closing the window also fails the test.";
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
SwingUtilities.invokeAndWait(RemotePrinterStatusRefresh::createUI);
|
|
|
|
latch.await();
|
|
if (!test.testResult) {
|
|
throw new RuntimeException("Test failed"
|
|
+ (test.testTimedOut ? " because of time out" : ""));
|
|
}
|
|
}
|
|
|
|
private static void createUI() {
|
|
test = new RemotePrinterStatusRefresh();
|
|
}
|
|
|
|
private RemotePrinterStatusRefresh() {
|
|
frame = new JFrame("RemotePrinterStatusRefresh");
|
|
frame.addWindowListener(this);
|
|
|
|
|
|
JPanel northPanel = new JPanel(new BorderLayout());
|
|
northPanel.add(createInfoPanel(), BorderLayout.NORTH);
|
|
northPanel.add(createInstructionsPanel(), BorderLayout.SOUTH);
|
|
|
|
|
|
beforeList = new ServiceItemListModel(
|
|
Collections.unmodifiableList(collectPrinterList()));
|
|
afterList = new ServiceItemListModel(new ArrayList<>());
|
|
logList("Before:", beforeList.list);
|
|
|
|
JPanel listPanel = new JPanel(new GridLayout(1, 2));
|
|
listPanel.setBorder(createTitledBorder("Print Services"));
|
|
listPanel.add(createListPanel(beforeList, "Before:", 'b'));
|
|
listPanel.add(createListPanel(afterList, "After:", 'a'));
|
|
|
|
|
|
JPanel mainPanel = new JPanel(new BorderLayout());
|
|
mainPanel.add(northPanel, BorderLayout.NORTH);
|
|
mainPanel.add(listPanel, BorderLayout.CENTER);
|
|
mainPanel.add(createButtonPanel(), BorderLayout.SOUTH);
|
|
|
|
|
|
frame.add(mainPanel);
|
|
frame.pack();
|
|
refreshButton.requestFocusInWindow();
|
|
frame.setVisible(true);
|
|
|
|
|
|
timer = new Timer(1000, this::updateTimeLeft);
|
|
timer.start();
|
|
startTime = System.currentTimeMillis();
|
|
updateTimeLeft(null);
|
|
}
|
|
|
|
private JPanel createInfoPanel() {
|
|
JLabel javaLabel = new JLabel("Java version:");
|
|
JTextField javaVersion =
|
|
new JTextField(System.getProperty("java.runtime.version"));
|
|
javaVersion.setEditable(false);
|
|
javaLabel.setLabelFor(javaVersion);
|
|
|
|
JLabel timeoutLabel = new JLabel("Time left:");
|
|
timeLeft = new JTextField();
|
|
timeLeft.setEditable(false);
|
|
timeoutLabel.setLabelFor(timeLeft);
|
|
|
|
JPanel infoPanel = new JPanel();
|
|
GroupLayout layout = new GroupLayout(infoPanel);
|
|
infoPanel.setLayout(layout);
|
|
infoPanel.setBorder(BorderFactory.createTitledBorder("Info"));
|
|
layout.setAutoCreateGaps(true);
|
|
layout.setHorizontalGroup(
|
|
layout.createSequentialGroup()
|
|
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
|
|
.addComponent(javaLabel)
|
|
.addComponent(timeoutLabel)
|
|
)
|
|
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING, true)
|
|
.addComponent(javaVersion)
|
|
.addComponent(timeLeft)
|
|
)
|
|
);
|
|
layout.setVerticalGroup(
|
|
layout.createSequentialGroup()
|
|
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
|
|
.addComponent(javaLabel)
|
|
.addComponent(javaVersion)
|
|
)
|
|
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
|
|
.addComponent(timeoutLabel)
|
|
.addComponent(timeLeft))
|
|
);
|
|
return infoPanel;
|
|
}
|
|
|
|
private JPanel createInstructionsPanel() {
|
|
JPanel instructionsPanel = new JPanel(new BorderLayout());
|
|
JTextArea instructionText = new JTextArea(INSTRUCTIONS_TEXT);
|
|
instructionText.setEditable(false);
|
|
instructionsPanel.setBorder(createTitledBorder("Test Instructions"));
|
|
instructionsPanel.add(new JScrollPane(instructionText));
|
|
return instructionsPanel;
|
|
}
|
|
|
|
private JPanel createListPanel(final ServiceItemListModel model,
|
|
final String title,
|
|
final char mnemonic) {
|
|
JPanel panel = new JPanel();
|
|
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
|
|
JList<ServiceItem> list = new JList<>(model);
|
|
list.setCellRenderer(new ServiceItemListRenderer());
|
|
|
|
JLabel label = new JLabel(title);
|
|
label.setLabelFor(list);
|
|
label.setDisplayedMnemonic(mnemonic);
|
|
JPanel labelPanel = new JPanel();
|
|
labelPanel.setLayout(new BoxLayout(labelPanel, BoxLayout.X_AXIS));
|
|
labelPanel.add(label, BorderLayout.EAST);
|
|
labelPanel.add(Box.createHorizontalGlue());
|
|
|
|
panel.add(labelPanel);
|
|
panel.add(new JScrollPane(list));
|
|
return panel;
|
|
}
|
|
|
|
private JPanel createButtonPanel() {
|
|
refreshButton = new JButton("Refresh");
|
|
refreshButton.addActionListener(this::refresh);
|
|
|
|
passButton = new JButton("Pass");
|
|
passButton.addActionListener(this::pass);
|
|
passButton.setEnabled(false);
|
|
|
|
failButton = new JButton("Fail");
|
|
failButton.addActionListener(this::fail);
|
|
failButton.setEnabled(false);
|
|
|
|
JPanel buttonPanel = new JPanel();
|
|
buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
|
|
buttonPanel.add(Box.createHorizontalGlue());
|
|
buttonPanel.add(refreshButton);
|
|
buttonPanel.add(passButton);
|
|
buttonPanel.add(failButton);
|
|
buttonPanel.add(Box.createHorizontalGlue());
|
|
return buttonPanel;
|
|
}
|
|
|
|
private static List<ServiceItem> collectPrinterList() {
|
|
PrintService[] printServices = PrintServiceLookup.lookupPrintServices(null, null);
|
|
List<ServiceItem> list = new ArrayList<>(printServices.length);
|
|
for (PrintService service : printServices) {
|
|
list.add(new ServiceItem(service.getName()));
|
|
}
|
|
return list;
|
|
}
|
|
|
|
private static void logList(final String title, final List<ServiceItem> list) {
|
|
System.out.println(title);
|
|
for (ServiceItem item : list) {
|
|
System.out.println(item.name);
|
|
}
|
|
System.out.println();
|
|
}
|
|
|
|
private static void compareLists(final ServiceItemListModel before, final ServiceItemListModel after) {
|
|
boolean beforeUpdated = false;
|
|
boolean afterUpdated = false;
|
|
|
|
for (ServiceItem item : before.list) {
|
|
if (!after.list.contains(item)) {
|
|
item.state = ServiceItem.State.REMOVED;
|
|
beforeUpdated = true;
|
|
} else if (item.state != ServiceItem.State.UNCHANGED) {
|
|
item.state = ServiceItem.State.UNCHANGED;
|
|
beforeUpdated = true;
|
|
}
|
|
}
|
|
|
|
for (ServiceItem item : after.list) {
|
|
if (!before.list.contains(item)) {
|
|
item.state = ServiceItem.State.ADDED;
|
|
afterUpdated = true;
|
|
} else if (item.state != ServiceItem.State.UNCHANGED) {
|
|
item.state = ServiceItem.State.UNCHANGED;
|
|
afterUpdated = true;
|
|
}
|
|
}
|
|
|
|
if (beforeUpdated) {
|
|
before.fireChanged();
|
|
}
|
|
if (afterUpdated) {
|
|
after.fireChanged();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void windowClosing(WindowEvent e) {
|
|
System.out.println("The window closed");
|
|
disposeUI();
|
|
}
|
|
|
|
private void disposeUI() {
|
|
timer.stop();
|
|
latch.countDown();
|
|
frame.dispose();
|
|
}
|
|
|
|
@SuppressWarnings("unused")
|
|
private void refresh(ActionEvent e) {
|
|
System.out.println("Refresh button pressed");
|
|
afterList.refreshList(collectPrinterList());
|
|
compareLists(beforeList, afterList);
|
|
passButton.setEnabled(true);
|
|
failButton.setEnabled(true);
|
|
logList("After:", afterList.list);
|
|
}
|
|
|
|
@SuppressWarnings("unused")
|
|
private void pass(ActionEvent e) {
|
|
System.out.println("Pass button pressed");
|
|
testResult = true;
|
|
disposeUI();
|
|
}
|
|
|
|
@SuppressWarnings("unused")
|
|
private void fail(ActionEvent e) {
|
|
System.out.println("Fail button pressed");
|
|
testResult = false;
|
|
disposeUI();
|
|
}
|
|
|
|
@SuppressWarnings("unused")
|
|
private void updateTimeLeft(ActionEvent e) {
|
|
long elapsed = (System.currentTimeMillis() - startTime) / 1000;
|
|
long left = TIMEOUT - elapsed;
|
|
if (left < 0) {
|
|
testTimedOut = true;
|
|
disposeUI();
|
|
}
|
|
timeLeft.setText(formatTime(left));
|
|
}
|
|
|
|
private static String formatTime(final long seconds) {
|
|
long minutes = seconds / 60;
|
|
return String.format("%d:%02d", minutes, seconds - minutes * 60);
|
|
}
|
|
|
|
}
|