package bluej.debugmgr.objectbench;

import java.awt.Color;
import java.lang.reflect.Array;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import bluej.views.ViewFilter.StaticOrInstance;
import javafx.animation.Animation;
import javafx.animation.ParallelTransition;
import javafx.animation.ScaleTransition;
import javafx.animation.TranslateTransition;
import javafx.beans.binding.When;
import javafx.collections.ObservableList;
import javafx.geometry.Point2D;
import javafx.geometry.Side;
import javafx.scene.Cursor;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.effect.DropShadow;
import javafx.scene.input.KeyCode;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;

import bluej.Config;
import bluej.debugger.DebuggerObject;
import bluej.debugger.gentype.GenTypeClass;
import bluej.debugger.gentype.GenTypeParameter;
import bluej.debugger.gentype.JavaType;
import bluej.debugmgr.Invoker;
import bluej.debugmgr.NamedValue;
import bluej.debugmgr.ResultWatcher;
import bluej.debugmgr.inspector.ObjectBackground;
import bluej.extensions.BObject;
import bluej.extensions.ExtensionBridge;
import bluej.extmgr.ExtensionsManager;
import bluej.extmgr.FXMenuManager;
import bluej.extmgr.ObjectExtensionMenu;
import bluej.pkgmgr.Package;
import bluej.pkgmgr.PkgMgrFrame;
import bluej.pkgmgr.Project;
import bluej.testmgr.record.InvokerRecord;
import bluej.testmgr.record.ObjectInspectInvokerRecord;
import bluej.utility.Debug;
import bluej.utility.JavaNames;
import bluej.utility.JavaReflective;
import bluej.utility.javafx.FXPlatformRunnable;
import bluej.utility.javafx.JavaFXUtil;
import bluej.views.ConstructorView;
import bluej.views.MethodView;
import bluej.views.View;
import bluej.views.ViewFilter;
import threadchecker.OnThread;
import threadchecker.Tag;


