1 package ext.edu.ucsf.rbvi.strucviz2;
3 import jalview.ws.HttpClientUtils;
6 import java.io.BufferedReader;
8 import java.io.IOException;
9 import java.io.InputStream;
10 import java.io.InputStreamReader;
11 import java.util.ArrayList;
12 import java.util.Collection;
13 import java.util.HashMap;
14 import java.util.List;
17 import org.apache.http.NameValuePair;
18 import org.apache.http.message.BasicNameValuePair;
19 import org.slf4j.Logger;
20 import org.slf4j.LoggerFactory;
22 import ext.edu.ucsf.rbvi.strucviz2.StructureManager.ModelType;
23 import ext.edu.ucsf.rbvi.strucviz2.port.ListenerThreads;
26 * This object maintains the Chimera communication information.
28 public class ChimeraManager
30 private static final int REST_REPLY_TIMEOUT_MS = 15000;
32 private static final int CONNECTION_TIMEOUT_MS = 100;
34 private static final boolean debug = false;
36 private int chimeraRestPort;
38 private Process chimera;
40 private ListenerThreads chimeraListenerThread;
42 private Map<Integer, ChimeraModel> currentModelsMap;
44 private Logger logger = LoggerFactory
45 .getLogger(ext.edu.ucsf.rbvi.strucviz2.ChimeraManager.class);
47 private StructureManager structureManager;
49 public ChimeraManager(StructureManager structureManager)
51 this.structureManager = structureManager;
53 chimeraListenerThread = null;
54 currentModelsMap = new HashMap<Integer, ChimeraModel>();
58 public List<ChimeraModel> getChimeraModels(String modelName)
60 List<ChimeraModel> models = getChimeraModels(modelName,
62 models.addAll(getChimeraModels(modelName, ModelType.SMILES));
66 public List<ChimeraModel> getChimeraModels(String modelName,
69 List<ChimeraModel> models = new ArrayList<ChimeraModel>();
70 for (ChimeraModel model : currentModelsMap.values())
72 if (modelName.equals(model.getModelName())
73 && modelType.equals(model.getModelType()))
81 public Map<String, List<ChimeraModel>> getChimeraModelsMap()
83 Map<String, List<ChimeraModel>> models = new HashMap<String, List<ChimeraModel>>();
84 for (ChimeraModel model : currentModelsMap.values())
86 String modelName = model.getModelName();
87 if (!models.containsKey(modelName))
89 models.put(modelName, new ArrayList<ChimeraModel>());
91 if (!models.get(modelName).contains(model))
93 models.get(modelName).add(model);
99 public ChimeraModel getChimeraModel(Integer modelNumber,
100 Integer subModelNumber)
102 Integer key = ChimUtils.makeModelKey(modelNumber, subModelNumber);
103 if (currentModelsMap.containsKey(key))
105 return currentModelsMap.get(key);
110 public ChimeraModel getChimeraModel()
112 return currentModelsMap.values().iterator().next();
115 public Collection<ChimeraModel> getChimeraModels()
117 // this method is invoked by the model navigator dialog
118 return currentModelsMap.values();
121 public int getChimeraModelsCount(boolean smiles)
123 // this method is invokes by the model navigator dialog
124 int counter = currentModelsMap.size();
130 for (ChimeraModel model : currentModelsMap.values())
132 if (model.getModelType() == ModelType.SMILES)
140 public boolean hasChimeraModel(Integer modelNubmer)
142 return hasChimeraModel(modelNubmer, 0);
145 public boolean hasChimeraModel(Integer modelNubmer, Integer subModelNumber)
147 return currentModelsMap.containsKey(ChimUtils.makeModelKey(modelNubmer,
151 public void addChimeraModel(Integer modelNumber, Integer subModelNumber,
154 currentModelsMap.put(
155 ChimUtils.makeModelKey(modelNumber, subModelNumber), model);
158 public void removeChimeraModel(Integer modelNumber, Integer subModelNumber)
160 int modelKey = ChimUtils.makeModelKey(modelNumber, subModelNumber);
161 if (currentModelsMap.containsKey(modelKey))
163 currentModelsMap.remove(modelKey);
167 public List<ChimeraModel> openModel(String modelPath, ModelType type)
169 return openModel(modelPath, getFileNameFromPath(modelPath), type);
173 * Overloaded method to allow Jalview to pass in a model name.
180 public List<ChimeraModel> openModel(String modelPath, String modelName,
183 logger.info("chimera open " + modelPath);
185 List<ChimeraModel> modelList = getModelList();
186 List<String> response = null;
187 // TODO: [Optional] Handle modbase models
188 if (type == ModelType.MODBASE_MODEL)
190 response = sendChimeraCommand("open modbase:" + modelPath, true);
191 // } else if (type == ModelType.SMILES) {
192 // response = sendChimeraCommand("open smiles:" + modelName, true);
193 // modelName = "smiles:" + modelName;
197 response = sendChimeraCommand("open " + modelPath, true);
199 if (response == null)
201 // something went wrong
202 logger.warn("Could not open " + modelPath);
206 // patch for Jalview - set model name in Chimera
207 // TODO: find a variant that works for sub-models
208 for (ChimeraModel newModel : getModelList())
210 if (!modelList.contains(newModel))
212 newModel.setModelName(modelName);
214 "setattr M name " + modelName + " #"
215 + newModel.getModelNumber(), false);
216 modelList.add(newModel);
220 // assign color and residues to open models
221 for (ChimeraModel chimeraModel : modelList)
224 Color modelColor = getModelColor(chimeraModel);
225 if (modelColor != null)
227 chimeraModel.setModelColor(modelColor);
230 // Get our properties (default color scheme, etc.)
231 // Make the molecule look decent
232 // chimeraSend("repr stick "+newModel.toSpec());
234 // Create the information we need for the navigator
235 if (type != ModelType.SMILES)
237 addResidues(chimeraModel);
241 sendChimeraCommand("focus", false);
242 // startListening(); // see ChimeraListener
247 * Refactored method to extract the last (or only) element delimited by file
253 private String getFileNameFromPath(String modelPath)
255 String modelName = modelPath;
256 if (modelPath == null)
260 // TODO: [Optional] Convert path to name in a better way
261 if (modelPath.lastIndexOf(File.separator) > 0)
263 modelName = modelPath
264 .substring(modelPath.lastIndexOf(File.separator) + 1);
266 else if (modelPath.lastIndexOf("/") > 0)
268 modelName = modelPath.substring(modelPath.lastIndexOf("/") + 1);
273 public void closeModel(ChimeraModel model)
275 // int model = structure.modelNumber();
276 // int subModel = structure.subModelNumber();
277 // Integer modelKey = makeModelKey(model, subModel);
279 logger.info("chimera close model " + model.getModelName());
280 if (currentModelsMap.containsKey(ChimUtils.makeModelKey(
281 model.getModelNumber(), model.getSubModelNumber())))
283 sendChimeraCommand("close " + model.toSpec(), false);
284 // currentModelNamesMap.remove(model.getModelName());
285 currentModelsMap.remove(ChimUtils.makeModelKey(
286 model.getModelNumber(), model.getSubModelNumber()));
287 // selectionList.remove(chimeraModel);
291 logger.warn("Could not find model " + model.getModelName()
297 public void startListening()
299 sendChimeraCommand("listen start models; listen start selection", false);
302 public void stopListening()
304 sendChimeraCommand("listen stop models ; listen stop selection ", false);
308 * Tell Chimera we are listening on the given URI
312 public void startListening(String uri)
314 sendChimeraCommand("listen start models url " + uri
315 + ";listen start select prefix SelectionChanged url " + uri,
320 * Select something in Chimera
323 * the selection command to pass to Chimera
325 public void select(String command)
327 sendChimeraCommand("listen stop selection; " + command
328 + "; listen start selection", false);
333 sendChimeraCommand("focus", false);
336 public void clearOnChimeraExit()
339 currentModelsMap.clear();
340 this.chimeraRestPort = 0;
341 structureManager.clearOnChimeraExit();
344 public void exitChimera()
346 if (isChimeraLaunched() && chimera != null)
348 sendChimeraCommand("stop really", false);
352 } catch (Exception ex)
357 clearOnChimeraExit();
360 public Map<Integer, ChimeraModel> getSelectedModels()
362 Map<Integer, ChimeraModel> selectedModelsMap = new HashMap<Integer, ChimeraModel>();
363 List<String> chimeraReply = sendChimeraCommand(
364 "list selection level molecule", true);
365 if (chimeraReply != null)
367 for (String modelLine : chimeraReply)
369 ChimeraModel chimeraModel = new ChimeraModel(modelLine);
370 Integer modelKey = ChimUtils.makeModelKey(
371 chimeraModel.getModelNumber(),
372 chimeraModel.getSubModelNumber());
373 selectedModelsMap.put(modelKey, chimeraModel);
376 return selectedModelsMap;
380 * Sends a 'list selection level residue' command to Chimera and returns the
381 * list of selected atomspecs
385 public List<String> getSelectedResidueSpecs()
387 List<String> selectedResidues = new ArrayList<String>();
388 List<String> chimeraReply = sendChimeraCommand(
389 "list selection level residue", true);
390 if (chimeraReply != null)
393 * expect 0, 1 or more lines of the format
394 * residue id #0:43.A type GLY
395 * where we are only interested in the atomspec #0.43.A
397 for (String inputLine : chimeraReply)
399 String[] inputLineParts = inputLine.split("\\s+");
400 if (inputLineParts.length == 5)
402 selectedResidues.add(inputLineParts[2]);
406 return selectedResidues;
409 public void getSelectedResidues(
410 Map<Integer, ChimeraModel> selectedModelsMap)
412 List<String> chimeraReply = sendChimeraCommand(
413 "list selection level residue", true);
414 if (chimeraReply != null)
416 for (String inputLine : chimeraReply)
418 ChimeraResidue r = new ChimeraResidue(inputLine);
419 Integer modelKey = ChimUtils.makeModelKey(r.getModelNumber(),
420 r.getSubModelNumber());
421 if (selectedModelsMap.containsKey(modelKey))
423 ChimeraModel model = selectedModelsMap.get(modelKey);
431 * Return the list of ChimeraModels currently open. Warning: if smiles model
432 * name too long, only part of it with "..." is printed.
435 * @return List of ChimeraModel's
437 // TODO: [Optional] Handle smiles names in a better way in Chimera?
438 public List<ChimeraModel> getModelList()
440 List<ChimeraModel> modelList = new ArrayList<ChimeraModel>();
441 List<String> list = sendChimeraCommand("list models type molecule",
445 for (String modelLine : list)
447 ChimeraModel chimeraModel = new ChimeraModel(modelLine);
448 modelList.add(chimeraModel);
455 * Return the list of depiction presets available from within Chimera. Chimera
456 * will return the list as a series of lines with the format: Preset type
457 * number "description"
459 * @return list of presets
461 public List<String> getPresets()
463 ArrayList<String> presetList = new ArrayList<String>();
464 List<String> output = sendChimeraCommand("preset list", true);
467 for (String preset : output)
469 preset = preset.substring(7); // Skip over the "Preset"
470 preset = preset.replaceFirst("\"", "(");
471 preset = preset.replaceFirst("\"", ")");
472 // string now looks like: type number (description)
473 presetList.add(preset);
479 public boolean isChimeraLaunched()
481 boolean launched = false;
487 // if we get here, process has ended
488 } catch (IllegalThreadStateException e)
490 // ok - not yet terminated
498 * Launch Chimera, unless an instance linked to this object is already
499 * running. Returns true if chimera is successfully launched, or already
500 * running, else false.
502 * @param chimeraPaths
505 public boolean launchChimera(List<String> chimeraPaths)
507 // Do nothing if Chimera is already launched
508 if (isChimeraLaunched())
513 // Try to launch Chimera (eventually using one of the possible paths)
514 String error = "Error message: ";
515 String workingPath = "";
516 // iterate over possible paths for starting Chimera
517 for (String chimeraPath : chimeraPaths)
519 File path = new File(chimeraPath);
520 // uncomment the next line to simulate Chimera not installed
521 // path = new File(chimeraPath + "x");
522 if (!path.canExecute())
524 error += "File '" + path + "' does not exist.\n";
529 List<String> args = new ArrayList<String>();
530 args.add(chimeraPath);
531 // shows Chimera output window but suppresses REST responses:
532 // args.add("--debug");
534 args.add("RESTServer");
535 ProcessBuilder pb = new ProcessBuilder(args);
536 chimera = pb.start();
538 workingPath = chimeraPath;
540 } catch (Exception e)
542 // Chimera could not be started
543 error += e.getMessage();
546 // If no error, then Chimera was launched successfully
547 if (error.length() == 0)
549 this.chimeraRestPort = getPortNumber();
550 System.out.println("Chimera REST API started on port "
552 // structureManager.initChimTable();
553 structureManager.setChimeraPathProperty(workingPath);
554 // TODO: [Optional] Check Chimera version and show a warning if below 1.8
555 // Ask Chimera to give us updates
556 // startListening(); // later - see ChimeraListener
557 return (chimeraRestPort > 0);
560 // Tell the user that Chimera could not be started because of an error
566 * Read and return the port number returned in the reply to --start RESTServer
568 private int getPortNumber()
571 InputStream readChan = chimera.getInputStream();
572 BufferedReader lineReader = new BufferedReader(new InputStreamReader(
574 StringBuilder responses = new StringBuilder();
577 String response = lineReader.readLine();
578 while (response != null)
580 responses.append("\n" + response);
581 // expect: REST server on host 127.0.0.1 port port_number
582 if (response.startsWith("REST server"))
584 String[] tokens = response.split(" ");
585 if (tokens.length == 7 && "port".equals(tokens[5]))
587 port = Integer.parseInt(tokens[6]);
591 response = lineReader.readLine();
593 } catch (Exception e)
595 logger.error("Failed to get REST port number from " + responses
596 + ": " + e.getMessage());
602 } catch (IOException e2)
609 .println("Failed to start Chimera with REST service, response was: "
612 logger.info("Chimera REST service listening on port " + chimeraRestPort);
617 * Determine the color that Chimera is using for this model.
620 * the ChimeraModel we want to get the Color for
621 * @return the default model Color for this model in Chimera
623 public Color getModelColor(ChimeraModel model)
625 List<String> colorLines = sendChimeraCommand(
626 "list model spec " + model.toSpec() + " attribute color", true);
627 if (colorLines == null || colorLines.size() == 0)
631 return ChimUtils.parseModelColor(colorLines.get(0));
636 * Get information about the residues associated with a model. This uses the
637 * Chimera listr command. We don't return the resulting residues, but we add
638 * the residues to the model.
641 * the ChimeraModel to get residue information for
644 public void addResidues(ChimeraModel model)
646 int modelNumber = model.getModelNumber();
647 int subModelNumber = model.getSubModelNumber();
648 // Get the list -- it will be in the reply log
649 List<String> reply = sendChimeraCommand(
650 "list residues spec " + model.toSpec(), true);
655 for (String inputLine : reply)
657 ChimeraResidue r = new ChimeraResidue(inputLine);
658 if (r.getModelNumber() == modelNumber
659 || r.getSubModelNumber() == subModelNumber)
666 public List<String> getAttrList()
668 List<String> attributes = new ArrayList<String>();
669 final List<String> reply = sendChimeraCommand("list resattr", true);
672 for (String inputLine : reply)
674 String[] lineParts = inputLine.split("\\s");
675 if (lineParts.length == 2 && lineParts[0].equals("resattr"))
677 attributes.add(lineParts[1]);
684 public Map<ChimeraResidue, Object> getAttrValues(String aCommand,
687 Map<ChimeraResidue, Object> values = new HashMap<ChimeraResidue, Object>();
688 final List<String> reply = sendChimeraCommand("list residue spec "
689 + model.toSpec() + " attribute " + aCommand, true);
692 for (String inputLine : reply)
694 String[] lineParts = inputLine.split("\\s");
695 if (lineParts.length == 5)
697 ChimeraResidue residue = ChimUtils
698 .getResidue(lineParts[2], model);
699 String value = lineParts[4];
702 if (value.equals("None"))
706 if (value.equals("True") || value.equals("False"))
708 values.put(residue, Boolean.valueOf(value));
713 Double doubleValue = Double.valueOf(value);
714 values.put(residue, doubleValue);
715 } catch (NumberFormatException ex)
717 values.put(residue, value);
726 private volatile boolean busy = false;
729 * Send a command to Chimera.
732 * Command string to be send.
734 * Flag indicating whether the method should return the reply from
736 * @return List of Strings corresponding to the lines in the Chimera reply or
739 public List<String> sendChimeraCommand(String command, boolean reply)
741 // System.out.println("chimeradebug>> " + command);
742 if (!isChimeraLaunched() || command == null
743 || "".equals(command.trim()))
747 // TODO do we need a maximum wait time before aborting?
753 } catch (InterruptedException q)
758 long startTime = System.currentTimeMillis();
761 return sendRestCommand(command);
765 * Make sure busy flag is reset come what may!
770 System.out.println("Chimera command took "
771 + (System.currentTimeMillis() - startTime) + "ms: "
779 * Sends the command to Chimera's REST API, and returns any response lines.
784 protected List<String> sendRestCommand(String command)
786 String restUrl = "http://127.0.0.1:" + this.chimeraRestPort + "/run";
787 List<NameValuePair> commands = new ArrayList<NameValuePair>(1);
788 commands.add(new BasicNameValuePair("command", command));
790 List<String> reply = new ArrayList<String>();
791 BufferedReader response = null;
794 response = HttpClientUtils.doHttpUrlPost(restUrl, commands, CONNECTION_TIMEOUT_MS,
795 REST_REPLY_TIMEOUT_MS);
797 while ((line = response.readLine()) != null)
801 } catch (Exception e)
803 logger.error("REST call '" + command + "' failed: " + e.getMessage());
806 if (response != null)
811 } catch (IOException e)
820 * Send a command to stdin of Chimera process, and optionally read any
827 protected List<String> sendStdinCommand(String command, boolean readReply)
829 chimeraListenerThread.clearResponse(command);
830 String text = command.concat("\n");
834 chimera.getOutputStream().write(text.getBytes());
835 chimera.getOutputStream().flush();
836 } catch (IOException e)
838 // logger.info("Unable to execute command: " + text);
839 // logger.info("Exiting...");
840 logger.warn("Unable to execute command: " + text);
841 logger.warn("Exiting...");
842 clearOnChimeraExit();
849 List<String> rsp = chimeraListenerThread.getResponse(command);
853 public StructureManager getStructureManager()
855 return structureManager;
858 public boolean isBusy()