/** * (c) 2011, Alejandro Serrano * Released under the terms of the EPL. */ package net.sf.eclipsefp.haskell.browser.client; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import java.util.zip.InflaterInputStream; import net.sf.eclipsefp.haskell.browser.BrowserEvent; import net.sf.eclipsefp.haskell.browser.BrowserPlugin; import net.sf.eclipsefp.haskell.browser.BrowserServer; import net.sf.eclipsefp.haskell.browser.Database; import net.sf.eclipsefp.haskell.browser.DatabaseLoadedEvent; import net.sf.eclipsefp.haskell.browser.DatabaseType; import net.sf.eclipsefp.haskell.browser.items.Declaration; import net.sf.eclipsefp.haskell.browser.items.DeclarationId; import net.sf.eclipsefp.haskell.browser.items.HaskellPackage; import net.sf.eclipsefp.haskell.browser.items.HoogleResult; import net.sf.eclipsefp.haskell.browser.items.HoogleStatus; import net.sf.eclipsefp.haskell.browser.items.Module; import net.sf.eclipsefp.haskell.browser.items.Packaged; import net.sf.eclipsefp.haskell.browser.util.BrowserText; import net.sf.eclipsefp.haskell.util.CappedStringWriter; import net.sf.eclipsefp.haskell.util.DispatchWriter; import net.sf.eclipsefp.haskell.util.FileUtil; import net.sf.eclipsefp.haskell.util.LangUtil; import net.sf.eclipsefp.haskell.util.StreamRedirect; import org.eclipse.core.runtime.IPath; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; /** * Class used for communicating with a Scion Browser instance. * * @author Alejandro Serrano * @author JP Moresmau */ public class StreamBrowserServer extends BrowserServer { private IPath serverExecutable; private Process process = null; private BufferedWriter in = null; private InputStream out = null; private boolean localDbLoaded = false; private String localDbPath = null; private boolean hackageDbLoaded = false; private String hackageDbPath = null; private boolean hoogleLoaded = false; private StreamRedirect errorRedirect; public LockObject lock= new LockObject(); /** * cache packages by database */ private Map<Database,HaskellPackage[]> packageCache=new HashMap<>(); //private DatabaseType currentDatabase; private HashMap<String, Packaged<Declaration>[]> declCache = new HashMap<>(); private boolean logError; private CappedStringWriter lastErrW=new CappedStringWriter(10000); private DispatchWriter allErrWs=new DispatchWriter(); public StreamBrowserServer(IPath serverExecutable,boolean logError) throws Exception { this.serverExecutable = serverExecutable; this.logError=logError; startServer(); } @Override public void setLogStream(Writer logStream) { super.setLogStream(logStream); if (logError){ allErrWs.getWriters().clear(); allErrWs.getWriters().add(lastErrW); if (logError && logStream!=null){ allErrWs.getWriters().add(logStream); } } } @Override public void setLogError(boolean logError) { if (logError!=this.logError && allErrWs!=null){ Writer logStream=getLogStream(); if (logStream!=null){ if (logError){ allErrWs.getWriters().add(logStream); } else { allErrWs.getWriters().remove(logStream); } } } this.logError = logError; } public void startServer() throws Exception { ProcessBuilder builder = new ProcessBuilder( serverExecutable.toOSString()); builder.redirectErrorStream(false); try { process = builder.start(); out = process.getInputStream(); allErrWs.getWriters().clear(); allErrWs.getWriters().add(lastErrW); if (logError && logStream!=null){ allErrWs.getWriters().add(logStream); } errorRedirect=new StreamRedirect(new InputStreamReader(process.getErrorStream(),FileUtil.UTF8), allErrWs); errorRedirect.start(); /*out = new BufferedReader(new InputStreamReader( process.getInputStream(), FileUtil.UTF8));*/ /*err = new BufferedReader(new InputStreamReader( process.getErrorStream(), FileUtil.UTF8)); */ in = new BufferedWriter(new OutputStreamWriter( process.getOutputStream(),FileUtil.UTF8)); } catch (Throwable ex) { throw new Exception("Could not load"); } } public String sendAndReceive(JSONObject input) throws IOException { synchronized(lock) { lock.setRunning(true); try { String jsonInput = input.toString(); sendCommand(jsonInput); String response=getALine(); // String response = out.readLine(); //log(response); if (response==null){ response=new JSONArray().toString(); } return response; } finally { lock.setRunning(false); } } } public boolean sendAndReceiveOk(JSONObject input) throws IOException { synchronized(lock) { lock.setRunning(true); try { String jsonInput = input.toString(); sendCommand(jsonInput); String response = null; do { response = getALine(); // out.readLine(); log(response); } while (response!=null && !response.equals("\"ok\"")); return "\"ok\"".equals(response); } finally { lock.setRunning(false); } } } public boolean sendAndReceiveBoolean(JSONObject input) throws IOException { synchronized(lock) { lock.setRunning(true); try { String jsonInput = input.toString(); sendCommand(jsonInput); String response = null; do { response = getALine(); // out.readLine(); log(response); } while (response!=null && !response.equals("true") && !response.equals("false")); return "true".equals(response); } finally { lock.setRunning(false); } } } private void sendCommand(String command) throws IOException{ log(">> " + command); lastErrW.clear(); in.write(command + "\n"); in.flush(); } public HoogleStatus sendAndReceiveStatus(JSONObject input) throws IOException { synchronized(lock) { lock.setRunning(true); try { String jsonInput = input.toString(); sendCommand(jsonInput); HoogleStatus st=null; String response = null; do { response = getALine(); // out.readLine(); log(response); try { st=HoogleStatus.valueOf(LangUtil.unquote(response).toUpperCase()); } catch (IllegalArgumentException iae){ // NOOP } } while (st==null); return st; } finally { lock.setRunning(false); } } } public String getALine() throws IOException{ try { InflaterInputStream gzip = new InflaterInputStream(out); BufferedReader reader = new BufferedReader( new InputStreamReader(gzip, FileUtil.UTF8)); return reader.readLine(); } catch (IOException e) { BrowserPlugin.logError(BrowserText.error_read, e); String lastErr=lastErrW.toString().trim(); try { process.exitValue(); startServer(); if (localDbLoaded && localDbPath!=null){ loadLocalDatabase(localDbPath, false); } if (hackageDbLoaded && hackageDbPath!=null){ loadHackageDatabase(hackageDbPath, false); } } catch (Exception ignore){ // noop } if (lastErr!=null && lastErr.length()>0){ throw new IOException(lastErr); } throw e; } } @Override public boolean isLocalDatabaseLoaded() { return localDbLoaded; } @Override public boolean isHackageDatabaseLoaded() { return hackageDbLoaded; } @Override public boolean isHoogleLoaded() { return hoogleLoaded; } @Override protected void loadLocalDatabaseInternal(String path, boolean rebuild) throws IOException, JSONException { if (sendAndReceiveOk(Commands.createLoadLocalDatabase(path, rebuild,BrowserPlugin.getSandboxPath()))){ localDbPath=path; packageCache.remove(Database.LOCAL); packageCache.remove(Database.ALL); // Notify listeners DatabaseLoadedEvent e = new DatabaseLoadedEvent(this, path, DatabaseType.LOCAL); localDbLoaded = true; notifyDatabaseLoaded(e); } } @Override protected void loadHackageDatabaseInternal(String path, boolean rebuild) throws IOException, JSONException { if (sendAndReceiveOk(Commands.createLoadHackageDatabase(path, rebuild))){ hackageDbPath=path; packageCache.remove(Database.HACKAGE); packageCache.remove(Database.ALL); // Notify listeners DatabaseLoadedEvent e = new DatabaseLoadedEvent(this, path, DatabaseType.HACKAGE); hackageDbLoaded = true; notifyDatabaseLoaded(e); } } // @Override // public void setCurrentDatabase(DatabaseType current, PackageIdentifier id) // throws IOException, JSONException { // if (this.currentDatabase==null || !this.currentDatabase.equals(current) || id!=null){ // this.currentDatabase = current; // sendAndReceiveOk(Commands.createSetCurrentDatabase(current, id)); // } // } @Override public HaskellPackage[] getPackages(Database db) throws IOException, JSONException { HaskellPackage[] ret=packageCache.get(db); if (ret==null){ String response = sendAndReceive(Commands.createGetPackages(db)); ret= Commands.responseGetPackages(response); packageCache.put(db, ret); } return ret; } @Override public Module[] getAllModules(Database db) throws IOException, JSONException { String response = sendAndReceive(Commands.createGetAllModules(db)); return Commands.responseGetModules(response); } @Override public Module[] getModules(Database db,String module) throws IOException, JSONException { String response = sendAndReceive(Commands.createGetModules(db,module)); return Commands.responseGetModules(response); } @SuppressWarnings("unchecked") @Override public Packaged<Declaration>[] getDeclarations(Database db,String module) throws Exception { // Try to find in cache //if (this.currentDatabase == DatabaseType.ALL) { Packaged<Declaration>[] decls=this.declCache.get(module); if (decls!=null){ return decls; } //} // If not, search String response = sendAndReceive(Commands.createGetDeclarations(db,module)); decls = Commands.responseGetDeclarations(response); if (decls==null){ decls=new Packaged[0]; } // Check if we need to save in cache //if (this.currentDatabase == DatabaseType.ALL) { this.declCache.put(module, decls); //} return decls; } @SuppressWarnings("unchecked") @Override public Packaged<Declaration>[] getDeclarationsFromPrefix(Database db,String prefix) throws Exception { String response = sendAndReceive(Commands.createGetDeclarationsFromPrefix(db, prefix)); Packaged<Declaration>[] decls = Commands.responseGetDeclarationsFromPrefix(response); if (decls==null){ decls=new Packaged[0]; } return decls; } @Override public DeclarationId[] findModulesForDeclaration(Database db,String decl) throws IOException, JSONException { String response = sendAndReceive(Commands.createFindModulesForDeclaration(db,decl)); return Commands.responseGetDeclarationId(response); } @Override public void setExtraHooglePath(String newPath) throws IOException, JSONException { sendAndReceiveOk(Commands.createSetExtraHooglePath(newPath)); } @Override public HoogleResult[] queryHoogle(Database db,String path,String query) throws Exception { String response = sendAndReceive(Commands.createHoogleQuery(db,path,query,BrowserPlugin.getToolSandboxPath())); return Commands.responseHoogleQuery(response); } // // @Override // public void downloadHoogleData() throws IOException, JSONException { // sendAndReceiveStatus(Commands.createDownloadHoogleData(BrowserPlugin.getSandboxPath())); // } // // @Override // public HoogleStatus checkHoogle() throws Exception { // HoogleStatus st = sendAndReceiveStatus(Commands.createCheckHoogleData(BrowserPlugin.getSandboxPath())); // // If is present, notify the views // if (HoogleStatus.OK.equals(st)) { // hoogleLoaded = true; // notifyHoogleLoaded(new BrowserEvent(this)); // } // return st; // } public HoogleStatus initHoogle(String path,boolean addToDB) throws Exception { HoogleStatus st = sendAndReceiveStatus(Commands.createInitHoogle(path,addToDB,BrowserPlugin.getToolSandboxPath())); if (HoogleStatus.OK.equals(st)) { hoogleLoaded = true; notifyHoogleLoaded(new BrowserEvent(this)); } return st; } @Override public void stop() { // Nothing is loaded localDbLoaded = false; hoogleLoaded = false; // Tell we no longer have a database BrowserEvent e = new BrowserEvent(this); notifyDatabaseUnloaded(e); // Nor a Hoogle connection notifyHoogleUnloaded(e); try { if (out!=null){ out.close(); } } catch (Exception ignore){ // noop } try { if (in!=null){ in.close(); } } catch (Exception ignore){ //noop } try { sendAndReceiveOk(Commands.createQuit()); if (process!=null){ process.destroy(); } } catch (Exception ex) { if (process!=null){ process.destroy(); } } process = null; } @Override public boolean isRunning(){ return lock.isRunning(); } private static class LockObject{ AtomicBoolean running=new AtomicBoolean(false); public boolean isRunning() { return running.get(); } public void setRunning(boolean running) { this.running.set(running); } } }