/* * Copyright (c) 2018, 2019, 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 * @bug 8153732 8212202 8221263 8221412 8222108 * @requires (os.family == "Windows") * @summary Windows remote printer changes do not reflect in lookupPrintServices() * @run main/manual/othervm -Dsun.java2d.print.minRefreshTime=120 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 DEFAULT_REFRESH_TIME = 240L; private static final long MINIMAL_REFRESH_TIME = 120L; private static final long refreshTime = getRefreshTime(); private static final long TIMEOUT = refreshTime * 4 + 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 nextRefresh; 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 { private final List list; private ServiceItemListModel(List 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 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: Wait for 2\u20134 minutes after adding or removing.\n" + " \"Next printer refresh in\" gives you a " + "rough estimation on when update will happen.\n" + "Step 3: Click Refresh." + "\"After\" list is populated with updated list " + "of printers.\n" + "Step 4: 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 5: Click Pass if the list of printers is correctly " + "updated.\n" + "Step 6: If the list is not updated, wait for another " + "2\u20134 minutes, and then click Refresh again.\n" + "Step 7: 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 long getRefreshTime() { String refreshTime = System.getProperty("sun.java2d.print.minRefreshTime", Long.toString(DEFAULT_REFRESH_TIME)); try { long value = Long.parseLong(refreshTime); return value < MINIMAL_REFRESH_TIME ? MINIMAL_REFRESH_TIME : value; } catch (NumberFormatException e) { return DEFAULT_REFRESH_TIME; } } 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 refreshTimeLabel = new JLabel("Refresh interval:"); long minutes = refreshTime / 60; long seconds = refreshTime % 60; String interval = String.format("%1$d seconds%2$s", refreshTime, minutes > 0 ? String.format(" (%1$d %2$s%3$s)", minutes, minutes > 1 ? "minutes" : "minute", seconds > 0 ? String.format(" %1$d %2$s", seconds, seconds > 1 ? "seconds" : "second") : "") : "" ); JTextField refreshInterval = new JTextField(interval); refreshInterval.setEditable(false); refreshTimeLabel.setLabelFor(refreshInterval); JLabel nextRefreshLabel = new JLabel("Next printer refresh in:"); nextRefresh = new JTextField(); nextRefresh.setEditable(false); nextRefreshLabel.setLabelFor(nextRefresh); 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(refreshTimeLabel) .addComponent(nextRefreshLabel) .addComponent(timeoutLabel) ) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING, true) .addComponent(javaVersion) .addComponent(refreshInterval) .addComponent(nextRefresh) .addComponent(timeLeft) ) ); layout.setVerticalGroup( layout.createSequentialGroup() .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(javaLabel) .addComponent(javaVersion) ) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(refreshTimeLabel) .addComponent(refreshInterval)) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(nextRefreshLabel) .addComponent(nextRefresh)) .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 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 collectPrinterList() { PrintService[] printServices = PrintServiceLookup.lookupPrintServices(null, null); List 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 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)); nextRefresh.setText(formatTime(refreshTime - (elapsed % refreshTime))); } private static String formatTime(final long seconds) { long minutes = seconds / 60; return String.format("%d:%02d", minutes, seconds - minutes * 60); } }