1 package ext.edu.ucsf.rbvi.strucviz2;
4 import java.io.BufferedReader;
6 import java.io.IOException;
7 import java.io.InputStream;
8 import java.io.InputStreamReader;
9 import java.util.ArrayList;
10 import java.util.Collection;
11 import java.util.HashMap;
12 import java.util.List;
15 import org.apache.http.NameValuePair;
16 import org.apache.http.message.BasicNameValuePair;
17 import org.slf4j.Logger;
18 import org.slf4j.LoggerFactory;
20 import ext.edu.ucsf.rbvi.strucviz2.StructureManager.ModelType;
21 import ext.edu.ucsf.rbvi.strucviz2.port.ListenerThreads;
23 import jalview.ws.HttpClientUtils;
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<String> response = null;
182 // TODO: [Optional] Handle modbase models
183 if (type == ModelType.MODBASE_MODEL)
185 response = sendChimeraCommand("open modbase:" + modelPath, true);
186 // } else if (type == ModelType.SMILES) {
187 // response = sendChimeraCommand("open smiles:" + modelName, true);
188 // modelName = "smiles:" + modelName;
192 response = sendChimeraCommand("open " + modelPath, true);
194 if (response == null)
196 // something went wrong
197 logger.warn("Could not open " + modelPath);
200 List<ChimeraModel> models = new ArrayList<ChimeraModel>();
201 int[] modelNumbers = null;
202 if (type == ModelType.PDB_MODEL)
204 for (String line : response)
206 if (line.startsWith("#"))
208 modelNumbers = ChimUtils.parseOpenedModelNumber(line);
209 if (modelNumbers != null)
211 int modelNumber = ChimUtils.makeModelKey(modelNumbers[0],
213 if (currentModelsMap.containsKey(modelNumber))
217 ChimeraModel newModel = new ChimeraModel(modelName, type,
218 modelNumbers[0], modelNumbers[1]);
219 currentModelsMap.put(modelNumber, newModel);
220 models.add(newModel);
223 // patch for Jalview - set model name in Chimera
225 sendChimeraCommand("setattr M name " + modelName + " #"
226 + modelNumbers[0], false);
227 // end patch for Jalview
236 // TODO: [Optional] Open smiles from file would fail. Do we need it?
237 // If parsing fails, iterate over all open models to get the right one
238 List<ChimeraModel> openModels = getModelList();
239 for (ChimeraModel openModel : openModels)
241 String openModelName = openModel.getModelName();
242 if (openModelName.endsWith("..."))
244 openModelName = openModelName.substring(0,
245 openModelName.length() - 3);
247 if (modelPath.startsWith(openModelName))
249 openModel.setModelName(modelPath);
250 int modelNumber = ChimUtils
251 .makeModelKey(openModel.getModelNumber(),
252 openModel.getSubModelNumber());
253 if (!currentModelsMap.containsKey(modelNumber))
255 currentModelsMap.put(modelNumber, openModel);
256 models.add(openModel);
262 // assign color and residues to open models
263 for (ChimeraModel newModel : models)
266 Color modelColor = getModelColor(newModel);
267 if (modelColor != null)
269 newModel.setModelColor(modelColor);
272 // Get our properties (default color scheme, etc.)
273 // Make the molecule look decent
274 // chimeraSend("repr stick "+newModel.toSpec());
276 // Create the information we need for the navigator
277 if (type != ModelType.SMILES)
279 addResidues(newModel);
283 sendChimeraCommand("focus", false);
284 // startListening(); // see ChimeraListener
289 * Refactored method to extract the last (or only) element delimited by file
295 private String getFileNameFromPath(String modelPath)
297 String modelName = modelPath;
298 if (modelPath == null)
302 // TODO: [Optional] Convert path to name in a better way
303 if (modelPath.lastIndexOf(File.separator) > 0)
305 modelName = modelPath.substring(modelPath
306 .lastIndexOf(File.separator) + 1);
308 else if (modelPath.lastIndexOf("/") > 0)
310 modelName = modelPath
311 .substring(modelPath.lastIndexOf("/") + 1);
316 public void closeModel(ChimeraModel model)
318 // int model = structure.modelNumber();
319 // int subModel = structure.subModelNumber();
320 // Integer modelKey = makeModelKey(model, subModel);
322 logger.info("chimera close model " + model.getModelName());
323 if (currentModelsMap.containsKey(ChimUtils.makeModelKey(
324 model.getModelNumber(), model.getSubModelNumber())))
326 sendChimeraCommand("close " + model.toSpec(), false);
327 // currentModelNamesMap.remove(model.getModelName());
328 currentModelsMap.remove(ChimUtils.makeModelKey(
329 model.getModelNumber(), model.getSubModelNumber()));
330 // selectionList.remove(chimeraModel);
334 logger.warn("Could not find model " + model.getModelName()
340 public void startListening()
342 sendChimeraCommand("listen start models; listen start selection", false);
345 public void stopListening()
347 sendChimeraCommand("listen stop models; listen stop selection", false);
351 * Tell Chimera we are listening on the given URI
355 public void startListening(String uri)
357 sendChimeraCommand("listen start models url " + uri, false);
358 sendChimeraCommand("listen start select prefix SelectionChanged url "
363 * Tell Chimera we have stopped listening on the given URI
367 public void stopListening(String uri)
369 sendChimeraCommand("listen stop models url " + uri, false);
370 sendChimeraCommand("listen stop selection url " + uri, false);
374 * Select something in Chimera
377 * the selection command to pass to Chimera
379 public void select(String command)
381 sendChimeraCommand("listen stop selection; " + command
382 + "; listen start selection", false);
387 sendChimeraCommand("focus", false);
390 public void clearOnChimeraExit()
393 currentModelsMap.clear();
394 this.chimeraRestPort = 0;
395 structureManager.clearOnChimeraExit();
398 public void exitChimera()
400 if (isChimeraLaunched() && chimera != null)
402 sendChimeraCommand("stop really", false);
406 } catch (Exception ex)
411 clearOnChimeraExit();
414 public Map<Integer, ChimeraModel> getSelectedModels()
416 Map<Integer, ChimeraModel> selectedModelsMap = new HashMap<Integer, ChimeraModel>();
417 List<String> chimeraReply = sendChimeraCommand(
418 "list selection level molecule", true);
419 if (chimeraReply != null)
421 for (String modelLine : chimeraReply)
423 ChimeraModel chimeraModel = new ChimeraModel(modelLine);
424 Integer modelKey = ChimUtils.makeModelKey(
425 chimeraModel.getModelNumber(),
426 chimeraModel.getSubModelNumber());
427 selectedModelsMap.put(modelKey, chimeraModel);
430 return selectedModelsMap;
434 * Sends a 'list selection level residue' command to Chimera and returns the
435 * list of selected atomspecs
439 public List<String> getSelectedResidueSpecs()
441 List<String> selectedResidues = new ArrayList<String>();
442 List<String> chimeraReply = sendChimeraCommand(
443 "list selection level residue", true);
444 if (chimeraReply != null)
446 for (String inputLine : chimeraReply)
448 String[] inputLineParts = inputLine.split("\\s+");
449 if (inputLineParts.length == 5)
451 selectedResidues.add(inputLineParts[2]);
455 return selectedResidues;
458 public void getSelectedResidues(
459 Map<Integer, ChimeraModel> selectedModelsMap)
461 List<String> chimeraReply = sendChimeraCommand(
462 "list selection level residue", true);
463 if (chimeraReply != null)
465 for (String inputLine : chimeraReply)
467 ChimeraResidue r = new ChimeraResidue(inputLine);
468 Integer modelKey = ChimUtils.makeModelKey(r.getModelNumber(),
469 r.getSubModelNumber());
470 if (selectedModelsMap.containsKey(modelKey))
472 ChimeraModel model = selectedModelsMap.get(modelKey);
480 * Return the list of ChimeraModels currently open. Warning: if smiles model
481 * name too long, only part of it with "..." is printed.
484 * @return List of ChimeraModel's
486 // TODO: [Optional] Handle smiles names in a better way in Chimera?
487 public List<ChimeraModel> getModelList()
489 List<ChimeraModel> modelList = new ArrayList<ChimeraModel>();
490 List<String> list = sendChimeraCommand("list models type molecule",
494 for (String modelLine : list)
496 ChimeraModel chimeraModel = new ChimeraModel(modelLine);
497 modelList.add(chimeraModel);
504 * Return the list of depiction presets available from within Chimera. Chimera
505 * will return the list as a series of lines with the format: Preset type
506 * number "description"
508 * @return list of presets
510 public List<String> getPresets()
512 ArrayList<String> presetList = new ArrayList<String>();
513 List<String> output = sendChimeraCommand("preset list", true);
516 for (String preset : output)
518 preset = preset.substring(7); // Skip over the "Preset"
519 preset = preset.replaceFirst("\"", "(");
520 preset = preset.replaceFirst("\"", ")");
521 // string now looks like: type number (description)
522 presetList.add(preset);
528 public boolean isChimeraLaunched()
530 boolean launched = false;
536 // if we get here, process has ended
537 } catch (IllegalThreadStateException e)
539 // ok - not yet terminated
546 public boolean launchChimera(List<String> chimeraPaths)
548 // Do nothing if Chimera is already launched
549 if (isChimeraLaunched())
554 // Try to launch Chimera (eventually using one of the possible paths)
555 String error = "Error message: ";
556 String workingPath = "";
557 // iterate over possible paths for starting Chimera
558 for (String chimeraPath : chimeraPaths)
560 File path = new File(chimeraPath);
561 if (!path.canExecute())
563 error += "File '" + path + "' does not exist.\n";
568 List<String> args = new ArrayList<String>();
569 args.add(chimeraPath);
571 args.add("RESTServer");
572 ProcessBuilder pb = new ProcessBuilder(args);
573 chimera = pb.start();
575 workingPath = chimeraPath;
577 } catch (Exception e)
579 // Chimera could not be started
580 error += e.getMessage();
583 // If no error, then Chimera was launched successfully
584 if (error.length() == 0)
586 this.chimeraRestPort = getPortNumber();
587 System.out.println("Chimera REST API started on port "
589 // structureManager.initChimTable();
590 structureManager.setChimeraPathProperty(workingPath);
591 // TODO: [Optional] Check Chimera version and show a warning if below 1.8
592 // Ask Chimera to give us updates
593 // startListening(); // later - see ChimeraListener
597 // Tell the user that Chimera could not be started because of an error
603 * Read and return the port number returned in the reply to --start RESTServer
605 private int getPortNumber()
608 InputStream readChan = chimera.getInputStream();
609 BufferedReader lineReader = new BufferedReader(new InputStreamReader(
611 String response = null;
614 // expect: REST server on host 127.0.0.1 port port_number
615 response = lineReader.readLine();
616 String [] tokens = response.split(" ");
617 if (tokens.length == 7 && "port".equals(tokens[5])) {
618 port = Integer.parseInt(tokens[6]);
619 logger.info("Chimera REST service listening on port "
622 } catch (Exception e)
624 logger.error("Failed to get REST port number from " + response + ": "
631 } catch (IOException e2)
639 * Determine the color that Chimera is using for this model.
642 * the ChimeraModel we want to get the Color for
643 * @return the default model Color for this model in Chimera
645 public Color getModelColor(ChimeraModel model)
647 List<String> colorLines = sendChimeraCommand(
648 "list model spec " + model.toSpec() + " attribute color", true);
649 if (colorLines == null || colorLines.size() == 0)
653 return ChimUtils.parseModelColor(colorLines.get(0));
658 * Get information about the residues associated with a model. This uses the
659 * Chimera listr command. We don't return the resulting residues, but we add
660 * the residues to the model.
663 * the ChimeraModel to get residue information for
666 public void addResidues(ChimeraModel model)
668 int modelNumber = model.getModelNumber();
669 int subModelNumber = model.getSubModelNumber();
670 // Get the list -- it will be in the reply log
671 List<String> reply = sendChimeraCommand(
672 "list residues spec " + model.toSpec(), true);
677 for (String inputLine : reply)
679 ChimeraResidue r = new ChimeraResidue(inputLine);
680 if (r.getModelNumber() == modelNumber
681 || r.getSubModelNumber() == subModelNumber)
688 public List<String> getAttrList()
690 List<String> attributes = new ArrayList<String>();
691 final List<String> reply = sendChimeraCommand("list resattr", true);
694 for (String inputLine : reply)
696 String[] lineParts = inputLine.split("\\s");
697 if (lineParts.length == 2 && lineParts[0].equals("resattr"))
699 attributes.add(lineParts[1]);
706 public Map<ChimeraResidue, Object> getAttrValues(String aCommand,
709 Map<ChimeraResidue, Object> values = new HashMap<ChimeraResidue, Object>();
710 final List<String> reply = sendChimeraCommand("list residue spec "
711 + model.toSpec() + " attribute " + aCommand, true);
714 for (String inputLine : reply)
716 String[] lineParts = inputLine.split("\\s");
717 if (lineParts.length == 5)
719 ChimeraResidue residue = ChimUtils
720 .getResidue(lineParts[2], model);
721 String value = lineParts[4];
724 if (value.equals("None"))
728 if (value.equals("True") || value.equals("False"))
730 values.put(residue, Boolean.valueOf(value));
735 Double doubleValue = Double.valueOf(value);
736 values.put(residue, doubleValue);
737 } catch (NumberFormatException ex)
739 values.put(residue, value);
748 private volatile boolean busy = false;
751 * Send a command to Chimera.
754 * Command string to be send.
756 * Flag indicating whether the method should return the reply from
758 * @return List of Strings corresponding to the lines in the Chimera reply or
761 public List<String> sendChimeraCommand(String command, boolean reply)
763 if (!isChimeraLaunched() || command == null
764 || "".equals(command.trim()))
768 // TODO do we need a maximum wait time before aborting?
774 } catch (InterruptedException q)
779 long startTime = System.currentTimeMillis();
782 return sendRestCommand(command);
786 * Make sure busy flag is reset come what may!
791 System.out.println("Chimera command took "
792 + (System.currentTimeMillis() - startTime) + "ms: "
800 * Sends the command to Chimera's REST API, and returns any response lines.
805 protected List<String> sendRestCommand(String command)
807 // System.out.println("Rest: " + command);
808 // TODO start a separate thread to do this so we don't block?
809 String restUrl = "http://127.0.0.1:" + this.chimeraRestPort + "/run";
810 List<NameValuePair> commands = new ArrayList<NameValuePair>(1);
811 commands.add(new BasicNameValuePair("command", command));
813 List<String> reply = new ArrayList<String>();
814 BufferedReader response = null;
816 response = HttpClientUtils
817 .doHttpUrlPost(restUrl, commands, 100, 2000);
819 while ((line = response.readLine()) != null) {
822 } catch (Exception e)
824 logger.error("REST call " + command + " failed: " + e.getMessage());
827 if (response != null)
832 } catch (IOException e)
841 * Send a command to stdin of Chimera process, and optionally read any
848 protected List<String> sendStdinCommand(String command, boolean readReply)
850 chimeraListenerThread.clearResponse(command);
851 String text = command.concat("\n");
855 chimera.getOutputStream().write(text.getBytes());
856 chimera.getOutputStream().flush();
857 } catch (IOException e)
859 // logger.info("Unable to execute command: " + text);
860 // logger.info("Exiting...");
861 logger.warn("Unable to execute command: " + text);
862 logger.warn("Exiting...");
863 clearOnChimeraExit();
870 List<String> rsp = chimeraListenerThread.getResponse(command);
874 public StructureManager getStructureManager()
876 return structureManager;
879 public boolean isBusy()