JAL-3551 copy Jalview features to Pymol 'p' (with pull refactoring)
[jalview.git] / src / jalview / structure / StructureCommandsBase.java
1 package jalview.structure;
2
3 import java.awt.Color;
4 import java.util.ArrayList;
5 import java.util.List;
6 import java.util.Map;
7 import java.util.Map.Entry;
8
9 /**
10  * A base class holding methods useful to all classes that implement commands
11  * for structure viewers
12  * 
13  * @author gmcarstairs
14  *
15  */
16 public abstract class StructureCommandsBase implements StructureCommandsI
17 {
18   private static final String CMD_SEPARATOR = ";";
19   public static final String NAMESPACE_PREFIX = "jv_";
20
21   /**
22    * Returns something that separates concatenated commands
23    * 
24    * @return
25    */
26   protected static String getCommandSeparator()
27   {
28     return CMD_SEPARATOR;
29   }
30
31   /**
32    * Returns the lowest model number used by the structure viewer
33    * 
34    * @return
35    */
36   @Override
37   public int getModelStartNo()
38   {
39     return 0;
40   }
41
42   /**
43    * Helper method to add one contiguous range to the AtomSpec model for the given
44    * value (creating the model if necessary). As used by Jalview, {@code value} is
45    * <ul>
46    * <li>a colour, when building a 'colour structure by sequence' command</li>
47    * <li>a feature value, when building a 'set Chimera attributes from features'
48    * command</li>
49    * </ul>
50    * 
51    * @param map
52    * @param value
53    * @param model
54    * @param startPos
55    * @param endPos
56    * @param chain
57    */
58   public static final void addAtomSpecRange(Map<Object, AtomSpecModel> map,
59           Object value, String model, int startPos, int endPos,
60           String chain)
61   {
62     /*
63      * Get/initialize map of data for the colour
64      */
65     AtomSpecModel atomSpec = map.get(value);
66     if (atomSpec == null)
67     {
68       atomSpec = new AtomSpecModel();
69       map.put(value, atomSpec);
70     }
71   
72     atomSpec.addRange(model, startPos, endPos, chain);
73   }
74
75   /**
76    * Makes a structure viewer attribute name for a Jalview feature type by
77    * prefixing it with "jv_", and replacing any non-alphanumeric characters with
78    * an underscore
79    * 
80    * @param featureType
81    * @return
82    */
83   protected String makeAttributeName(String featureType)
84   {
85     StringBuilder sb = new StringBuilder();
86     if (featureType != null)
87     {
88       for (char c : featureType.toCharArray())
89       {
90         sb.append(Character.isLetterOrDigit(c) ? c : '_');
91       }
92     }
93     String attName = NAMESPACE_PREFIX + sb.toString();
94     return attName;
95   }
96
97   /**
98    * Traverse the map of colours/models/chains/positions to construct a list of
99    * 'color' commands (one per distinct colour used). The format of each command
100    * is specific to the structure viewer.
101    * <p>
102    * The default implementation returns a single command containing one command
103    * per colour, concatenated.
104    * 
105    * @param colourMap
106    * @return
107    */
108   @Override
109   public List<StructureCommandI> colourBySequence(
110           Map<Object, AtomSpecModel> colourMap)
111   {
112     List<StructureCommandI> commands = new ArrayList<>();
113     StringBuilder sb = new StringBuilder(colourMap.size() * 20);
114     boolean first = true;
115     for (Object key : colourMap.keySet())
116     {
117       Color colour = (Color) key;
118       final AtomSpecModel colourData = colourMap.get(colour);
119       StructureCommandI command = getColourCommand(colourData, colour);
120       if (!first)
121       {
122         sb.append(getCommandSeparator());
123       }
124       first = false;
125       sb.append(command.getCommand());
126     }
127
128     commands.add(new StructureCommand(sb.toString()));
129     return commands;
130   }
131
132   /**
133    * Returns a command to colour the atoms represented by {@code atomSpecModel}
134    * with the colour specified by {@code colourCode}.
135    * 
136    * @param atomSpecModel
137    * @param colour
138    * @return
139    */
140   protected StructureCommandI getColourCommand(AtomSpecModel atomSpecModel,
141           Color colour)
142   {
143     String atomSpec = getAtomSpec(atomSpecModel, false);
144     return colourResidues(atomSpec, colour);
145   }
146
147   /**
148    * Returns a command to colour the atoms described (in viewer command syntax)
149    * by {@code atomSpec} with the colour specified by {@code colourCode}
150    * 
151    * @param atomSpec
152    * @param colour
153    * @return
154    */
155   protected abstract StructureCommandI colourResidues(String atomSpec,
156           Color colour);
157
158   @Override
159   public List<StructureCommandI> colourByResidues(
160           Map<String, Color> colours)
161   {
162     List<StructureCommandI> commands = new ArrayList<>();
163     for (Entry<String, Color> entry : colours.entrySet())
164     {
165       commands.add(colourResidue(entry.getKey(), entry.getValue()));
166     }
167     return commands;
168   }
169
170   private StructureCommandI colourResidue(String resName, Color col)
171   {
172     String atomSpec = getResidueSpec(resName);
173     return colourResidues(atomSpec, col);
174   }
175
176   /**
177    * Helper method to append one start-end range to an atomspec string
178    * 
179    * @param sb
180    * @param start
181    * @param end
182    * @param chain
183    * @param firstPositionForModel
184    */
185   protected void appendRange(StringBuilder sb, int start, int end,
186           String chain, boolean firstPositionForModel, boolean isChimeraX)
187   {
188     if (!firstPositionForModel)
189     {
190       sb.append(",");
191     }
192     if (end == start)
193     {
194       sb.append(start);
195     }
196     else
197     {
198       sb.append(start).append("-").append(end);
199     }
200
201     if (!isChimeraX)
202     {
203       sb.append(".");
204       if (!" ".equals(chain))
205       {
206         sb.append(chain);
207       }
208     }
209   }
210
211   /**
212    * Returns the atom specifier meaning all occurrences of the given residue
213    * 
214    * @param residue
215    * @return
216    */
217   protected abstract String getResidueSpec(String residue);
218
219   @Override
220   public List<StructureCommandI> setAttributes(
221           Map<String, Map<Object, AtomSpecModel>> featureValues)
222   {
223     // default does nothing, override where this is implemented
224     return null;
225   }
226 }