| /* | |
| * 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 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); | |
| } | |
| } |