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