Workspace Server
Example code on how to use the Workspace Server
The Workspace Server is a component in MoJ that can be reused to create your own Java programming
game. It provides the basic setup, load, save, compile, test, submit and shutdown functionality you
need to do a programming game. No GUI, No client, just the basic service.
Hidden in the nl.moj.workspace.server.WorkspaceServer class is a really cool and reusable server component. If you run the class it will open a socket on port 8081 and listen to it. You can connect to it by using the nl.moj.workspace.factory.RemoteWorkspaceFactory or if you intend to do loadbalancing the nl.moj.workspace.factory.MultiRemoteWorkspaceFactory which allows connections to multiple WorkspaceServers.
The following bit of code demonstrates the opening of a workspace, the loading of an assignment into it, extracting the important files and then saving,compiling,testing and sumbitting and finally closing the workspace.
Before running this example the Workspace Server must be up and running ofcouse. Just launch the nl.moj.workspace.server.WorkspaceServer class (default configuration will do nicely :-)
public static void main(String[] args) throws Throwable {
//
// Setup security manager and evil threadgroup.
// Should not be neccecary. However...
//
ThreadGroup evilThreadGroup=new ThreadGroup("evil");
//
System.setSecurityManager(
new SandboxSecurityManager(
evilThreadGroup
)
);
//
// Create the new RemoteWorkspaceFactory that connects
// to the Workspace server.
//
RemoteWorkspaceFactory factory=
new RemoteWorkspaceFactory("127.0.0.1",8081);
//
// Setup the workspace.
// - the name is the name of the workspace. Must be unique.
// Is used to create the workspace folder.
// - Target is a redirection of the std output and std error
// of the process. Anything that gets
// System.out.println'ed will appear here.
// - ProcessListener will notify you of the progress of any
// Operations that are being executed. There can be only one
// operation running at the same time (for obvious reasons)
//
Workspace ws=factory.createWorkspace("Demo",new Target() {
public void append(String context, String s) {
System.out.println(context+" : "+s);
}
},new ProcessListener() {
public void queued(Runnable r) {
System.out.println("Queued : "+r);
}
public void executing(Runnable r) {
System.out.println("Executing : "+r);
}
public void complete(Runnable r) {
System.out.println("Complete : "+r);
ready=true;
}
});
//
try {
//
// Loads the assignment.
//
ws.loadAssignment(
new JarFileAssignment(
new File("./data/cases/haikuCase.jar")
,null
,null
),false
);
//
// Get the filenames in the assignment.
//
String[] fileNames=ws.getEditorFiles();
String[] contents=new String[fileNames.length];
//
String fileNameToEdit=null;
String fileContentsToEdit=null;
//
// Display some useful information about the files.
//
for (int t=0;t<fileNames.length;t++) {
contents[t]=ws.getContents(fileNames[t]);
//
System.out.println(fileNames[t]);
//
// Find out which one can be edited by the user.
//
if ((!ws.isReadOnly(fileNames[t]))&&
(ws.isJava(fileNames[t]))) {
fileNameToEdit=fileNames[t];
fileContentsToEdit=contents[t];
}
}
//
if (fileNameToEdit==null) {
throw new NullPointerException("Missing editable file");
}
//
// Start the updater Thread that reads incoming messages
// from the server. Note that any blocking commands
// (such as getContents()) should not be executed while
// this Thread is running.
//
startUpdater(ws);
//
// Get the Operations that can be applied to the Workspace.
//
Operation save=ws.getOperationByName("Save");
Operation compile=ws.getOperationByName("Compile");
Operation test=ws.getOperationByName("Test");
Operation submit=ws.getOperationByName("Submit");
//
// Save the file using the Context (a container for files
// and other operation info)
//
ready=false;
ws.perform(
save,
new ContextImpl(
fileNameToEdit,
fileContentsToEdit,
Operation.IDX_EVERYTHING
)
);
//
// Wait until the operation has finished.
//
while (!ready) try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
// Ignore.
}
//
// Save and Compile the file
//
ready=false;
ws.perform(compile,new ContextImpl());
//
// Wait until the operation has finished.
//
while (!ready) try {
Thread.sleep(100);
} catch (InterruptedException ex) {
// Ignore.
}
//
// Save and Compile and Test the file
// In this case the Context says that it
// may only test the first test.
//
ready=false;
ws.perform(test,new ContextImpl(0));
//
// Wait until the operation has finished.
//
while (!ready) try {
Thread.sleep(100);
} catch (InterruptedException ex) {
// Ignore.
}
//
// Save and Compile and Test and Score the file
//
ready=false;
ws.perform(
submit,
new ContextImpl(
fileNameToEdit,
fileContentsToEdit,
Operation.IDX_EVERYTHING
)
);
//
while (!ready) try {
Thread.sleep(100);
} catch (InterruptedException ex) {
// Ignore.
}
//
} finally {
//
// Delete the workspace.
//
// Alternatively use ws.suspend() to suspend
// the workspace and continue later.
//
ws.dispose();
//
}
//
}
The UpdateThread is used to read incoming messages and signal the completion of a command such as 'Compile' or 'Test'. The reason for doing this asynchronously is that a command can take a while to complete.
// very stupid command synchronization flag.
// Just to show that it works.
public static boolean ready=true;
//
// starts the updated Thread. Is used to read
// incoming messages asynchronously.
//
private static void startUpdater(final Workspace ws) {
Thread updater=new Thread(new Runnable() {
public void run() {
while (true) {
try {
ws.update();
} catch (IOException ex) {
ex.printStackTrace();
}
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
// Ignore.
}
}
}
});
updater.setDaemon(true);
updater.start();
}

