Pad gaps when annotation editing
[jalview.git] / src / jalview / commands / EditCommand.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer
3  * Copyright (C) 2007 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18  */
19 package jalview.commands;
20
21 import java.util.*;
22
23 import jalview.datamodel.*;
24
25 /**
26  *
27  * <p>Title: EditCommmand</p>
28  *
29  * <p>Description: Essential information for performing
30  * undo and redo for cut/paste insert/delete gap
31  * which can be stored in the HistoryList </p>
32  *
33  * <p>Copyright: Copyright (c) 2006</p>
34  *
35  * <p>Company: Dundee University</p>
36  *
37  * @author not attributable
38  * @version 1.0
39  */
40 public class EditCommand
41     implements CommandI
42 {
43   public static final int INSERT_GAP = 0;
44   public static final int DELETE_GAP = 1;
45   public static final int CUT = 2;
46   public static final int PASTE = 3;
47
48   Edit[] edits;
49
50   String description;
51
52   public EditCommand()
53   {}
54
55   public EditCommand(String description)
56   {
57     this.description = description;
58   }
59
60   public EditCommand(String description,
61                      int command,
62                      SequenceI[] seqs,
63                      int position,
64                      int number,
65                      AlignmentI al)
66   {
67     this.description = description;
68     if (command == CUT || command == PASTE)
69     {
70       edits = new Edit[]
71           {
72           new Edit(command, seqs, position, number, al)};
73     }
74
75     performEdit(0);
76   }
77
78   final public String getDescription()
79   {
80     return description;
81   }
82
83   public int getSize()
84   {
85     return edits == null ? 0 : edits.length;
86   }
87
88   final public AlignmentI getAlignment()
89   {
90     return edits[0].al;
91   }
92
93   final public void appendEdit(int command,
94                                SequenceI[] seqs,
95                                int position,
96                                int number,
97                                AlignmentI al,
98                                boolean performEdit)
99   {
100     Edit edit = new Edit(command, seqs, position, number, al.getGapCharacter());
101     if (al.getHeight() == seqs.length)
102     {
103       edit.al = al;
104       edit.fullAlignmentHeight = true;
105     }
106
107     if (edits != null)
108     {
109       Edit[] temp = new Edit[edits.length + 1];
110       System.arraycopy(edits, 0, temp, 0, edits.length);
111       edits = temp;
112       edits[edits.length - 1] = edit;
113     }
114     else
115     {
116       edits = new Edit[]
117           {
118           edit};
119     }
120
121     if (performEdit)
122     {
123       performEdit(edits.length - 1);
124     }
125   }
126
127   final void performEdit(int commandIndex)
128   {
129     int eSize = edits.length;
130     for (int e = commandIndex; e < eSize; e++)
131     {
132       if (edits[e].command == INSERT_GAP)
133       {
134         insertGap(edits[e]);
135       }
136       else if (edits[e].command == DELETE_GAP)
137       {
138         deleteGap(edits[e]);
139       }
140       else if (edits[e].command == CUT)
141       {
142         cut(edits[e]);
143       }
144       else if (edits[e].command == PASTE)
145       {
146         paste(edits[e]);
147       }
148     }
149   }
150
151   final public void doCommand()
152   {
153     performEdit(0);
154   }
155
156   final public void undoCommand()
157   {
158     int e = 0, eSize = edits.length;
159     for (e = eSize - 1; e > -1; e--)
160     {
161       if (edits[e].command == INSERT_GAP)
162       {
163         deleteGap(edits[e]);
164       }
165       else if (edits[e].command == DELETE_GAP)
166       {
167         insertGap(edits[e]);
168       }
169       else if (edits[e].command == CUT)
170       {
171         paste(edits[e]);
172       }
173       else if (edits[e].command == PASTE)
174       {
175         cut(edits[e]);
176       }
177     }
178   }
179
180   final void insertGap(Edit command)
181   {
182     for (int s = 0; s < command.seqs.length; s++)
183     {
184       command.seqs[s].insertCharAt(command.position,
185                                    command.number,
186                                    command.gapChar);
187     }
188
189     adjustAnnotations(command, true, false);
190   }
191
192   final void deleteGap(Edit command)
193   {
194     for (int s = 0; s < command.seqs.length; s++)
195     {
196       command.seqs[s].deleteChars(command.position,
197                                   command.position + command.number);
198     }
199
200     adjustAnnotations(command, false, false);
201   }
202
203   void cut(Edit command)
204   {
205     boolean seqDeleted=false;
206     command.string = new char[command.seqs.length][];
207
208     for (int i = 0; i < command.seqs.length; i++)
209     {
210       if (command.seqs[i].getLength() > command.position)
211       {
212         command.string[i] = command.seqs[i].getSequence(command.position,
213             command.position + command.number);
214
215         if (command.seqs[i].getDatasetSequence() != null
216             || command.seqs[i].getSequenceFeatures() != null)
217         {
218           for (int s = command.position; s < command.position + command.number;
219                s++)
220           {
221             if (jalview.schemes.ResidueProperties
222                 .aaIndex[command.seqs[i].getCharAt(s)] != 23)
223             {
224               adjustFeatures(command, i,
225                              command.seqs[i].findPosition(command.position),
226                              command.seqs[i].findPosition(command.position +
227                   command.number),
228                              false);
229               break;
230             }
231           }
232         }
233         command.seqs[i].deleteChars(command.position,
234                                     command.position + command.number);
235       }
236
237       if (command.seqs[i].getLength() < 1)
238       {
239         command.al.deleteSequence(command.seqs[i]);
240         seqDeleted=true;
241       }
242     }
243
244     adjustAnnotations(command, false, seqDeleted);
245   }
246
247   void paste(Edit command)
248   {
249     StringBuffer tmp;
250     boolean newDSNeeded;
251     boolean seqWasDeleted=false;
252     int start = 0, end = 0;
253
254     for (int i = 0; i < command.seqs.length; i++)
255     {
256       newDSNeeded = false;
257       if (command.seqs[i].getLength() < 1)
258       {
259         // ie this sequence was deleted, we need to
260         // read it to the alignment
261         if (command.alIndex[i] < command.al.getHeight())
262         {
263           command.al.getSequences().insertElementAt(command.seqs[i],
264               command.alIndex[i]);
265         }
266         else
267         {
268           command.al.addSequence(command.seqs[i]);
269         }
270         seqWasDeleted=true;
271       }
272       tmp = new StringBuffer();
273       tmp.append(command.seqs[i].getSequence());
274
275       if (command.string != null && command.string[i] != null)
276       {
277         if (command.position >= tmp.length())
278         {
279           //This occurs if padding is on, and residues
280           //are removed from end of alignment
281           int length = command.position - tmp.length();
282           while (length > 0)
283           {
284             tmp.append(command.gapChar);
285             length--;
286           }
287         }
288         tmp.insert(command.position, command.string[i]);
289
290         for (int s = 0; s < command.string[i].length; s++)
291         {
292           if (jalview.schemes.ResidueProperties.aaIndex[command.string[i][s]] !=
293               23)
294           {
295             newDSNeeded = true;
296             start = command.seqs[i].findPosition(command.position);
297             end = command.seqs[i].findPosition(command.position +
298                                                command.number);
299             break;
300           }
301         }
302         command.string[i] = null;
303       }
304
305       command.seqs[i].setSequence(tmp.toString());
306
307       if (newDSNeeded)
308       {
309         if (command.seqs[i].getDatasetSequence() != null)
310         { // use new ds mechanism here
311           Sequence ds = new Sequence(command.seqs[i].getName(),
312                                      jalview.analysis.AlignSeq.extractGaps(
313                                          jalview.util.Comparison.GapChars,
314                                          command.seqs[i].getSequenceAsString()
315                                      ),
316                                      command.seqs[i].getStart(),
317                                      command.seqs[i].getEnd());
318           ds.setDescription(command.seqs[i].getDescription());
319           command.seqs[i].setDatasetSequence(ds);
320         }
321
322         adjustFeatures(command, i, start, end, true);
323       }
324     }
325     adjustAnnotations(command, true, seqWasDeleted);
326
327     command.string = null;
328   }
329
330   final void adjustAnnotations(Edit command, boolean insert, boolean modifyVisibility)
331   {
332
333     AlignmentAnnotation[] annotations = null;
334
335     if (modifyVisibility && !insert)
336     {
337       // only occurs if a sequence was added or deleted.
338       command.deletedAnnotationRows = new Hashtable();
339     }
340     if (command.fullAlignmentHeight)
341     {
342       annotations = command.al.getAlignmentAnnotation();
343     }
344     else
345     {
346       int aSize = 0;
347       AlignmentAnnotation[] tmp;
348       for (int s = 0; s < command.seqs.length; s++)
349       {
350         if (modifyVisibility)
351         {
352           // Rows are only removed or added to sequence object.
353           if (!insert) {
354             // remove rows
355             tmp = command.seqs[s].getAnnotation();
356             if (tmp!=null) {
357               command.deletedAnnotationRows.put(command.seqs[s], tmp);
358               for (int aa =0; aa<tmp.length; aa++)
359               {
360                 command.al.deleteAnnotation(tmp[aa]);
361               }
362               command.seqs[s].setAlignmentAnnotation(null);
363             }
364           } else {
365             // recover rows
366             if (command.deletedAnnotationRows!=null && command.deletedAnnotationRows.containsKey(command.seqs[s]))
367             {
368               AlignmentAnnotation[] revealed = (AlignmentAnnotation[]) command.deletedAnnotationRows.get(command.seqs[s]);
369               command.seqs[s].setAlignmentAnnotation(revealed);
370               if (revealed!=null) {
371                 for (int aa =0; aa<revealed.length; aa++)
372                 {
373                   command.al.addAnnotation(revealed[aa]);
374                 }
375                 for (int aa =0; aa<revealed.length; aa++)
376                 {
377                   command.al.setAnnotationIndex(revealed[aa], aa);
378                 }
379               }
380             }
381           }
382           continue;
383         }
384
385         if (command.seqs[s].getAnnotation() == null)
386         {
387           continue;
388         }
389
390         if (aSize == 0)
391         {
392           annotations = command.seqs[s].getAnnotation();
393         }
394         else
395         {
396           tmp = new AlignmentAnnotation
397               [aSize + command.seqs[s].getAnnotation().length];
398
399           System.arraycopy(annotations, 0, tmp, 0, aSize);
400
401           System.arraycopy(command.seqs[s].getAnnotation(),
402                            0, tmp, aSize,
403                            command.seqs[s].getAnnotation().length);
404
405           annotations = tmp;
406         }
407         aSize = annotations.length;
408       }
409     }
410
411     if (annotations == null)
412     {
413       return;
414     }
415
416     if (!insert)
417     {
418       command.deletedAnnotations = new Hashtable();
419     }
420
421     int aSize;
422     Annotation[] temp;
423     for (int a = 0; a < annotations.length; a++)
424     {
425       if (annotations[a].autoCalculated)
426       {
427         continue;
428       }
429
430       int tSize = 0;
431
432       aSize = annotations[a].annotations.length;
433       if (insert)
434       {
435         temp = new Annotation[aSize + command.number];
436         if(annotations[a].padGaps)
437           for (int aa = 0; aa < temp.length; aa++)
438           {
439             temp[aa] = new Annotation(
440                 command.al.getGapCharacter()+"",
441                 null, ' ', 0);
442           }
443       }
444       else
445       {
446         if (command.position < aSize)
447         {
448           if (command.position + command.number > aSize)
449           {
450             tSize = aSize;
451           }
452           else
453           {
454             tSize = aSize - command.number + command.position;
455           }
456         }
457         else
458         {
459           tSize = aSize;
460         }
461
462         if (tSize < 0)
463         {
464           tSize = aSize;
465         }
466         temp = new Annotation[tSize];
467
468       }
469
470       if (insert)
471       {
472         if (command.position < annotations[a].annotations.length)
473         {
474           System.arraycopy(annotations[a].annotations,
475                            0, temp, 0, command.position);
476
477           if (command.deletedAnnotations != null
478               &&
479               command.deletedAnnotations.containsKey(annotations[a].
480               annotationId))
481           {
482             Annotation[] restore = (Annotation[])
483                 command.deletedAnnotations.get(annotations[a].annotationId);
484
485             System.arraycopy(restore,
486                              0,
487                              temp,
488                              command.position,
489                              command.number);
490
491           }
492
493           System.arraycopy(annotations[a].annotations,
494                            command.position, temp,
495                            command.position + command.number,
496                            aSize - command.position);
497         }
498         else
499         {
500           if (command.deletedAnnotations != null
501               &&
502               command.deletedAnnotations.containsKey(annotations[a].
503               annotationId))
504           {
505             Annotation[] restore = (Annotation[])
506                 command.deletedAnnotations.get(annotations[a].annotationId);
507
508             temp = new Annotation[annotations[a].annotations.length +
509                 restore.length];
510             System.arraycopy(annotations[a].annotations,
511                              0, temp, 0,
512                              annotations[a].annotations.length);
513             System.arraycopy(restore, 0, temp,
514                              annotations[a].annotations.length, restore.length);
515           }
516           else
517           {
518             temp = annotations[a].annotations;
519           }
520         }
521       }
522       else
523       {
524         if (tSize != aSize || command.position < 2)
525         {
526           int copylen = Math.min(command.position, annotations[a].annotations.length);
527           if (copylen>0)
528             System.arraycopy(annotations[a].annotations,
529                            0, temp, 0, copylen); //command.position);
530
531           Annotation[] deleted = new Annotation[command.number];
532           if (copylen>command.position) {
533             copylen = Math.min(command.number, annotations[a].annotations.length-command.position);
534             if (copylen>0)
535             {
536               System.arraycopy(annotations[a].annotations,
537                       command.position, deleted, 0, copylen); // command.number);
538             }
539           }
540
541           command.deletedAnnotations.put(annotations[a].annotationId,
542                                          deleted);
543           if (annotations[a].annotations.length>command.position+command.number) {
544             System.arraycopy(annotations[a].annotations,
545                            command.position + command.number,
546                            temp, command.position,
547                            annotations[a].annotations.length - command.position - command.number); // aSize
548           }
549         }
550         else
551         {
552           int dSize = aSize - command.position;
553
554           if (dSize > 0)
555           {
556             Annotation[] deleted = new Annotation[command.number];
557             System.arraycopy(annotations[a].annotations,
558                              command.position, deleted, 0, dSize);
559
560             command.deletedAnnotations.put(annotations[a].annotationId,
561                                            deleted);
562
563             tSize = Math.min(annotations[a].annotations.length,
564                              command.position);
565             temp = new Annotation[tSize];
566             System.arraycopy(annotations[a].annotations,
567                              0, temp, 0, tSize);
568           }
569           else
570           {
571             temp = annotations[a].annotations;
572           }
573         }
574       }
575
576       annotations[a].annotations = temp;
577     }
578   }
579
580   final void adjustFeatures(Edit command, int index, int i, int j,
581                             boolean insert)
582   {
583     SequenceI seq = command.seqs[index];
584     SequenceI sequence = seq.getDatasetSequence();
585     if (sequence == null)
586     {
587       sequence = seq;
588     }
589
590     if (insert)
591     {
592       if (command.editedFeatures != null
593           && command.editedFeatures.containsKey(seq))
594       {
595         sequence.setSequenceFeatures(
596             (SequenceFeature[]) command.editedFeatures.get(seq)
597             );
598       }
599
600       return;
601     }
602
603     SequenceFeature[] sf = sequence.getSequenceFeatures();
604
605     if (sf == null)
606     {
607       return;
608     }
609
610     SequenceFeature[] oldsf = new SequenceFeature[sf.length];
611
612     int cSize = j - i;
613
614     for (int s = 0; s < sf.length; s++)
615     {
616       SequenceFeature copy = new SequenceFeature(sf[s]);
617
618       oldsf[s] = copy;
619
620       if (sf[s].getEnd() < i)
621       {
622         continue;
623       }
624
625       if (sf[s].getBegin() > j)
626       {
627         sf[s].setBegin(copy.getBegin() - cSize);
628         sf[s].setEnd(copy.getEnd() - cSize);
629         continue;
630       }
631
632       if (sf[s].getBegin() >= i)
633       {
634         sf[s].setBegin(i);
635       }
636
637       if (sf[s].getEnd() < j)
638       {
639         sf[s].setEnd(j - 1);
640       }
641
642       sf[s].setEnd(sf[s].getEnd() - (cSize));
643
644       if (sf[s].getBegin() > sf[s].getEnd())
645       {
646         sequence.deleteFeature(sf[s]);
647       }
648     }
649
650     if (command.editedFeatures == null)
651     {
652       command.editedFeatures = new Hashtable();
653     }
654
655     command.editedFeatures.put(seq, oldsf);
656
657   }
658
659   class Edit
660   {
661     boolean fullAlignmentHeight = false;
662     Hashtable deletedAnnotationRows;
663     Hashtable deletedAnnotations;
664     Hashtable editedFeatures;
665     AlignmentI al;
666     int command;
667     char[][] string;
668     SequenceI[] seqs;
669     int[] alIndex;
670     int position, number;
671     char gapChar;
672
673     Edit(int command,
674          SequenceI[] seqs,
675          int position,
676          int number,
677          char gapChar)
678     {
679       this.command = command;
680       this.seqs = seqs;
681       this.position = position;
682       this.number = number;
683       this.gapChar = gapChar;
684     }
685
686     Edit(int command,
687          SequenceI[] seqs,
688          int position,
689          int number,
690          AlignmentI al)
691     {
692       this.gapChar = al.getGapCharacter();
693       this.command = command;
694       this.seqs = seqs;
695       this.position = position;
696       this.number = number;
697       this.al = al;
698
699       alIndex = new int[seqs.length];
700       for (int i = 0; i < seqs.length; i++)
701       {
702         alIndex[i] = al.findIndex(seqs[i]);
703       }
704
705       fullAlignmentHeight = (al.getHeight() == seqs.length);
706     }
707   }
708
709 }