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