setup
This commit is contained in:
parent
f695c49a61
commit
1bee6bad4d
23
linda/AsynchronousCallback.java
Normal file
23
linda/AsynchronousCallback.java
Normal 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
14
linda/Callback.java
Normal 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
64
linda/Linda.java
Normal 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
197
linda/Tuple.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
10
linda/TupleFormatException.java
Normal file
10
linda/TupleFormatException.java
Normal file
|
@ -0,0 +1,10 @@
|
|||
package linda;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class TupleFormatException extends IllegalArgumentException {
|
||||
|
||||
public TupleFormatException(String s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
}
|
7
linda/search/basic/Code.java
Normal file
7
linda/search/basic/Code.java
Normal 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
|
||||
}
|
18
linda/search/basic/Main.java
Normal file
18
linda/search/basic/Main.java
Normal 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();
|
||||
}
|
||||
}
|
67
linda/search/basic/Manager.java
Normal file
67
linda/search/basic/Manager.java
Normal 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();
|
||||
}
|
||||
}
|
1
linda/search/basic/README
Normal file
1
linda/search/basic/README
Normal file
|
@ -0,0 +1 @@
|
|||
java -cp bin linda.search.basic.Main 'agneau' /usr/share/dict/french
|
66
linda/search/basic/Searcher.java
Normal file
66
linda/search/basic/Searcher.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
21
linda/server/LindaClient.java
Normal file
21
linda/server/LindaClient.java
Normal 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
|
||||
|
||||
}
|
15
linda/shm/CentralizedLinda.java
Normal file
15
linda/shm/CentralizedLinda.java
Normal 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
|
||||
|
||||
}
|
56
linda/test/BasicTest1.java
Normal file
56
linda/test/BasicTest1.java
Normal 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();
|
||||
|
||||
}
|
||||
}
|
56
linda/test/BasicTest2.java
Normal file
56
linda/test/BasicTest2.java
Normal 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();
|
||||
|
||||
}
|
||||
}
|
44
linda/test/BasicTestAsyncCallback.java
Normal file
44
linda/test/BasicTestAsyncCallback.java
Normal 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)");
|
||||
|
||||
}
|
||||
|
||||
}
|
49
linda/test/BasicTestCallback.java
Normal file
49
linda/test/BasicTestCallback.java
Normal 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
11
linda/whiteboard.README
Normal 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"
|
20
linda/whiteboard/ColoredShape.java
Normal file
20
linda/whiteboard/ColoredShape.java
Normal 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;
|
||||
}
|
||||
}
|
46
linda/whiteboard/Command.java
Normal file
46
linda/whiteboard/Command.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
27
linda/whiteboard/Whiteboard.java
Normal file
27
linda/whiteboard/Whiteboard.java
Normal 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]));
|
||||
}
|
||||
}
|
||||
|
163
linda/whiteboard/WhiteboardControls.java
Normal file
163
linda/whiteboard/WhiteboardControls.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
174
linda/whiteboard/WhiteboardModel.java
Normal file
174
linda/whiteboard/WhiteboardModel.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
224
linda/whiteboard/WhiteboardView.java
Normal file
224
linda/whiteboard/WhiteboardView.java
Normal 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) {
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in a new issue