//---------------------------------------------------------------------------
// Copyright 2006-2009
// Dan Roozemond, d.a.roozemond@tue.nl, (TU Eindhoven, Netherlands)
// Peter Horn, horn@math.uni-kassel.de (University Kassel, Germany)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//---------------------------------------------------------------------------
package org.symcomp.wupsi;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.symcomp.scscp.SCSCPClient;
import org.symcomp.scscp.ComputationState;
import org.symcomp.openmath.*;
import org.symcomp.wupsi.handlers.*;
import org.symcomp.notification.NotificationReceiver;
import org.symcomp.notification.Notification;
import org.symcomp.notification.NotificationCenter;
import java.util.*;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.io.*;
import jline.ConsoleReader;
import jline.History;
import jline.SimpleCompletor;
/**
* is main loop class. It first evaluates the options, then registeres the
* handlers for the commands. Most state infos of the WUPSI are stored here
* and all console user interaction takes place here.
*/
public class Wupsifer implements NotificationReceiver {
Map<String, SCSCPClient> clients;
String activeClient;
String prompt;
BufferedReader stdin;
PrintStream stdout;
String outputFormat;
Map<String, OpenMathBase> locals;
ConsoleReader consoleReader;
List<OpenMathBase> inputHistory;
List<OpenMathBase> outputHistory;
List<OpenMathBase> examples;
List<WupsiHandler> handlers;
WupsiOptions options;
private static Wupsifer instance;
private Wupsifer(WupsiOptions options) {
if (instance != null)
throw new RuntimeException("Only one Instance, please!");
this.options = options;
clients = new Hashtable<String, SCSCPClient>();
locals = new Hashtable<String, OpenMathBase>();
inputHistory = new LinkedList<OpenMathBase>();
outputHistory = new LinkedList<OpenMathBase>();
examples = new LinkedList<OpenMathBase>();
activeClient = null;
prompt = "WUPSI[$ACT]$N> ";
handlers = new LinkedList<WupsiHandler>();
handlers.add(new AliasHandler(this));
handlers.add(new ConnectHandler(this));
handlers.add(new DescribeHandler(this));
handlers.add(new DisconnectHandler(this));
handlers.add(new HelpHandler(this));
handlers.add(new LocalHandler(this));
handlers.add(new QuitHandler(this));
handlers.add(new SetHandler(this));
handlers.add(new ShowHandler(this));
handlers.add(new UseHandler(this));
handlers.add(new PrintHandler(this));
handlers.add(new ReadHandler(this));
handlers.add(new SPSDHandler(this));
WebHandler webHandler = new WebHandler(this);
handlers.add(new WebHandler(this));
handlers.add(new IrcHandler(this));
// now, do processing of command line parameters
// 1. find the 'in' channel
try {
consoleReader = new ConsoleReader();
History history = new History(new File(".wupsi_history"));
consoleReader.setHistory(history);
consoleReader.addCompletor(new SimpleCompletor(CdDescription.completions()));
} catch (IOException ignore) {
if (options.output == null)
info("# could not initialize JLine, falling back: "+ignore.getMessage());
consoleReader = null;
stdin = new BufferedReader(new InputStreamReader(System.in));
}
if (options.commDir != null) {
if (options.input != null || options.output != null) {
System.err.println("Error: If -c is specified, -i and -o are not reasonable.");
System.exit(1);
}
}
if (options.input != null) {
try {
stdin = new BufferedReader(new FileReader(options.input));
} catch (FileNotFoundException e) {
error("# ERROR: can't open input file '"+options.input+"'");
System.exit(2);
}
}
// 2. define the 'out' channel
if (options.output == null) {
stdout = System.out;
} else {
try {
stdout = new PrintStream(options.output);
} catch (FileNotFoundException e) {
error("# ERROR: can't open output file '"+options.output+"'");
System.exit(2);
}
}
// 3. define standard output format
if (options.outputFormat != null) {
String of = options.outputFormat.toUpperCase().trim();
if (of.equals("XML") || of.equals("POPCORN") || of.equals("LATEX") || of.equals("BINARY")) {
outputFormat = of;
} else {
warning("# WARNING: Illegal output format '"+of+"' given, ignoring.");
}
} else {
outputFormat = "POPCORN";
}
// 4. handle the a-priori-connects
if (options.connects!=null) {
for(String c : options.connects) {
Pattern p = Pattern.compile("(([0-9A-Za-z\\_\\-\\.]+)(\\:([0-9]+))?) ([A-Za-z][0-9A-Za-z\\_]*)");
Matcher m = p.matcher(c);
if (!m.matches()) {
error("# ERROR: illegal connect specification: '"+c+"'");
System.exit(3);
}
connect(m.group(1), m.group(5));
}
}
// 5. start webserver
if (options.webserver != null) {
webHandler.startWeb(options.webserver);
}
// 99. done
info("\nWUPSI 1.6-SNAPSHOT -- WUPSI Universal Popcorn SCSCP Interface");
if (Math.random()<0.5) info("(c) 2010 D. Roozemond & P. Horn"); else info("(c) 2010 P. Horn & D. Roozemond");
instance = this;
}
public void run() throws IOException {
while(true) {
boolean handeled = false;
String in = getInput();
if (in == null) in = "quit"; //handle ^D
if(in.length() == 0)
continue;
for (Iterator<WupsiHandler> it = handlers.iterator(); it.hasNext() && !handeled;) {
WupsiHandler wh = it.next();
if (in.startsWith(wh.command())) {
wh.handle(in);
handeled = true;
}
}
if (!handeled) {
if (activeClient != null) {
compute(clients.get(activeClient), in);
} else {
error("# ERROR: no system active.");
}
}
}
} // run
/**
* meta-handler for computing
* @param client the client on which to execute
* @param in the input string
*/
public void compute(SCSCPClient client, String in) {
String s, m;
OpenMathBase omb;
try {
m = "";
omb = OpenMathBase.parse(in);
} catch (Exception e) {
omb = null;
m = e.getMessage();
}
if (omb == null) {
error("# ERROR: Could not parse command: " + m);
return;
}
OpenMathBase res = compute(client, omb);
if (null == res) return;
println(res);
} // compute
/**
* does the actual computation
* @param client
* @param omb
* @return
*/
public OpenMathBase compute(SCSCPClient client, OpenMathBase omb) {
if (client==null) {
error("# ERROR: no system chosen so far.");
return null;
}
omb = replace(omb);
String token = client.compute(omb);
while (!client.resultAvailable(token)) { try { Thread.sleep(80); } catch(Exception e) {} }
Integer st = client.getComputationState(token);
if (st == ComputationState.READY) {
OpenMathBase res = client.getResult(token);
inputHistory.add(omb);
outputHistory.add(res);
return res;
} else if (st == ComputationState.ERRONEOUS) {
info("# ComputationState: ERRONEOUS");
OpenMathBase res = client.getResult(token);
inputHistory.add(omb);
outputHistory.add(res);
return res;
} else {
if (st == ComputationState.WAITING) info("# ComputationState: WAITING");
if (st == ComputationState.COMPUTING) info("# ComputationState: COMPUTING");
}
return null;
} // compute
/**
* replace all occurrencies of special variables in the OpenMath tree
* @param omb the OpenMath tree
* @return a replaced version
*/
public OpenMathBase replace(OpenMathBase omb) {
omb = omb.toOMObject();
omb = omb.traverse(new OpenMathVisitor() {
public OpenMathBase visitVariable(OMVariable om) {
String name = om.getName();
OpenMathBase r = locals.get(name);
if (null != r) return r;
if (name.matches("_in\\d+")) {
int n = Integer.parseInt(name.substring(3));
if (inputHistory.get(n) != null) return inputHistory.get(n);
}
if (name.matches("_out\\d+")) {
int n = Integer.parseInt(name.substring(4));
if (outputHistory.get(n) != null) return outputHistory.get(n);
}
if (name.matches("_example\\d+")) {
int n = Integer.parseInt(name.substring(8));
if (examples.get(n) != null) return examples.get(n);
}
return om;
}
});
return omb.deOMObject();
} // replace
/**
* produces a nice info string
* @param client the client for which to get the infos
* @return the description
*/
public String serviceInfo(SCSCPClient client) {
return "service Name '"+client.getServiceName()+"', service version '"+client.getServiceVersion()+"'";
} // serviceInfo
/**
* reads input -- either using JLine, or usind readLine() on a stream
* @return the input
* @throws IOException if s/th went wrong
*/
private String getInput() throws IOException {
if (options.commDir != null) {
return getInputDirectory();
}
String p = prompt;
if (null != activeClient) {
p = prompt.replaceAll("\\$ACT", activeClient);
} else {
p = prompt.replaceAll("\\$ACT", "n/a");
}
p = p.replaceAll("\\$N", ""+inputHistory.size());
String s = null;
if (null==consoleReader || options.input!=null) {
if (options.input == null) print("\n"+p);
if (options.input != null && options.atomic) {
s = stdin.readLine();
if (s==null)
System.exit(0);
String s2;
while(null!=(s2=stdin.readLine()))
s += s2;
} else {
s = stdin.readLine();
}
if (s==null && options.input!=null && (options.quit || options.atomic)) {
System.exit(0);
} else if (s==null && options.input!=null && !options.quit) {
options.input = null;
return getInput();
}
} else {
System.out.print("\n"+p);
consoleReader.setDefaultPrompt(p);
s = consoleReader.readLine();
}
if (s != null) s = s.trim();
return s;
} // getInput
private String getInputConsole() {
return "";
}
/**
* Gets the next input from the input directory
* @return the next input
* @throws java.io.IOException
*/
private String getInputDirectory() throws IOException {
assert (options.commDir != null);
int n = inputHistory.size();
if (n > 0) {
// clean up
stdout.flush();
stdout.close();
// move the tempfile to the final name
File oldfile=new File(options.commDir+"/tempout");
File newfile=new File(options.commDir+"/output"+n);
boolean ok = oldfile.renameTo(newfile);
assert ok;
// System.out.println("Things worked: " + ok);
}
// open new input file
File infile=new File(options.commDir+"/input"+(n+1));
try {
while (!infile.exists()) { Thread.sleep(10); }
} catch (InterruptedException ex) {
return "quit";
}
// If we came here, the file obviously exists.
stdin = new BufferedReader(new FileReader(infile));
String s2, s="";
while(null!=(s2=stdin.readLine()))
s += s2;
stdin.close();
File outfile=new File(options.commDir+"/tempout");
stdout = new PrintStream(outfile);
return s;
}
/**
* connect
* @param url
* @param name
* @return true of the connection could be established
*/
public boolean connect(String url, String name) {
if (clients.containsKey(name)) {
error("# ERROR: name '"+name+"' already used, try 'disconnect'.");
return false;
}
Pattern p = Pattern.compile("([0-9A-Za-z\\_\\-\\.]+)(\\:([0-9]+))?");
Matcher m = p.matcher(url);
if (!m.matches()) {
return false;
}
String host = m.group(1);
int port = 26133;
if (m.group(3) != null) {
port = Integer.parseInt(m.group(3));
}
// Make connection.
try {
// connect
SCSCPClient client = new SCSCPClient(host, port);
clients.put(name, client);
info("# connected to '"+host+"' on port '"+port+"' using symbolic name '"+name+"'");
info("# Service Info: "+serviceInfo(client));
activeClient = name;
NotificationCenter.getNC().register(this, "DEAD");
} catch (Exception e) {
error("# ERROR: could not connect to '" + host +"' on port '" + port + "'");
error("# ERROR: " + e.getMessage());
return false;
}
return true;
}
private void hexdump(char[] s) {
stdout.printf ("---- Total Length: %d ----", s.length);
for (int i = 0; i<s.length; i++) {
if (i%20 == 0)
stdout.println("");
else if (i%10 == 0)
stdout.print(" ");
else if (i%5 == 0)
stdout.print(" ");
stdout.printf("%02x ", (int) s[i]);
}
stdout.println("");
}
public void print(OpenMathBase om) {
if (outputFormat.toUpperCase().equals("POPCORN")) { print(om.toPopcorn()); }
else if (outputFormat.toUpperCase().equals("XML")) { print(om.toXml()); }
else if (outputFormat.toUpperCase().equals("LATEX")) { print(om.toLatex()); }
else if (outputFormat.toUpperCase().equals("BINARY")) { print(om.toOMObject().toBinary()); }
else if (outputFormat.toUpperCase().equals("HEXDUMP")) { hexdump(om.toOMObject().toBinary()); }
else { print("[can't resolve output format '"+outputFormat+"']");
}
}
public void println(OpenMathBase om) {
print(om);
println("");
}
public void println(String s) {
stdout.println(s);
}
public void print(String s) {
stdout.print(s);
}
public void print(char[] s) {
stdout.print(s);
}
public void info(String s) {
if (!options.quiet)
println(s);
}
public void warning(String s) {
if (!options.quiet)
println(s);
}
public void error(String s) {
System.out.println(s);
}
public SCSCPClient getClient(String name) {
return clients.get(name);
}
public Map<String, SCSCPClient> getClients() {
return clients;
}
public void setClients(Map<String, SCSCPClient> clients) {
this.clients = clients;
}
public String getActiveClient() {
return activeClient;
}
public void setActiveClient(String activeClient) {
this.activeClient = activeClient;
}
public String getOutputFormat() {
return outputFormat;
}
public void setOutputFormat(String outputFormat) {
this.outputFormat = outputFormat;
}
public Map<String, OpenMathBase> getLocals() {
return locals;
}
public void setLocals(Map<String, OpenMathBase> locals) {
this.locals = locals;
}
public List<OpenMathBase> getInputHistory() {
return inputHistory;
}
public void setInputHistory(List<OpenMathBase> inputHistory) {
this.inputHistory = inputHistory;
}
public List<OpenMathBase> getOutputHistory() {
return outputHistory;
}
public void setOutputHistory(List<OpenMathBase> outputHistory) {
this.outputHistory = outputHistory;
}
public List<OpenMathBase> getExamples() {
return examples;
}
public void setExamples(List<OpenMathBase> examples) {
this.examples = examples;
}
public void setPrompt(String prompt) {
this.prompt = prompt;
}
public static Wupsifer getInstance() {
return instance;
}
public static Wupsifer getInstance(WupsiOptions options) {
if (null==instance)
return new Wupsifer(options);
return instance;
}
public List<WupsiHandler> getHandlers() {
return handlers;
}
public void systemDied(SCSCPClient client) {
systemCleanUp(client, true);
}
public void systemDisconnect(SCSCPClient client) {
systemCleanUp(client, false);
}
public void systemCleanUp(SCSCPClient client, boolean died) {
//Traverse handlers to see if anything should happen
for (WupsiHandler wh : getHandlers()) {
wh.systemDiedHook(client);
}
//Quit the client
try {
client.quit();
} catch (Exception idc) {}
//Remove (awkwardly) from the clients hashmap,
//by first creating a temporary array...
List<String> tormv = new ArrayList<String>();
for (String key : clients.keySet()) {
SCSCPClient val = clients.get(key);
if (client == val) {
if (died) error("# The system '" + key + "' died.");
tormv.add(key);
}
}
//...and then actually removing them
for (String key : tormv) {
clients.remove(key);
//Set active client to null if needed
if (activeClient != null && activeClient.equals(key)) {
activeClient = null;
}
//Log
info("# The system '"+key+"' was removed from the list");
}
}
public void receiveNotification(Notification notification) {
assert notification.getMessage().equals("DEAD");
systemDied((SCSCPClient) notification.getSender());
}
}