Clover icon

Coverage Report

  1. Project Clover database Mon Sep 2 2024 17:57:51 BST
  2. Package ext.edu.ucsf.rbvi.strucviz2

File ChimeraManager.java

 

Coverage histogram

../../../../../img/srcFileCovDistChart0.png
60% of files have more coverage

Code metrics

112
293
44
1
990
701
122
0.42
6.66
44
2.77

Classes

Class Line # Actions
ChimeraManager 63 293 122
0.00%
 

Contributing tests

No tests hitting this source file were found.

Source view

1    /* vim: set ts=2: */
2    /**
3    * Copyright (c) 2006 The Regents of the University of California.
4    * All rights reserved.
5    *
6    * Redistribution and use in source and binary forms, with or without
7    * modification, are permitted provided that the following conditions
8    * are met:
9    * 1. Redistributions of source code must retain the above copyright
10    * notice, this list of conditions, and the following disclaimer.
11    * 2. Redistributions in binary form must reproduce the above
12    * copyright notice, this list of conditions, and the following
13    * disclaimer in the documentation and/or other materials provided
14    * with the distribution.
15    * 3. Redistributions must acknowledge that this software was
16    * originally developed by the UCSF Computer Graphics Laboratory
17    * under support by the NIH National Center for Research Resources,
18    * grant P41-RR01081.
19    *
20    * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY
21    * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22    * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23    * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS BE LIABLE
24    * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25    * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
26    * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
27    * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28    * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
29    * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
30    * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31    *
32    */
33    package ext.edu.ucsf.rbvi.strucviz2;
34   
35    import java.awt.Color;
36    import java.io.BufferedReader;
37    import java.io.File;
38    import java.io.IOException;
39    import java.io.InputStream;
40    import java.io.InputStreamReader;
41    import java.io.UnsupportedEncodingException;
42    import java.net.URLEncoder;
43    import java.nio.charset.StandardCharsets;
44    import java.nio.file.Paths;
45    import java.util.ArrayList;
46    import java.util.Collection;
47    import java.util.HashMap;
48    import java.util.List;
49    import java.util.Map;
50   
51    import org.apache.http.NameValuePair;
52    import org.apache.http.message.BasicNameValuePair;
53    import org.slf4j.Logger;
54    import org.slf4j.LoggerFactory;
55   
56    import ext.edu.ucsf.rbvi.strucviz2.StructureManager.ModelType;
57    import ext.edu.ucsf.rbvi.strucviz2.port.ListenerThreads;
58    import jalview.ws.HttpClientUtils;
59   
60    /**
61    * This object maintains the Chimera communication information.
62    */
 
