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 boolean debug = false;
32 private int chimeraRestPort;
34 private Process chimera;
36 private ListenerThreads chimeraListenerThread;
38 private Map<Integer, ChimeraModel> currentModelsMap;
40 private Logger logger = LoggerFactory
41 .getLogger(ext.edu.ucsf.rbvi.strucviz2.ChimeraManager.class);
43 private StructureManager structureManager;
45 public ChimeraManager(StructureManager structureManager)
47 this.structureManager = structureManager;
49 chimeraListenerThread = null;
50 currentModelsMap = new HashMap<Integer, ChimeraModel>();
54 public List<ChimeraModel> getChimeraModels(String modelName)
56 List<ChimeraModel> models = getChimeraModels(modelName,
58 models.addAll(getChimeraModels(modelName, ModelType.SMILES));
62 public List<ChimeraModel> getChimeraModels(String modelName,
65 List<ChimeraModel> models = new ArrayList<ChimeraModel>();
66 for (ChimeraModel model : currentModelsMap.values())
68 if (modelName.equals(model.getModelName())
69 && modelType.equals(model.getModelType()))
77 public Map<String, List<ChimeraModel>> getChimeraModelsMap()
79 Map<String, List<ChimeraModel>> models = new HashMap<String, List<ChimeraModel>>();
80 for (ChimeraModel model : currentModelsMap.values())
82 String modelName = model.getModelName();
83 if (!models.containsKey(modelName))
85 models.put(modelName, new ArrayList<ChimeraModel>());
87 if (!models.get(modelName).contains(model))
89 models.get(modelName).add(model);
95 public ChimeraModel getChimeraModel(Integer modelNumber,
96 Integer subModelNumber)
98 Integer key = ChimUtils.makeModelKey(modelNumber, subModelNumber);
99 if (currentModelsMap.containsKey(key))
101 return currentModelsMap.get(key);
106 public ChimeraModel getChimeraModel()
108 return currentModelsMap.values().iterator().next();
111 public Collection<ChimeraModel> getChimeraModels()
113 // this method is invoked by the model navigator dialog
114 return currentModelsMap.values();
117 public int getChimeraModelsCount(boolean smiles)
119 // this method is invokes by the model navigator dialog
120 int counter = currentModelsMap.size();
126 for (ChimeraModel model : currentModelsMap.values())
128 if (model.getModelType() == ModelType.SMILES)
136 public boolean hasChimeraModel(Integer modelNubmer)
138 return hasChimeraModel(modelNubmer, 0);
141 public boolean hasChimeraModel(Integer modelNubmer, Integer subModelNumber)
143 return currentModelsMap.containsKey(ChimUtils.makeModelKey(modelNubmer,
147 public void addChimeraModel(Integer modelNumber, Integer subModelNumber,
150 currentModelsMap.put(
151 ChimUtils.makeModelKey(modelNumber, subModelNumber), model);
154 public void removeChimeraModel(Integer modelNumber, Integer subModelNumber)
156 int modelKey = ChimUtils.makeModelKey(modelNumber, subModelNumber);
157 if (currentModelsMap.containsKey(modelKey))
159 currentModelsMap.remove(modelKey);
163 public List<ChimeraModel> openModel(String modelPath, ModelType type)
165 return openModel(modelPath, getFileNameFromPath(modelPath), type);
169 * Overloaded method to allow Jalview to pass in a model name.
176 public List<ChimeraModel> openModel(String modelPath, String modelName,
179 logger.info("chimera open " + modelPath);
181 List<ChimeraModel> modelList = getModelList();
182 List<String> response = null;
183 // TODO: [Optional] Handle modbase models
184 if (type == ModelType.MODBASE_MODEL)
186 response = sendChimeraCommand("open modbase:" + modelPath, true);
187 // } else if (type == ModelType.SMILES) {
188 // response = sendChimeraCommand("open smiles:" + modelName, true);
189 // modelName = "smiles:" + modelName;
193 response = sendChimeraCommand("open " + modelPath, true);
195 if (response == null)
197 // something went wrong
198 logger.warn("Could not open " + modelPath);
202 List<ChimeraModel> newModelList = getModelList();
203 for (ChimeraModel newModel : newModelList)
205 if (!modelList.contains(newModel))
207 newModel.setModelName(modelName);
209 "setattr M name " + modelName + " #"
210 + newModel.getModelNumber(), false);
211 modelList.add(newModel);
216 // assign color and residues to open models
217 for (ChimeraModel chimeraModel : modelList)
219 // // patch for Jalview - set model name in Chimera
220 // // TODO: find a variant that works for sub-models
222 Color modelColor = getModelColor(chimeraModel);
223 if (modelColor != null)
225 chimeraModel.setModelColor(modelColor);
228 // Get our properties (default color scheme, etc.)
229 // Make the molecule look decent
230 // chimeraSend("repr stick "+newModel.toSpec());
232 // Create the information we need for the navigator
233 if (type != ModelType.SMILES)
235 addResidues(chimeraModel);
239 sendChimeraCommand("focus", false);
240 // startListening(); // see ChimeraListener
245 * Refactored method to extract the last (or only) element delimited by file
251 private String getFileNameFromPath(String modelPath)
253 String modelName = modelPath;
254 if (modelPath == null)
258 // TODO: [Optional] Convert path to name in a better way
259 if (modelPath.lastIndexOf(File.separator) > 0)
261 modelName = modelPath
262 .substring(modelPath.lastIndexOf(File.separator) + 1);
264 else if (modelPath.lastIndexOf("/") > 0)
266 modelName = modelPath.substring(modelPath.lastIndexOf("/") + 1);
271 public void closeModel(ChimeraModel model)
273 // int model = structure.modelNumber();
274 // int subModel = structure.subModelNumber();
275 // Integer modelKey = makeModelKey(model, subModel);
277 logger.info("chimera close model " + model.getModelName());
278 if (currentModelsMap.containsKey(ChimUtils.makeModelKey(
279 model.getModelNumber(), model.getSubModelNumber())))
281 sendChimeraCommand("close " + model.toSpec(), false);
282 // currentModelNamesMap.remove(model.getModelName());
283 currentModelsMap.remove(ChimUtils.makeModelKey(
284 model.getModelNumber(), model.getSubModelNumber()));
285 // selectionList.remove(chimeraModel);
289 logger.warn("Could not find model " + model.getModelName()
295 public void startListening()
297 sendChimeraCommand("listen start models; listen start selection", false);
300 public void stopListening()
302 sendChimeraCommand("listen stop models ; listen stop selection ", false);
306 * Tell Chimera we are listening on the given URI
310 public void startListening(String uri)
312 sendChimeraCommand("listen start models url " + uri
313 + ";listen start select prefix SelectionChanged url " + uri,
318 * Select something in Chimera
321 * the selection command to pass to Chimera
323 public void select(String command)
325 sendChimeraCommand("listen stop selection; " + command
326 + "; listen start selection", false);
331 sendChimeraCommand("focus", false);
334 public void clearOnChimeraExit()
337 currentModelsMap.clear();
338 this.chimeraRestPort = 0;
339 structureManager.clearOnChimeraExit();
342 public void exitChimera()
344 if (isChimeraLaunched() && chimera != null)
346 sendChimeraCommand("stop really", false);
350 } catch (Exception ex)
355 clearOnChimeraExit();
358 public Map<Integer, ChimeraModel> getSelectedModels()
360 Map<Integer, ChimeraModel> selectedModelsMap = new HashMap<Integer, ChimeraModel>();
361 List<String> chimeraReply = sendChimeraCommand(
362 "list selection level molecule", true);
363 if (chimeraReply != null)
365 for (String modelLine : chimeraReply)
367 ChimeraModel chimeraModel = new ChimeraModel(modelLine);
368 Integer modelKey = ChimUtils.makeModelKey(
369 chimeraModel.getModelNumber(),
370 chimeraModel.getSubModelNumber());
371 selectedModelsMap.put(modelKey, chimeraModel);
374 return selectedModelsMap;
378 * Sends a 'list selection level residue' command to Chimera and returns the
379 * list of selected atomspecs
383 public List<String> getSelectedResidueSpecs()
385 List<String> selectedResidues = new ArrayList<String>();
386 List<String> chimeraReply = sendChimeraCommand(
387 "list selection level residue", true);
388 if (chimeraReply != null)
390 for (String inputLine : chimeraReply)
392 String[] inputLineParts = inputLine.split("\\s+");
393 if (inputLineParts.length == 5)
395 selectedResidues.add(inputLineParts[2]);
399 return selectedResidues;
402 public void getSelectedResidues(
403 Map<Integer, ChimeraModel> selectedModelsMap)
405 List<String> chimeraReply = sendChimeraCommand(
406 "list selection level residue", true);
407 if (chimeraReply != null)
409 for (String inputLine : chimeraReply)
411 ChimeraResidue r = new ChimeraResidue(inputLine);
412 Integer modelKey = ChimUtils.makeModelKey(r.getModelNumber(),
413 r.getSubModelNumber());
414 if (selectedModelsMap.containsKey(modelKey))
416 ChimeraModel model = selectedModelsMap.get(modelKey);
424 * Return the list of ChimeraModels currently open. Warning: if smiles model
425 * name too long, only part of it with "..." is printed.
428 * @return List of ChimeraModel's
430 // TODO: [Optional] Handle smiles names in a better way in Chimera?
431 public List<ChimeraModel> getModelList()
433 List<ChimeraModel> modelList = new ArrayList<ChimeraModel>();
434 List<String> list = sendChimeraCommand("list models type molecule",
438 for (String modelLine : list)
440 ChimeraModel chimeraModel = new ChimeraModel(modelLine);
441 modelList.add(chimeraModel);
448 * Return the list of depiction presets available from within Chimera. Chimera
449 * will return the list as a series of lines with the format: Preset type
450 * number "description"
452 * @return list of presets
454 public List<String> getPresets()
456 ArrayList<String> presetList = new ArrayList<String>();
457 List<String> output = sendChimeraCommand("preset list", true);
460 for (String preset : output)
462 preset = preset.substring(7); // Skip over the "Preset"
463 preset = preset.replaceFirst("\"", "(");
464 preset = preset.replaceFirst("\"", ")");
465 // string now looks like: type number (description)
466 presetList.add(preset);
472 public boolean isChimeraLaunched()
474 boolean launched = false;
480 // if we get here, process has ended
481 } catch (IllegalThreadStateException e)
483 // ok - not yet terminated
491 * Launch Chimera, unless an instance linked to this object is already
492 * running. Returns true if chimera is successfully launched, or already
493 * running, else false.
495 * @param chimeraPaths
498 public boolean launchChimera(List<String> chimeraPaths)
500 // Do nothing if Chimera is already launched
501 if (isChimeraLaunched())
506 // Try to launch Chimera (eventually using one of the possible paths)
507 String error = "Error message: ";
508 String workingPath = "";
509 // iterate over possible paths for starting Chimera
510 for (String chimeraPath : chimeraPaths)
512 File path = new File(chimeraPath);
513 // uncomment the next line to simulate Chimera not installed
514 // path = new File(chimeraPath + "x");
515 if (!path.canExecute())
517 error += "File '" + path + "' does not exist.\n";
522 List<String> args = new ArrayList<String>();
523 args.add(chimeraPath);
524 // shows Chimera output window but suppresses REST responses:
525 // args.add("--debug");
527 args.add("RESTServer");
528 ProcessBuilder pb = new ProcessBuilder(args);
529 chimera = pb.start();
531 workingPath = chimeraPath;
533 } catch (Exception e)
535 // Chimera could not be started
536 error += e.getMessage();
539 // If no error, then Chimera was launched successfully
540 if (error.length() == 0)
542 this.chimeraRestPort = getPortNumber();
543 System.out.println("Chimera REST API started on port "
545 // structureManager.initChimTable();
546 structureManager.setChimeraPathProperty(workingPath);
547 // TODO: [Optional] Check Chimera version and show a warning if below 1.8
548 // Ask Chimera to give us updates
549 // startListening(); // later - see ChimeraListener
550 return (chimeraRestPort > 0);
553 // Tell the user that Chimera could not be started because of an error
559 * Read and return the port number returned in the reply to --start RESTServer
561 private int getPortNumber()
564 InputStream readChan = chimera.getInputStream();
565 BufferedReader lineReader = new BufferedReader(new InputStreamReader(
567 StringBuilder responses = new StringBuilder();
570 String response = lineReader.readLine();
571 while (response != null)
573 responses.append("\n" + response);
574 // expect: REST server on host 127.0.0.1 port port_number
575 if (response.startsWith("REST server"))
577 String[] tokens = response.split(" ");
578 if (tokens.length == 7 && "port".equals(tokens[5]))
580 port = Integer.parseInt(tokens[6]);
584 response = lineReader.readLine();
586 } catch (Exception e)
588 logger.error("Failed to get REST port number from " + responses
589 + ": " + e.getMessage());
595 } catch (IOException e2)
602 .println("Failed to start Chimera with REST service, response was: "
605 logger.info("Chimera REST service listening on port " + chimeraRestPort);
610 * Determine the color that Chimera is using for this model.
613 * the ChimeraModel we want to get the Color for
614 * @return the default model Color for this model in Chimera
616 public Color getModelColor(ChimeraModel model)
618 List<String> colorLines = sendChimeraCommand(
619 "list model spec " + model.toSpec() + " attribute color", true);
620 if (colorLines == null || colorLines.size() == 0)
624 return ChimUtils.parseModelColor(colorLines.get(0));
629 * Get information about the residues associated with a model. This uses the
630 * Chimera listr command. We don't return the resulting residues, but we add
631 * the residues to the model.
634 * the ChimeraModel to get residue information for
637 public void addResidues(ChimeraModel model)
639 int modelNumber = model.getModelNumber();
640 int subModelNumber = model.getSubModelNumber();
641 // Get the list -- it will be in the reply log
642 List<String> reply = sendChimeraCommand(
643 "list residues spec " + model.toSpec(), true);
648 for (String inputLine : reply)
650 ChimeraResidue r = new ChimeraResidue(inputLine);
651 if (r.getModelNumber() == modelNumber
652 || r.getSubModelNumber() == subModelNumber)
659 public List<String> getAttrList()
661 List<String> attributes = new ArrayList<String>();
662 final List<String> reply = sendChimeraCommand("list resattr", true);
665 for (String inputLine : reply)
667 String[] lineParts = inputLine.split("\\s");
668 if (lineParts.length == 2 && lineParts[0].equals("resattr"))
670 attributes.add(lineParts[1]);
677 public Map<ChimeraResidue, Object> getAttrValues(String aCommand,
680 Map<ChimeraResidue, Object> values = new HashMap<ChimeraResidue, Object>();
681 final List<String> reply = sendChimeraCommand("list residue spec "
682 + model.toSpec() + " attribute " + aCommand, true);
685 for (String inputLine : reply)
687 String[] lineParts = inputLine.split("\\s");
688 if (lineParts.length == 5)
690 ChimeraResidue residue = ChimUtils
691 .getResidue(lineParts[2], model);
692 String value = lineParts[4];
695 if (value.equals("None"))
699 if (value.equals("True") || value.equals("False"))
701 values.put(residue, Boolean.valueOf(value));
706 Double doubleValue = Double.valueOf(value);
707 values.put(residue, doubleValue);
708 } catch (NumberFormatException ex)
710 values.put(residue, value);
719 private volatile boolean busy = false;
722 * Send a command to Chimera.
725 * Command string to be send.
727 * Flag indicating whether the method should return the reply from
729 * @return List of Strings corresponding to the lines in the Chimera reply or
732 public List<String> sendChimeraCommand(String command, boolean reply)
734 // System.out.println("chimeradebug>> " + command);
735 if (!isChimeraLaunched() || command == null
736 || "".equals(command.trim()))
740 // TODO do we need a maximum wait time before aborting?
746 } catch (InterruptedException q)
751 long startTime = System.currentTimeMillis();
754 return sendRestCommand(command);
758 * Make sure busy flag is reset come what may!
763 System.out.println("Chimera command took "
764 + (System.currentTimeMillis() - startTime) + "ms: "
772 * Sends the command to Chimera's REST API, and returns any response lines.
777 protected List<String> sendRestCommand(String command)
779 String restUrl = "http://127.0.0.1:" + this.chimeraRestPort + "/run";
780 List<NameValuePair> commands = new ArrayList<NameValuePair>(1);
781 commands.add(new BasicNameValuePair("command", command));
783 List<String> reply = new ArrayList<String>();
784 BufferedReader response = null;
787 response = HttpClientUtils
788 .doHttpUrlPost(restUrl, commands, 100, 5000);
790 while ((line = response.readLine()) != null)
794 } catch (Exception e)
796 logger.error("REST call '" + command + "' failed: " + e.getMessage());
799 if (response != null)
804 } catch (IOException e)
813 * Send a command to stdin of Chimera process, and optionally read any
820 protected List<String> sendStdinCommand(String command, boolean readReply)
822 chimeraListenerThread.clearResponse(command);
823 String text = command.concat("\n");
827 chimera.getOutputStream().write(text.getBytes());
828 chimera.getOutputStream().flush();
829 } catch (IOException e)
831 // logger.info("Unable to execute command: " + text);
832 // logger.info("Exiting...");
833 logger.warn("Unable to execute command: " + text);
834 logger.warn("Exiting...");
835 clearOnChimeraExit();
842 List<String> rsp = chimeraListenerThread.getResponse(command);
846 public StructureManager getStructureManager()
848 return structureManager;
851 public boolean isBusy()