This commit is contained in:
gdamms 2021-11-27 17:50:33 +01:00
parent f695c49a61
commit 1bee6bad4d
23 changed files with 1373 additions and 0 deletions

View file

@ -0,0 +1,23 @@
package linda;
/** The class helps to transform a callback to behave asynchronously.
* The callback fires exactly once.
* The callback fires asynchronously with other threads and may do whatever it wants (it may block).
* @author philippe.queinnec@enseeiht.fr
*/
public class AsynchronousCallback implements Callback {
private Callback cb;
public AsynchronousCallback (Callback cb) { this.cb = cb; }
/** Asynchronous call: the associated callback is concurrently run and this one immediately returns.
* */
public void call(final Tuple t) {
new Thread() {
public void run() {
cb.call(t);
}
}.start();
}
}

14
linda/Callback.java Normal file
View file

@ -0,0 +1,14 @@
package linda;
/** Callback when a tuple appears.
* @author philippe.queinnec@enseeiht.fr
*/
public interface Callback {
/** Callback when a tuple appears.
* See Linda.eventRegister for details.
*
* @param t the new tuple
*/
void call(Tuple t);
}

64
linda/Linda.java Normal file
View file

@ -0,0 +1,64 @@
package linda;
import java.util.Collection;
/** Public interface to a Linda implementation.
* @author philippe.queinnec@enseeiht.fr
*/
public interface Linda {
/** Adds a tuple t to the tuplespace. */
public void write(Tuple t);
/** Returns a tuple matching the template and removes it from the tuplespace.
* Blocks if no corresponding tuple is found. */
public Tuple take(Tuple template);
/** Returns a tuple matching the template and leaves it in the tuplespace.
* Blocks if no corresponding tuple is found. */
public Tuple read(Tuple template);
/** Returns a tuple matching the template and removes it from the tuplespace.
* Returns null if none found. */
public Tuple tryTake(Tuple template);
/** Returns a tuple matching the template and leaves it in the tuplespace.
* Returns null if none found. */
public Tuple tryRead(Tuple template);
/** Returns all the tuples matching the template and removes them from the tuplespace.
* Returns an empty collection if none found (never blocks).
* Note: there is no atomicity or consistency constraints between takeAll and other methods;
* for instance two concurrent takeAll with similar templates may split the tuples between the two results.
*/
public Collection<Tuple> takeAll(Tuple template);
/** Returns all the tuples matching the template and leaves them in the tuplespace.
* Returns an empty collection if none found (never blocks).
* Note: there is no atomicity or consistency constraints between readAll and other methods;
* for instance (write([1]);write([2])) || readAll([?Integer]) may return only [2].
*/
public Collection<Tuple> readAll(Tuple template);
public enum eventMode { READ, TAKE };
public enum eventTiming { IMMEDIATE, FUTURE };
/** Registers a callback which will be called when a tuple matching the template appears.
* If the mode is Take, the found tuple is removed from the tuplespace.
* The callback is fired once. It may re-register itself if necessary.
* If timing is immediate, the callback may immediately fire if a matching tuple is already present; if timing is future, current tuples are ignored.
* Beware: a callback should never block as the calling context may be the one of the writer (see also {@link AsynchronousCallback} class).
* Callbacks are not ordered: if more than one may be fired, the chosen one is arbitrary.
* Beware of loop with a READ/IMMEDIATE re-registering callback !
*
* @param mode read or take mode.
* @param timing (potentially) immediate or only future firing.
* @param template the filtering template.
* @param callback the callback to call if a matching tuple appears.
*/
public void eventRegister(eventMode mode, eventTiming timing, Tuple template, Callback callback);
/** To debug, prints any information it wants (e.g. the tuples in tuplespace or the registered callbacks), prefixed by <code>prefix</code. */
public void debug(String prefix);
}

197
linda/Tuple.java Normal file
View file