63    public class ChimeraManager
64    {
65    private static final int REST_REPLY_TIMEOUT_MS = 15000;
66   
67    private static final int CONNECTION_TIMEOUT_MS = 100;
68   
69    private static final boolean debug = false;
70   
71    private int chimeraRestPort;
72   
73    private Process chimera;
74   
75    private ListenerThreads chimeraListenerThread;
76   
77    private Map<Integer, ChimeraModel> currentModelsMap;
78   
79    private Logger logger = LoggerFactory
80    .getLogger(ext.edu.ucsf.rbvi.strucviz2.ChimeraManager.class);
81   
82    private StructureManager structureManager;
83   
 
84  0 toggle public ChimeraManager(StructureManager structureManager)
85    {
86  0 this.structureManager = structureManager;
87  0 chimera = null;
88  0 chimeraListenerThread = null;
89  0 currentModelsMap = new HashMap<>();
90   
91    }
92   
 
93  0 toggle public List<ChimeraModel> getChimeraModels(String modelName)
94    {
95  0 List<ChimeraModel> models = getChimeraModels(modelName,
96    ModelType.PDB_MODEL);
97  0 models.addAll(getChimeraModels(modelName, ModelType.SMILES));
98  0 return models;
99    }
100   
 
101  0 toggle public List<ChimeraModel> getChimeraModels(String modelName,
102    ModelType modelType)
103    {
104  0 List<ChimeraModel> models = new ArrayList<>();
105  0 for (ChimeraModel model : currentModelsMap.values())
106    {
107  0 if (modelName.equals(model.getModelName())
108    && modelType.equals(model.getModelType()))
109    {
110  0 models.add(model);
111    }
112    }
113  0 return models;
114    }
115   
 
116  0 toggle public Map<String, List<ChimeraModel>> getChimeraModelsMap()
117    {
118  0 Map<String, List<ChimeraModel>> models = new HashMap<>();
119  0 for (ChimeraModel model : currentModelsMap.values())
120    {
121  0 String modelName = model.getModelName();
122  0 if (!models.containsKey(modelName))
123    {
124  0 models.put(modelName, new ArrayList<ChimeraModel>());
125    }
126  0 if (!models.get(modelName).contains(model))
127    {
128  0 models.get(modelName).add(model);
129    }
130    }
131  0 return models;
132    }
133   
 
134  0 toggle public ChimeraModel getChimeraModel(Integer modelNumber,
135    Integer subModelNumber)
136    {
137  0 Integer key = ChimUtils.makeModelKey(modelNumber, subModelNumber);
138  0 if (currentModelsMap.containsKey(key))
139    {
140  0 return currentModelsMap.get(key);
141    }
142  0 return null;
143    }
144   
 
145  0 toggle public ChimeraModel getChimeraModel()
146    {
147  0 return currentModelsMap.values().iterator().next();
148    }
149   
 
150  0 toggle public Collection<ChimeraModel> getChimeraModels()
151    {
152    // this method is invoked by the model navigator dialog
153  0 return currentModelsMap.values();
154    }
155   
 
156  0 toggle public int getChimeraModelsCount(boolean smiles)
157    {
158    // this method is invokes by the model navigator dialog
159  0 int counter = currentModelsMap.size();
160  0 if (smiles)
161    {
162  0 return counter;
163    }
164   
165  0 for (ChimeraModel model : currentModelsMap.values())
166    {
167  0 if (model.getModelType() == ModelType.SMILES)
168    {
169  0 counter--;
170    }
171    }
172  0 return counter;
173    }
174   
 
175  0 toggle public boolean hasChimeraModel(Integer modelNubmer)
176    {
177  0 return hasChimeraModel(modelNubmer, 0);
178    }
179   
 
180  0 toggle public boolean hasChimeraModel(Integer modelNubmer,
181    Integer subModelNumber)
182    {
183  0 return currentModelsMap.containsKey(
184    ChimUtils.makeModelKey(modelNubmer, subModelNumber));
185    }
186   
 
187  0 toggle public void addChimeraModel(Integer modelNumber, Integer subModelNumber,
188    ChimeraModel model)
189    {
190  0 currentModelsMap.put(
191    ChimUtils.makeModelKey(modelNumber, subModelNumber), model);
192    }
193   
 
194  0 toggle public void removeChimeraModel(Integer modelNumber,
195    Integer subModelNumber)
196    {
197  0 int modelKey = ChimUtils.makeModelKey(modelNumber, subModelNumber);
198  0 if (currentModelsMap.containsKey(modelKey))
199    {
200  0 currentModelsMap.remove(modelKey);
201    }
202    }
203   
 
204  0 toggle public List<ChimeraModel> openModel(String modelPath, ModelType type)
205    {
206  0 return openModel(modelPath, getFileNameFromPath(modelPath), type);
207    }
208   
209    /**
210    * Overloaded method to allow Jalview to pass in a model name.
211    *
212    * @param modelPath
213    * @param modelName
214    * @param type
215    * @return
216    */
 
217  0 toggle public List<ChimeraModel> openModel(String modelPath, String modelName,
218    ModelType type)
219    {
220  0 logger.info("chimera open " + modelPath);
221    // stopListening();
222  0 List<ChimeraModel> modelList = getModelList();
223  0 List<String> response = null;
224    // TODO: [Optional] Handle modbase models
225  0 if (type == ModelType.MODBASE_MODEL)
226    {
227  0 response = sendChimeraCommand("open modbase:" + modelPath, true);
228    // } else if (type == ModelType.SMILES) {
229    // response = sendChimeraCommand("open smiles:" + modelName, true);
230    // modelName = "smiles:" + modelName;
231    }
232    else
233    {
234  0 response = sendChimeraCommand("open " + modelPath, true);
235    }
236  0 if (response == null)
237    {
238    // something went wrong
239  0 logger.warn("Could not open " + modelPath);
240  0 return null;
241    }
242   
243    // patch for Jalview - set model name in Chimera
244    // TODO: find a variant that works for sub-models
245  0 for (ChimeraModel newModel : getModelList())
246    {
247  0 if (!modelList.contains(newModel))
248    {
249  0 newModel.setModelName(modelName);
250  0 sendChimeraCommand("setattr M name " + modelName + " #"
251    + newModel.getModelNumber(), false);
252  0 modelList.add(newModel);
253    }
254    }
255   
256    // assign color and residues to open models
257  0 for (ChimeraModel chimeraModel : modelList)
258    {
259    // get model color
260  0 Color modelColor = isChimeraX() ? null : getModelColor(chimeraModel);
261  0 if (modelColor != null)
262    {
263  0 chimeraModel.setModelColor(modelColor);
264    }
265   
266    // Get our properties (default color scheme, etc.)
267    // Make the molecule look decent
268    // chimeraSend("repr stick "+newModel.toSpec());
269   
270    // Create the information we need for the navigator
271  0 if (type != ModelType.SMILES && !isChimeraX())
272    {
273  0 addResidues(chimeraModel);
274    }
275    }
276   
277  0 sendChimeraCommand("focus", false);
278    // startListening(); // see ChimeraListener
279  0 return modelList;
280    }
281   
282    /**
283    * Refactored method to extract the last (or only) element delimited by file
284    * path separator.
285    *
286    * @param modelPath
287    * @return
288    */
 
289  0 toggle private String getFileNameFromPath(String modelPath)
290    {
291  0 String modelName = modelPath;
292  0 if (modelPath == null)
293    {
294  0 return null;
295    }
296    // TODO: [Optional] Convert path to name in a better way
297  0 if (modelPath.lastIndexOf(File.separator) > 0)
298    {
299  0 modelName = modelPath
300    .substring(modelPath.lastIndexOf(File.separator) + 1);
301    }
302  0 else if (modelPath.lastIndexOf("/") > 0)
303    {
304  0 modelName = modelPath.substring(modelPath.lastIndexOf("/") + 1);
305    }
306  0 return modelName;
307    }
308   
 
309  0 toggle public void closeModel(ChimeraModel model)
310    {
311    // int model = structure.modelNumber();
312    // int subModel = structure.subModelNumber();
313    // Integer modelKey = makeModelKey(model, subModel);
314  0 stopListening();
315  0 logger.info("chimera close model " + model.getModelName());
316  0 if (currentModelsMap.containsKey(ChimUtils.makeModelKey(
317    model.getModelNumber(), model.getSubModelNumber())))
318    {
319  0 sendChimeraCommand("close " + model.toSpec(), false);
320    // currentModelNamesMap.remove(model.getModelName());
321  0 currentModelsMap.remove(ChimUtils.makeModelKey(model.getModelNumber(),
322    model.getSubModelNumber()));
323    // selectionList.remove(chimeraModel);
324    }
325    else
326    {
327  0 logger.warn("Could not find model " + model.getModelName()
328    + " to close.");
329    }
330  0 startListening();
331    }
332   
 
333  0 toggle public void startListening()
334    {
335  0 sendChimeraCommand("listen start models; listen start selection",
336    false);
337    }
338   
 
339  0 toggle public void stopListening()
340    {
341  0 String command = "listen stop models ; listen stop selection ";
342  0 sendChimeraCommand(command, false);
343    }
344   
345    /**
346    * Tell Chimera we are listening on the given URI
347    *
348    * @param uri
349    */
 
350  0 toggle public void startListening(String uri)
351    {
352    /*
353    * listen for model changes
354    */
355  0 String command = "listen start models url " + uri;
356  0 sendChimeraCommand(command, false);
357   
358    /*
359    * listen for selection changes
360    */
361  0 command = "listen start select prefix SelectionChanged url " + uri;
362  0 sendChimeraCommand(command, false);
363    }
364   
365    /**
366    * Select something in Chimera
367    *
368    * @param command
369    * the selection command to pass to Chimera
370    */
 
371  0 toggle public void select(String command)
372    {
373  0 sendChimeraCommand("listen stop selection; " + command
374    + "; listen start selection", false);
375    }
376   
 
377  0 toggle public void focus()
378    {
379  0 sendChimeraCommand("focus", false);
380    }
381   
 
382  0 toggle public void clearOnChimeraExit()
383    {
384  0 chimera = null;
385  0 currentModelsMap.clear();
386  0 this.chimeraRestPort = 0;
387  0 structureManager.clearOnChimeraExit();
388    }
389   
 
390  0 toggle public void exitChimera()
391    {
392  0 if (isChimeraLaunched() && chimera != null)
393    {
394  0 sendChimeraCommand("stop really", false);
395  0 try
396    {
397    // TODO is this too violent? could it force close the process
398    // before it has done an orderly shutdown?
399  0 chimera.destroy();
400    } catch (Exception ex)
401    {
402    // ignore
403    }
404    }
405  0 clearOnChimeraExit();
406    }
407   
 
408  0 toggle public Map<Integer, ChimeraModel> getSelectedModels()
409    {
410  0 Map<Integer, ChimeraModel> selectedModelsMap = new HashMap<>();
411  0 List<String> chimeraReply = sendChimeraCommand(
412    "list selection level molecule", true);
413  0 if (chimeraReply != null)
414    {
415  0 for (String modelLine : chimeraReply)
416    {
417  0 ChimeraModel chimeraModel = new ChimeraModel(modelLine);
418  0 Integer modelKey = ChimUtils.makeModelKey(
419    chimeraModel.getModelNumber(),
420    chimeraModel.getSubModelNumber());
421  0 selectedModelsMap.put(modelKey, chimeraModel);
422    }
423    }
424  0 return selectedModelsMap;
425    }
426   
427    /**
428    * Sends a 'list selection level residue' command to Chimera and returns the
429    * list of selected atomspecs
430    *
431    * @return
432    */
 
433  0 toggle public List<String> getSelectedResidueSpecs()
434    {
435  0 List<String> selectedResidues = new ArrayList<>();
436   
437  0 String command = "list selection level residue";
438  0 List<String> chimeraReply = sendChimeraCommand(command, true);
439  0 if (chimeraReply != null)
440    {
441    /*
442    * expect 0, 1 or more lines of the format either
443    * Chimera:
444    * residue id #0:43.A type GLY
445    * ChimeraX:
446    * residue id /A:89 name THR index 88
447    * We are only interested in the atomspec (third token of the reply)
448    */
449  0 for (String inputLine : chimeraReply)
450    {
451  0 String[] inputLineParts = inputLine.split("\\s+");
452  0 if (inputLineParts.length >= 5)
453    {
454  0 selectedResidues.add(inputLineParts[2]);
455    }
456    }
457    }
458  0 return selectedResidues;
459    }
460   
 
461  0 toggle public void getSelectedResidues(
462    Map<Integer, ChimeraModel> selectedModelsMap)
463    {
464  0 List<String> chimeraReply = sendChimeraCommand(
465    "list selection level residue", true);
466  0 if (chimeraReply != null)
467    {
468  0 for (String inputLine : chimeraReply)
469    {
470  0 ChimeraResidue r = new ChimeraResidue(inputLine);
471  0 Integer modelKey = ChimUtils.makeModelKey(r.getModelNumber(),
472    r.getSubModelNumber());
473  0 if (selectedModelsMap.containsKey(modelKey))
474    {
475  0 ChimeraModel model = selectedModelsMap.get(modelKey);
476  0 model.addResidue(r);
477    }
478    }
479    }
480    }
481   
482    /**
483    * Return the list of ChimeraModels currently open. Warning: if smiles model
484    * name too long, only part of it with "..." is printed.
485    *
486    *
487    * @return List of ChimeraModel's
488    */
489    // TODO: [Optional] Handle smiles names in a better way in Chimera?
 
490  0 toggle public List<ChimeraModel> getModelList()
491    {
492  0 List<ChimeraModel> modelList = new ArrayList<>();
493  0 String command = "list models type "
494  0 + (isChimeraX() ? "AtomicStructure" : "molecule");
495  0 List<String> list = sendChimeraCommand(command, true);
496  0 if (list != null)
497    {
498  0 for (String modelLine : list)
499    {
500  0 try
501    {
502  0 ChimeraModel chimeraModel = new ChimeraModel(modelLine);
503  0 modelList.add(chimeraModel);
504    } catch (NullPointerException e)
505    {
506    // hack for now
507    }
508    }
509    }
510  0 return modelList;
511    }
512   
513    /**
514    * Return the list of depiction presets available from within Chimera. Chimera
515    * will return the list as a series of lines with the format: Preset type
516    * number "description"
517    *
518    * @return list of presets
519    */
 
520  0 toggle public List<String> getPresets()
521    {
522  0 ArrayList<String> presetList = new ArrayList<>();
523  0 List<String> output = sendChimeraCommand("preset list", true);
524  0 if (output != null)
525    {
526  0 for (String preset : output)
527    {
528  0 preset = preset.substring(7); // Skip over the "Preset"
529  0 preset = preset.replaceFirst("\"", "(");
530  0 preset = preset.replaceFirst("\"", ")");
531    // string now looks like: type number (description)
532  0 presetList.add(preset);
533    }
534    }
535  0 return presetList;
536    }
537   
 
538  0 toggle public boolean isChimeraLaunched()
539    {
540  0 boolean launched = false;
541  0 if (chimera != null)
542    {
543  0 try
544    {
545  0 chimera.exitValue();
546    // if we get here, process has ended
547    } catch (IllegalThreadStateException e)
548    {
549    // ok - not yet terminated
550  0 launched = true;
551    }
552    }
553  0 return launched;
554    }
555   
556    /**
557    * Launch Chimera, unless an instance linked to this object is already
558    * running. Returns true if chimera is successfully launched, or already
559    * running, else false.
560    *
561    * @param chimeraPaths
562    * @return
563    */
 
564  0 toggle public boolean launchChimera(List<String> chimeraPaths)
565    {
566    // Do nothing if Chimera is already launched
567  0 if (isChimeraLaunched())
568    {
569  0 return true;
570    }
571   
572    // Try to launch Chimera (eventually using one of the possible paths)
573  0 String error = "Error message: ";
574  0 String workingPath = "";
575    // iterate over possible paths for starting Chimera
576  0 for (String chimeraPath : chimeraPaths)
577    {
578  0 try
579    {
580    // ensure symbolic links are resolved
581  0 chimeraPath = Paths.get(chimeraPath).toRealPath().toString();
582  0 File path = new File(chimeraPath);
583    // uncomment the next line to simulate Chimera not installed
584    // path = new File(chimeraPath + "x");
585  0 if (!path.canExecute())
586    {
587  0 error += "File '" + path + "' does not exist.\n";
588  0 continue;
589    }
590  0 List<String> args = new ArrayList<>();
591  0 args.add(chimeraPath);
592    // shows Chimera output window but suppresses REST responses:
593    // args.add("--debug");
594  0 addLaunchArguments(args);
595  0 ProcessBuilder pb = new ProcessBuilder(args);
596  0 chimera = pb.start();
597  0 error = "";
598  0 workingPath = chimeraPath;
599  0 break;
600    } catch (Exception e)
601    {
602    // Chimera could not be started using this path
603  0 error += e.getMessage();
604    }
605    }
606    // If no error, then Chimera was launched successfully
607  0 if (error.length() == 0)
608    {
609  0 this.chimeraRestPort = getPortNumber();
610  0 System.out.println(
611    "Chimera REST API started on port " + chimeraRestPort);
612    // structureManager.initChimTable();
613  0 structureManager.setChimeraPathProperty(workingPath);
614    // TODO: [Optional] Check Chimera version and show a warning if below 1.8
615    // Ask Chimera to give us updates
616    // startListening(); // later - see ChimeraListener
617  0 return (chimeraRestPort > 0);
618    }
619   
620    // Tell the user that Chimera could not be started because of an error
621  0 logger.warn(error);
622  0 return false;
623    }
624   
625    /**
626    * Adds command-line arguments to start the REST server
627    * <p>
628    * Method extracted for Jalview to allow override in ChimeraXManager
629    *
630    * @param args
631    */
 
632  0 toggle protected void addLaunchArguments(List<String> args)
633    {
634  0 args.add("--start");
635  0 args.add("RESTServer");
636    }
637   
638    /**
639    * Read and return the port number returned in the reply to --start RESTServer
640    */
 
641  0 toggle private int getPortNumber()
642    {
643  0 int port = 0;
644  0 InputStream readChan = chimera.getInputStream();
645  0 BufferedReader lineReader = new BufferedReader(
646    new InputStreamReader(readChan));
647  0 StringBuilder responses = new StringBuilder();
648  0 try
649    {
650  0 String response = lineReader.readLine();
651  0 while (response != null)
652    {
653  0 responses.append("\n" + response);
654    // expect: REST server on host 127.0.0.1 port port_number
655    // ChimeraX is the same except "REST server started on host..."
656  0 if (response.startsWith("REST server"))
657    {
658  0 String[] tokens = response.split(" ");
659  0 for (int i = 0; i < tokens.length - 1; i++)
660    {
661  0 if ("port".equals(tokens[i]))
662    {
663  0 port = Integer.parseInt(tokens[i + 1]);
664  0 break;
665    }
666    }
667    }
668  0 if (port > 0)
669    {
670  0 break; // hack for hanging readLine()
671    }
672  0 response = lineReader.readLine();
673    }
674    } catch (Exception e)
675    {
676  0 logger.error("Failed to get REST port number from " + responses + ": "
677    + e.getMessage());
678    } finally
679    {
680  0 try
681    {
682  0 lineReader.close();
683    } catch (IOException e2)
684    {
685    }
686    }
687  0 if (port == 0)
688    {
689  0 System.err.println(
690    "Failed to start Chimera with REST service, response was: "
691    + responses);
692    }
693  0 logger.info(
694    "Chimera REST service listening on port " + chimeraRestPort);
695  0 return port;
696    }
697   
698    /**
699    * Determine the color that Chimera is using for this model.
700    *
701    * @param model
702    * the ChimeraModel we want to get the Color for
703    * @return the default model Color for this model in Chimera
704    */
 
705  0 toggle public Color getModelColor(ChimeraModel model)
706    {
707  0 List<String> colorLines = sendChimeraCommand(
708    "list model spec " + model.toSpec() + " attribute color", true);
709  0 if (colorLines == null || colorLines.size() == 0)
710    {
711  0 return null;
712    }
713  0 return ChimUtils.parseModelColor(colorLines.get(0));
714    }
715   
716    /**
717    *
718    * Get information about the residues associated with a model. This uses the
719    * Chimera listr command. We don't return the resulting residues, but we add
720    * the residues to the model.
721    *
722    * @param model
723    * the ChimeraModel to get residue information for
724    *
725    */
 
726  0 toggle public void addResidues(ChimeraModel model)
727    {
728  0 int modelNumber = model.getModelNumber();
729  0 int subModelNumber = model.getSubModelNumber();
730    // Get the list -- it will be in the reply log
731  0 List<String> reply = sendChimeraCommand(
732    "list residues spec " + model.toSpec(), true);
733  0 if (reply == null)
734    {
735  0 return;
736    }
737  0 for (String inputLine : reply)
738    {
739  0 ChimeraResidue r = new ChimeraResidue(inputLine);
740  0 if (r.getModelNumber() == modelNumber
741    || r.getSubModelNumber() == subModelNumber)
742    {
743  0 model.addResidue(r);
744    }
745    }
746    }
747   
 
748  0 toggle public List<String> getAttrList()
749    {
750  0 List<String> attributes = new ArrayList<>();
751  0 String command = (isChimeraX() ? "info " : "list ") + "resattr";
752  0 final List<String> reply = sendChimeraCommand(command, true);
753  0 if (reply != null)
754    {
755  0 for (String inputLine : reply)
756    {
757  0 String[] lineParts = inputLine.split("\\s");
758  0 if (lineParts.length == 2 && lineParts[0].equals("resattr"))
759    {
760  0 attributes.add(lineParts[1]);
761    }
762    }
763    }
764  0 return attributes;
765    }
766   
 
767  0 toggle public Map<ChimeraResidue, Object> getAttrValues(String aCommand,
768    ChimeraModel model)
769    {
770  0 Map<ChimeraResidue, Object> values = new HashMap<>();
771  0 final List<String> reply = sendChimeraCommand("list residue spec "
772    + model.toSpec() + " attribute " + aCommand, true);
773  0 if (reply != null)
774    {
775  0 for (String inputLine : reply)
776    {
777  0 String[] lineParts = inputLine.split("\\s");
778  0 if (lineParts.length == 5)
779    {
780  0 ChimeraResidue residue = ChimUtils.getResidue(lineParts[2],
781    model);
782  0 String value = lineParts[4];
783  0 if (residue != null)
784    {
785  0 if (value.equals("None"))
786    {
787  0 continue;
788    }
789  0 if (value.equals("True") || value.equals("False"))
790    {
791  0 values.put(residue, Boolean.valueOf(value));
792  0 continue;
793    }
794  0 try
795    {
796  0 Double doubleValue = Double.valueOf(value);
797  0 values.put(residue, doubleValue);
798    } catch (NumberFormatException ex)
799    {
800  0 values.put(residue, value);
801    }
802    }
803    }
804    }
805    }
806  0 return values;
807    }
808   
809    private volatile boolean busy = false;
810   
811    /**
812    * Send a command to Chimera.
813    *
814    * @param command
815    * Command string to be send.
816    * @param reply
817    * Flag indicating whether the method should return the reply from
818    * Chimera or not.
819    * @return List of Strings corresponding to the lines in the Chimera reply or
820    * <code>null</code>.
821    */
 
822  0 toggle public List<String> sendChimeraCommand(String command, boolean reply)
823    {
824  0 if (debug)
825    {
826  0 System.out.println("chimeradebug>> " + command);
827    }
828  0 if (!isChimeraLaunched() || command == null
829    || "".equals(command.trim()))
830    {
831  0 return null;
832    }
833    /*
834    * set a maximum wait time before trying anyway
835    * to avoid hanging indefinitely
836    */
837  0 int waited = 0;
838  0 int pause = 25;
839  0 while (busy && waited < 1001)
840    {
841  0 try
842    {
843  0 Thread.sleep(pause);
844  0 waited += pause;
845    } catch (InterruptedException q)
846    {
847    }
848    }
849  0 busy = true;
850  0 long startTime = System.currentTimeMillis();
851  0 try
852    {
853  0 return sendRestCommand(command);
854    } finally
855    {
856    /*
857    * Make sure busy flag is reset come what may!
858    */
859  0 busy = false;
860  0 if (debug)
861    {
862  0 System.out.println("Chimera command took "
863    + (System.currentTimeMillis() - startTime) + "ms: "
864    + command);
865    }
866    }
867    }
868   
869    /**
870    * Sends the command to Chimera's REST API, and returns any response lines.
871    *
872    * @param command
873    * @return
874    */
 
875  0 toggle protected List<String> sendRestCommand(String command)
876    {
877  0 String restUrl = "http://127.0.0.1:" + this.chimeraRestPort + "/run";
878  0 List<NameValuePair> commands = new ArrayList<>(1);
879  0 String method = getHttpRequestMethod();
880  0 if ("GET".equals(method))
881    {
882  0 try
883    {
884  0 command = URLEncoder.encode(command, StandardCharsets.UTF_8.name());
885    } catch (UnsupportedEncodingException e)
886    {
887  0 command = command.replace(" ", "+").replace("#", "%23")
888    .replace("|", "%7C").replace(";", "%3B")
889    .replace(":", "%3A");
890    }
891    }
892  0 commands.add(new BasicNameValuePair("command", command));
893   
894  0 List<String> reply = new ArrayList<>();
895  0 BufferedReader response = null;
896  0 try
897    {
898  0 response = "GET".equals(method)
899    ? HttpClientUtils.doHttpGet(restUrl, commands,
900    CONNECTION_TIMEOUT_MS, REST_REPLY_TIMEOUT_MS)
901    : HttpClientUtils.doHttpUrlPost(restUrl, commands,
902    CONNECTION_TIMEOUT_MS, REST_REPLY_TIMEOUT_MS);
903  0 String line = "";
904  0 while ((line = response.readLine()) != null)
905    {
906  0 reply.add(line);
907    }
908    } catch (Exception e)
909    {
910  0 logger.error("REST call '" + command + "' failed: " + e.getMessage());
911    } finally
912    {
913  0 if (response != null)
914    {
915  0 try
916    {
917  0 response.close();
918    } catch (IOException e)
919    {
920    }
921    }
922    }
923  0 return reply;
924    }
925   
926    /**
927    * Returns "POST" as the HTTP request method to use for REST service calls to
928    * Chimera
929    *
930    * @return
931    */
 
932  0 toggle protected String getHttpRequestMethod()
933    {
934  0 return "POST";
935    }
936   
937    /**
938    * Send a command to stdin of Chimera process, and optionally read any
939    * responses.
940    *
941    * @param command
942    * @param readReply
943    * @return
944    */
 
945  0 toggle protected List<String> sendStdinCommand(String command, boolean readReply)
946    {
947  0 chimeraListenerThread.clearResponse(command);
948  0 String text = command.concat("\n");
949  0 try
950    {
951    // send the command
952  0 chimera.getOutputStream().write(text.getBytes());
953  0 chimera.getOutputStream().flush();
954    } catch (IOException e)
955    {
956    // logger.info("Unable to execute command: " + text);
957    // logger.info("Exiting...");
958  0 logger.warn("Unable to execute command: " + text);
959  0 logger.warn("Exiting...");
960  0 clearOnChimeraExit();
961  0 return null;
962    }
963  0 if (!readReply)
964    {
965  0 return null;
966    }
967  0 List<String> rsp = chimeraListenerThread.getResponse(command);
968  0 return rsp;
969    }
970   
 
971  0 toggle public StructureManager getStructureManager()
972    {
973  0 return structureManager;
974    }
975   
 
976  0 toggle public boolean isBusy()
977    {
978  0 return busy;
979    }
980   
 
981  0 toggle public Process getChimeraProcess()
982    {
983  0 return chimera;
984    }
985   
 
986  0 toggle public boolean isChimeraX()
987    {
988  0 return false;
989    }
990    }