1 /* 2 * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* 25 * @test 26 * @bug 8153732 8212202 8221263 8221412 27 * @requires (os.family == "Windows") 28 * @summary Windows remote printer changes do not reflect in lookupPrintServices() 29 * @run main/manual RemotePrinterStatusRefresh 30 */ 31 32 import java.awt.BorderLayout; 33 import java.awt.Color; 34 import java.awt.Component; 35 import java.awt.GridLayout; 36 import java.awt.event.ActionEvent; 37 import java.awt.event.WindowAdapter; 38 import java.awt.event.WindowEvent; 39 import java.util.ArrayList; 40 import java.util.Collections; 41 import java.util.List; 42 import java.util.concurrent.CountDownLatch; 43 import javax.print.PrintService; 44 import javax.print.PrintServiceLookup; 45 import javax.swing.AbstractListModel; 46 import javax.swing.BorderFactory; 47 import javax.swing.Box; 48 import javax.swing.BoxLayout; 49 import javax.swing.DefaultListCellRenderer; 50 import javax.swing.GroupLayout; 51 import javax.swing.JButton; 52 import javax.swing.JFrame; 53 import javax.swing.JLabel; 54 import javax.swing.JList; 55 import javax.swing.JPanel; 56 import javax.swing.JScrollPane; 57 import javax.swing.JTextArea; 58 import javax.swing.JTextField; 59 import javax.swing.SwingUtilities; 60 import javax.swing.Timer; 61 62 import static javax.swing.BorderFactory.createTitledBorder; 63 64 public class RemotePrinterStatusRefresh extends WindowAdapter { 65 66 private static final long refreshTime = getRefreshTime(); 67 68 private static final long TIMEOUT = refreshTime * 4 + 60; 69 70 71 private static final CountDownLatch latch = new CountDownLatch(1); 72 private static volatile RemotePrinterStatusRefresh test; 73 74 private volatile boolean testResult; 75 private volatile boolean testTimedOut; 76 77 private final JFrame frame; 78 79 private JButton refreshButton; 80 private JButton passButton; 81 private JButton failButton; 82 83 private final ServiceItemListModel beforeList; 84 private final ServiceItemListModel afterList; 85 86 private JTextField nextRefresh; 87 private JTextField timeLeft; 88 89 private final Timer timer; 90 private final long startTime; 91 92 93 private static class ServiceItem { 94 private enum State { 95 REMOVED, UNCHANGED, ADDED 96 } 97 98 final String name; 99 State state; 100 101 private ServiceItem(final String name) { 102 this.name = name; 103 state = State.UNCHANGED; 104 } 105 106 @Override 107 public String toString() { 108 return name; 109 } 110 111 @Override 112 public boolean equals(Object obj) { 113 return (obj instanceof ServiceItem) 114 && ((ServiceItem) obj).name.equals(name); 115 } 116 117 @Override 118 public int hashCode() { 119 return name.hashCode(); 120 } 121 } 122 123 private static class ServiceItemListModel extends AbstractListModel<ServiceItem> { 124 private final List<ServiceItem> list; 125 126 private ServiceItemListModel(List<ServiceItem> list) { 127 this.list = list; 128 } 129 130 @Override 131 public int getSize() { 132 return list.size(); 133 } 134 135 @Override 136 public ServiceItem getElementAt(int index) { 137 return list.get(index); 138 } 139 140 private void refreshList(List<ServiceItem> newList) { 141 list.clear(); 142 list.addAll(newList); 143 fireChanged(); 144 } 145 146 private void fireChanged() { 147 fireContentsChanged(this, 0, list.size() - 1); 148 } 149 } 150 151 private static class ServiceItemListRenderer extends DefaultListCellRenderer { 152 @Override 153 public Component getListCellRendererComponent(JList<?> list, 154 Object value, 155 int index, 156 boolean isSelected, 157 boolean cellHasFocus) { 158 Component component = 159 super.getListCellRendererComponent(list, value, index, 160 isSelected, cellHasFocus); 161 switch (((ServiceItem) value).state) { 162 case REMOVED: 163 component.setBackground(Color.RED); 164 component.setForeground(Color.WHITE); 165 break; 166 case ADDED: 167 component.setBackground(Color.GREEN); 168 component.setForeground(Color.BLACK); 169 break; 170 case UNCHANGED: 171 default: 172 break; 173 } 174 return component; 175 } 176 } 177 178 private static final String INSTRUCTIONS_TEXT = 179 "Please follow the steps for this manual test:\n" 180 + "Step 0: \"Before\" list is populated with currently " 181 + "configured printers.\n" 182 + "Step 1: Add or Remove a network printer using " 183 + "Windows Control Panel.\n" 184 + "Step 2: Wait for 4 minutes after adding or removing.\n" 185 + " \"Next printer refresh in\" gives you a " 186 + "rough estimation on when update will happen.\n" 187 + "Step 3: Click Refresh." 188 + "\"After\" list is populated with updated list " 189 + "of printers.\n" 190 + "Step 4: Compare the list of printers in \"Before\" and " 191 + "\"After\" lists.\n" 192 + " Added printers are highlighted with " 193 + "green color, removed ones \u2014 with " 194 + "red color.\n" 195 + "Step 5: Click Pass if the list of printers is correctly " 196 + "updated.\n" 197 + "Step 6: If the list is not updated, wait for another " 198 + "4 minutes, and then click Refresh again.\n" 199 + "Step 7: If the list does not update, click Fail.\n" 200 + "\n" 201 + "You have to click Refresh to enable Pass and Fail buttons. " 202 + "If no button is pressed,\n" 203 + "the test will time out. " 204 + "Closing the window also fails the test."; 205 206 public static void main(String[] args) throws Exception { 207 SwingUtilities.invokeAndWait(RemotePrinterStatusRefresh::createUI); 208 209 latch.await(); 210 if (!test.testResult) { 211 throw new RuntimeException("Test failed" 212 + (test.testTimedOut ? " because of time out" : "")); 213 } 214 } 215 216 private static long getRefreshTime() { 217 String refreshTime = 218 System.getProperty("sun.java2d.print.minRefreshTime", "240"); 219 try { 220 long value = Long.parseLong(refreshTime); 221 return value < 240L ? 240L : value; 222 } catch (NumberFormatException e) { 223 return 240L; 224 } 225 } 226 227 private static void createUI() { 228 test = new RemotePrinterStatusRefresh(); 229 } 230 231 private RemotePrinterStatusRefresh() { 232 frame = new JFrame("RemotePrinterStatusRefresh"); 233 frame.addWindowListener(this); 234 235 236 JPanel northPanel = new JPanel(new BorderLayout()); 237 northPanel.add(createInfoPanel(), BorderLayout.NORTH); 238 northPanel.add(createInstructionsPanel(), BorderLayout.SOUTH); 239 240 241 beforeList = new ServiceItemListModel( 242 Collections.unmodifiableList(collectPrinterList())); 243 afterList = new ServiceItemListModel(new ArrayList<>()); 244 logList("Before:", beforeList.list); 245 246 JPanel listPanel = new JPanel(new GridLayout(1, 2)); 247 listPanel.setBorder(createTitledBorder("Print Services")); 248 listPanel.add(createListPanel(beforeList, "Before:", 'b')); 249 listPanel.add(createListPanel(afterList, "After:", 'a')); 250 251 252 JPanel mainPanel = new JPanel(new BorderLayout()); 253 mainPanel.add(northPanel, BorderLayout.NORTH); 254 mainPanel.add(listPanel, BorderLayout.CENTER); 255 mainPanel.add(createButtonPanel(), BorderLayout.SOUTH); 256 257 258 frame.add(mainPanel); 259 frame.pack(); 260 refreshButton.requestFocusInWindow(); 261 frame.setVisible(true); 262 263 264 timer = new Timer(1000, this::updateTimeLeft); 265 timer.start(); 266 startTime = System.currentTimeMillis(); 267 updateTimeLeft(null); 268 } 269 270 private JPanel createInfoPanel() { 271 JLabel javaLabel = new JLabel("Java version:"); 272 JTextField javaVersion = 273 new JTextField(System.getProperty("java.runtime.version")); 274 javaVersion.setEditable(false); 275 javaLabel.setLabelFor(javaVersion); 276 277 JLabel refreshTimeLabel = new JLabel("Refresh interval:"); 278 long minutes = refreshTime / 60; 279 long seconds = refreshTime % 60; 280 String interval = String.format("%1$d seconds%2$s", 281 refreshTime, 282 minutes > 0 283 ? String.format(" (%1$d %2$s%3$s)", 284 minutes, 285 minutes > 1 ? "minutes" : "minute", 286 seconds > 0 287 ? String.format(" %1$d %2$s", 288 seconds, 289 seconds > 1 ? "seconds" : "second") 290 : "") 291 : "" 292 ); 293 JTextField refreshInterval = new JTextField(interval); 294 refreshInterval.setEditable(false); 295 refreshTimeLabel.setLabelFor(refreshInterval); 296 297 JLabel nextRefreshLabel = new JLabel("Next printer refresh in:"); 298 nextRefresh = new JTextField(); 299 nextRefresh.setEditable(false); 300 nextRefreshLabel.setLabelFor(nextRefresh); 301 302 JLabel timeoutLabel = new JLabel("Time left:"); 303 timeLeft = new JTextField(); 304 timeLeft.setEditable(false); 305 timeoutLabel.setLabelFor(timeLeft); 306 307 JPanel infoPanel = new JPanel(); 308 GroupLayout layout = new GroupLayout(infoPanel); 309 infoPanel.setLayout(layout); 310 infoPanel.setBorder(BorderFactory.createTitledBorder("Info")); 311 layout.setAutoCreateGaps(true); 312 layout.setHorizontalGroup( 313 layout.createSequentialGroup() 314 .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) 315 .addComponent(javaLabel) 316 .addComponent(refreshTimeLabel) 317 .addComponent(nextRefreshLabel) 318 .addComponent(timeoutLabel) 319 ) 320 .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING, true) 321 .addComponent(javaVersion) 322 .addComponent(refreshInterval) 323 .addComponent(nextRefresh) 324 .addComponent(timeLeft) 325 ) 326 ); 327 layout.setVerticalGroup( 328 layout.createSequentialGroup() 329 .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) 330 .addComponent(javaLabel) 331 .addComponent(javaVersion) 332 ) 333 .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) 334 .addComponent(refreshTimeLabel) 335 .addComponent(refreshInterval)) 336 .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) 337 .addComponent(nextRefreshLabel) 338 .addComponent(nextRefresh)) 339 .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) 340 .addComponent(timeoutLabel) 341 .addComponent(timeLeft)) 342 ); 343 return infoPanel; 344 } 345 346 private JPanel createInstructionsPanel() { 347 JPanel instructionsPanel = new JPanel(new BorderLayout()); 348 JTextArea instructionText = new JTextArea(INSTRUCTIONS_TEXT); 349 instructionText.setEditable(false); 350 instructionsPanel.setBorder(createTitledBorder("Test Instructions")); 351 instructionsPanel.add(new JScrollPane(instructionText)); 352 return instructionsPanel; 353 } 354 355 private JPanel createListPanel(final ServiceItemListModel model, 356 final String title, 357 final char mnemonic) { 358 JPanel panel = new JPanel(); 359 panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); 360 JList<ServiceItem> list = new JList<>(model); 361 list.setCellRenderer(new ServiceItemListRenderer()); 362 363 JLabel label = new JLabel(title); 364 label.setLabelFor(list); 365 label.setDisplayedMnemonic(mnemonic); 366 JPanel labelPanel = new JPanel(); 367 labelPanel.setLayout(new BoxLayout(labelPanel, BoxLayout.X_AXIS)); 368 labelPanel.add(label, BorderLayout.EAST); 369 labelPanel.add(Box.createHorizontalGlue()); 370 371 panel.add(labelPanel); 372 panel.add(new JScrollPane(list)); 373 return panel; 374 } 375 376 private JPanel createButtonPanel() { 377 refreshButton = new JButton("Refresh"); 378 refreshButton.addActionListener(this::refresh); 379 380 passButton = new JButton("Pass"); 381 passButton.addActionListener(this::pass); 382 passButton.setEnabled(false); 383 384 failButton = new JButton("Fail"); 385 failButton.addActionListener(this::fail); 386 failButton.setEnabled(false); 387 388 JPanel buttonPanel = new JPanel(); 389 buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); 390 buttonPanel.add(Box.createHorizontalGlue()); 391 buttonPanel.add(refreshButton); 392 buttonPanel.add(passButton); 393 buttonPanel.add(failButton); 394 buttonPanel.add(Box.createHorizontalGlue()); 395 return buttonPanel; 396 } 397 398 private static List<ServiceItem> collectPrinterList() { 399 PrintService[] printServices = PrintServiceLookup.lookupPrintServices(null, null); 400 List<ServiceItem> list = new ArrayList<>(printServices.length); 401 for (PrintService service : printServices) { 402 list.add(new ServiceItem(service.getName())); 403 } 404 return list; 405 } 406 407 private static void logList(final String title, final List<ServiceItem> list) { 408 System.out.println(title); 409 for (ServiceItem item : list) { 410 System.out.println(item.name); 411 } 412 System.out.println(); 413 } 414 415 private static void compareLists(final ServiceItemListModel before, final ServiceItemListModel after) { 416 boolean beforeUpdated = false; 417 boolean afterUpdated = false; 418 419 for (ServiceItem item : before.list) { 420 if (!after.list.contains(item)) { 421 item.state = ServiceItem.State.REMOVED; 422 beforeUpdated = true; 423 } else if (item.state != ServiceItem.State.UNCHANGED) { 424 item.state = ServiceItem.State.UNCHANGED; 425 beforeUpdated = true; 426 } 427 } 428 429 for (ServiceItem item : after.list) { 430 if (!before.list.contains(item)) { 431 item.state = ServiceItem.State.ADDED; 432 afterUpdated = true; 433 } else if (item.state != ServiceItem.State.UNCHANGED) { 434 item.state = ServiceItem.State.UNCHANGED; 435 afterUpdated = true; 436 } 437 } 438 439 if (beforeUpdated) { 440 before.fireChanged(); 441 } 442 if (afterUpdated) { 443 after.fireChanged(); 444 } 445 } 446 447 @Override 448 public void windowClosing(WindowEvent e) { 449 System.out.println("The window closed"); 450 disposeUI(); 451 } 452 453 private void disposeUI() { 454 timer.stop(); 455 latch.countDown(); 456 frame.dispose(); 457 } 458 459 @SuppressWarnings("unused") 460 private void refresh(ActionEvent e) { 461 System.out.println("Refresh button pressed"); 462 afterList.refreshList(collectPrinterList()); 463 compareLists(beforeList, afterList); 464 passButton.setEnabled(true); 465 failButton.setEnabled(true); 466 logList("After:", afterList.list); 467 } 468 469 @SuppressWarnings("unused") 470 private void pass(ActionEvent e) { 471 System.out.println("Pass button pressed"); 472 testResult = true; 473 disposeUI(); 474 } 475 476 @SuppressWarnings("unused") 477 private void fail(ActionEvent e) { 478 System.out.println("Fail button pressed"); 479 testResult = false; 480 disposeUI(); 481 } 482 483 @SuppressWarnings("unused") 484 private void updateTimeLeft(ActionEvent e) { 485 long elapsed = (System.currentTimeMillis() - startTime) / 1000; 486 long left = TIMEOUT - elapsed; 487 if (left < 0) { 488 testTimedOut = true; 489 disposeUI(); 490 } 491 timeLeft.setText(formatTime(left)); 492 nextRefresh.setText(formatTime(refreshTime - (elapsed % refreshTime))); 493 } 494 495 private static String formatTime(final long seconds) { 496 long minutes = seconds / 60; 497 return String.format("%d:%02d", minutes, seconds - minutes * 60); 498 } 499 500 }