@ -0,0 +1,197 @@
package linda;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.StringTokenizer;
/** Representation of a tuple.
* @author philippe.queinnec@enseeiht.fr
*/
public class Tuple extends LinkedList<Serializable> implements Serializable {
private static final long serialVersionUID = 1L;
/** Creates a new tuple.
* Example :
* new Tuple(4, 5, "foo", true) -> [ 4 5 "foo" true ]
* new Tuple(4, Integer.class, "foo".getclass(), Boolean.class) -> [ 4 ?Integer ?String ?Boolean ]
*/
public Tuple(Serializable... components) {
for (Serializable c : components) {
this.add(c);
}
}
private static boolean matches(Serializable thiscomponent, Serializable templatecomponent) {
if (templatecomponent instanceof Tuple) {
if (! (thiscomponent instanceof Tuple))
return false;
else
return ((Tuple)thiscomponent).matches((Tuple)templatecomponent);
} else if (templatecomponent instanceof Class) {
if (thiscomponent instanceof Class)
return ((Class<?>)templatecomponent).isAssignableFrom((Class<?>)thiscomponent);
else
return ((Class<?>) templatecomponent).isInstance(thiscomponent);
} else {
return thiscomponent.equals(templatecomponent);
}
}
/** Returns true if this tuple matches the given template.
* Matching rules : a tuple matches a template if all their components match two by two.
* Two components match :
* - if they are both values and are equals;
* - if the template component is a class/interface, and the tuple component is an instance/implementation of this class/interface (Class.isInstance);
* - if the template component is a class/interface, and the tuple component is a subclass/subinterface of this class/interface (Class.isAsssignableFrom);
* - recursively if both are tuples.
*
* Examples:
* [ 3 5 "foo" ] matches [ 3 5 "foo" ], [ ?Integer 5 "foo" ], [ ?Integer ?Integer ?String ]
* [ 3 ?Integer [ 6 7 ] [ 7 8 ] ] matches [ ?Integer ?Integer [ ?Integer 7 ] ?Tuple ], [3 ?Integer ?Tuple ?Tuple ]
*
* @param template the template which this tuple is compared to.
*/
public boolean matches(Tuple template) {
if (this.size() != template.size())
return false;
Iterator<Serializable> itthis = this.iterator();
Iterator<Serializable> itmotif = template.iterator();
while (itthis.hasNext()) {
Serializable othis = itthis.next();
Serializable omotif = itmotif.next();
if (! matches(othis, omotif))
return false;
}
return true;
}
/** Returns true if this tuple (seen as a template) contains <code>t</code>.
* This is the reverse of {@link #matches(Tuple)}. */
public boolean contains(Tuple t) {
return t.matches(this);
}
/**
* Returns a deep copy of the tuple.
* @return a deep copy of this object
*/
/* Les éléments types sont représentés par des instances de Class, qui n'est pas cloneable.
* Le plus simple de passer par une sérialisation/desérialisation ce qui marche pour toutes les classes qui implantent serializable.
*/
public Tuple deepclone() {
Tuple copy = null;
try {
ByteArrayOutputStream buf = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream (buf);
out.writeObject (this);
ObjectInputStream in = new ObjectInputStream (new ByteArrayInputStream (buf.toByteArray()));
copy = (Tuple) in.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return copy;
}
/** Returns a string representation of this tuple.
* @return a string representation of this tuple.
*/
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[");
for (Object o : this) {
if (o instanceof Class) {
sb.append(" ?" + ((Class<?>)o).getName());
} else if (o instanceof String) {
sb.append(" \"" + o + "\"");
} else if (o instanceof Character) {
sb.append(" '" + o + "'");
} else {
sb.append(" " + o.toString());
}
}
sb.append(" ]");
return sb.toString();
}
/** Parses a sequence of words to form a tuple.
* The initial [ must be missing.
* @param stk
* @return
* @throws TupleFormatException
*/
private static Tuple valueOf(StringTokenizer stk) throws TupleFormatException {
Tuple res = new Tuple();
while (stk.hasMoreTokens()) {
String token = stk.nextToken();
if (token.equals("]"))
return res; // BEWARE
if (token.startsWith("\"") && token.endsWith("\"")) {
String val = token.substring(1, token.length()-1);
res.add(val);
} else if (token.startsWith("'") && token.endsWith("'") && (token.length() > 2)) {
res.add(Character.valueOf(token.charAt(1)));
} else if (token.startsWith("?")) {
String classname = token.substring(1);
Class<?> c = null;
final String[] prefixes = { "", "java.lang.", "linda." };
for (String prefix : prefixes) {
try {
c = Class.forName(prefix + classname);
break; // oh !
} catch (ClassNotFoundException e) {
// ignore and try next prefix
}
}
if (c != null)
res.add(c);
else
throw new TupleFormatException("Unknown class ?"+classname);
} else if ("-0123456789".indexOf(token.charAt(0)) != -1) {
int val;
try {
val = Integer.valueOf(token);
} catch (NumberFormatException e) {
throw new TupleFormatException("NumberFormatException on '"+token+"'");
}
res.add(val);
} else if (token.equals("true")) {
res.add(true);
} else if (token.equals("false")) {
res.add(false);
} else if (token.equals("[")) {
Tuple val = valueOf(stk); // yeepi!
res.add(val);
} else {
throw new TupleFormatException("Unhandled chars: '"+token+"'");
}
}
throw new TupleFormatException("Missing closing ']'");
}
/** Returns a Tuple with a value represented by the specified String.
* Known values: integer (45, -67), boolean (true, false), string ("toto"), classname (?Integer, ?java.awt.Rectangle), recursive tuple.
* Examples: [ 3 4 ], [ ?Integer "toto" true 78 ?Boolean ], [ ?Integer ?Tuple ], [ [ true 78 ] [ 3 4 [ 5 6 ] 7 ] ]
* For these components, the parsable strings are identical to the printed strings.
* Note: do not expect the parser to be resilient to arbitrary strings.
*
* @param s the string to be parsed.
* @return a Tuple object holding the value represented by the string argument.
* @throws TupleFormatException
*/
public static Tuple valueOf(String s) throws TupleFormatException {
StringTokenizer stk = new StringTokenizer(s);
if (! stk.hasMoreTokens() || !stk.nextToken().equals("["))
throw new TupleFormatException("Missing initial '['");
Tuple res = valueOf(stk);
if (stk.hasMoreTokens())
throw new TupleFormatException("Trailing chars after ']'");
return res;
}
}

View file

@ -0,0 +1,10 @@
package linda;
@SuppressWarnings("serial")
public class TupleFormatException extends IllegalArgumentException {
public TupleFormatException(String s) {
super(s);
}
}

View file

@ -0,0 +1,7 @@
package linda.search.basic;
public enum Code {
Request, // Request, UUID, String
Value, // Value, String
Result, // Result, UUID, String, Int
Searcher, // Result, "done", UUID
}

View file

@ -0,0 +1,18 @@
package linda.search.basic;
import linda.*;
public class Main {
public static void main(String args[]) {
if (args.length != 2) {
System.err.println("linda.search.basic.Main search file.");
return;
}
Linda linda = new linda.shm.CentralizedLinda();
Manager manager = new Manager(linda, args[1], args[0]);
Searcher searcher = new Searcher(linda);
(new Thread(manager)).start();
(new Thread(searcher)).start();
}
}

View file

@ -0,0 +1,67 @@
package linda.search.basic;
import java.util.UUID;
import java.util.Timer;
import java.util.TimerTask;
import java.util.stream.Stream;
import java.nio.file.Files;
import java.nio.file.Paths;
import linda.*;
public class Manager implements Runnable {
private Linda linda;
private UUID reqUUID;
private String pathname;
private String search;
private int bestvalue = Integer.MAX_VALUE; // lower is better
private String bestresult;
public Manager(Linda linda, String pathname, String search) {
this.linda = linda;
this.pathname = pathname;
this.search = search;
}
private void addSearch(String search) {
this.search = search;
this.reqUUID = UUID.randomUUID();
System.out.println("Search " + this.reqUUID + " for " + this.search);
linda.eventRegister(Linda.eventMode.TAKE, Linda.eventTiming.IMMEDIATE, new Tuple(Code.Result, this.reqUUID, String.class, Integer.class), new CbGetResult());
linda.write(new Tuple(Code.Request, this.reqUUID, this.search));
}
private void loadData(String pathname) {
try (Stream<String> stream = Files.lines(Paths.get(pathname))) {
stream.limit(10000).forEach(s -> linda.write(new Tuple(Code.Value, s.trim())));
} catch (java.io.IOException e) {
e.printStackTrace();
}
}
private void waitForEndSearch() {
linda.take(new Tuple(Code.Searcher, "done", this.reqUUID));
linda.take(new Tuple(Code.Request, this.reqUUID, String.class)); // remove query
System.out.println("query done");
}
private class CbGetResult implements linda.Callback {
public void call(Tuple t) { // [ Result, ?UUID, ?String, ?Integer ]
String s = (String) t.get(2);
Integer v = (Integer) t.get(3);
if (v < bestvalue) {
bestvalue = v;
bestresult = s;
System.out.println("New best (" + bestvalue + "): \"" + bestresult + "\"");
}
linda.eventRegister(Linda.eventMode.TAKE, Linda.eventTiming.IMMEDIATE, new Tuple(Code.Result, reqUUID, String.class, Integer.class), this);
}
}
public void run() {
this.loadData(pathname);
this.addSearch(search);
this.waitForEndSearch();
}
}

View file

@ -0,0 +1 @@
java -cp bin linda.search.basic.Main 'agneau' /usr/share/dict/french

View file

@ -0,0 +1,66 @@
package linda.search.basic;
import linda.*;
import java.util.Arrays;
import java.util.UUID;
public class Searcher implements Runnable {
private Linda linda;
public Searcher(Linda linda) {
this.linda = linda;
}
public void run() {
System.out.println("Ready to do a search");
Tuple treq = linda.read(new Tuple(Code.Request, UUID.class, String.class));
UUID reqUUID = (UUID)treq.get(1);
String req = (String) treq.get(2);
Tuple tv;
System.out.println("Looking for: " + req);
while ((tv = linda.tryTake(new Tuple(Code.Value, String.class))) != null) {
String val = (String) tv.get(1);
int dist = getLevenshteinDistance(req, val);
if (dist < 10) { // arbitrary
linda.write(new Tuple(Code.Result, reqUUID, val, dist));
}
}
linda.write(new Tuple(Code.Searcher, "done", reqUUID));
}
/*****************************************************************/
/* Levenshtein distance is rather slow */
/* Copied from https://www.baeldung.com/java-levenshtein-distance */
static int getLevenshteinDistance(String x, String y) {
int[][] dp = new int[x.length() + 1][y.length() + 1];
for (int i = 0; i <= x.length(); i++) {
for (int j = 0; j <= y.length(); j++) {
if (i == 0) {
dp[i][j] = j;
}
else if (j == 0) {
dp[i][j] = i;
}
else {
dp[i][j] = min(dp[i - 1][j - 1]
+ costOfSubstitution(x.charAt(i - 1), y.charAt(j - 1)),
dp[i - 1][j] + 1,
dp[i][j - 1] + 1);
}
}
}
return dp[x.length()][y.length()];
}
private static int costOfSubstitution(char a, char b) {
return a == b ? 0 : 1;
}
private static int min(int... numbers) {
return Arrays.stream(numbers).min().orElse(Integer.MAX_VALUE);
}
}

View file

@ -0,0 +1,21 @@
package linda.server;
import linda.Callback;
import linda.Linda;
import linda.Tuple;
/** Client part of a client/server implementation of Linda.
* It implements the Linda interface and propagates everything to the server it is connected to.
* */
public class LindaClient implements Linda {
/** Initializes the Linda implementation.
* @param serverURI the URI of the server, e.g. "rmi://localhost:4000/LindaServer" or "//localhost:4000/LindaServer".
*/
public LindaClient(String serverURI) {
// TO BE COMPLETED
}
// TO BE COMPLETED
}

View file

@ -0,0 +1,15 @@
package linda.shm;
import linda.Callback;
import linda.Linda;
import linda.Tuple;
/** Shared memory implementation of Linda. */
public class CentralizedLinda implements Linda {
public CentralizedLinda() {
}
// TO BE COMPLETED
}

View file

@ -0,0 +1,56 @@
package linda.test;
import linda.*;
public class BasicTest1 {
public static void main(String[] a) {
final Linda linda = new linda.shm.CentralizedLinda();
// final Linda linda = new linda.server.LindaClient("//localhost:4000/aaa");
new Thread() {
public void run() {
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
Tuple motif = new Tuple(Integer.class, String.class);
Tuple res = linda.take(motif);
System.out.println("(1) Resultat:" + res);
linda.debug("(1)");
}
}.start();
new Thread() {
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Tuple t1 = new Tuple(4, 5);
System.out.println("(2) write: " + t1);
linda.write(t1);
Tuple t11 = new Tuple(4, 5);
System.out.println("(2) write: " + t11);
linda.write(t11);
Tuple t2 = new Tuple("hello", 15);
System.out.println("(2) write: " + t2);
linda.write(t2);
Tuple t3 = new Tuple(4, "foo");
System.out.println("(2) write: " + t3);
linda.write(t3);
linda.debug("(2)");
}
}.start();
}
}