| A wrapper around a Java object that handles calling methods, inspecting, etc. | | <p>The wrapper is represented by the red oval that is visible on the | object bench. | | @author Michael Kolling | @OnThread(Tag.FXPlatform) public class ObjectWrapper extends StackPane implements InvokeListener, NamedValue{ @OnThread(Tag.Any) static final String methodException = Config.getString("debugger.objectwrapper.methodException"); @OnThread(Tag.Any) static final String invocationException = Config.getString("debugger.objectwrapper.invocationException"); @OnThread(Tag.Any) static final String inspect = Config.getString("debugger.objectwrapper.inspect"); @OnThread(Tag.Any) static final String remove = Config.getString("debugger.objectwrapper.remove"); @OnThread(Tag.Any) static final String redefinedIn = Config.getString("debugger.objectwrapper.redefined"); @OnThread(Tag.Any) static final String inheritedFrom = Config.getString("debugger.objectwrapper.inherited"); @OnThread(Tag.Any) static final Color envOpColour = new Color(152,32,32); public static final int WIDTH = 100; public static final int HEIGHT = 70; public static final double CORNER_SIZE = 10.0; public static final double FOCUSED_BORDER = 6.0; public static final double UNFOCUSED_BORDER = 2.0; public static final double SHADOW_RADIUS = 3.0; private static int itemHeight = 28; private static boolean itemHeightKnown = false; @OnThread(Tag.Any) private static int itemsOnScreen;
| The Java object that this wraps | @OnThread(Tag.Any) protected final DebuggerObject obj; private final String objClassName; @OnThread(Tag.Any) protected GenTypeClass iType;
| Fully qualified type this object represents, including type parameters | private final String className; @OnThread(value = Tag.Any, requireSynchronized = true) private String objInstanceName; protected String displayClassName; protected ContextMenu menu; protected final Rectangle highlight = new ResizableRectangle(); private final Package pkg; private final PkgMgrFrame pmf; private final ObjectBench ob; private static final String MENU_STYLE_INBUILT = "object-action-inbuilt";
| Get an object wrapper for a user object. | | @param pmf The package manager frame | @param ob The object bench | @param obj The object to wrap | @param iType The static type of the object, used as a fallback if | the runtime type is inaccessible | @param instanceName The name for the object reference | @return A new object wrapper for the user's object | @OnThread(Tag.FXPlatform) static public ObjectWrapper getWrapper(PkgMgrFrame pmf, ObjectBench ob, DebuggerObject obj, GenTypeClass iType, String instanceName) { if (obj.isArray()) { return new ArrayWrapper(pmf, ob, obj, instanceName); } else { return new ObjectWrapper(pmf, ob, obj, iType, instanceName); } } @OnThread(Tag.FXPlatform) protected ObjectWrapper(PkgMgrFrame pmf, ObjectBench ob, DebuggerObject obj, GenTypeClass iType, String instanceName) { if (!itemHeightKnown) { itemsOnScreen = (int)Config.screenBounds.getHeight() / itemHeight; } this.pmf = pmf; this.pkg = pmf.getPackage(); this.ob = ob; this.obj = obj; this.objClassName = obj.getClassName(); this.iType = iType; this.setName(instanceName); if (obj.isNullObject()) { className = ""; displayClassName = ""; } else { GenTypeClass objType = obj.getGenType(); className = objType.toString(); displayClassName = objType.toString(true); } Class<?> cl = findIType(); ExtensionsManager extMgr = ExtensionsManager.getInstance(); createMenu(extMgr, cl); JavaFXUtil.listenForContextMenu(this, (x, y) -> { menu.show(this, x, y); return true; }, KeyCode.SPACE, KeyCode.ENTER); setMinWidth(WIDTH); setMinHeight(HEIGHT); setMaxWidth(WIDTH); setMaxHeight(HEIGHT); setCursor(Cursor.HAND); setFocusTraversable(true); setOnMouseClicked(this::clicked); JavaFXUtil.addFocusListener(this, focused -> { if (focused) ob.objectGotFocus(this); else if (ob.getSelectedObject() == this) ob.setSelectedObject(null); }); JavaFXUtil.addStyleClass(this, "object-wrapper"); Label label = new Label(getName() + ":\n" + displayClassName); JavaFXUtil.addStyleClass(label, "object-wrapper-text"); createComponent(label); highlight.setMouseTransparent(true); highlight.setVisible(false); highlight.getStyleClass().add("object-debug-highlight"); } protected void createComponent(Label label) { getChildren().addAll(new ObjectBackground(CORNER_SIZE, new When(focusedProperty()).then(FOCUSED_BORDER).otherwise(UNFOCUSED_BORDER)), label, highlight); setBackground(null); setEffect(new DropShadow(SHADOW_RADIUS, SHADOW_RADIUS/2.0, SHADOW_RADIUS/2.0, javafx.scene.paint.Color.GRAY)); } @OnThread(Tag.Any) public Package getPackage() { return pkg; }
| Get the PkgMgrFrame which is housing this object wrapper. | public PkgMgrFrame getFrame() { return pmf; } @OnThread(Tag.Any) public String getClassName() { return objClassName; } @OnThread(Tag.Any) public String getTypeName() { return className; }
| Return the invocation type for this object. The invocation type is the | type which should be written in the shell file. It is not necessarily the | same as the actual (dynamic) type of the object. | @Override @OnThread(Tag.FXPlatform) public JavaType getGenType() { return iType; } @Override @OnThread(Tag.Any) public boolean isFinal() { return true; } @Override @OnThread(Tag.Any) public boolean isInitialized() { return true; } @OnThread(value = Tag.Any, requireSynchronized = true) private BObject singleBObject; | Return the extensions BObject associated with this ObjectWrapper. | There should be only one BObject object associated with each Package. | @return the BPackage associated with this Package. | @OnThread(Tag.SwingIsFX) public synchronized final BObject getBObject() { if ( singleBObject == null ) singleBObject = ExtensionBridge.newBObject(this); return singleBObject; }
| Perform any necessary cleanup before removal from the object bench. | public void prepareRemove() { Project proj = pkg.getProject(); proj.removeInspectorInstance(obj); }
| Check whether the given class is accessible (from this wrapper's package) | | @param cl The class to check for accessibility | @return True if the class is accessible, false otherwise | @OnThread(Tag.FXPlatform) private boolean classIsAccessible(Class<?> cl) { int clMods = cl.getModifiers(); String classPackage = JavaNames.getPrefix(cl.getName()); return !(Modifier.isProtected(clMods) && !pkg.getQualifiedName().equals(classPackage) || Modifier.isPrivate(clMods)); }
| Determine an appropriate type to use for this object in shell files. | The type must be accessible in the current package. | | IType will be set to the chosen type. | | @return The class of the chosen type. | @OnThread(Tag.FXPlatform) private Class findIType() { String className = obj.getClassName(); Class<?> cl = pkg.loadClass(className); if (cl == null && obj.isArray() && className.endsWith("[]")) { String memberType = className.substring(0, className.length() - 2); switch (memberType) { case "boolean": return boolean[].class; case "byte": return byte[].class; case "short": return short[].class; case "int": return int[].class; case "long": return long[].class; case "float": return float[].class; case "double": return double[].class; case "char": return char[].class; } cl = Array.newInstance(pkg.loadClass(memberType), 0).getClass(); } if (cl != null) { if (! classIsAccessible(cl)) { cl = pkg.loadClass(iType.classloaderName()); while (cl != null && ! classIsAccessible(cl)){ cl = cl.getSuperclass(); if (cl != null) { iType = iType.mapToSuper(cl.getName()); } else { JavaReflective objectReflective = new JavaReflective(Object.class); iType = new GenTypeClass(objectReflective); } } } else { iType = obj.getGenType(); } } return cl; }
| Creates the popup menu structure by parsing the object's | class inheritance hierarchy. | protected void createMenu(ExtensionsManager extMgr, Class<?> cl) { menu = new ContextMenu(); createMethodMenuItems(menu.getItems(), cl, iType, this, pkg.getQualifiedName(), true); MenuItem item; menu.getItems().add(item = new MenuItem(inspect)); JavaFXUtil.addStyleClass(item, MENU_STYLE_INBUILT); item.setOnAction(e -> inspectObject()); menu.getItems().add(item = new MenuItem(remove)); JavaFXUtil.addStyleClass(item, MENU_STYLE_INBUILT); item.setOnAction(e -> removeObject()); FXMenuManager menuManager = new FXMenuManager(menu, extMgr, new ObjectExtensionMenu(this)); menuManager.addExtensionMenu(pkg.getProject()); }
| Creates the menu items for all the methods in the class, which is a raw | class type. | | @param menu The menu to add the menu items to | @param cl The class whose methods to add | @param il The invoke listener to notify when a method is called | @param currentPackageName Name of the package that this object will be | shown from (used to determine wheter to show package protected | methods) | @param showObjectMethods Whether to show the submenu with methods from java.lang.Object | public static void createMethodMenuItems(ObservableList<MenuItem> menu, Class<?> cl, InvokeListener il, String currentPackageName, boolean showObjectMethods) { GenTypeClass gt = new GenTypeClass(new JavaReflective(cl)); createMethodMenuItems(menu, cl, gt, il, currentPackageName, showObjectMethods); }
| Creates the menu items for all the methods in the class | | @param menu The menu to add the menu items to | @param cl The class whose methods to add | @param gtype The generic type of the class | @param il The invoke listener to notify when a method is called | @param currentPackageName Name of the package that this object will be | shown from (used to determine wheter to show package protected | methods) | @param showObjectMethods Whether to show the submenu for methods inherited from java.lang.Object | public static void createMethodMenuItems(ObservableList<MenuItem> menu, Class<?> cl, GenTypeClass gtype, InvokeListener il, String currentPackageName, boolean showObjectMethods) { if (cl != null) { View view = View.getView(cl); Hashtable<String, String> methodsUsed = new Hashtable<>(); List<Class<?>> classes = getClassHierarchy(cl); ViewFilter filter = new ViewFilter(StaticOrInstance.INSTANCE, currentPackageName); menu.add(new SeparatorMenuItem()); MethodView[] declaredMethods = view.getDeclaredMethods(); GenTypeClass curType = gtype; if (curType == null) { curType = new GenTypeClass(new JavaReflective(cl)); } if (itemsOnScreen <= 0 ) { itemsOnScreen = 30; } int itemLimit = itemsOnScreen - 8 - classes.size(); createMenuItems(menu, declaredMethods, il, filter, itemLimit, curType.getMap(), methodsUsed); for (int i = 1; i < classes.size(); i++ ) { Class<?> currentClass = classes.get(i); view = View.getView(currentClass); filter = new ViewFilter(StaticOrInstance.INSTANCE, currentPackageName); curType = curType.mapToSuper(currentClass.getName()); if (!"java.lang.Object".equals(currentClass.getName()) || showObjectMethods) { declaredMethods = view.getDeclaredMethods(); Menu subMenu = new Menu(inheritedFrom + " " + JavaNames.stripPrefix(currentClass.getName())); createMenuItems(subMenu.getItems(), declaredMethods, il, filter, (itemsOnScreen / 2), curType.getMap(), methodsUsed); menu.add(0, subMenu); } } for (Class<?> iface : getInterfacesWithDefaultMethods(cl)) { view = View.getView(iface); declaredMethods = view.getDeclaredMethods(); Menu subMenu = new Menu(inheritedFrom + " " + JavaNames.stripPrefix(iface.getName())); createMenuItems(subMenu.getItems(), declaredMethods, il, filter, (itemsOnScreen / 2), curType.getMap(), methodsUsed); menu.add(0, subMenu); } menu.add(new SeparatorMenuItem()); } }
| Creates the individual menu items for an object's popup menu. | The method checks for previously defined methods with the same signature | and appends information referring to this. | | @param menu the menu that the items are to be created for | @param methods the methods for which menu items should be created | @param il the listener to be notified when a method should be called interactively | @param filter the filter which decides on which methods should be shown | @param sizeLimit the limit to which the menu should grow before openeing submenus | @param genericParams the mapping of generic type parameter names to their corresponding | types in the object instance (a map of String -> GenType). | @param methodsUsed the table to store the methods that already been ddealt | private static void createMenuItems(List<MenuItem> menu, MethodView[] methods, InvokeListener il, ViewFilter filter, int sizeLimit, Map<String, GenTypeParameter> genericParams, Hashtable<String, String> methodsUsed) { MenuItem item; boolean menuEmpty = true; Arrays.sort(methods); for (MethodView method : methods) { try { if (!filter.test(method)) continue; menuEmpty = false; String methodSignature = method.getCallSignature(); String methodDescription = method.getLongDesc(genericParams); if (methodsUsed.containsKey(methodSignature)) { methodDescription = methodDescription + " [ " + redefinedIn + " " + JavaNames.stripPrefix( methodsUsed.get(methodSignature)) + " ]"; } else { methodsUsed.put(methodSignature, method.getClassName()); } item = new MenuItem(methodDescription); item.setOnAction(e -> il.executeMethod(method)); int itemCount = menu.size(); if (itemCount >= sizeLimit) { Menu subMenu = new Menu(Config.getString("debugger.objectwrapper.moreMethods")); menu.add(subMenu); menu = subMenu.getItems(); sizeLimit = itemsOnScreen / 2; } menu.add(item); } catch (Exception e) { Debug.reportError(methodException + e); e.printStackTrace(); } } if (menuEmpty) { MenuItem mi = new MenuItem(Config.getString("debugger.objectwrapper.noMethods")); mi.setDisable(true); menu.add(mi); } }
| Creates a List containing all classes in an inheritance hierarchy | working back to Object | | @param derivedClass the class whose hierarchy is mapped (including self) | @return the List containng the classes in the inheritance hierarchy | @OnThread(Tag.Any) private static List> getClassHierarchy(Class<?> derivedClass) { Class<?> currentClass = derivedClass; List<Class<?>> classVector = new ArrayList<>(); while (currentClass != null) { classVector.add(currentClass); currentClass = currentClass.getSuperclass(); } return classVector; }
| Gets a list containing interfaces implemented by the given class, anywhere | in its parent hierarchy, which have default methods (regardless of whether | the methods are overridden elsewhere in the hierarchy). | | So if you have interface I with default method getI, and interface J with no | default methods, and: class A implements I, class B extends A implements J, | and class C extends B, then calling getInterfacesWithDefaultMethods for | A, B or C will return a singleton list with I in it. | | @param cls The class whose implements interfaces are to be searched. | @return The list containing the implemented interfaces which have default methods. | @OnThread(Tag.Any) private static List> getInterfacesWithDefaultMethods(Class<?> cls) { return getClassHierarchy(cls).stream() .flatMap(c -> Arrays.stream(c.getInterfaces())) .filter(i -> Arrays.stream(i.getDeclaredMethods()).anyMatch(m -> m.isDefault())) .collect(Collectors.toList()); } @Override @OnThread(value = Tag.Any, ignoreParent = true) public synchronized String getName() { return objInstanceName; } @OnThread(Tag.Any) public synchronized void setName(String newName) { objInstanceName = newName; } @OnThread(Tag.Any) public DebuggerObject getObject() { return obj; }
| Process a mouse click into this object. If it was a popup event, show the object's | menu. If it was a double click, inspect the object. If it was a normal mouse click, | insert it into a parameter field (if any). | private void clicked(MouseEvent evt) { if (!evt.isPopupTrigger() && evt.getButton() == MouseButton.PRIMARY) { if (evt.getClickCount() > 1) inspectObject(); else { ob.fireObjectSelectedEvent(this); } } requestFocus(); }
| Open this object for inspection. | @OnThread(Tag.FXPlatform) protected void inspectObject() { InvokerRecord ir = new ObjectInspectInvokerRecord(getName()); pkg.getProject().getInspectorInstance(obj, getName(), pkg, ir, pmf.getFXWindow(), this); } protected void removeObject() { ob.removeObject(this, pkg.getId()); }
| Execute an interactive method call. If the method has results, | create a watcher to watch out for the result coming back, do the | actual invocation, and update open object viewers after the call. | @Override @OnThread(Tag.FXPlatform) public void executeMethod(final MethodView method) { ResultWatcher watcher = null; pkg.forgetLastSource(); String instanceName = getName(); watcher = new BluejResultWatcher(obj, instanceName, pkg, pmf, method) { @Override protected void addInteraction(InvokerRecord ir) { ob.addInteraction(ir); } }; if (pmf.checkDebuggerState()) { Invoker invoker = new Invoker(pmf, method, instanceName, obj, watcher); invoker.invokeInteractive(); } } @Override public void callConstructor(ConstructorView cv) { }
| @param isSelected The isSelected to set. | public void setSelected(boolean isSelected) { if (isSelected) { pmf.setStatus(getName() + " : " + displayClassName); } } public void showMenu() { menu.show(this, Side.LEFT, 5, 5); } public void animateIn(Optional<Point2D> animateFromScenePoint) { setScaleX(0.2); setScaleY(0.2); JavaFXUtil.listenOnce(layoutYProperty(), layoutY -> { setVisible(true); ScaleTransition scale = new ScaleTransition(Duration.millis(300), this); scale.setFromX(0.2); scale.setFromY(0.2); scale.setToX(1.0); scale.setToY(1.0); if (animateFromScenePoint.isPresent()) { TranslateTransition move = new TranslateTransition(Duration.millis(300), this); Point2D local = sceneToLocal(animateFromScenePoint.get()); move.setFromX(local.getX()); move.setFromY(local.getY()); move.setToX(0.0); move.setToY(0.0); if (Math.hypot(local.getX(), local.getY()) >= 300.0) { scale.setDuration(Duration.millis(600.0)); move.setDuration(Duration.millis(600.0)); } new ParallelTransition(scale, move).play(); } else { scale.play(); } }); } public void animateOut(FXPlatformRunnable after) { ScaleTransition t = new ScaleTransition(Duration.millis(300), this); t.setToX(0.0); t.setToY(0.0); t.setOnFinished(e -> { if (after != null) after.run(); }); t.play(); }
| Sets the highlight (for current object while debugging) on or off | @param highlightOn True to highlight this object, false to turn it off | public void setHighlight(boolean highlightOn) { highlight.setVisible(highlightOn); }
| A Rectangle subclass that can be resized to any size during layout. | @OnThread(Tag.FX) private static class ResizableRectangle extends Rectangle { @Override public boolean isResizable() { return true; } @Override public void resize(double width, double height) { setWidth(width); setHeight(height); } @Override public double maxWidth(double height) { return Double.MAX_VALUE; } @Override public double maxHeight(double width) { return Double.MAX_VALUE; } }
| | |@Override | |public AccessibleContext getAccessibleContext() | |{}if (accessibleContext == null) {} accessibleContext = new AccessibleJComponent() {} | |@Override | |public String getAccessibleName() {}return getName() + ": " + displayClassName; } // If we leave the default role, NVDA ignores this component. // List item works, and seemed like an okay fit @Override public AccessibleRole getAccessibleRole() {} return AccessibleRole.LIST_ITEM; | |} | |}; | |} | |return accessibleContext; | |} }
top, use, map, class ObjectWrapper

.   getWrapper
.   ObjectWrapper
.   createComponent
.   getPackage
.   getFrame
.   getClassName
.   getTypeName
.   getGenType
.   isFinal
.   isInitialized
.   getBObject
.   prepareRemove
.   classIsAccessible
.   findIType
.   createMenu
.   createMethodMenuItems
.   createMethodMenuItems
.   createMenuItems
.   getClassHierarchy
.   getInterfacesWithDefaultMethods
.   getName
.   setName
.   getObject
.   clicked
.   inspectObject
.   removeObject
.   executeMethod
.   addInteraction
.   callConstructor
.   setSelected
.   showMenu
.   animateIn
.   animateOut
.   setHighlight

top, use, map, class ResizableRectangle

.   isResizable
.   resize
.   maxWidth
.   maxHeight




889 neLoCode + 95 LoComm