package greenfoot.record;

import bluej.debugger.DebuggerObject;
import bluej.editor.Editor;
import bluej.utility.javafx.FXPlatformFunction;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;

import bluej.Config;
import bluej.debugger.gentype.JavaType;
import bluej.stride.framedjava.elements.CallElement;
import bluej.stride.framedjava.elements.CodeElement;
import bluej.stride.framedjava.elements.NormalMethodElement;
import bluej.stride.framedjava.elements.VarElement;
import threadchecker.OnThread;
import threadchecker.Tag;


| Builder for code sequences representing a recording of what the user has | done interactively to the world. | public class GreenfootRecorder {
| A map of known objects to their name as it appears in the code | private final HashMap<DebuggerObject, String> objectNames; private final ArrayList<CodeElement> code; private DebuggerObject world; public static final String METHOD_ACCESS = "private"; public static final String METHOD_RETURN = "void"; public static final String METHOD_NAME = "prepare"; private String lastWorldClass; private boolean validToSave = false;
| Construct a new GreenfootRecorder instance. | public GreenfootRecorder() { objectNames = new HashMap<>(); code = new ArrayList<>(); }
| Record the interactive construction of an actor object. | @param actor The newly constructed actor | @param args The arguments supplied to the actor's constructor, as Java expresssions | @param paramTypes The parameter types of the called constructor | @OnThread(Tag.Any) @SuppressWarnings("threadchecker") public synchronized void createActor(DebuggerObject actor, String[] args, JavaType[] paramTypes) { String className = actor.getGenType().toString(true); String name = nameActor(actor); if (name != null) { code.add(new VarElement(null, className, name, "new " + className + "(" + withCommas(args, paramTypes, false) + ")")); } }
| Called when the prepare method is replayed to indicate that the actor's name should be recorded. | Returns the name assigned to the actor (or null on failure). | | This is called from the debugger thread. | private synchronized String nameActor(DebuggerObject actor) { if (objectNames.containsKey(actor)) { return objectNames.get(actor); } String root = actor.getClassName().replace("." , "").replace("$", ""); root = root.substring(0, 1).toLowerCase() + root.substring(1); String name = root; for (int i = 2; objectNames.values().contains(name); i++) { name = root + i; } objectNames.put(actor, name); return name; }
| Names all the actors in the list, in order (first to last) | public synchronized void nameActors(List<DebuggerObject> actors) { for (DebuggerObject actor : actors) { nameActor(actor); } }
| Insert commas and other necessary syntax into an argument list | @param args The arguments to a method or constructor call (as Java expressions) | @param paramTypes The parameter types of the method/constructor | @return The arguments as a comma-separated list | @OnThread(Tag.Any) @SuppressWarnings("threadchecker") private static String withCommas(String[] args, JavaType[] paramTypes, boolean isVarArgs) { if (args == null) { return ""; } StringBuffer commaArgs = new StringBuffer(); for (int i = 0; i < args.length;i++) { String arg = args[i].trim(); if (arg.startsWith("{") && arg.endsWith("}")) { String paramTypeName; if (isVarArgs && i >= paramTypes.length - 1) { paramTypeName = paramTypes[paramTypes.length - 1].toString(); paramTypeName = paramTypeName.substring(0,paramTypeName.length()-2); } else { paramTypeName = paramTypes[i].toString(); } arg = "new " + paramTypeName + " " + arg; } commaArgs.append(arg); if (i != args.length - 1) { commaArgs.append(", "); } } return commaArgs.toString(); }
| An actor was interactively added to the world: record the interaction | @param actor The added actor | @param x The actor's x position | @param y The actor's y position | @OnThread(Tag.Any) public synchronized void addActorToWorld(DebuggerObject actor, int x, int y) { String actorObjectName = objectNames.get(actor); if (null == actorObjectName) { return; } code.add(callElement("addObject(" + actorObjectName + "," + String.valueOf(x) + "," + String.valueOf(y) + ")")); }
| Record an interactive method call on object (actor or world). Called after the method | successfully returns. | | @param obj The object on which the method was invoked | @param actorName The assigned object name | @param method The method being called | @param args The arguments to the method, as Java expressions | @param paramTypes The parameter types of the method | public synchronized void callActorOrWorldMethod(DebuggerObject obj, Method method, String[] args, JavaType[] paramTypes) { if (obj != null && !objectNames.containsKey(obj) && !obj.equals(world)) { return; } String name; if (world != null && world.equals(obj)) { name = method.getName(); } else { name = objectNames.get(obj) + "." + method.getName(); } code.add(callElement(name + "(" + withCommas(args, paramTypes, method.isVarArgs()) + ")")); }
| Record an interactive static method call. Called after the method | successfully returns. | | @param className The name of the class to which the called method belongs | @param method The method | @param args The arguments to the method, as a | @param argTypes | public void callStaticMethod(String className, Method method, String[] args, JavaType[] argTypes) { code.add(callElement(className + "." + method.getName() + "(" + withCommas(args, argTypes, method.isVarArgs()) + ")")); }
| Notify the recorder that it should clear its recording. | | @param clearObjectNames Whether to clear the object names | public synchronized void clearCode(boolean clearObjectNames) { code.clear(); if (clearObjectNames) { objectNames.clear(); } }
| Notify the recorder that a new world has become the current world. | public synchronized void setWorld(DebuggerObject newWorld) { world = newWorld; lastWorldClass = newWorld.getClassName(); }
| Record a dragged actor interaction. This is currently called from the simulation | thread (i.e. with the world locked). | public synchronized void moveActor(DebuggerObject actor, int xCell, int yCell) { String actorObjectName = objectNames.get(actor); if (null == actorObjectName) { return; } code.add(callElement(actorObjectName + ".setLocation(" + String.valueOf(xCell) + "," + String.valueOf(yCell) + ")")); }
| Record a remove actor interaction. | public void removeActor(DebuggerObject obj) { String actorObjectName = objectNames.get(obj); if (null == actorObjectName) { return; } code.add(callElement("removeObject(" + actorObjectName + ")")); objectNames.remove(obj); }
| Retrieve the code elements representing the interactions recorded up to this point. | public synchronized List getCode() { return new LinkedList<CodeElement>(code); } public NormalMethodElement getPrepareMethod() { StringBuffer documentation = new StringBuffer(); documentation.append(Config.getString("record.method.comment1")).append("\n"); documentation.append(Config.getString("record.method.comment2")); return new NormalMethodElement(METHOD_ACCESS, METHOD_RETURN, METHOD_NAME, null, code, documentation.toString()); } public CallElement getPrepareMethodCall() { return callElement(METHOD_NAME + "()"); } private CallElement callElement(String content) { return new CallElement(content, content); }
| Write the recorded code to the world class. | @param getEditor A function that takes a world class name and returns an editor | @return True if world code successfully saved, false if it could not because save the world isn't valid. | @OnThread(Tag.FXPlatform) public boolean writeCode(FXPlatformFunction<String, Editor> getEditor) { if (!validToSave) { return false; } NormalMethodElement method = getPrepareMethod(); CallElement methodCall = getPrepareMethodCall(); Editor editor = getEditor.apply(lastWorldClass); editor.insertMethodCallInConstructor(lastWorldClass, methodCall, inserted -> { }); editor.insertAppendMethod(method, inserted -> { if (inserted) { editor.setEditorVisible(true, false); } }); clearCode(false); return true; }
| Mark the recording as not valid (because Act/Run has been used) | public void invalidateRecording() { validToSave = false; }
| Mark the recording as valid, because the user has manually created a World, | or triggered a reset (including by recompiling or focusing main window). | public void recordingValid() { validToSave = true; } }
top, use, map, class GreenfootRecorder

.   GreenfootRecorder
.   createActor
.   nameActor
.   nameActors
.   withCommas
.   addActorToWorld
.   callActorOrWorldMethod
.   callStaticMethod
.   clearCode
.   setWorld
.   moveActor
.   removeActor
.   getCode
.   getPrepareMethod
.   getPrepareMethodCall
.   callElement
.   writeCode
.   invalidateRecording
.   recordingValid




350 neLoCode + 46 LoComm