package greenfoot.guifx.classes;

import bluej.Config;
import bluej.debugger.gentype.Reflective;
import bluej.extensions.ClassNotFoundException;
import bluej.extensions.PackageNotFoundException;
import bluej.extensions.ProjectNotOpenException;
import bluej.pkgmgr.Project;
import bluej.pkgmgr.target.ClassTarget;
import bluej.pkgmgr.target.Target;
import bluej.utility.Utility;
import bluej.utility.javafx.FXPlatformRunnable;
import bluej.utility.javafx.FXRunnable;
import bluej.utility.javafx.JavaFXUtil;
import bluej.views.View;
import bluej.views.ViewFilter;
import bluej.views.ViewFilter.StaticOrInstance;
import greenfoot.guifx.GreenfootStage;
import javafx.beans.binding.ObjectExpression;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.image.Image;
import javafx.scene.layout.BorderPane;
import threadchecker.OnThread;
import threadchecker.Tag;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Properties;


| The class diagram on the right-hand side of the Greenfoot window. | | For now, this is very primitive, but is useful for implementing other Greenfoot functionality. | @OnThread(Tag.FXPlatform) public class GClassDiagram extends BorderPane{ private final ContextMenu contextMenu;
| Is there a user-made World subclass (i.e. excluding World itself)? | public boolean hasUserWorld() { return worldClasses.streamAllClasses() .filter(c -> !c.getQualifiedName().equals("greenfoot.World")) .findFirst().isPresent(); }
| Is there a World subclass that we could instantiate using a package-visible | no-args constructor? | public boolean hasInstantiatableWorld() { return worldClasses.streamAllClasses().anyMatch(c -> { Target t = project.getTarget(c.getQualifiedName()); if (t instanceof ClassTarget) { Class<?> cl = project.loadClass(((ClassTarget)t).getQualifiedName()); if (cl == null) { return false; } View view = View.getView(cl); if (!java.lang.reflect.Modifier.isAbstract(cl.getModifiers())) { ViewFilter filter = new ViewFilter(StaticOrInstance.INSTANCE, ""); return Arrays.stream(view.getConstructors()) .filter(filter) .anyMatch(cv -> !cv.hasParameters()); } } return false; }); } public static enum GClassType { ACTOR, WORLD, OTHER } private final ClassDisplaySelectionManager selectionManager = new ClassDisplaySelectionManager(); private final ClassGroup worldClasses; private final ClassGroup actorClasses; private final ClassGroup otherClasses; private final GreenfootStage greenfootStage; private Project project;
| Construct a GClassDiagram for the given stage. | public GClassDiagram(GreenfootStage greenfootStage) { this.greenfootStage = greenfootStage; getStyleClass().add("gclass-diagram"); this.worldClasses = new ClassGroup(greenfootStage); this.actorClasses = new ClassGroup(greenfootStage); this.otherClasses = new ClassGroup(greenfootStage); setTop(worldClasses); setCenter(actorClasses); setBottom(otherClasses); BorderPane.setAlignment(actorClasses, Pos.TOP_LEFT); BorderPane.setAlignment(otherClasses, Pos.BOTTOM_LEFT); BorderPane.setMargin(actorClasses, new Insets(20, 0, 20, 0)); BorderPane.setMargin(otherClasses, new Insets(0, 0, ClassGroup.VERTICAL_SPACING, 0)); setMaxWidth(USE_PREF_SIZE); setMaxHeight(Double.MAX_VALUE); contextMenu = new ContextMenu(); contextMenu.getItems().add(JavaFXUtil.makeMenuItem( Config.getString("new.other.class"), () -> greenfootStage.newNonImageClass(project.getUnnamedPackage(), null), null)); contextMenu.getItems().add(JavaFXUtil.makeMenuItem( Config.getString("import.action"), () -> greenfootStage.doImportClass(), null)); setOnContextMenuRequested(e -> { hideContextMenu(); contextMenu.show(this, e.getScreenX(), e.getScreenY()); e.consume(); }); }
| Hide the context menu if it is showing. | protected void hideContextMenu() { if (contextMenu.isShowing()) { contextMenu.hide(); } }
| Set the project for this class diagram. | | @param project the project whose classes to display (may be null) | public void setProject(Project project) { this.project = project; if (project != null) { recalculateGroups(); setDisable(false); } else { worldClasses.setClasses(Collections.emptyList()); actorClasses.setClasses(Collections.emptyList()); otherClasses.setClasses(Collections.emptyList()); setDisable(true); } }
| Looks up list of ClassTargets in the project, and puts them into a tree structure | according to their superclass relations, with Actor and World subclasses | going into their own group. It needs to be called whenever the class inheritance hierarchy changes. | public void recalculateGroups() { ArrayList<ClassTarget> originalClassTargets = project.getUnnamedPackage().getClassTargets(); HashMap<ClassTarget, Boolean> classTargets = new HashMap<>(); for (ClassTarget originalClassTarget : originalClassTargets) { classTargets.put(originalClassTarget, false); } List<GClassNode> worldSubclasses = findAllSubclasses("greenfoot.World", classTargets, GClassType.WORLD); GClassNode worldClassesInfo = new BuiltInGClassNode(GClassType.WORLD, worldSubclasses, this); worldClasses.setClasses(Collections.singletonList(worldClassesInfo)); List<GClassNode> actorSubclasses = findAllSubclasses("greenfoot.Actor", classTargets, GClassType.ACTOR); GClassNode actorClassesInfo = new BuiltInGClassNode(GClassType.ACTOR, actorSubclasses, this); actorClasses.setClasses(Collections.singletonList(actorClassesInfo)); otherClasses.setClasses(findAllSubclasses(null, classTargets, GClassType.OTHER)); }
| Finds all subclasses of the given fully-qualified parent class name. The subclass search | is recursive, so if you pass "Grandparent", then both "Parent" and "Child" will be found |* and removed. Any found subclasses will have their boolean changed to true in the given map, * and only those that currently map to false will be searched. * * @param parentClassName The fully-qualified parent class name to search. If null, then all classes | in the classTargets list will be processed and returned. | @param classTargets Class targets to search -- only those mapped to false will be searched. If | they are processed into a GClassNode, their value will be flipped to true. | @return The list of GClassNode at the requested level (there may be a deeper tree inside). | private List findAllSubclasses(String parentClassName, Map<ClassTarget, Boolean> classTargets, GClassType type) { List<GClassNode> curLevel = new ArrayList<>(); for (Entry<ClassTarget, Boolean> classTargetAndVal : classTargets.entrySet()) { if (classTargetAndVal.getValue() == true) continue; ClassTarget classTarget = classTargetAndVal.getKey(); bluej.parser.symtab.ClassInfo classInfo = classTarget.analyseSource(); String superClassName = null; if (classInfo != null) { superClassName = classInfo.getSuperclass(); } else { try { Class<?> javaClass = classTarget.getBClassTarget().getBClass().getJavaClass(); if (javaClass != null) { Class<?> superClass = javaClass.getSuperclass(); if (superClass != null) { superClassName = superClass.getName(); } } } catch (ProjectNotOpenException | ClassNotFoundException | PackageNotFoundException e) { e.printStackTrace(); } } boolean includeAtThisLevel; if (parentClassName == null) { String finalSuperClassName = superClassName; includeAtThisLevel = finalSuperClassName == null || !classTargets.keySet().stream().anyMatch(ct -> Objects.equals(ct.getQualifiedName(), finalSuperClassName)); } else { includeAtThisLevel = Objects.equals(superClassName, parentClassName); } if (includeAtThisLevel) { classTargetAndVal.setValue(true); List<GClassNode> subClasses = findAllSubclasses(classTarget.getQualifiedName(), classTargets, type); curLevel.add(makeClassInfo(classTarget, subClasses, type)); } } return curLevel; }
| Adds a new class to the diagram at the appropriate place, based on its superclass, | and returns the constructed class info. | | @return A class info reference for the class added. | public LocalGClassNode addClass(ClassTarget classTarget) { String superClass = null; bluej.parser.symtab.ClassInfo info = classTarget.analyseSource(); if (info != null) { superClass = info.getSuperclass(); } if (superClass != null) { for (GClassType type : GClassType.values()) { ClassGroup classGroup; switch (type) { case ACTOR: classGroup = actorClasses; break; case WORLD: classGroup = worldClasses; break; case OTHER: classGroup = otherClasses; break; default: continue; } LocalGClassNode classInfo = findAndAdd(classGroup.getLiveTopLevelClasses(), classTarget, superClass, type); if (classInfo != null) { classGroup.updateAfterAdd(); return classInfo; } } } LocalGClassNode classInfo = makeClassInfo(classTarget, Collections.emptyList(), GClassType.OTHER); otherClasses.getLiveTopLevelClasses().add(classInfo); otherClasses.updateAfterAdd(); return classInfo; }
| Looks within the whole tree formed by the list of class info for the right place for classTarget. | If found, create a class info for the class target passed, adds it and return it. | | @param classInfos The tree to search. The list itself will not be modified. | @param classTarget The class to add to the tree. | @param classTargetSuperClass The super-class of classTarget | @param type The source type of the class added. | @return The class info created if right place found and added, null if not. | private LocalGClassNode findAndAdd(List<GClassNode> classInfos, ClassTarget classTarget, String classTargetSuperClass, GClassType type) { for (GClassNode classInfo : classInfos) { if (classInfo.getQualifiedName().equals(classTargetSuperClass)) { LocalGClassNode newClassInfo = makeClassInfo(classTarget, Collections.emptyList(), type); classInfo.add(newClassInfo); return newClassInfo; } else { LocalGClassNode newClassInfo = findAndAdd(classInfo.getSubClasses(), classTarget, classTargetSuperClass, type); if (newClassInfo != null) { return newClassInfo; } } } return null; }
| Make the LocalGClassNode for a ClassTarget | protected LocalGClassNode makeClassInfo(ClassTarget classTarget, List<GClassNode> subClasses, GClassType type) { return new LocalGClassNode(this, classTarget, subClasses, type); }
| Make a context menu item with the given text and action, and the inbuilt-menu-item | style (which shows up as dark-red, and italic on non-Mac) | public static MenuItem contextInbuilt(String text, FXPlatformRunnable action) { MenuItem menuItem = JavaFXUtil.makeMenuItem(text, action, null); JavaFXUtil.addStyleClass(menuItem, ClassTarget.MENU_STYLE_INBUILT); return menuItem; }
| Gets the currently selected class target in the diagram. May be null if no selection, | or if the selection is a class outside the default package (e.g. greenfoot.World) | public ClassTarget getSelectedClassTarget() { ClassDisplay selected = selectionManager.getSelected(); if (selected != null) { Target target = project.getUnnamedPackage().getTarget(selected.getQualifiedName()); if (target instanceof ClassTarget) { return (ClassTarget) target; } } return null; }
| Gets the selection manager for this class diagram | public ClassDisplaySelectionManager getSelectionManager() { return selectionManager; }
| Gets the GreenfootStage which contains this class diagram | public GreenfootStage getGreenfootStage() { return greenfootStage; }
| Save class-related properties to the given property map. | public void save(Properties p) { worldClasses.saveImageSelections(p); actorClasses.saveImageSelections(p); otherClasses.saveImageSelections(p); }
| Get the name of the image file for an actor class (searching up the class hierarchy | if there is no specific image set for the specified class). May return null. | public String getImageForActorClass(Reflective r) { List<Reflective> inheritanceChain = new ArrayList<Reflective>(); inheritanceChain.add(r); Reflective[] superClass = new Reflective[1]; while (r != null && ! r.getName().equals("greenfoot.Actor")) { superClass[0] = null; r.getSuperTypesR().stream() .filter(s -> ! s.isInterface()) .findFirst() .ifPresent(e -> { superClass[0] = e; inheritanceChain.add(e); }); r = superClass[0]; } if (r == null) { return null; } int i = inheritanceChain.size() - 1; ClassGroup group = actorClasses; GClassNode classNode = group.getLiveTopLevelClasses().get(0); i--; String fileName = null; outer_loop: while (i >= 0) { List<GClassNode> subs = classNode.getSubClasses(); for (GClassNode candidate : subs) { if (candidate.getQualifiedName().equals(inheritanceChain.get(i).getName())) { i--; classNode = candidate; String candidateImage = candidate.getImageFilename(); if (candidateImage != null) { fileName = candidateImage; } continue outer_loop; } } break; } return fileName; } }
top, use, map, class GClassDiagram

.   hasUserWorld
.   hasInstantiatableWorld
.   GClassDiagram
.   hideContextMenu
.   setProject
.   recalculateGroups
.   findAllSubclasses
.   addClass
.   findAndAdd
.   makeClassInfo
.   contextInbuilt
.   getSelectedClassTarget
.   getSelectionManager
.   getGreenfootStage
.   save
.   getImageForActorClass




564 neLoCode + 38 LoComm