Changeset 822 for trunk/JFCMonitor/src/main/java/de/ugoe/cs
- Timestamp:
- 09/19/12 10:46:39 (12 years ago)
- Location:
- trunk/JFCMonitor
- Files:
-
- 2 added
- 6 edited
- 1 moved
Legend:
- Unmodified
- Added
- Removed
-
trunk/JFCMonitor
-
Property
svn:ignore
set to
.settings
-
Property
svn:ignore
set to
-
trunk/JFCMonitor/src/main/java/de/ugoe/cs/eventbench/jfcmonitor/JFCComponent.java
r368 r822 1 1 2 package de.ugoe.cs.eventbench.jfcmonitor; 2 3 … … 18 19 /** 19 20 * <p> 20 * This class manages information about the current GUI. It always contains the 21 * current GUIhierarchy.21 * This class manages information about the current GUI. It always contains the current GUI 22 * hierarchy. 22 23 * </p> 23 24 * … … 27 28 public class JFCComponent { 28 29 29 /** 30 * <p> 31 * Map of all known GUI components. 32 * </p> 33 */ 34 private static Map<Component, JFCComponent> knownComponents = new HashMap<Component, JFCComponent>(); 35 36 /** 37 * <p> 38 * Adds a AWT component to the GUI hierarchy. If the component already 39 * exists in the hierarchy, it is not added a second time. 40 * </p> 41 * 42 * @param component 43 * component that is added 44 */ 45 public static void add(Component component) { 46 add(component, find(component.getParent())); 47 } 48 49 /** 50 * <p> 51 * Adds a AWT component to the GUI hierarchy. If the component already 52 * exists in the hierarchy, it is not added a second time. 53 * </p> 54 * 55 * @param component 56 * component that is added 57 * @param parent 58 * parent of the component 59 */ 60 public static void add(Component component, JFCComponent parent) { 61 if (!knownComponents.containsKey(component)) { 62 knownComponents.put(component, new JFCComponent(component, parent)); 63 } 64 } 65 66 /** 67 * <p> 68 * Finds a component in the GUI hierarchy and returns the corresponding 69 * JFComponent instance. Returns null if the component is not found. 70 * </p> 71 * 72 * @param component 73 * component that is searched for 74 * @return corresponding JFComponent instance; null if the compenent is not 75 * found 76 */ 77 public static JFCComponent find(Component component) { 78 return knownComponents.get(component); 79 } 80 81 /** 82 * <p> 83 * Removes a component from the GUI hierarchy. In case the component is not 84 * part of the known hierachy, nothing happens. 85 * </p> 86 * 87 * @param component 88 * component to be removed 89 */ 90 public static void remove(Component component) { 91 JFCComponent jfcComponent = knownComponents.remove(component); 92 if (jfcComponent != null) { 93 jfcComponent.removeFromParent(); 94 jfcComponent.removeChildren(); 95 } 96 } 97 98 /** 99 * <p> 100 * Parent of the GUI component. null means, that the component has no 101 * parent. 102 * </p> 103 */ 104 private JFCComponent parent = null; 105 106 /** 107 * <p> 108 * Child components of the component. 109 * </p> 110 */ 111 private List<JFCComponent> children = new LinkedList<JFCComponent>(); 112 113 /** 114 * <p> 115 * Reference to the actual GUI component. 116 * </p> 117 */ 118 private Component component; 119 120 /** 121 * <p> 122 * Helper attribute that contains the title of the component. Set by 123 * {@link #setTitle()}. 124 * </p> 125 */ 126 private String title = null; 127 128 /** 129 * <p> 130 * Helper attribute that contains the class of the component. Set by 131 * {@link #setClass()}. 132 * </p> 133 */ 134 private String componentClass = null; 135 136 /** 137 * <p> 138 * Helper attribute that contains the icon of the component. Set by 139 * {@link #setIcon()}. 140 * </p> 141 */ 142 private String icon = null; 143 144 /** 145 * <p> 146 * Helper attribute that contains the icon of the component. Set by 147 * {@link #setIndex()}. 148 * </p> 149 */ 150 private int index = -1; 151 152 /** 153 * <p> 154 * Constructor. Creates a new JFCComponent. Only used internally by 155 * {@link #add(Component, JFCComponent)}. 156 * </p> 157 * 158 * @param component 159 * component associated with the JFCComponent 160 * @param parent 161 * parent of the component; null if there is no parent 162 */ 163 private JFCComponent(Component component, JFCComponent parent) { 164 if (component == null) { 165 throw new InvalidParameterException( 166 "parameter component must not be null"); 167 } 168 this.component = component; 169 this.parent = parent; 170 if (parent != null) { 171 parent.addChild(this); 172 } 173 174 if (component instanceof Container) { 175 for (Component childComponent : ((Container) component) 176 .getComponents()) { 177 add(childComponent, this); 178 } 179 } 180 } 181 182 /** 183 * <p> 184 * Adds a child component to the current component. 185 * </p> 186 * 187 * @param child 188 * child component to be added 189 */ 190 private void addChild(JFCComponent child) { 191 children.add(child); 192 } 193 194 /** 195 * <p> 196 * Returns an XML representation of the component. 197 * </p> 198 * 199 * @return XLM snippet 200 */ 201 public String getXML() { 202 setClass(); 203 setIcon(); 204 setIndex(); 205 setTitle(); 206 StringBuilder builder = new StringBuilder(); 207 if (parent != null) { 208 builder.append(parent.getXML()); 209 } 210 builder.append(" <component>" + StringTools.ENDLINE); 211 builder.append(" <param name=\"title\" value=\"" + title + "\" />" 212 + StringTools.ENDLINE); 213 builder.append(" <param name=\"class\" value=\"" + componentClass 214 + "\" />" + StringTools.ENDLINE); 215 builder.append(" <param name=\"icon\" value=\"" + icon + "\" />" 216 + StringTools.ENDLINE); 217 builder.append(" <param name=\"index\" value=\"" + index + "\" />" 218 + StringTools.ENDLINE); 219 builder.append(" <param name=\"hash\" value=\"" 220 + Integer.toHexString(component.hashCode()) + "\" />" 221 + StringTools.ENDLINE); 222 builder.append(" </component>" + StringTools.ENDLINE); 223 return builder.toString(); 224 } 225 226 /** 227 * <p> 228 * Removes a child component from the current component. 229 * </p> 230 * 231 * @param child 232 * child component to be removed 233 */ 234 private void removeChild(JFCComponent child) { 235 children.remove(child); 236 } 237 238 /** 239 * <p> 240 * Removes the component from the list of children of its parent. 241 * </p> 242 */ 243 private void removeFromParent() { 244 if (parent != null) { 245 parent.removeChild(this); 246 } 247 } 248 249 /** 250 * <p> 251 * Triggers the removals of all child components from the GUI hierarchy, 252 * i.e., calls {@link #remove(Component)} for all child components. 253 * </p> 254 */ 255 private void removeChildren() { 256 for (JFCComponent child : children) { 257 remove(child.component); 258 } 259 } 260 261 /** 262 * <p> 263 * Sets the {@link #title} of the component. The title is defined as follows 264 * (first in the list, that is not null): 265 * <ul> 266 * <li>accessible name of the component if available</li> 267 * <li>{@link #icon} of the component</li> 268 * <li>name of the component</li> 269 * <li>coordinates of the component</li> 270 * </ul> 271 * </p> 272 */ 273 private void setTitle() { 274 title = null; // reset title 275 276 AccessibleContext accessibleContext = component.getAccessibleContext(); 277 if (accessibleContext != null) { 278 title = accessibleContext.getAccessibleName(); 279 } 280 if (title == null) { 281 title = icon; 282 } 283 if (title == null) { 284 title = component.getName(); 285 } 286 if (title == null) { 287 // use coordinates as last resort 288 title = "Pos(" + component.getX() + "," + component.getY() + ")"; 289 } 290 } 291 292 /** 293 * <p> 294 * Sets the {@link #componentClass} of the component. 295 * </p> 296 */ 297 private void setClass() { 298 componentClass = component.getClass().getName(); 299 } 300 301 /** 302 * <p> 303 * Sets the {@link #icon} of the component. 304 * </p> 305 */ 306 private void setIcon() { 307 icon = null; // reset icon 308 309 Method getIconMethod; 310 try { 311 getIconMethod = component.getClass().getMethod("getIcon", 312 new Class[0]); 313 if (getIconMethod != null) { 314 Object iconObject = getIconMethod.invoke(component, 315 new Object[] {}); 316 if (iconObject != null) { 317 String iconPath = iconObject.toString(); 318 if (!iconPath.contains("@")) { 319 System.out.println("iconPath"); 320 String[] splitResult = iconPath 321 .split(File.separatorChar == '\\' ? "\\\\" 322 : File.separator); 323 icon = splitResult[splitResult.length - 1]; 324 } 325 } 326 } 327 } catch (SecurityException e) { 328 } catch (NoSuchMethodException e) { 329 } catch (IllegalArgumentException e) { 330 } catch (IllegalAccessException e) { 331 } catch (InvocationTargetException e) { 332 System.err.println("Found method with name " + "getIcon" 333 + " but could not access it."); 334 } 335 } 336 337 /** 338 * <p> 339 * Sets the {@link #index} of the component as the index in the parent, if 340 * it is accessible. 341 * </p> 342 */ 343 private void setIndex() { 344 index = -1; // reset index 345 346 AccessibleContext accessibleContext = component.getAccessibleContext(); 347 if (accessibleContext != null) { 348 index = accessibleContext.getAccessibleIndexInParent(); 349 } 350 } 30 /** 31 * <p> 32 * Map of all known GUI components. 33 * </p> 34 */ 35 private static Map<Component, JFCComponent> knownComponents = 36 new HashMap<Component, JFCComponent>(); 37 38 /** 39 * <p> 40 * Adds a AWT component to the GUI hierarchy. If the component already exists in the hierarchy, 41 * it is not added a second time. 42 * </p> 43 * 44 * @param component 45 * component that is added 46 */ 47 public static void add(Component component) { 48 add(component, find(component.getParent())); 49 } 50 51 /** 52 * <p> 53 * Adds a AWT component to the GUI hierarchy. If the component already exists in the hierarchy, 54 * it is not added a second time. 55 * </p> 56 * 57 * @param component 58 * component that is added 59 * @param parent 60 * parent of the component 61 */ 62 public static void add(Component component, JFCComponent parent) { 63 if (!knownComponents.containsKey(component)) { 64 knownComponents.put(component, new JFCComponent(component, parent)); 65 } 66 } 67 68 /** 69 * <p> 70 * Finds a component in the GUI hierarchy and returns the corresponding JFComponent instance. 71 * Returns null if the component is not found. 72 * </p> 73 * 74 * @param component 75 * component that is searched for 76 * @return corresponding JFComponent instance; null if the compenent is not found 77 */ 78 public static JFCComponent find(Component component) { 79 return knownComponents.get(component); 80 } 81 82 /** 83 * <p> 84 * Removes a component from the GUI hierarchy. In case the component is not part of the known 85 * hierachy, nothing happens. 86 * </p> 87 * 88 * @param component 89 * component to be removed 90 */ 91 public static void remove(Component component) { 92 JFCComponent jfcComponent = knownComponents.remove(component); 93 if (jfcComponent != null) { 94 jfcComponent.removeFromParent(); 95 jfcComponent.removeChildren(); 96 } 97 } 98 99 /** 100 * <p> 101 * Parent of the GUI component. null means, that the component has no parent. 102 * </p> 103 */ 104 private JFCComponent parent = null; 105 106 /** 107 * <p> 108 * Child components of the component. 109 * </p> 110 */ 111 private List<JFCComponent> children = new LinkedList<JFCComponent>(); 112 113 /** 114 * <p> 115 * Reference to the actual GUI component. 116 * </p> 117 */ 118 private Component component; 119 120 /** 121 * <p> 122 * Helper attribute that contains the title of the component. Set by {@link #setTitle()}. 123 * </p> 124 */ 125 private String title = null; 126 127 /** 128 * <p> 129 * Helper attribute that contains the class of the component. Set by {@link #setClass()}. 130 * </p> 131 */ 132 private String componentClass = null; 133 134 /** 135 * <p> 136 * Helper attribute that contains the icon of the component. Set by {@link #setIcon()}. 137 * </p> 138 */ 139 private String icon = null; 140 141 /** 142 * <p> 143 * Helper attribute that contains the icon of the component. Set by {@link #setIndex()}. 144 * </p> 145 */ 146 private int index = -1; 147 148 /** 149 * <p> 150 * Constructor. Creates a new JFCComponent. Only used internally by 151 * {@link #add(Component, JFCComponent)}. 152 * </p> 153 * 154 * @param component 155 * component associated with the JFCComponent 156 * @param parent 157 * parent of the component; null if there is no parent 158 */ 159 private JFCComponent(Component component, JFCComponent parent) { 160 if (component == null) { 161 throw new InvalidParameterException("parameter component must not be null"); 162 } 163 this.component = component; 164 this.parent = parent; 165 if (parent != null) { 166 parent.addChild(this); 167 } 168 169 if (component instanceof Container) { 170 for (Component childComponent : ((Container) component).getComponents()) { 171 add(childComponent, this); 172 } 173 } 174 } 175 176 /** 177 * <p> 178 * Adds a child component to the current component. 179 * </p> 180 * 181 * @param child 182 * child component to be added 183 */ 184 private void addChild(JFCComponent child) { 185 children.add(child); 186 } 187 188 /** 189 * <p> 190 * Returns an XML representation of the component. 191 * </p> 192 * 193 * @return XLM snippet 194 */ 195 public String getXML() { 196 setClass(); 197 setIcon(); 198 setIndex(); 199 setTitle(); 200 StringBuilder builder = new StringBuilder(); 201 if (parent != null) { 202 builder.append(parent.getXML()); 203 } 204 builder.append(" <component>" + StringTools.ENDLINE); 205 builder.append(" <param name=\"title\" value=\"" + title + "\" />" + StringTools.ENDLINE); 206 builder.append(" <param name=\"class\" value=\"" + componentClass + "\" />" + 207 StringTools.ENDLINE); 208 builder.append(" <param name=\"icon\" value=\"" + icon + "\" />" + StringTools.ENDLINE); 209 builder.append(" <param name=\"index\" value=\"" + index + "\" />" + StringTools.ENDLINE); 210 builder.append(" <param name=\"hash\" value=\"" + 211 Integer.toHexString(component.hashCode()) + "\" />" + StringTools.ENDLINE); 212 builder.append(" </component>" + StringTools.ENDLINE); 213 return builder.toString(); 214 } 215 216 /** 217 * <p> 218 * Removes a child component from the current component. 219 * </p> 220 * 221 * @param child 222 * child component to be removed 223 */ 224 private void removeChild(JFCComponent child) { 225 children.remove(child); 226 } 227 228 /** 229 * <p> 230 * Removes the component from the list of children of its parent. 231 * </p> 232 */ 233 private void removeFromParent() { 234 if (parent != null) { 235 parent.removeChild(this); 236 } 237 } 238 239 /** 240 * <p> 241 * Triggers the removals of all child components from the GUI hierarchy, i.e., calls 242 * {@link #remove(Component)} for all child components. 243 * </p> 244 */ 245 private void removeChildren() { 246 for (JFCComponent child : children) { 247 remove(child.component); 248 } 249 } 250 251 /** 252 * <p> 253 * Sets the {@link #title} of the component. The title is defined as follows (first in the list, 254 * that is not null): 255 * <ul> 256 * <li>accessible name of the component if available</li> 257 * <li>{@link #icon} of the component</li> 258 * <li>name of the component</li> 259 * <li>coordinates of the component</li> 260 * </ul> 261 * </p> 262 */ 263 private void setTitle() { 264 title = null; // reset title 265 266 AccessibleContext accessibleContext = component.getAccessibleContext(); 267 if (accessibleContext != null) { 268 title = accessibleContext.getAccessibleName(); 269 } 270 if (title == null) { 271 title = icon; 272 } 273 if (title == null) { 274 title = component.getName(); 275 } 276 if (title == null) { 277 // use coordinates as last resort 278 title = "Pos(" + component.getX() + "," + component.getY() + ")"; 279 } 280 } 281 282 /** 283 * <p> 284 * Sets the {@link #componentClass} of the component. 285 * </p> 286 */ 287 private void setClass() { 288 componentClass = component.getClass().getName(); 289 } 290 291 /** 292 * <p> 293 * Sets the {@link #icon} of the component. 294 * </p> 295 */ 296 private void setIcon() { 297 icon = null; // reset icon 298 299 Method getIconMethod; 300 try { 301 getIconMethod = component.getClass().getMethod("getIcon", new Class[0]); 302 if (getIconMethod != null) { 303 Object iconObject = getIconMethod.invoke(component, new Object[] { }); 304 if (iconObject != null) { 305 String iconPath = iconObject.toString(); 306 if (!iconPath.contains("@")) { 307 System.out.println("iconPath"); 308 String[] splitResult = 309 iconPath.split(File.separatorChar == '\\' ? "\\\\" : File.separator); 310 icon = splitResult[splitResult.length - 1]; 311 } 312 } 313 } 314 } 315 catch (SecurityException e) {} 316 catch (NoSuchMethodException e) {} 317 catch (IllegalArgumentException e) {} 318 catch (IllegalAccessException e) {} 319 catch (InvocationTargetException e) { 320 System.err.println("Found method with name " + "getIcon" + " but could not access it."); 321 } 322 } 323 324 /** 325 * <p> 326 * Sets the {@link #index} of the component as the index in the parent, if it is accessible. 327 * </p> 328 */ 329 private void setIndex() { 330 index = -1; // reset index 331 332 AccessibleContext accessibleContext = component.getAccessibleContext(); 333 if (accessibleContext != null) { 334 index = accessibleContext.getAccessibleIndexInParent(); 335 } 336 } 351 337 352 338 } -
trunk/JFCMonitor/src/main/java/de/ugoe/cs/eventbench/jfcmonitor/JFCListener.java
r421 r822 1 1 2 package de.ugoe.cs.eventbench.jfcmonitor; 2 3 … … 14 15 /** 15 16 * <p> 16 * This class implements monitoring of AWT and Swing mouse and keyboard events. 17 * Each of the eventsis written to an output stream.17 * This class implements monitoring of AWT and Swing mouse and keyboard events. Each of the events 18 * is written to an output stream. 18 19 * </p> 19 20 * … … 23 24 public class JFCListener implements AWTEventListener { 24 25 25 26 27 28 29 30 26 /** 27 * <p> 28 * Writer for logging events. 29 * </p> 30 */ 31 final private OutputStreamWriter outputWriter; 31 32 32 /** 33 * <p> 34 * Constructor. Creates a new JFCListener with a given 35 * {@link OutputStreamWriter}, where the monitored information is logged. 36 * </p> 37 * 38 * @param outputWriter 39 * writer for the logged information 40 */ 41 public JFCListener(OutputStreamWriter outputWriter) { 42 this.outputWriter = outputWriter; 43 try { 44 outputWriter.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" 45 + StringTools.ENDLINE); 46 outputWriter.write("<sessions>" + StringTools.ENDLINE); 47 } catch (IOException e) { 48 System.err.println("JFCMONITOR -- Failure writing to log: " 49 + e.getMessage()); 50 } 51 } 33 /** 34 * <p> 35 * Constructor. Creates a new JFCListener with a given {@link OutputStreamWriter}, where the 36 * monitored information is logged. 37 * </p> 38 * 39 * @param outputWriter 40 * writer for the logged information 41 */ 42 public JFCListener(OutputStreamWriter outputWriter) { 43 this.outputWriter = outputWriter; 44 try { 45 outputWriter.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + StringTools.ENDLINE); 46 outputWriter.write("<sessions>" + StringTools.ENDLINE); 47 } 48 catch (IOException e) { 49 System.err.println("JFCMONITOR -- Failure writing to log: " + e.getMessage()); 50 } 51 } 52 52 53 /** 54 * <p> 55 * Writes all received {@link MouseEvent}s and {@link KeyEvent}s to the 56 * {@link #outputWriter}. 57 * </p> 58 * 59 * @see java.awt.event.AWTEventListener#eventDispatched(java.awt.AWTEvent) 60 */ 61 @Override 62 public void eventDispatched(AWTEvent event) { 63 StringBuilder builder = new StringBuilder(); 53 /** 54 * <p> 55 * Writes all received {@link MouseEvent}s and {@link KeyEvent}s to the {@link #outputWriter}. 56 * </p> 57 * 58 * @see java.awt.event.AWTEventListener#eventDispatched(java.awt.AWTEvent) 59 */ 60 @Override 61 public void eventDispatched(AWTEvent event) { 62 StringBuilder builder = new StringBuilder(); 64 63 65 if (event instanceof MouseEvent) { 66 if (!isMouseMovement(event.getID())) { 67 MouseEvent mouseEvent = (MouseEvent) event; 68 builder.append("<event id=\"" + event.getID() + "\">" 69 + StringTools.ENDLINE); 70 builder.append(" <param name=\"X\" value=\"" 71 + mouseEvent.getX() + "\" />" + StringTools.ENDLINE); 72 builder.append(" <param name=\"Y\" value=\"" 73 + mouseEvent.getY() + "\" />" + StringTools.ENDLINE); 74 builder.append(" <param name=\"Button\" value=\"" 75 + mouseEvent.getButton() + "\" />" 76 + StringTools.ENDLINE); 77 builder.append(" <param name=\"Modifiers\" value=\"" 78 + mouseEvent.getModifiers() + "\" />" 79 + StringTools.ENDLINE); 80 addSourceInfo(builder, event); 81 builder.append("</event>" + StringTools.ENDLINE); 82 } 83 } 84 else if (event instanceof KeyEvent) { 85 if (event.getID() == KeyEvent.KEY_PRESSED 86 || event.getID() == KeyEvent.KEY_RELEASED) { 87 KeyEvent keyEvent = (KeyEvent) event; 88 builder.append("<event id=\"" + event.getID() + "\">" 89 + StringTools.ENDLINE); 90 builder.append(" <param name=\"KeyCode\" value=\"" 91 + keyEvent.getKeyCode() + "\" />" + StringTools.ENDLINE); 92 builder.append(" <param name=\"Modifiers\" value=\"" 93 + keyEvent.getModifiers() + "\" />" 94 + StringTools.ENDLINE); 95 addSourceInfo(builder, event); 96 builder.append("</event>" + StringTools.ENDLINE); 97 } 98 } 99 else if (event instanceof FocusEvent) { 100 if (event.getID() == FocusEvent.FOCUS_GAINED) { 101 builder.append("<event id=\"" + event.getID() + "\">" 102 + StringTools.ENDLINE); 103 addSourceInfo(builder, event); 104 builder.append("</event>" + StringTools.ENDLINE); 105 } 106 } 107 if (builder.length() > 0 && outputWriter != null) { 108 try { 109 outputWriter.write(builder.toString()); 110 outputWriter.flush(); 111 } catch (IOException e) { 112 System.err.println("JFCMONITOR -- Failure writing to log: " 113 + e.getMessage()); 114 } 115 } 116 } 64 if (event instanceof MouseEvent) { 65 if (!isMouseMovement(event.getID())) { 66 MouseEvent mouseEvent = (MouseEvent) event; 67 builder.append("<event id=\"" + event.getID() + "\">" + StringTools.ENDLINE); 68 builder.append(" <param name=\"X\" value=\"" + mouseEvent.getX() + "\" />" + 69 StringTools.ENDLINE); 70 builder.append(" <param name=\"Y\" value=\"" + mouseEvent.getY() + "\" />" + 71 StringTools.ENDLINE); 72 builder.append(" <param name=\"Button\" value=\"" + mouseEvent.getButton() + 73 "\" />" + StringTools.ENDLINE); 74 builder.append(" <param name=\"Modifiers\" value=\"" + mouseEvent.getModifiers() + 75 "\" />" + StringTools.ENDLINE); 76 addSourceInfo(builder, event); 77 builder.append("</event>" + StringTools.ENDLINE); 78 } 79 } 80 else if (event instanceof KeyEvent) { 81 if (event.getID() == KeyEvent.KEY_PRESSED || event.getID() == KeyEvent.KEY_RELEASED) { 82 KeyEvent keyEvent = (KeyEvent) event; 83 builder.append("<event id=\"" + event.getID() + "\">" + StringTools.ENDLINE); 84 builder.append(" <param name=\"KeyCode\" value=\"" + keyEvent.getKeyCode() + 85 "\" />" + StringTools.ENDLINE); 86 builder.append(" <param name=\"Modifiers\" value=\"" + keyEvent.getModifiers() + 87 "\" />" + StringTools.ENDLINE); 88 addSourceInfo(builder, event); 89 builder.append("</event>" + StringTools.ENDLINE); 90 } 91 } 92 else if (event instanceof FocusEvent) { 93 if (event.getID() == FocusEvent.FOCUS_GAINED) { 94 builder.append("<event id=\"" + event.getID() + "\">" + StringTools.ENDLINE); 95 addSourceInfo(builder, event); 96 builder.append("</event>" + StringTools.ENDLINE); 97 } 98 } 99 if (builder.length() > 0 && outputWriter != null) { 100 try { 101 outputWriter.write(builder.toString()); 102 outputWriter.flush(); 103 } 104 catch (IOException e) { 105 System.err.println("JFCMONITOR -- Failure writing to log: " + e.getMessage()); 106 } 107 } 108 } 117 109 118 /** 119 * <p> 120 * Appends information about the event to a {@link StringBuilder}. 121 * </p> 122 * 123 * @param builder 124 * {@link StringBuilder} where the information is appended 125 * @param event 126 * event whose information is appended 127 */ 128 private void addSourceInfo(StringBuilder builder, AWTEvent event) { 129 builder.append(" <source>" + StringTools.ENDLINE); 130 builder.append(" <param name=\"toString\" value=\"" 131 + StringTools 132 .xmlEntityReplacement(event.getSource().toString()) 133 + "\" />" + StringTools.ENDLINE); 134 if (event.getSource() instanceof Component) { 135 Component source = (Component) event.getSource(); 136 JFCComponent jfcComponent = JFCComponent.find(source); 137 if (jfcComponent != null) { 138 builder.append(jfcComponent.getXML()); 139 } 140 } 141 builder.append(" </source>" + StringTools.ENDLINE); 142 } 110 /** 111 * <p> 112 * Appends information about the event to a {@link StringBuilder}. 113 * </p> 114 * 115 * @param builder 116 * {@link StringBuilder} where the information is appended 117 * @param event 118 * event whose information is appended 119 */ 120 private void addSourceInfo(StringBuilder builder, AWTEvent event) { 121 builder.append(" <source>" + StringTools.ENDLINE); 122 builder.append(" <param name=\"toString\" value=\"" + 123 StringTools.xmlEntityReplacement(event.getSource().toString()) + "\" />" + 124 StringTools.ENDLINE); 125 if (event.getSource() instanceof Component) { 126 Component source = (Component) event.getSource(); 127 JFCComponent jfcComponent = JFCComponent.find(source); 128 if (jfcComponent != null) { 129 builder.append(jfcComponent.getXML()); 130 } 131 } 132 builder.append(" </source>" + StringTools.ENDLINE); 133 } 143 134 144 /** 145 * <p> 146 * Checks if the Id of an {@link AWTEvent} is a mouse movement Id. 147 * </p> 148 * 149 * @param eventId 150 * id of the {@link AWTEvent} 151 * @return true, if the event is a mouse movement event; false otherwise 152 */ 153 private boolean isMouseMovement(int eventId) { 154 return eventId == MouseEvent.MOUSE_MOVED 155 || eventId == MouseEvent.MOUSE_DRAGGED 156 || eventId == MouseEvent.MOUSE_ENTERED 157 || eventId == MouseEvent.MOUSE_EXITED; 158 } 135 /** 136 * <p> 137 * Checks if the Id of an {@link AWTEvent} is a mouse movement Id. 138 * </p> 139 * 140 * @param eventId 141 * id of the {@link AWTEvent} 142 * @return true, if the event is a mouse movement event; false otherwise 143 */ 144 private boolean isMouseMovement(int eventId) { 145 return eventId == MouseEvent.MOUSE_MOVED || eventId == MouseEvent.MOUSE_DRAGGED || 146 eventId == MouseEvent.MOUSE_ENTERED || eventId == MouseEvent.MOUSE_EXITED; 147 } 159 148 160 149 } -
trunk/JFCMonitor/src/main/java/de/ugoe/cs/eventbench/jfcmonitor/JarLauncher.java
r286 r822 1 1 2 package de.ugoe.cs.eventbench.jfcmonitor; 2 3 … … 15 16 /** 16 17 * <p> 17 * Class that launches an executable Jar-file in the same thread and VM where 18 * the JarLauncherinstance is created. The requirements on the Jar file are:18 * Class that launches an executable Jar-file in the same thread and VM where the JarLauncher 19 * instance is created. The requirements on the Jar file are: 19 20 * <li>Must contain a MANIFEST.</li> 20 * <li>The MANIFEST must define the main-class of the application ("Main-Class" 21 * entry).</li> 22 * <li>The MANIFEST must define the classpath of the application ("Class-Path" 23 * entry).</li> 21 * <li>The MANIFEST must define the main-class of the application ("Main-Class" entry).</li> 22 * <li>The MANIFEST must define the classpath of the application ("Class-Path" entry).</li> 24 23 * </p> 25 24 * … … 29 28 public class JarLauncher { 30 29 31 /** 32 * <p> 33 * Name of the Jar file to be executed. 34 * </p> 35 */ 36 private String jarfile; 37 38 /** 39 * <p> 40 * Arguments for launching the Jar file. 41 * </p> 42 */ 43 private String[] args; 44 45 /** 46 * <p> 47 * Helper variable with the path to the working directory. 48 * </p> 49 */ 50 final String workingDir = System.getProperty("user.dir") + "/"; 51 52 /** 53 * <p> 54 * Internal variable used to store the classpath extracted from the Jar 55 * file's MANIFEST. 56 * </p> 57 */ 58 private String[] classPath = new String[] {}; 59 60 /** 61 * <p> 62 * Internal variable used to store the name (including package information) 63 * of the Jar file's main function extracted from the Jar file's MANIFEST. 64 * </p> 65 */ 66 private String mainClassName = ""; 67 68 /** 69 * <p> 70 * Inner class that defines an exception that is thrown if launching the 71 * application in the Jar file fails. 72 * </p> 73 * 74 * @author Steffen Herbold 75 * @version 1.0 76 */ 77 private static class JarLaunchException extends Exception { 78 79 /** 80 * <p> 81 * Id for object serialization. 82 * </p> 83 */ 84 private static final long serialVersionUID = 1L; 85 86 /** 87 * <p> 88 * Constructor. Creates a new JarLaunchException. 89 * </p> 90 * 91 * @param string 92 * error message of the exception 93 */ 94 public JarLaunchException(String string) { 95 super(string); 96 } 97 98 /** 99 * <p> 100 * Constructor. Creates a new JarLaunchException as a copy of an 101 * existing exception. 102 * </p> 103 * 104 * @param e 105 * exception that is copied 106 */ 107 public JarLaunchException(Exception e) { 108 super(e); 109 } 110 111 } 112 113 /** 114 * <p> 115 * Constructor. Creates a new JarLauncher. 116 * </p> 117 * 118 * @param jarfile 119 * file to be launched; must not be complete path but in relation 120 * to the current working directory 121 * @param args 122 * arguments with which the main function of the Jar file is 123 * called 124 */ 125 public JarLauncher(String jarfile, String[] args) { 126 this.jarfile = jarfile; 127 this.args = Arrays.copyOf(args, args.length); 128 } 129 130 /** 131 * <p> 132 * Executes the main function of the Jar file associated with this launcher. 133 * </p> 134 */ 135 public void exec() { 136 try { 137 getInfoFromJar(); 138 initClassLoader(); 139 runMain(); 140 } catch (JarLaunchException e) { 141 System.err.println("Failure to launch application."); 142 System.err.println(e.getMessage()); 143 } 144 } 145 146 /** 147 * <p> 148 * Retrieves the classpath and main function from the Jar file's MANIFEST. 149 * </p> 150 * 151 * @throws JarLaunchException 152 * thrown if reading of Jar file or MANIFEST fails 153 */ 154 private void getInfoFromJar() throws JarLaunchException { 155 JarInputStream jarInputStream; 156 try { 157 jarInputStream = new JarInputStream(new FileInputStream(workingDir 158 + jarfile)); 159 } catch (FileNotFoundException e) { 160 throw new JarLaunchException(e); 161 } catch (IOException e) { 162 throw new JarLaunchException(e); 163 } 164 Manifest manifest = jarInputStream.getManifest(); 165 mainClassName = manifest.getMainAttributes().getValue("Main-Class"); 166 String jarClassPath = manifest.getMainAttributes().getValue( 167 "Class-Path"); 168 String[] jarClassPathElements = jarClassPath.split(" "); 169 classPath = new String[jarClassPathElements.length]; 170 for (int i = 0; i < jarClassPathElements.length; i++) { 171 classPath[i] = "file:" + workingDir + jarClassPathElements[i]; 172 } 173 try { 174 jarInputStream.close(); 175 } catch (IOException e) { 176 e.printStackTrace(); 177 } 178 } 179 180 /** 181 * <p> 182 * Modifies the {@link ClassLoader} of the current VM such that it includes 183 * the class path defined in the Jar file's MANIFEST. 184 * </p> 185 * 186 * @throws JarLaunchException 187 * thrown if modification of {@link ClassLoader} fails. 188 */ 189 private void initClassLoader() throws JarLaunchException { 190 URLClassLoader classLoader = (URLClassLoader) ClassLoader 191 .getSystemClassLoader(); 192 Method method; 193 try { 194 method = URLClassLoader.class.getDeclaredMethod("addURL", 195 new Class[] { URL.class }); 196 } catch (SecurityException e) { 197 throw new JarLaunchException( 198 "addURL method of URLClassLoader not accessible via reflection."); 199 } catch (NoSuchMethodException e) { 200 throw new JarLaunchException( 201 "URLClassLoader does not have addURL method. Should be impossible!!"); 202 } 203 method.setAccessible(true); 204 205 try { 206 method.invoke(classLoader, new Object[] { new URL("file:" 207 + workingDir + jarfile) }); 208 for (String element : classPath) { 209 method.invoke(classLoader, new Object[] { new URL(element) }); 210 } 211 } catch (IllegalArgumentException e) { 212 throw new JarLaunchException( 213 "Illegal arguments for addURL method. Should be impossible!!"); 214 } catch (MalformedURLException e) { 215 throw new JarLaunchException(e); 216 } catch (IllegalAccessException e) { 217 throw new JarLaunchException( 218 "addURL method of URLClassLoader not accessible via reflection."); 219 } catch (InvocationTargetException e) { 220 e.printStackTrace(); 221 } 222 } 223 224 /** 225 * <p> 226 * Executes the main function. 227 * </p> 228 * 229 * @throws JarLaunchException 230 * thrown if execution of main function fails or the main 231 * function itself throws an exception 232 */ 233 private void runMain() throws JarLaunchException { 234 Class<?> mainClass; 235 try { 236 mainClass = Class.forName(mainClassName); 237 } catch (ClassNotFoundException e) { 238 throw new JarLaunchException("Main class not found: " 239 + mainClassName); 240 } 241 Method mainMethod; 242 try { 243 mainMethod = mainClass.getMethod("main", 244 new Class[] { String[].class }); 245 } catch (SecurityException e) { 246 throw new JarLaunchException("Main method not accessible."); 247 } catch (NoSuchMethodException e) { 248 throw new JarLaunchException("Main method not found."); 249 } 250 try { 251 mainMethod.invoke(null, new Object[] { args }); 252 } catch (IllegalArgumentException e) { 253 throw new JarLaunchException( 254 "Illegal arguments for main method. Should be impossible!!"); 255 } catch (IllegalAccessException e) { 256 throw new JarLaunchException("Main method not accessible."); 257 } catch (InvocationTargetException e) { 258 throw new JarLaunchException(e); 259 } 260 } 30 /** 31 * <p> 32 * Name of the Jar file to be executed. 33 * </p> 34 */ 35 private String jarfile; 36 37 /** 38 * <p> 39 * Arguments for launching the Jar file. 40 * </p> 41 */ 42 private String[] args; 43 44 /** 45 * <p> 46 * Helper variable with the path to the working directory. 47 * </p> 48 */ 49 final String workingDir = System.getProperty("user.dir") + "/"; 50 51 /** 52 * <p> 53 * Internal variable used to store the classpath extracted from the Jar file's MANIFEST. 54 * </p> 55 */ 56 private String[] classPath = new String[] { }; 57 58 /** 59 * <p> 60 * Internal variable used to store the name (including package information) of the Jar file's 61 * main function extracted from the Jar file's MANIFEST. 62 * </p> 63 */ 64 private String mainClassName = ""; 65 66 /** 67 * <p> 68 * Inner class that defines an exception that is thrown if launching the application in the Jar 69 * file fails. 70 * </p> 71 * 72 * @author Steffen Herbold 73 * @version 1.0 74 */ 75 private static class JarLaunchException extends Exception { 76 77 /** 78 * <p> 79 * Id for object serialization. 80 * </p> 81 */ 82 private static final long serialVersionUID = 1L; 83 84 /** 85 * <p> 86 * Constructor. Creates a new JarLaunchException. 87 * </p> 88 * 89 * @param string 90 * error message of the exception 91 */ 92 public JarLaunchException(String string) { 93 super(string); 94 } 95 96 /** 97 * <p> 98 * Constructor. Creates a new JarLaunchException as a copy of an existing exception. 99 * </p> 100 * 101 * @param e 102 * exception that is copied 103 */ 104 public JarLaunchException(Exception e) { 105 super(e); 106 } 107 108 } 109 110 /** 111 * <p> 112 * Constructor. Creates a new JarLauncher. 113 * </p> 114 * 115 * @param jarfile 116 * file to be launched; must not be complete path but in relation to the current 117 * working directory 118 * @param args 119 * arguments with which the main function of the Jar file is called 120 */ 121 public JarLauncher(String jarfile, String[] args) { 122 this.jarfile = jarfile; 123 this.args = Arrays.copyOf(args, args.length); 124 } 125 126 /** 127 * <p> 128 * Executes the main function of the Jar file associated with this launcher. 129 * </p> 130 */ 131 public void exec() { 132 try { 133 getInfoFromJar(); 134 initClassLoader(); 135 runMain(); 136 } 137 catch (JarLaunchException e) { 138 System.err.println("Failure to launch application."); 139 System.err.println(e.getMessage()); 140 } 141 } 142 143 /** 144 * <p> 145 * Retrieves the classpath and main function from the Jar file's MANIFEST. 146 * </p> 147 * 148 * @throws JarLaunchException 149 * thrown if reading of Jar file or MANIFEST fails 150 */ 151 private void getInfoFromJar() throws JarLaunchException { 152 JarInputStream jarInputStream; 153 try { 154 jarInputStream = new JarInputStream(new FileInputStream(workingDir + jarfile)); 155 } 156 catch (FileNotFoundException e) { 157 throw new JarLaunchException(e); 158 } 159 catch (IOException e) { 160 throw new JarLaunchException(e); 161 } 162 Manifest manifest = jarInputStream.getManifest(); 163 mainClassName = manifest.getMainAttributes().getValue("Main-Class"); 164 String jarClassPath = manifest.getMainAttributes().getValue("Class-Path"); 165 String[] jarClassPathElements = jarClassPath.split(" "); 166 classPath = new String[jarClassPathElements.length]; 167 for (int i = 0; i < jarClassPathElements.length; i++) { 168 classPath[i] = "file:" + workingDir + jarClassPathElements[i]; 169 } 170 try { 171 jarInputStream.close(); 172 } 173 catch (IOException e) { 174 e.printStackTrace(); 175 } 176 } 177 178 /** 179 * <p> 180 * Modifies the {@link ClassLoader} of the current VM such that it includes the class path 181 * defined in the Jar file's MANIFEST. 182 * </p> 183 * 184 * @throws JarLaunchException 185 * thrown if modification of {@link ClassLoader} fails. 186 */ 187 private void initClassLoader() throws JarLaunchException { 188 URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader(); 189 Method method; 190 try { 191 method = URLClassLoader.class.getDeclaredMethod("addURL", new Class[] 192 { URL.class }); 193 } 194 catch (SecurityException e) { 195 throw new JarLaunchException( 196 "addURL method of URLClassLoader not accessible via reflection."); 197 } 198 catch (NoSuchMethodException e) { 199 throw new JarLaunchException( 200 "URLClassLoader does not have addURL method. Should be impossible!!"); 201 } 202 method.setAccessible(true); 203 204 try { 205 method.invoke(classLoader, new Object[] 206 { new URL("file:" + workingDir + jarfile) }); 207 for (String element : classPath) { 208 method.invoke(classLoader, new Object[] 209 { new URL(element) }); 210 } 211 } 212 catch (IllegalArgumentException e) { 213 throw new JarLaunchException( 214 "Illegal arguments for addURL method. Should be impossible!!"); 215 } 216 catch (MalformedURLException e) { 217 throw new JarLaunchException(e); 218 } 219 catch (IllegalAccessException e) { 220 throw new JarLaunchException( 221 "addURL method of URLClassLoader not accessible via reflection."); 222 } 223 catch (InvocationTargetException e) { 224 e.printStackTrace(); 225 } 226 } 227 228 /** 229 * <p> 230 * Executes the main function. 231 * </p> 232 * 233 * @throws JarLaunchException 234 * thrown if execution of main function fails or the main function itself throws an 235 * exception 236 */ 237 private void runMain() throws JarLaunchException { 238 Class<?> mainClass; 239 try { 240 mainClass = Class.forName(mainClassName); 241 } 242 catch (ClassNotFoundException e) { 243 throw new JarLaunchException("Main class not found: " + mainClassName); 244 } 245 Method mainMethod; 246 try { 247 mainMethod = mainClass.getMethod("main", new Class[] 248 { String[].class }); 249 } 250 catch (SecurityException e) { 251 throw new JarLaunchException("Main method not accessible."); 252 } 253 catch (NoSuchMethodException e) { 254 throw new JarLaunchException("Main method not found."); 255 } 256 try { 257 mainMethod.invoke(null, new Object[] 258 { args }); 259 } 260 catch (IllegalArgumentException e) { 261 throw new JarLaunchException( 262 "Illegal arguments for main method. Should be impossible!!"); 263 } 264 catch (IllegalAccessException e) { 265 throw new JarLaunchException("Main method not accessible."); 266 } 267 catch (InvocationTargetException e) { 268 throw new JarLaunchException(e); 269 } 270 } 261 271 } -
trunk/JFCMonitor/src/main/java/de/ugoe/cs/eventbench/jfcmonitor/Runner.java
r371 r822 1 1 2 package de.ugoe.cs.eventbench.jfcmonitor; 2 3 … … 24 25 public class Runner { 25 26 26 /** 27 * <p> 28 * Debugging variable. If set to true, the logging is also written to the 29 * console. 30 * </p> 31 */ 32 private final static boolean stdOutputWrite = true; 27 /** 28 * <p> 29 * Debugging variable. If set to true, the logging is also written to the console. 30 * </p> 31 */ 32 private final static boolean stdOutputWrite = true; 33 33 34 /** 35 * <p> 36 * Main method of the application. 37 * </p> 38 * 39 * @param args 40 * the first parameter defines the Jar file that contains the 41 * start-up information of the application under test. The 42 * remaining parameters are passed on the toe application under 43 * test. 44 */ 45 public static void main(String[] args) { 46 String logfileName = "jfcmonitor_" + System.currentTimeMillis() 47 + ".log"; 34 /** 35 * <p> 36 * Main method of the application. 37 * </p> 38 * 39 * @param args 40 * the first parameter defines the Jar file that contains the start-up information of 41 * the application under test. The remaining parameters are passed on the toe 42 * application under test. 43 */ 44 public static void main(String[] args) { 45 String logfileName = "jfcmonitor_" + System.currentTimeMillis() + ".log"; 48 46 49 50 51 52 53 54 55 } catch (IOException e) { 56 System.err.println("JFCMONITOR -- failure opening logfile: " 57 58 59 47 FileOutputStream fis; 48 OutputStreamWriter writer; 49 try { 50 // the writer is not closed explicitly! 51 fis = new FileOutputStream(logfileName, true); 52 writer = new OutputStreamWriter(fis, "UTF-16"); 53 } 54 catch (IOException e) { 55 System.err.println("JFCMONITOR -- failure opening logfile: " + e.getMessage()); 56 return; 57 } 60 58 61 AWTEventListener listenerFile = new JFCListener(writer); 62 Toolkit.getDefaultToolkit().addAWTEventListener(listenerFile, 63 AWTEvent.KEY_EVENT_MASK); 64 Toolkit.getDefaultToolkit().addAWTEventListener(listenerFile, 65 AWTEvent.MOUSE_EVENT_MASK); 66 Toolkit.getDefaultToolkit().addAWTEventListener(listenerFile, 67 FocusEvent.FOCUS_EVENT_MASK); 68 Toolkit.getDefaultToolkit().addAWTEventListener(new WindowMonitor(), 69 AWTEvent.WINDOW_EVENT_MASK); 59 AWTEventListener listenerFile = new JFCListener(writer); 60 Toolkit.getDefaultToolkit().addAWTEventListener(listenerFile, AWTEvent.KEY_EVENT_MASK); 61 Toolkit.getDefaultToolkit().addAWTEventListener(listenerFile, AWTEvent.MOUSE_EVENT_MASK); 62 Toolkit.getDefaultToolkit().addAWTEventListener(listenerFile, FocusEvent.FOCUS_EVENT_MASK); 63 Toolkit.getDefaultToolkit().addAWTEventListener(new WindowMonitor(), 64 AWTEvent.WINDOW_EVENT_MASK); 70 65 71 72 73 74 listenerStdOut = new JFCListener(new OutputStreamWriter( 75 System.out, "UTF-8")); 76 Toolkit.getDefaultToolkit().addAWTEventListener(listenerStdOut, 77 AWTEvent.KEY_EVENT_MASK); 78 Toolkit.getDefaultToolkit().addAWTEventListener(listenerStdOut, 79 AWTEvent.MOUSE_EVENT_MASK); 80 Toolkit.getDefaultToolkit().addAWTEventListener(listenerStdOut, 81 FocusEvent.FOCUS_EVENT_MASK); 82 }catch (UnsupportedEncodingException e) {83 84 85 86 66 if (stdOutputWrite) { 67 AWTEventListener listenerStdOut; 68 try { 69 listenerStdOut = new JFCListener(new OutputStreamWriter(System.out, "UTF-8")); 70 Toolkit.getDefaultToolkit().addAWTEventListener(listenerStdOut, 71 AWTEvent.KEY_EVENT_MASK); 72 Toolkit.getDefaultToolkit().addAWTEventListener(listenerStdOut, 73 AWTEvent.MOUSE_EVENT_MASK); 74 Toolkit.getDefaultToolkit().addAWTEventListener(listenerStdOut, 75 FocusEvent.FOCUS_EVENT_MASK); 76 } 77 catch (UnsupportedEncodingException e) { 78 System.err 79 .println("JFCMONITOR -- failure to create OutputStreamWriter with UTF-8 encoding to System.out"); 80 } 81 } 87 82 88 JarLauncher launcher = new JarLauncher(args[0], Arrays.copyOfRange( 89 args, 1, args.length)); 90 launcher.exec(); 91 } 83 JarLauncher launcher = new JarLauncher(args[0], Arrays.copyOfRange(args, 1, args.length)); 84 launcher.exec(); 85 } 92 86 } -
trunk/JFCMonitor/src/main/java/de/ugoe/cs/eventbench/jfcmonitor/WindowMonitor.java
r365 r822 1 1 2 package de.ugoe.cs.eventbench.jfcmonitor; 2 3 … … 8 9 /** 9 10 * <p> 10 * An AWT event listener responsible to monitor the window creation and 11 * destruction. 11 * An AWT event listener responsible to monitor the window creation and destruction. 12 12 * </p> 13 13 * … … 17 17 public class WindowMonitor implements AWTEventListener { 18 18 19 20 21 * Adds all created windows (and their child components) to the GUI 22 * hierarchy maintained by {@link JFCComponent} and removes them if a window 23 * is destroyed. 24 25 * </p> 26 * 27 * @see java.awt.event.AWTEventListener#eventDispatched(java.awt.AWTEvent) 28 */ 29 @Override 30 public void eventDispatched(AWTEvent event) { 31 Window window; 32 switch (event.getID()){33 34 35 36 37 38 39 40 41 42 43 44 19 /** 20 * <p> 21 * Adds all created windows (and their child components) to the GUI hierarchy maintained by 22 * {@link JFCComponent} and removes them if a window is destroyed. 23 * </p> 24 * </p> 25 * 26 * @see java.awt.event.AWTEventListener#eventDispatched(java.awt.AWTEvent) 27 */ 28 @Override 29 public void eventDispatched(AWTEvent event) { 30 Window window; 31 switch (event.getID()) 32 { 33 case WindowEvent.WINDOW_OPENED: 34 window = ((WindowEvent) event).getWindow(); 35 JFCComponent.add(window); 36 break; 37 case WindowEvent.WINDOW_CLOSED: 38 window = ((WindowEvent) event).getWindow(); 39 JFCComponent.remove(window); 40 break; 41 default: 42 break; 43 } 44 } 45 45 46 46 }
Note: See TracChangeset
for help on using the changeset viewer.