View file

@ -0,0 +1,56 @@
package linda.test;
import linda.*;
public class BasicTest2 {
public static void main(String[] a) {
final Linda linda = new linda.shm.CentralizedLinda();
// final Linda linda = new linda.server.LindaClient("//localhost:4000/MonServeur");
for (int i = 1; i <= 3; i++) {
final int j = i;
new Thread() {
public void run() {
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
Tuple motif = new Tuple(Integer.class, String.class);
Tuple res = linda.read(motif);
System.out.println("("+j+") Resultat:" + res);
linda.debug("("+j+")");
}
}.start();
}
new Thread() {
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Tuple t1 = new Tuple(4, 5);
System.out.println("(0) write: " + t1);
linda.write(t1);
Tuple t2 = new Tuple("hello", 15);
System.out.println("(0) write: " + t2);
linda.write(t2);
linda.debug("(0)");
Tuple t3 = new Tuple(4, "foo");
System.out.println("(0) write: " + t3);
linda.write(t3);
linda.debug("(0)");
}
}.start();
}
}

View file

@ -0,0 +1,44 @@
package linda.test;
import linda.*;
import linda.Linda.eventMode;
import linda.Linda.eventTiming;
public class BasicTestAsyncCallback {
private static class MyCallback implements Callback {
public void call(Tuple t) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println("Got "+t);
}
}
public static void main(String[] a) {
Linda linda = new linda.shm.CentralizedLinda();
// Linda linda = new linda.server.LindaClient("//localhost:4000/MonServeur");
Tuple motif = new Tuple(Integer.class, String.class);
linda.eventRegister(eventMode.TAKE, eventTiming.IMMEDIATE, motif, new AsynchronousCallback(new MyCallback()));
Tuple t1 = new Tuple(4, 5);
System.out.println("(2) write: " + t1);
linda.write(t1);
Tuple t2 = new Tuple("hello", 15);
System.out.println("(2) write: " + t2);
linda.write(t2);
linda.debug("(2)");
Tuple t3 = new Tuple(4, "foo");
System.out.println("(2) write: " + t3);
linda.write(t3);
linda.debug("(2)");
}
}

