support for multiple accession retrieval from a database source
[jalview.git] / src / jalview / datamodel / Alignment.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.4)
3  * Copyright (C) 2008 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.datamodel;
20
21 import java.util.*;
22
23 import jalview.analysis.*;
24
25 /**
26  * Data structure to hold and manipulate a multiple sequence alignment
27  */
28 public class Alignment implements AlignmentI
29 {
30   protected Alignment dataset;
31
32   protected Vector sequences;
33
34   protected Vector groups = new Vector();
35
36   protected char gapCharacter = '-';
37
38   protected int type = NUCLEOTIDE;
39
40   public static final int PROTEIN = 0;
41
42   public static final int NUCLEOTIDE = 1;
43
44   /** DOCUMENT ME!! */
45   public AlignmentAnnotation[] annotations;
46
47   HiddenSequences hiddenSequences = new HiddenSequences(this);
48
49   public Hashtable alignmentProperties;
50
51   private void initAlignment(SequenceI[] seqs)
52   {
53     int i = 0;
54
55     if (jalview.util.Comparison.isNucleotide(seqs))
56     {
57       type = NUCLEOTIDE;
58     }
59     else
60     {
61       type = PROTEIN;
62     }
63
64     sequences = new Vector();
65
66     for (i = 0; i < seqs.length; i++)
67     {
68       sequences.addElement(seqs[i]);
69     }
70
71   }
72
73   /**
74    * Make an alignment from an array of Sequences.
75    * 
76    * @param sequences
77    */
78   public Alignment(SequenceI[] seqs)
79   {
80     initAlignment(seqs);
81   }
82
83   /**
84    * Make a new alignment from an array of SeqCigars
85    * 
86    * @param seqs
87    *                SeqCigar[]
88    */
89   public Alignment(SeqCigar[] alseqs)
90   {
91     SequenceI[] seqs = SeqCigar.createAlignmentSequences(alseqs,
92             gapCharacter, new ColumnSelection(), null);
93     initAlignment(seqs);
94   }
95
96   /**
97    * Make a new alignment from an CigarArray JBPNote - can only do this when
98    * compactAlignment does not contain hidden regions. JBPNote - must also check
99    * that compactAlignment resolves to a set of SeqCigars - or construct them
100    * appropriately.
101    * 
102    * @param compactAlignment
103    *                CigarArray
104    */
105   public static AlignmentI createAlignment(CigarArray compactAlignment)
106   {
107     throw new Error("Alignment(CigarArray) not yet implemented");
108     // this(compactAlignment.refCigars);
109   }
110
111   /**
112    * DOCUMENT ME!
113    * 
114    * @return DOCUMENT ME!
115    */
116   public Vector getSequences()
117   {
118     return sequences;
119   }
120
121   public SequenceI[] getSequencesArray()
122   {
123     if (sequences == null)
124       return null;
125     SequenceI[] reply = new SequenceI[sequences.size()];
126     for (int i = 0; i < sequences.size(); i++)
127     {
128       reply[i] = (SequenceI) sequences.elementAt(i);
129     }
130     return reply;
131   }
132
133   /**
134    * DOCUMENT ME!
135    * 
136    * @param i
137    *                DOCUMENT ME!
138    * 
139    * @return DOCUMENT ME!
140    */
141   public SequenceI getSequenceAt(int i)
142   {
143     if (i < sequences.size())
144     {
145       return (SequenceI) sequences.elementAt(i);
146     }
147
148     return null;
149   }
150
151   /**
152    * Adds a sequence to the alignment. Recalculates maxLength and size.
153    * 
154    * @param snew
155    */
156   public void addSequence(SequenceI snew)
157   {
158     if (dataset != null)
159     {
160       // maintain dataset integrity
161       if (snew.getDatasetSequence() != null)
162       {
163         getDataset().addSequence(snew.getDatasetSequence());
164       }
165       else
166       {
167         // derive new sequence
168         SequenceI adding = snew.deriveSequence();
169         getDataset().addSequence(adding.getDatasetSequence());
170         snew = adding;
171       }
172     }
173     if (sequences == null)
174     {
175       initAlignment(new SequenceI[]
176       { snew });
177     }
178     else
179     {
180       sequences.addElement(snew);
181     }
182     if (hiddenSequences != null)
183       hiddenSequences.adjustHeightSequenceAdded();
184   }
185
186   /**
187    * Adds a sequence to the alignment. Recalculates maxLength and size.
188    * 
189    * @param snew
190    */
191   public void setSequenceAt(int i, SequenceI snew)
192   {
193     SequenceI oldseq = getSequenceAt(i);
194     deleteSequence(oldseq);
195
196     sequences.setElementAt(snew, i);
197   }
198
199   /**
200    * DOCUMENT ME!
201    * 
202    * @return DOCUMENT ME!
203    */
204   public Vector getGroups()
205   {
206     return groups;
207   }
208
209   public void finalize()
210   {
211     if (getDataset() != null)
212       getDataset().removeAlignmentRef();
213
214     dataset = null;
215     sequences = null;
216     groups = null;
217     annotations = null;
218     hiddenSequences = null;
219   }
220
221   /**
222    * decrement the alignmentRefs counter by one and call finalize if it goes to
223    * zero.
224    */
225   private void removeAlignmentRef()
226   {
227     if (--alignmentRefs == 0)
228     {
229       finalize();
230     }
231   }
232
233   /**
234    * DOCUMENT ME!
235    * 
236    * @param s
237    *                DOCUMENT ME!
238    */
239   public void deleteSequence(SequenceI s)
240   {
241     deleteSequence(findIndex(s));
242   }
243
244   /**
245    * DOCUMENT ME!
246    * 
247    * @param i
248    *                DOCUMENT ME!
249    */
250   public void deleteSequence(int i)
251   {
252     if (i > -1 && i < getHeight())
253     {
254       sequences.removeElementAt(i);
255       hiddenSequences.adjustHeightSequenceDeleted(i);
256     }
257   }
258
259   /**    */
260   public SequenceGroup findGroup(SequenceI s)
261   {
262     for (int i = 0; i < this.groups.size(); i++)
263     {
264       SequenceGroup sg = (SequenceGroup) groups.elementAt(i);
265
266       if (sg.getSequences(null).contains(s))
267       {
268         return sg;
269       }
270     }
271
272     return null;
273   }
274
275   /**
276    * DOCUMENT ME!
277    * 
278    * @param s
279    *                DOCUMENT ME!
280    * 
281    * @return DOCUMENT ME!
282    */
283   public SequenceGroup[] findAllGroups(SequenceI s)
284   {
285     Vector temp = new Vector();
286
287     int gSize = groups.size();
288     for (int i = 0; i < gSize; i++)
289     {
290       SequenceGroup sg = (SequenceGroup) groups.elementAt(i);
291       if (sg == null || sg.getSequences(null) == null)
292       {
293         this.deleteGroup(sg);
294         gSize--;
295         continue;
296       }
297
298       if (sg.getSequences(null).contains(s))
299       {
300         temp.addElement(sg);
301       }
302     }
303
304     SequenceGroup[] ret = new SequenceGroup[temp.size()];
305
306     for (int i = 0; i < temp.size(); i++)
307     {
308       ret[i] = (SequenceGroup) temp.elementAt(i);
309     }
310
311     return ret;
312   }
313
314   /**    */
315   public void addGroup(SequenceGroup sg)
316   {
317     if (!groups.contains(sg))
318     {
319       if (hiddenSequences.getSize() > 0)
320       {
321         int i, iSize = sg.getSize();
322         for (i = 0; i < iSize; i++)
323         {
324           if (!sequences.contains(sg.getSequenceAt(i)))
325           {
326             sg.deleteSequence(sg.getSequenceAt(i), false);
327             iSize--;
328             i--;
329           }
330         }
331
332         if (sg.getSize() < 1)
333         {
334           return;
335         }
336       }
337
338       groups.addElement(sg);
339     }
340   }
341
342   /**
343    * DOCUMENT ME!
344    */
345   public void deleteAllGroups()
346   {
347     groups.removeAllElements();
348   }
349
350   /**    */
351   public void deleteGroup(SequenceGroup g)
352   {
353     if (groups.contains(g))
354     {
355       groups.removeElement(g);
356     }
357   }
358
359   /**    */
360   public SequenceI findName(String name)
361   {
362     return findName(name, false);
363   }
364
365   /*
366    * (non-Javadoc)
367    * 
368    * @see jalview.datamodel.AlignmentI#findName(java.lang.String, boolean)
369    */
370   public SequenceI findName(String token, boolean b)
371   {
372     return findName(null, token, b);
373   }
374
375   /*
376    * (non-Javadoc)
377    * 
378    * @see jalview.datamodel.AlignmentI#findName(SequenceI, java.lang.String,
379    *      boolean)
380    */
381   public SequenceI findName(SequenceI startAfter, String token, boolean b)
382   {
383
384     int i = 0;
385     SequenceI sq = null;
386     String sqname = null;
387     if (startAfter != null)
388     {
389       // try to find the sequence in the alignment
390       boolean matched = false;
391       while (i < sequences.size())
392       {
393         if (getSequenceAt(i++) == startAfter)
394         {
395           matched = true;
396           break;
397         }
398       }
399       if (!matched)
400       {
401         i = 0;
402       }
403     }
404     while (i < sequences.size())
405     {
406       sq = getSequenceAt(i);
407       sqname = sq.getName();
408       if (sqname.equals(token) // exact match
409               || (b && // allow imperfect matches - case varies
410               (sqname.equalsIgnoreCase(token))))
411       {
412         return getSequenceAt(i);
413       }
414
415       i++;
416     }
417
418     return null;
419   }
420
421   public SequenceI[] findSequenceMatch(String name)
422   {
423     Vector matches = new Vector();
424     int i = 0;
425
426     while (i < sequences.size())
427     {
428       if (getSequenceAt(i).getName().equals(name))
429       {
430         matches.addElement(getSequenceAt(i));
431       }
432       i++;
433     }
434
435     SequenceI[] result = new SequenceI[matches.size()];
436     for (i = 0; i < result.length; i++)
437     {
438       result[i] = (SequenceI) matches.elementAt(i);
439     }
440
441     return result;
442
443   }
444
445   /*
446    * (non-Javadoc)
447    * 
448    * @see jalview.datamodel.AlignmentI#findIndex(jalview.datamodel.SequenceI)
449    */
450   public int findIndex(SequenceI s)
451   {
452     int i = 0;
453
454     while (i < sequences.size())
455     {
456       if (s == getSequenceAt(i))
457       {
458         return i;
459       }
460
461       i++;
462     }
463
464     return -1;
465   }
466
467   /*
468    * (non-Javadoc)
469    * 
470    * @see jalview.datamodel.AlignmentI#findIndex(jalview.datamodel.SearchResults)
471    */
472   public int findIndex(SearchResults results)
473   {
474     int i = 0;
475
476     while (i < sequences.size())
477     {
478       if (results.involvesSequence(getSequenceAt(i)))
479       {
480         return i;
481       }
482       i++;
483     }
484     return -1;
485   }
486
487   /**
488    * DOCUMENT ME!
489    * 
490    * @return DOCUMENT ME!
491    */
492   public int getHeight()
493   {
494     return sequences.size();
495   }
496
497   /**
498    * DOCUMENT ME!
499    * 
500    * @return DOCUMENT ME!
501    */
502   public int getWidth()
503   {
504     int maxLength = -1;
505
506     for (int i = 0; i < sequences.size(); i++)
507     {
508       if (getSequenceAt(i).getLength() > maxLength)
509       {
510         maxLength = getSequenceAt(i).getLength();
511       }
512     }
513
514     return maxLength;
515   }
516
517   /**
518    * DOCUMENT ME!
519    * 
520    * @param gc
521    *                DOCUMENT ME!
522    */
523   public void setGapCharacter(char gc)
524   {
525     gapCharacter = gc;
526
527     for (int i = 0; i < sequences.size(); i++)
528     {
529       Sequence seq = (Sequence) sequences.elementAt(i);
530       seq.setSequence(seq.getSequenceAsString().replace('.', gc).replace(
531               '-', gc).replace(' ', gc));
532     }
533   }
534
535   /**
536    * DOCUMENT ME!
537    * 
538    * @return DOCUMENT ME!
539    */
540   public char getGapCharacter()
541   {
542     return gapCharacter;
543   }
544
545   /**
546    * DOCUMENT ME!
547    * 
548    * @return DOCUMENT ME!
549    */
550   public boolean isAligned()
551   {
552     int width = getWidth();
553
554     for (int i = 0; i < sequences.size(); i++)
555     {
556       if (getSequenceAt(i).getLength() != width)
557       {
558         return false;
559       }
560     }
561
562     return true;
563   }
564
565   /*
566    * (non-Javadoc)
567    * 
568    * @see jalview.datamodel.AlignmentI#deleteAnnotation(jalview.datamodel.AlignmentAnnotation)
569    */
570   public boolean deleteAnnotation(AlignmentAnnotation aa)
571   {
572     int aSize = 1;
573
574     if (annotations != null)
575     {
576       aSize = annotations.length;
577     }
578
579     if (aSize < 1)
580     {
581       return false;
582     }
583
584     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize - 1];
585
586     boolean swap = false;
587     int tIndex = 0;
588
589     for (int i = 0; i < aSize; i++)
590     {
591       if (annotations[i] == aa)
592       {
593         swap = true;
594         continue;
595       }
596       if (tIndex < temp.length)
597         temp[tIndex++] = annotations[i];
598     }
599
600     if (swap)
601     {
602       annotations = temp;
603       if (aa.sequenceRef != null)
604         aa.sequenceRef.removeAlignmentAnnotation(aa);
605     }
606     return swap;
607   }
608
609   /**
610    * DOCUMENT ME!
611    * 
612    * @param aa
613    *                DOCUMENT ME!
614    */
615   public void addAnnotation(AlignmentAnnotation aa)
616   {
617     int aSize = 1;
618     if (annotations != null)
619     {
620       aSize = annotations.length + 1;
621     }
622
623     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize];
624
625     temp[aSize - 1] = aa;
626
627     int i = 0;
628
629     if (aSize > 1)
630     {
631       for (i = 0; i < (aSize - 1); i++)
632       {
633         temp[i] = annotations[i];
634       }
635     }
636
637     annotations = temp;
638   }
639
640   public void setAnnotationIndex(AlignmentAnnotation aa, int index)
641   {
642     if (aa == null || annotations == null || annotations.length - 1 < index)
643     {
644       return;
645     }
646
647     int aSize = annotations.length;
648     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize];
649
650     temp[index] = aa;
651
652     for (int i = 0; i < aSize; i++)
653     {
654       if (i == index)
655       {
656         continue;
657       }
658
659       if (i < index)
660       {
661         temp[i] = annotations[i];
662       }
663       else
664       {
665         temp[i] = annotations[i - 1];
666       }
667     }
668
669     annotations = temp;
670   }
671
672   /**
673    * DOCUMENT ME!
674    * 
675    * @return DOCUMENT ME!
676    */
677   public AlignmentAnnotation[] getAlignmentAnnotation()
678   {
679     return annotations;
680   }
681
682   public void setNucleotide(boolean b)
683   {
684     if (b)
685     {
686       type = NUCLEOTIDE;
687     }
688     else
689     {
690       type = PROTEIN;
691     }
692   }
693
694   public boolean isNucleotide()
695   {
696     if (type == NUCLEOTIDE)
697     {
698       return true;
699     }
700     else
701     {
702       return false;
703     }
704   }
705
706   public void setDataset(Alignment data)
707   {
708     if (dataset == null && data == null)
709     {
710       // Create a new dataset for this alignment.
711       // Can only be done once, if dataset is not null
712       // This will not be performed
713       SequenceI[] seqs = new SequenceI[getHeight()];
714       SequenceI currentSeq;
715       for (int i = 0; i < getHeight(); i++)
716       {
717         currentSeq = getSequenceAt(i);
718         if (currentSeq.getDatasetSequence() != null)
719         {
720           seqs[i] = (Sequence) currentSeq.getDatasetSequence();
721         }
722         else
723         {
724           seqs[i] = currentSeq.createDatasetSequence();
725         }
726       }
727
728       dataset = new Alignment(seqs);
729     }
730     else if (dataset == null && data != null)
731     {
732       dataset = data;
733     }
734     dataset.addAlignmentRef();
735   }
736
737   /**
738    * reference count for number of alignments referencing this one.
739    */
740   int alignmentRefs = 0;
741
742   /**
743    * increase reference count to this alignment.
744    */
745   private void addAlignmentRef()
746   {
747     alignmentRefs++;
748   }
749
750   public Alignment getDataset()
751   {
752     return dataset;
753   }
754
755   public boolean padGaps()
756   {
757     boolean modified = false;
758
759     // Remove excess gaps from the end of alignment
760     int maxLength = -1;
761
762     SequenceI current;
763     for (int i = 0; i < sequences.size(); i++)
764     {
765       current = getSequenceAt(i);
766       for (int j = current.getLength(); j > maxLength; j--)
767       {
768         if (j > maxLength
769                 && !jalview.util.Comparison.isGap(current.getCharAt(j)))
770         {
771           maxLength = j;
772           break;
773         }
774       }
775     }
776
777     maxLength++;
778
779     int cLength;
780     for (int i = 0; i < sequences.size(); i++)
781     {
782       current = getSequenceAt(i);
783       cLength = current.getLength();
784
785       if (cLength < maxLength)
786       {
787         current.insertCharAt(cLength, maxLength - cLength, gapCharacter);
788         modified = true;
789       }
790       else if (current.getLength() > maxLength)
791       {
792         current.deleteChars(maxLength, current.getLength());
793       }
794     }
795     return modified;
796   }
797
798   public HiddenSequences getHiddenSequences()
799   {
800     return hiddenSequences;
801   }
802
803   public CigarArray getCompactAlignment()
804   {
805     SeqCigar alseqs[] = new SeqCigar[sequences.size()];
806     for (int i = 0; i < sequences.size(); i++)
807     {
808       alseqs[i] = new SeqCigar((SequenceI) sequences.elementAt(i));
809     }
810     CigarArray cal = new CigarArray(alseqs);
811     cal.addOperation(CigarArray.M, getWidth());
812     return cal;
813   }
814
815   public void setProperty(Object key, Object value)
816   {
817     if (alignmentProperties == null)
818       alignmentProperties = new Hashtable();
819
820     alignmentProperties.put(key, value);
821   }
822
823   public Object getProperty(Object key)
824   {
825     if (alignmentProperties != null)
826       return alignmentProperties.get(key);
827     else
828       return null;
829   }
830
831   public Hashtable getProperties()
832   {
833     return alignmentProperties;
834   }
835
836   AlignedCodonFrame[] codonFrameList = null;
837
838   /*
839    * (non-Javadoc)
840    * 
841    * @see jalview.datamodel.AlignmentI#addCodonFrame(jalview.datamodel.AlignedCodonFrame)
842    */
843   public void addCodonFrame(AlignedCodonFrame codons)
844   {
845     if (codons == null)
846       return;
847     if (codonFrameList == null)
848     {
849       codonFrameList = new AlignedCodonFrame[]
850       { codons };
851       return;
852     }
853     AlignedCodonFrame[] t = new AlignedCodonFrame[codonFrameList.length + 1];
854     System.arraycopy(codonFrameList, 0, t, 0, codonFrameList.length);
855     t[codonFrameList.length] = codons;
856     codonFrameList = t;
857   }
858
859   /*
860    * (non-Javadoc)
861    * 
862    * @see jalview.datamodel.AlignmentI#getCodonFrame(int)
863    */
864   public AlignedCodonFrame getCodonFrame(int index)
865   {
866     return codonFrameList[index];
867   }
868
869   /*
870    * (non-Javadoc)
871    * 
872    * @see jalview.datamodel.AlignmentI#getCodonFrame(jalview.datamodel.SequenceI)
873    */
874   public AlignedCodonFrame[] getCodonFrame(SequenceI seq)
875   {
876     if (seq == null || codonFrameList == null)
877       return null;
878     Vector cframes = new Vector();
879     for (int f = 0; f < codonFrameList.length; f++)
880     {
881       if (codonFrameList[f].involvesSequence(seq))
882         cframes.addElement(codonFrameList[f]);
883     }
884     if (cframes.size() == 0)
885       return null;
886     AlignedCodonFrame[] cfr = new AlignedCodonFrame[cframes.size()];
887     cframes.copyInto(cfr);
888     return cfr;
889   }
890
891   /*
892    * (non-Javadoc)
893    * 
894    * @see jalview.datamodel.AlignmentI#getCodonFrames()
895    */
896   public AlignedCodonFrame[] getCodonFrames()
897   {
898     return codonFrameList;
899   }
900
901   /*
902    * (non-Javadoc)
903    * 
904    * @see jalview.datamodel.AlignmentI#removeCodonFrame(jalview.datamodel.AlignedCodonFrame)
905    */
906   public boolean removeCodonFrame(AlignedCodonFrame codons)
907   {
908     if (codons == null || codonFrameList == null)
909       return false;
910     boolean removed = false;
911     int i = 0, iSize = codonFrameList.length;
912     while (i < iSize)
913     {
914       if (codonFrameList[i] == codons)
915       {
916         removed = true;
917         if (i + 1 < iSize)
918         {
919           System.arraycopy(codonFrameList, i + 1, codonFrameList, i, iSize
920                   - i - 1);
921         }
922         iSize--;
923       }
924       else
925       {
926         i++;
927       }
928     }
929     return removed;
930   }
931
932   public void append(AlignmentI toappend)
933   {
934     // TODO test this method for a future 2.5 release
935     // currently tested for use in jalview.gui.SequenceFetcher
936     boolean samegap = toappend.getGapCharacter()==getGapCharacter();
937     char oldc = toappend.getGapCharacter();
938     boolean hashidden = toappend.getHiddenSequences()!=null && toappend.getHiddenSequences().hiddenSequences!=null;
939     // get all sequences including any hidden ones
940     Vector sqs = (hashidden) ? toappend.getHiddenSequences().getFullAlignment().getSequences() : toappend.getSequences();
941     if (sqs != null)
942     {
943       Enumeration sq = sqs.elements();
944       while (sq.hasMoreElements())
945       {
946         SequenceI addedsq=(SequenceI) sq.nextElement();
947         if (!samegap)
948         {
949           char[] oldseq = addedsq.getSequence();
950           for (int c=0;c<oldseq.length;c++)
951           {
952             if (oldseq[c]==oldc)
953             {
954               oldseq[c] = gapCharacter;
955             }
956           }
957         }
958         addSequence(addedsq);
959       }
960     }
961     AlignmentAnnotation[] alan = toappend.getAlignmentAnnotation();
962     for (int a = 0; alan != null && a < alan.length; a++)
963     {
964       addAnnotation(alan[a]);
965     }
966     AlignedCodonFrame[] acod = toappend.getCodonFrames();
967     for (int a = 0; acod != null && a < acod.length; a++)
968     {
969       this.addCodonFrame(acod[a]);
970     }
971     Vector sg = toappend.getGroups();
972     if (sg != null)
973     {
974       Enumeration el = sg.elements();
975       while (el.hasMoreElements())
976       {
977         addGroup((SequenceGroup) el.nextElement());
978       }
979     }
980     if (toappend.getHiddenSequences()!=null)
981     {
982       HiddenSequences hs = toappend.getHiddenSequences();
983       if (hiddenSequences==null)
984       {
985         hiddenSequences = new HiddenSequences(this);
986       }
987       if (hs.hiddenSequences!=null)
988       {
989         for (int s=0;s<hs.hiddenSequences.length; s++)
990         {
991           // hide the newly appended sequence in the alignment
992           if (hs.hiddenSequences[s]!=null)
993           {
994             hiddenSequences.hideSequence(hs.hiddenSequences[s]);
995           }
996         }
997       }
998     }
999     if (toappend.getProperties()!=null)
1000     {
1001       // we really can't do very much here - just try to concatenate strings where property collisions occur.
1002       Enumeration key = toappend.getProperties().keys();
1003       while (key.hasMoreElements())
1004       {
1005         Object k = key.nextElement();
1006         Object ourval = this.getProperty(k);
1007         Object toapprop = toappend.getProperty(k);
1008         if (ourval!=null)
1009         {
1010           if (ourval.getClass().equals(toapprop.getClass()) && !ourval.equals(toapprop))
1011           {
1012             if (ourval instanceof String)
1013             {
1014               // append strings
1015               this.setProperty(k, ((String) ourval)+"; "+((String) toapprop));
1016             } else {
1017               if (ourval instanceof Vector)
1018               {
1019                 // append vectors
1020                 Enumeration theirv = ((Vector) toapprop).elements();
1021                 while (theirv.hasMoreElements())
1022                 {
1023                   ((Vector)ourval).addElement(theirv);
1024                 }
1025               }
1026             }
1027           }
1028         } else {
1029           // just add new property directly
1030           setProperty(k, toapprop);
1031         }
1032        
1033       }
1034     }
1035   }
1036
1037 }