View file

@ -0,0 +1,49 @@
package linda.test;
import linda.*;
import linda.Linda.eventMode;
import linda.Linda.eventTiming;
public class BasicTestCallback {
private static Linda linda;
private static Tuple cbmotif;
private static class MyCallback implements Callback {
public void call(Tuple t) {
System.out.println("CB got "+t);
linda.eventRegister(eventMode.TAKE, eventTiming.IMMEDIATE, cbmotif, this);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println("CB done with "+t);
}
}
public static void main(String[] a) {
linda = new linda.shm.CentralizedLinda();
// linda = new linda.server.LindaClient("//localhost:4000/MonServeur");
cbmotif = new Tuple(Integer.class, String.class);
linda.eventRegister(eventMode.TAKE, eventTiming.IMMEDIATE, cbmotif, new MyCallback());
Tuple t1 = new Tuple(4, 5);
System.out.println("(2) write: " + t1);
linda.write(t1);
Tuple t2 = new Tuple("hello", 15);
System.out.println("(2) write: " + t2);
linda.write(t2);
linda.debug("(2)");
Tuple t3 = new Tuple(4, "foo");
System.out.println("(2) write: " + t3);
linda.write(t3);
linda.debug("(2)");
}
}

11
linda/whiteboard.README Normal file
View file

@ -0,0 +1,11 @@
Pour exécuter le whiteboard avec plusieurs machines :
(https://docs.oracle.com/javase/7/docs/technotes/guides/rmi/faq.html#domain)
java -Djava.rmi.server.hostname=vrainomdelamachine ...
(il s'agit de la machine où ce code s'exécute, pas du serveur rmi)
Par exemple, serveur sur gorgone et clients sur gobelin et turing :
queinnec@gorgone$ java -Djava.rmi.server.hostname=gorgone linda.server.CreateServer toto
queinnec@gobelin$ java -Djava.rmi.server.hostname=gobelin linda.whiteboard.Whiteboard "//gorgone:1099/toto"
queinnec@turing$ java -Djava.rmi.server.hostname=turing linda.whiteboard.Whiteboard "//gorgone:1099/toto"

View file

@ -0,0 +1,20 @@
package linda.whiteboard;
import java.awt.Shape;
import java.awt.geom.Line2D;
import java.awt.Color;
public class ColoredShape implements java.io.Serializable {
public Shape shape;
public Color color;
public ColoredShape(Shape s, Color c) {
shape = s;
color = c;
}
public ColoredShape(int x1, int y1, int x2, int y2, Color c) {
shape = new Line2D.Double(x1, y1, x2, y2);
color = c;
}
}

View file

@ -0,0 +1,46 @@
package linda.whiteboard;
import java.util.*;
public class Command implements java.io.Serializable {
enum Action { DRAW, ERASEALL, ROTATE, LOCK, UNLOCK };
public Action action;
public ColoredShape shape; // only if action = DRAW
public int angle; // in degrees; only if action = ROTATE
private Command() {}
public static Command newCommandLock() {
Command c = new Command();
c.action = Action.LOCK;
return c;
}
public static Command newCommandUnlock() {
Command c = new Command();
c.action = Action.UNLOCK;
return c;
}
public static Command newCommandEraseAll() {
Command c = new Command();
c.action = Action.ERASEALL;
return c;
}
public static Command newCommandDraw(ColoredShape rc) {
Command c = new Command();
c.action = Action.DRAW;
c.shape = rc;
return c;
}
public static Command newCommandRotateAll(int angle) {
Command c = new Command();
c.action = Action.ROTATE;
c.angle = angle;
return c;
}
}

View file

@ -0,0 +1,27 @@
/*
** @author philippe.queinnec@enseeiht.fr
** Inspired by IBM TSpaces exemples.
**
**/
package linda.whiteboard;
public class Whiteboard {
/*** main **
** Run the whiteboard as an application.
**
** @param args - command line arguments
*/
public static void main(String args[]) {
if (args.length != 1) {
System.err.println("Whiteboard serverURI.");
return;
}
WhiteboardModel model = new WhiteboardModel();
WhiteboardView view = new WhiteboardView(model);
model.setView(view);
model.start(new linda.server.LindaClient(args[0]));
}
}

View file

@ -0,0 +1,163 @@
/*
** @author philippe.queinnec@enseeiht.fr
** Based on IBM TSpaces exemples.
**
**/
package linda.whiteboard;
import java.awt.event.*;
import java.awt.*;
/**
** The controls for the whiteboard.
**
*/
public class WhiteboardControls extends Panel implements ItemListener {
private WhiteboardView view;
private WhiteboardModel model;
private static String ERASEALL_LABEL = "Erase All";
private static String EXIT_LABEL = "Exit";
private static String ROTATE_LABEL = "Rotate";
private static String EXCLUSIVE_LABEL = "Exclusive Access";
private static String PENDING_LABEL = "Pending...";
private static String RELEASE_LABEL = "Release Exclusive";
private Button eraseAllButton;
private Button rotateButton;
private Button exitButton;
private Button exclusiveButton;
private ExclusiveButtonAction exclusiveButtonAction;
/**
** The constructor.
**
** @param target - the whiteboard panel
*/
public WhiteboardControls(WhiteboardView view, WhiteboardModel model) {
this.view = view;
this.model = model;
Panel pLine1 = new Panel();
Panel pLine2 = new Panel();
setLayout(new GridLayout(2,1));
add(pLine1);
add(pLine2);
pLine1.setLayout(new FlowLayout());
eraseAllButton = new Button();
eraseAllButton.setLabel(ERASEALL_LABEL);
eraseAllButton.addActionListener((e) -> { model.eraseAll(); });
pLine1.add(eraseAllButton);
rotateButton = new Button();
rotateButton.setLabel(ROTATE_LABEL);
rotateButton.addActionListener((e) -> { model.rotateAll(90); });
pLine1.add(rotateButton);
exclusiveButton = new Button();
exclusiveButton.setLabel(EXCLUSIVE_LABEL);
exclusiveButtonAction = new ExclusiveButtonAction(exclusiveButton, model);
exclusiveButton.addActionListener(exclusiveButtonAction);
pLine1.add(exclusiveButton);
exitButton = new Button();
exitButton.setLabel(EXIT_LABEL);
exitButton.addActionListener((e) -> { model.terminate(); });
pLine1.add(exitButton);
pLine1.setBackground(Color.lightGray);
pLine2.setBackground(Color.lightGray);
view.drawing.setForeground(Color.red);
CheckboxGroup group = new CheckboxGroup();
Checkbox b;
pLine2.add(b = new Checkbox(null, group, false));
b.addItemListener(this);
b.setBackground(Color.red);
pLine2.add(b = new Checkbox(null, group, false));
b.addItemListener(this);
b.setBackground(Color.green);
pLine2.add(b = new Checkbox(null, group, false));
b.addItemListener(this);
b.setBackground(Color.blue);
pLine2.add(b = new Checkbox(null, group, false));
b.addItemListener(this);
b.setBackground(Color.pink);
pLine2.add(b = new Checkbox(null, group, false));
b.addItemListener(this);
b.setBackground(Color.orange);
pLine2.add(b = new Checkbox(null, group, true));
b.addItemListener(this);
b.setBackground(Color.black);
view.drawing.setForeground(b.getForeground());
Choice shapes = new Choice();
shapes.addItemListener(this);
shapes.addItem("Lines");
shapes.addItem("Points");
shapes.setBackground(Color.lightGray);
pLine2.add(shapes);
} // WhiteboardControls
/**
** Paints me.
**
** @param g - a graphics context
*/
public void paint(Graphics g) {
Rectangle r = getBounds();
g.setColor(Color.lightGray);
g.draw3DRect(0, 0, r.width, r.height, false);
}
/**
** The state of the item was changed.
**
** @param e - the item event
*/
public void itemStateChanged(ItemEvent e) {
if (e.getSource() instanceof Checkbox) {
view.drawing.setForeground(((Component)e.getSource()).getBackground());
} else if (e.getSource() instanceof Choice) {
String choice = (String) e.getItem();
if (choice.equals("Lines")) {
view.setDrawMode(WhiteboardView.DrawMode.LINES);
} else if (choice.equals("Points")) {
view.setDrawMode(WhiteboardView.DrawMode.POINTS);
}
}
}
/**
** Handling of button AcquireExclusive/Pending/ReleaseExclusive.
*/
private class ExclusiveButtonAction implements ActionListener {
protected boolean hasExclusive = false;
private Button button;
private WhiteboardModel target;
public ExclusiveButtonAction(Button b, WhiteboardModel t) {
button = b;
target = t;
}
public void actionPerformed(ActionEvent e) {
if (! hasExclusive) {
target.acquireExclusiveAccess();
exclusiveButtonAction.hasExclusive = true;
exclusiveButton.setLabel(RELEASE_LABEL);
} else {
target.releaseExclusiveAccess();
exclusiveButton.setLabel(EXCLUSIVE_LABEL);
hasExclusive = false;
}
}
}
}

View file

@ -0,0 +1,174 @@
/*
** @author philippe.queinnec@enseeiht.fr
** Inspired by IBM TSpaces exemples.
**
**/
package linda.whiteboard;
import java.awt.event.*;
import java.awt.*;
import java.awt.geom.*;
import java.util.*;
import linda.*;
import linda.Linda.eventMode;
import linda.Linda.eventTiming;
/**
** The model of the whiteboard.
**
** All of the Linda handling is done inside this object.
**
** [ KEY_WHITEBOARD, Command.DRAW, ColorShaped ]
** [ KEY_WHITEBOARD, Command.ERASEALL ]
**
** (where KEY_WHITEBOARD="Whiteboard")
**
*/
public class WhiteboardModel {
/** This holds a reference to the current Linda. */
protected Linda linda = null;
/** The graphic part of the whiteboard. */
private WhiteboardView view;
private static final String KEY_WHITEBOARD = "Whiteboard";
/** The commands that can be sent on the tuple spaces. */
enum Command { DRAW, ERASEALL, ROTATE };
/** The lines and their respective colors that this client knows about. */
private Set<ColoredShape> lines = new HashSet<>();
private boolean eraseFlag = false; // set true when erase command received
private Tuple motifShape = new Tuple(KEY_WHITEBOARD, Command.DRAW, ColoredShape.class);
private Tuple motifErase = new Tuple(KEY_WHITEBOARD, Command.ERASEALL);
private Tuple motifRotate = new Tuple(KEY_WHITEBOARD, Command.ROTATE, Integer.class);
public WhiteboardModel() {
}
public void setView(WhiteboardView view) {
this.view = view;
}
public Set<ColoredShape> getLines() {
return lines;
}
public void start(Linda linda) {
this.linda = linda;
// Create a template to indicate what we are interested in.
linda.eventRegister(eventMode.READ, eventTiming.FUTURE, motifErase, new CallbackErase());
linda.eventRegister(eventMode.READ, eventTiming.FUTURE, motifShape, new CallbackShape());
linda.eventRegister(eventMode.READ, eventTiming.FUTURE, motifRotate, new CallbackRotate());
System.out.println("Scan for current status");
// During initialization, we need to read all the current lines
// stored at the lindaSpaces server.
Collection<Tuple> tupleSet = linda.readAll(motifShape);
for (Tuple t : tupleSet) {
System.out.println("Tuple " + t);
ColoredShape line = (ColoredShape)t.get(2);
lines.add(line);
}
// and redraw the view
view.redraw();
}
/**
** This is called when the windowClosing event arrives.
*/
public void terminate() {
System.exit(0);
}
/** Global Erase of the whiteboard.
** Since we will be informed of this in the callback,
** we will let the callback update the set of all lines.
*/
public void eraseAll() {
System.out.println("Erase all");
// Delete all rectangle tuples,
linda.takeAll(new Tuple(KEY_WHITEBOARD, Command.DRAW, ColoredShape.class));
// Tell all clients that we did an erase by writing an erase tuple,
linda.write(new Tuple(KEY_WHITEBOARD, Command.ERASEALL));
// and delete this tuple (potential synchronization problem !)
linda.takeAll(new Tuple(KEY_WHITEBOARD, Command.ERASEALL));
}
/** Rotate all the shapes. */
public void rotateAll(int degree) {
System.out.println("Rotate");
// Tell all clients to rotate by writing a rotate tuple,
Tuple action = new Tuple(KEY_WHITEBOARD, Command.ROTATE, degree);
linda.write(action);
// and delete this tuple (potential synchronization problem !)
linda.takeAll(action);
}
/** Request an exclusive access to the whiteboard.
* Block until it has succeeded.
*/
public void acquireExclusiveAccess() {
System.err.println("Exclusive access: not implemented");
}
/** Release the exclusive access. */
public void releaseExclusiveAccess() {
System.err.println("Exclusive access: not implemented");
}
/**
** Publish a new shape (line or point) to the tuple space
*/
public void addShape (ColoredShape shape)
{
// Build a new rectangle tuple and write it into the tuple space.
Tuple publish = new Tuple(KEY_WHITEBOARD, Command.DRAW, shape);
linda.write(publish);
}
/***************************************************************/
private class CallbackShape implements linda.Callback {
public void call(Tuple t) {
System.out.println("Draw Request received from server");
ColoredShape shape = (ColoredShape)(t.get(2));
lines.add(shape);
view.redraw();
linda.eventRegister(eventMode.READ, eventTiming.FUTURE, motifShape, this);
}
}
private class CallbackErase implements linda.Callback {
public void call(Tuple t) {
System.out.println("Erase Request received from server");
lines.clear();
view.setClear();
linda.eventRegister(eventMode.READ, eventTiming.FUTURE, motifErase, this);
}
}
private class CallbackRotate implements linda.Callback {
public void call(Tuple t) {
System.out.println("Rotate Request received from server");
Integer angle = (Integer)(t.get(2));
// Let's be careful: rotation with center in WIDTH/2, HEIGHT/2
AffineTransform at = new AffineTransform();
at.rotate(Math.toRadians(angle), view.drawing.getSize().width / 2.0, view.drawing.getSize().height / 2.0);
//at.quadrantRotate(1, view.drawing.getSize().width / 2.0, view.drawing.getSize().height / 2.0);
for (ColoredShape rc : lines) {
rc.shape = at.createTransformedShape(rc.shape);
}
view.setClear();
view.redraw();
linda.eventRegister(eventMode.READ, eventTiming.FUTURE, motifRotate, this);
}
}
}

View file

@ -0,0 +1,224 @@
/*
** @author philippe.queinnec@enseeiht.fr
** Inspired by IBM TSpaces exemples.
**
**/
package linda.whiteboard;
import java.awt.event.*;
import java.awt.*;
import java.awt.geom.*;
import java.util.*;
/**
** The graphic part of the whiteboard.
**
*/
public class WhiteboardView implements MouseListener, MouseMotionListener {
/** clear graphics before drawing lines (an erase command has been received) */
private boolean clearFlag = false;
public enum DrawMode { LINES, POINTS, OTHER };
private DrawMode mode = DrawMode.LINES;
private static Color BACKGROUND_COLOR = Color.white;
private int x1, y1;
private int x2, y2;
private int xl, yl;
private WhiteboardControls controls;
private WhiteboardModel model;
protected Panel drawing; // where lines are drawn
private static final int WIDTH = 350;
private static final int HEIGHT = 350;
public WhiteboardView(WhiteboardModel model) {
this.model = model;
Frame appFrame = new Frame("Whiteboard");
appFrame.setSize(WIDTH,HEIGHT);
appFrame.setVisible(true);
appFrame.setLayout(new BorderLayout());
drawing = new Panel();
appFrame.add("Center", drawing);
controls = new WhiteboardControls(this, this.model);
appFrame.add("North", controls);
drawing.setBackground(BACKGROUND_COLOR);
drawing.addMouseMotionListener(this);
drawing.addMouseListener(this);
// Handle the user exiting/killing the application
appFrame.addWindowListener(new WindowAdapter() {
public void windowClosing (WindowEvent e) {
model.terminate();
}
} );
Graphics g = drawing.getGraphics();
if (g != null)
this.paint(g);
setDrawMode(DrawMode.LINES);
}
/** Force the window to be cleared before drawing the lines. */
public void setClear() {
clearFlag = true;
}
/** Redraw all the lines. */
public void redraw() {
Graphics g = drawing.getGraphics();
synchronized (model) {
DrawMode savemode = mode;
mode = DrawMode.OTHER;
this.paint(g);
setDrawMode(savemode);
}
}
/**
** Paints the panel based on the set of shapes
** If an Erase request was received from TupleSpace then the
** clearFlag was set and we will erase the window before
** proceeding.
*/
private void paint(Graphics g) {
if (clearFlag) {
g.setColor(drawing.getBackground());
g.fillRect(0,0, drawing.getSize().width, drawing.getSize().height);
clearFlag = false;
}
g.setColor(drawing.getForeground());
g.setPaintMode();
for (ColoredShape rc : model.getLines()) {
g.setColor(rc.color);
((Graphics2D)g).draw(rc.shape);
}
if (mode == DrawMode.LINES) {
g.setXORMode(drawing.getBackground());
if (xl != -1) { // erase the last line.
g.drawLine(x1, y1, xl, yl);
}
g.setColor(drawing.getForeground());
g.setPaintMode();
if (x2 != -1) {
g.drawLine(x1, y1, x2, y2);
}
}
}
/**
** Set the draw mode.
** @param mode - the draw mode
*/
public void setDrawMode(DrawMode mode) {
switch (mode) {
case LINES:
case POINTS:
this.mode = mode;
break;
default:
throw new IllegalArgumentException();
}
}
/**
** The mouse was dragged.
** @param e - the mouse event
*/
public void mouseDragged(MouseEvent e) {
e.consume();
switch (mode) {
case LINES:
xl = x2;
yl = y2;
x2 = e.getX();
y2 = e.getY();
break;
case POINTS:
default:
model.addShape(new ColoredShape(x1, y1, e.getX(), e.getY(), drawing.getForeground()));
x1 = e.getX();
y1 = e.getY();
break;
}
redraw();
}
/**
** The mouse was moved.
** @param e - the mouse event
*/
public void mouseMoved(MouseEvent e) {
// not much to do here
}
/**
** The mouse button was pressed.
** @param e - the mouse event
*/
public void mousePressed(MouseEvent e) {
e.consume();
switch (mode) {
case LINES:
x1 = e.getX();
y1 = e.getY();
x2 = -1;
break;
case POINTS:
default:
//model.addShape(new ColoredShape(e.getX(), e.getY(), -1, -1, drawing.getForeground()));
model.addShape(new ColoredShape(e.getX(), e.getY(), e.getX(), e.getY(), drawing.getForeground()));
x1 = e.getX();
y1 = e.getY();
drawing.repaint();
break;
}
}
/**
** The mouse button was released.
** @param e - the mouse event
*/
public void mouseReleased(MouseEvent e) {
e.consume();
switch (mode) {
case LINES:
model.addShape(new ColoredShape(x1, y1, e.getX(), e.getY(), drawing.getForeground()));
x2 = xl = -1;
break;
case POINTS:
default:
break;
}
redraw();
}
/**
** The mouse entered this panel.
** @param e - the mouse event
*/
public void mouseEntered(MouseEvent e) {
}
/**
** The mouse left this panel.
** @param e - the mouse event
*/
public void mouseExited(MouseEvent e) {
}
/**
** The mouse button was clicked.
** @param e - the mouse event
*/
public void mouseClicked(MouseEvent e) {
}
}