inprogress
[jalview.git] / forester / java / src / org / forester / archaeopteryx / NodeEditPanel.java
1 // $Id:
2 // FORESTER -- software libraries and applications
3 // for evolutionary biology research and applications.
4 //
5 // Copyright (C) 2008-2009 Christian M. Zmasek
6 // Copyright (C) 2008-2009 Burnham Institute for Medical Research
7 // All rights reserved
8 //
9 // This library is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU Lesser General Public
11 // License as published by the Free Software Foundation; either
12 // version 2.1 of the License, or (at your option) any later version.
13 //
14 // This library is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 // Lesser General Public License for more details.
18 //
19 // You should have received a copy of the GNU Lesser General Public
20 // License along with this library; if not, write to the Free Software
21 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 //
23 // Contact: phylosoft @ gmail . com
24 // WWW: https://sites.google.com/site/cmzmasek/home/software/forester
25
26 package org.forester.archaeopteryx;
27
28 import java.awt.event.KeyEvent;
29 import java.awt.event.KeyListener;
30 import java.math.BigDecimal;
31 import java.net.URL;
32 import java.text.ParseException;
33 import java.util.ArrayList;
34 import java.util.HashMap;
35 import java.util.List;
36 import java.util.Map;
37
38 import javax.swing.JEditorPane;
39 import javax.swing.JOptionPane;
40 import javax.swing.JPanel;
41 import javax.swing.JScrollPane;
42 import javax.swing.JSplitPane;
43 import javax.swing.JTree;
44 import javax.swing.event.TreeSelectionEvent;
45 import javax.swing.event.TreeSelectionListener;
46 import javax.swing.text.Position;
47 import javax.swing.tree.DefaultMutableTreeNode;
48 import javax.swing.tree.TreePath;
49 import javax.swing.tree.TreeSelectionModel;
50
51 import org.forester.archaeopteryx.tools.ImageLoader;
52 import org.forester.io.parsers.phyloxml.PhyloXmlDataFormatException;
53 import org.forester.phylogeny.PhylogenyNode;
54 import org.forester.phylogeny.data.Accession;
55 import org.forester.phylogeny.data.BranchWidth;
56 import org.forester.phylogeny.data.Confidence;
57 import org.forester.phylogeny.data.Date;
58 import org.forester.phylogeny.data.Distribution;
59 import org.forester.phylogeny.data.Event;
60 import org.forester.phylogeny.data.Identifier;
61 import org.forester.phylogeny.data.MultipleUris;
62 import org.forester.phylogeny.data.PhylogenyData;
63 import org.forester.phylogeny.data.PhylogenyDataUtil;
64 import org.forester.phylogeny.data.Point;
65 import org.forester.phylogeny.data.Reference;
66 import org.forester.phylogeny.data.Sequence;
67 import org.forester.phylogeny.data.Taxonomy;
68 import org.forester.phylogeny.data.Uri;
69 import org.forester.util.FailedConditionCheckException;
70 import org.forester.util.ForesterUtil;
71
72 class NodeEditPanel extends JPanel {
73
74     private static final long                            serialVersionUID = 5120159904388100771L;
75     private final JTree                                  _tree;
76     private final JEditorPane                            _pane;
77     private final PhylogenyNode                          _my_node;
78     private final TreePanel                              _tree_panel;
79     private final Map<DefaultMutableTreeNode, TagNumber> _map;
80
81     public NodeEditPanel( final PhylogenyNode phylogeny_node, final TreePanel tree_panel ) {
82         _map = new HashMap<DefaultMutableTreeNode, TagNumber>();
83         _my_node = phylogeny_node;
84         _tree_panel = tree_panel;
85         String node_name = "";
86         if ( !ForesterUtil.isEmpty( phylogeny_node.getName() ) ) {
87             node_name = phylogeny_node.getName() + " ";
88         }
89         final DefaultMutableTreeNode top = new DefaultMutableTreeNode( "Node " + node_name );
90         createNodes( top, phylogeny_node );
91         _tree = new JTree( top );
92         getJTree().setEditable( true );
93         getJTree().setFocusable( true );
94         getJTree().setToggleClickCount( 1 );
95         getJTree().setInvokesStopCellEditing( true );
96         final JScrollPane tree_view = new JScrollPane( getJTree() );
97         _pane = new JEditorPane();
98         _pane.setEditable( true );
99         final JScrollPane data_view = new JScrollPane( _pane );
100         final JSplitPane split_pane = new JSplitPane( JSplitPane.VERTICAL_SPLIT );
101         split_pane.setTopComponent( tree_view );
102         // split_pane.setBottomComponent( data_view );
103         data_view.setMinimumSize( Constants.NODE_PANEL_SPLIT_MINIMUM_SIZE );
104         tree_view.setMinimumSize( Constants.NODE_PANEL_SPLIT_MINIMUM_SIZE );
105         // split_pane.setDividerLocation( 400 );
106         split_pane.setPreferredSize( Constants.NODE_PANEL_SIZE );
107         add( split_pane );
108         getJTree().getSelectionModel().setSelectionMode( TreeSelectionModel.SINGLE_TREE_SELECTION );
109         getJTree().addKeyListener( new KeyListener() {
110
111             @Override
112             public void keyPressed( final KeyEvent e ) {
113                 keyEvent( e );
114             }
115
116             @Override
117             public void keyReleased( final KeyEvent e ) {
118                 keyEvent( e );
119             }
120
121             @Override
122             public void keyTyped( final KeyEvent e ) {
123                 keyEvent( e );
124             }
125         } );
126         for( int i = 0; i < getJTree().getRowCount(); i++ ) {
127             getJTree().expandRow( i );
128         }
129         collapsePath( NodePanel.BASIC );
130         collapsePath( NodePanel.TAXONOMY );
131         collapsePath( NodePanel.SEQUENCE );
132         collapsePath( NodePanel.EVENTS );
133         collapsePath( NodePanel.DATE );
134         collapsePath( NodePanel.DISTRIBUTION );
135         collapsePath( NodePanel.LIT_REFERENCE );
136         getJTree().addTreeSelectionListener( new TreeSelectionListener() {
137
138             @Override
139             public void valueChanged( final TreeSelectionEvent e ) {
140                 final TreePath new_path = e.getNewLeadSelectionPath();
141                 final TreePath old_path = e.getOldLeadSelectionPath();
142                 if ( new_path != null ) {
143                     writeBack( ( DefaultMutableTreeNode ) new_path.getLastPathComponent() );
144                 }
145                 if ( old_path != null ) {
146                     writeBack( ( DefaultMutableTreeNode ) old_path.getLastPathComponent() );
147                 }
148             }
149         } );
150     }
151
152     private void addBasics( final DefaultMutableTreeNode top, final PhylogenyNode phylogeny_node, final String name ) {
153         final DefaultMutableTreeNode category = new DefaultMutableTreeNode( name );
154         top.add( category );
155         addSubelementEditable( category, NodePanel.NODE_NAME, phylogeny_node.getName(), PHYLOXML_TAG.NODE_NAME );
156         String bl = "";
157         if ( phylogeny_node.getDistanceToParent() != PhylogenyDataUtil.BRANCH_LENGTH_DEFAULT ) {
158             bl = ForesterUtil.FORMATTER_6.format( phylogeny_node.getDistanceToParent() );
159         }
160         addSubelementEditable( category, NodePanel.NODE_BRANCH_LENGTH, bl, PHYLOXML_TAG.NODE_BRANCH_LENGTH );
161         int counter = 0;
162         if ( phylogeny_node.getBranchData().isHasConfidences() ) {
163             for( int i = phylogeny_node.getBranchData().getConfidences().size() - 1; i >= 0; i-- ) {
164                 if ( phylogeny_node.getBranchData().getConfidences().get( i ).getValue() == Confidence.CONFIDENCE_DEFAULT_VALUE ) {
165                     phylogeny_node.getBranchData().getConfidences().remove( i );
166                 }
167             }
168             for( final PhylogenyData conf : phylogeny_node.getBranchData().getConfidences() ) {
169                 final Confidence my_conf = ( Confidence ) ( conf );
170                 addSubelementEditable( category,
171                                        NodePanel.CONFIDENCE + " [" + counter + "]",
172                                        ForesterUtil.FORMATTER_6.format( my_conf.getValue() ),
173                                        PHYLOXML_TAG.CONFIDENCE_VALUE,
174                                        NodePanel.CONFIDENCE_TYPE,
175                                        my_conf.getType(),
176                                        PHYLOXML_TAG.CONFIDENCE_TYPE,
177                                        counter++ );
178             }
179         }
180         addSubelementEditable( category,
181                                NodePanel.CONFIDENCE + " [" + counter + "]",
182                                "",
183                                PHYLOXML_TAG.CONFIDENCE_VALUE,
184                                NodePanel.CONFIDENCE_TYPE,
185                                "",
186                                PHYLOXML_TAG.CONFIDENCE_TYPE,
187                                counter );
188         String bw = "1";
189         if ( ( phylogeny_node.getBranchData().getBranchWidth() != null )
190                 && ( phylogeny_node.getBranchData().getBranchWidth().getValue() != BranchWidth.BRANCH_WIDTH_DEFAULT_VALUE ) ) {
191             bw = ForesterUtil.FORMATTER_3.format( phylogeny_node.getBranchData().getBranchWidth().getValue() );
192         }
193         addSubelementEditable( category, NodePanel.NODE_BRANCH_WIDTH, bw, PHYLOXML_TAG.NODE_BRANCH_WIDTH );
194     }
195
196     //    private void addAnnotation( final DefaultMutableTreeNode top, final Annotation ann, final String name ) {
197     //        DefaultMutableTreeNode category;
198     //        category = new DefaultMutableTreeNode( name );
199     //        top.add( category );
200     //        addSubelementEditable( category, "Reference", ann.getRef() , PHYLOXML_TAG.);
201     //        addSubelementEditable( category, "Description", ann.getDesc() , PHYLOXML_TAG.);
202     //        addSubelementEditable( category, "Source", ann.getSource(), PHYLOXML_TAG. );
203     //        addSubelementEditable( category, "Type", ann.getType(), PHYLOXML_TAG. );
204     //        addSubelementEditable( category, "Evidence", ann.getEvidence() , PHYLOXML_TAG.);
205     //        if ( ann.getConfidence() != null ) {
206     //            addSubelementEditable( category, "Confidence", ann.getConfidence().asText().toString() , PHYLOXML_TAG.);
207     //        }
208     //        if ( ann.getProperties() != null ) {
209     //            addProperties( category, ann.getProperties(), "Properties", PHYLOXML_TAG. );
210     //        }
211     //    }
212     //    private void addAnnotations( final DefaultMutableTreeNode top,
213     //                                 final List<PhylogenyData> annotations,
214     //                                 final DefaultMutableTreeNode category ) {
215     //        if ( ( annotations != null ) && ( annotations.size() > 0 ) ) {
216     //            category.add( new DefaultMutableTreeNode( "Annotations" ) );
217     //            final DefaultMutableTreeNode last = top.getLastLeaf();
218     //            int i = 0;
219     //            for( final PhylogenyData ann : annotations ) {
220     //                addAnnotation( last, ( Annotation ) ann, "Annotation " + ( i++ ) );
221     //            }
222     //        }
223     //    }
224     private void addDate( final DefaultMutableTreeNode top, Date date, final String name ) {
225         if ( date == null ) {
226             date = new Date();
227         }
228         DefaultMutableTreeNode category;
229         category = new DefaultMutableTreeNode( name );
230         top.add( category );
231         addSubelementEditable( category, NodePanel.DATE_DESCRIPTION, date.getDesc(), PHYLOXML_TAG.DATE_DESCRIPTION );
232         addSubelementEditable( category,
233                                NodePanel.DATE_VALUE,
234                                String.valueOf( date.getValue() != null ? date.getValue() : "" ),
235                                PHYLOXML_TAG.DATE_VALUE );
236         addSubelementEditable( category,
237                                NodePanel.DATE_MIN,
238                                String.valueOf( date.getMin() != null ? date.getMin() : "" ),
239                                PHYLOXML_TAG.DATE_MIN );
240         addSubelementEditable( category,
241                                NodePanel.DATE_MAX,
242                                String.valueOf( date.getMax() != null ? date.getMax() : "" ),
243                                PHYLOXML_TAG.DATE_MAX );
244         addSubelementEditable( category, NodePanel.DATE_UNIT, date.getUnit(), PHYLOXML_TAG.DATE_UNIT );
245     }
246
247     private void addDistribution( final DefaultMutableTreeNode top, Distribution dist, final String name ) {
248         if ( dist == null ) {
249             dist = new Distribution( "" );
250         }
251         final DefaultMutableTreeNode category = new DefaultMutableTreeNode( name );
252         top.add( category );
253         Point p0 = null;
254         if ( ( dist.getPoints() != null ) && ( dist.getPoints().size() > 0 ) ) {
255             p0 = dist.getPoints().get( 0 );
256         }
257         else {
258             p0 = new Point();
259         }
260         addSubelementEditable( category, NodePanel.DIST_DESCRIPTION, dist.getDesc(), PHYLOXML_TAG.DIST_DESC );
261         addSubelementEditable( category,
262                                NodePanel.DIST_GEODETIC_DATUM,
263                                p0.getGeodeticDatum(),
264                                PHYLOXML_TAG.DIST_GEODETIC );
265         addSubelementEditable( category,
266                                NodePanel.DIST_LATITUDE,
267                                String.valueOf( p0.getLatitude() != null ? p0.getLatitude() : "" ),
268                                PHYLOXML_TAG.DIST_LAT );
269         addSubelementEditable( category,
270                                NodePanel.DIST_LONGITUDE,
271                                String.valueOf( p0.getLongitude() != null ? p0.getLongitude() : "" ),
272                                PHYLOXML_TAG.DIST_LONG );
273         addSubelementEditable( category,
274                                NodePanel.DIST_ALTITUDE,
275                                String.valueOf( p0.getAltitude() != null ? p0.getAltitude() : "" ),
276                                PHYLOXML_TAG.DIST_ALT );
277         addSubelementEditable( category,
278                                NodePanel.DIST_ALT_UNIT,
279                                String.valueOf( p0.getAltiudeUnit() != null ? p0.getAltiudeUnit() : "" ),
280                                PHYLOXML_TAG.DIST_ALT_UNIT );
281     }
282
283     private void addEvents( final DefaultMutableTreeNode top, Event events, final String name ) {
284         final DefaultMutableTreeNode category = new DefaultMutableTreeNode( name );
285         if ( events == null ) {
286             events = new Event();
287         }
288         top.add( category );
289         addSubelementEditable( category,
290                                NodePanel.EVENTS_DUPLICATIONS,
291                                String.valueOf( events.getNumberOfDuplications() >= 0 ? events.getNumberOfDuplications()
292                                        : 0 ),
293                                PHYLOXML_TAG.EVENTS_DUPLICATIONS );
294         addSubelementEditable( category,
295                                NodePanel.EVENTS_SPECIATIONS,
296                                String.valueOf( events.getNumberOfSpeciations() >= 0 ? events.getNumberOfSpeciations()
297                                        : 0 ),
298                                PHYLOXML_TAG.EVENTS_SPECIATIONS );
299         addSubelementEditable( category,
300                                NodePanel.EVENTS_GENE_LOSSES,
301                                String.valueOf( events.getNumberOfGeneLosses() >= 0 ? events.getNumberOfGeneLosses() : 0 ),
302                                PHYLOXML_TAG.EVENTS_GENE_LOSSES );
303     }
304
305     private void addMapping( final DefaultMutableTreeNode mtn, final TagNumber tag ) {
306         if ( getMap().containsKey( mtn ) ) {
307             throw new IllegalArgumentException( "key " + mtn + " already present" );
308         }
309         if ( getMap().containsValue( tag ) ) {
310             throw new IllegalArgumentException( "value " + tag + " already present" );
311         }
312         getMap().put( mtn, tag );
313     }
314
315     private void addReference( final DefaultMutableTreeNode top, Reference ref, final String name ) {
316         if ( ref == null ) {
317             ref = new Reference( "" );
318         }
319         final DefaultMutableTreeNode category = new DefaultMutableTreeNode( name );
320         top.add( category );
321         addSubelementEditable( category,
322                                NodePanel.LIT_REFERENCE_DESC,
323                                ref.getDescription(),
324                                PHYLOXML_TAG.LIT_REFERENCE_DESC );
325         addSubelementEditable( category, NodePanel.LIT_REFERENCE_DOI, ref.getDoi(), PHYLOXML_TAG.LIT_REFERENCE_DOI );
326     }
327
328     private void addSequence( final DefaultMutableTreeNode top, Sequence seq, final String name ) {
329         if ( seq == null ) {
330             seq = new Sequence();
331         }
332         final DefaultMutableTreeNode category = new DefaultMutableTreeNode( name );
333         top.add( category );
334         Accession acc = seq.getAccession();
335         if ( acc == null ) {
336             acc = new Accession( "", "" );
337         }
338         addSubelementEditable( category, NodePanel.SEQ_NAME, seq.getName(), PHYLOXML_TAG.SEQ_NAME );
339         addSubelementEditable( category, NodePanel.SEQ_SYMBOL, seq.getSymbol(), PHYLOXML_TAG.SEQ_SYMBOL );
340         addSubelementEditable( category, NodePanel.SEQ_GENE_NAME, seq.getGeneName(), PHYLOXML_TAG.SEQ_GENE_NAME );
341         addSubelementEditable( category,
342                                NodePanel.SEQ_ACCESSION,
343                                acc.getValue(),
344                                PHYLOXML_TAG.SEQ_ACC_VALUE,
345                                "Source",
346                                acc.getSource(),
347                                PHYLOXML_TAG.SEQ_ACC_SOURCE );
348         addSubelementEditable( category, NodePanel.SEQ_LOCATION, seq.getLocation(), PHYLOXML_TAG.SEQ_LOCATION );
349         addSubelementEditable( category, NodePanel.SEQ_TYPE, seq.getType(), PHYLOXML_TAG.SEQ_TYPE );
350         addSubelementEditable( category, NodePanel.SEQ_MOL_SEQ, seq.getMolecularSequence(), PHYLOXML_TAG.SEQ_MOL_SEQ );
351         int uri_counter = 0;
352         if ( seq.getUris() != null ) {
353             for( final Uri uri : seq.getUris() ) {
354                 if ( uri != null ) {
355                     addSubelementEditable( category, NodePanel.SEQ_URI + " [" + uri_counter + "]", uri.getValue()
356                             .toString(), PHYLOXML_TAG.SEQ_URI, uri_counter++ );
357                 }
358             }
359         }
360         addSubelementEditable( category,
361                                NodePanel.SEQ_URI + " [" + uri_counter + "]",
362                                "",
363                                PHYLOXML_TAG.SEQ_URI,
364                                uri_counter );
365         //  addAnnotations( top, seq.getAnnotations(), category );
366     }
367
368     private void addSubelementEditable( final DefaultMutableTreeNode node,
369                                         final String name,
370                                         final String value,
371                                         final PHYLOXML_TAG phyloxml_tag ) {
372         addSubelementEditable( node, name, value, phyloxml_tag, 0 );
373     }
374
375     private void addSubelementEditable( final DefaultMutableTreeNode node,
376                                         final String name,
377                                         final String value,
378                                         final PHYLOXML_TAG phyloxml_tag,
379                                         final int number ) {
380         String my_value = value;
381         if ( ForesterUtil.isEmpty( my_value ) ) {
382             my_value = "";
383         }
384         final DefaultMutableTreeNode name_node = new DefaultMutableTreeNode( name );
385         final DefaultMutableTreeNode value_node = new DefaultMutableTreeNode( my_value );
386         name_node.add( value_node );
387         node.add( name_node );
388         addMapping( name_node, new TagNumber( phyloxml_tag, number ) );
389     }
390
391     private void addSubelementEditable( final DefaultMutableTreeNode node,
392                                         final String name,
393                                         final String value,
394                                         final PHYLOXML_TAG phyloxml_value_tag,
395                                         final String source_name,
396                                         final String source_value,
397                                         final PHYLOXML_TAG phyloxml_source_tag ) {
398         addSubelementEditable( node, name, value, phyloxml_value_tag, source_name, source_value, phyloxml_source_tag, 0 );
399     }
400
401     private void addSubelementEditable( final DefaultMutableTreeNode node,
402                                         final String name,
403                                         final String value,
404                                         final PHYLOXML_TAG phyloxml_value_tag,
405                                         final String source_name,
406                                         final String source_value,
407                                         final PHYLOXML_TAG phyloxml_source_tag,
408                                         final int number ) {
409         String my_value = value;
410         if ( ForesterUtil.isEmpty( my_value ) ) {
411             my_value = "";
412         }
413         String my_source_value = source_value;
414         if ( ForesterUtil.isEmpty( my_source_value ) ) {
415             my_source_value = "";
416         }
417         final DefaultMutableTreeNode name_node = new DefaultMutableTreeNode( name );
418         final DefaultMutableTreeNode source_name_node = new DefaultMutableTreeNode( source_name );
419         final DefaultMutableTreeNode source_value_node = new DefaultMutableTreeNode( my_source_value );
420         final DefaultMutableTreeNode value_node = new DefaultMutableTreeNode( my_value );
421         name_node.add( source_name_node );
422         source_name_node.add( source_value_node );
423         name_node.add( value_node );
424         node.add( name_node );
425         addMapping( name_node, new TagNumber( phyloxml_value_tag, number ) );
426         addMapping( source_name_node, new TagNumber( phyloxml_source_tag, number ) );
427     }
428
429     private void addTaxonomy( final DefaultMutableTreeNode top, Taxonomy tax, final String name ) {
430         if ( tax == null ) {
431             tax = new Taxonomy();
432         }
433         final DefaultMutableTreeNode category = new DefaultMutableTreeNode( name );
434         top.add( category );
435         Identifier id = tax.getIdentifier();
436         if ( id == null ) {
437             id = new Identifier();
438         }
439         addSubelementEditable( category,
440                                NodePanel.TAXONOMY_IDENTIFIER,
441                                id.getValue(),
442                                PHYLOXML_TAG.TAXONOMY_ID_VALUE,
443                                "Provider",
444                                id.getProvider(),
445                                PHYLOXML_TAG.TAXONOMY_ID_PROVIDER );
446         addSubelementEditable( category, NodePanel.TAXONOMY_CODE, tax.getTaxonomyCode(), PHYLOXML_TAG.TAXONOMY_CODE );
447         addSubelementEditable( category,
448                                NodePanel.TAXONOMY_SCIENTIFIC_NAME,
449                                tax.getScientificName(),
450                                PHYLOXML_TAG.TAXONOMY_SCIENTIFIC_NAME );
451         addSubelementEditable( category,
452                                NodePanel.TAXONOMY_AUTHORITY,
453                                tax.getAuthority(),
454                                PHYLOXML_TAG.TAXONOMY_AUTHORITY );
455         addSubelementEditable( category,
456                                NodePanel.TAXONOMY_COMMON_NAME,
457                                tax.getCommonName(),
458                                PHYLOXML_TAG.TAXONOMY_COMMON_NAME );
459         for( int i = tax.getSynonyms().size() - 1; i >= 0; i-- ) {
460             if ( ForesterUtil.isEmpty( tax.getSynonyms().get( i ) ) ) {
461                 tax.getSynonyms().remove( i );
462             }
463         }
464         int syn_counter = 0;
465         for( final String syn : tax.getSynonyms() ) {
466             addSubelementEditable( category,
467                                    NodePanel.TAXONOMY_SYNONYM + " [" + syn_counter + "]",
468                                    syn,
469                                    PHYLOXML_TAG.TAXONOMY_SYNONYM,
470                                    syn_counter++ );
471         }
472         addSubelementEditable( category,
473                                NodePanel.TAXONOMY_SYNONYM + " [" + syn_counter + "]",
474                                "",
475                                PHYLOXML_TAG.TAXONOMY_SYNONYM,
476                                syn_counter );
477         addSubelementEditable( category, NodePanel.TAXONOMY_RANK, tax.getRank(), PHYLOXML_TAG.TAXONOMY_RANK );
478         int uri_counter = 0;
479         if ( tax.getUris() != null ) {
480             for( final Uri uri : tax.getUris() ) {
481                 if ( uri != null ) {
482                     addSubelementEditable( category, NodePanel.TAXONOMY_URI + " [" + uri_counter + "]", uri.getValue()
483                             .toString(), PHYLOXML_TAG.TAXONOMY_URI, uri_counter++ );
484                 }
485             }
486         }
487         addSubelementEditable( category,
488                                NodePanel.TAXONOMY_URI + " [" + uri_counter + "]",
489                                "",
490                                PHYLOXML_TAG.TAXONOMY_URI,
491                                uri_counter );
492     }
493
494     private void collapsePath( final String name ) {
495         final TreePath tp = getJTree().getNextMatch( name, 0, Position.Bias.Forward );
496         if ( tp != null ) {
497             getJTree().collapsePath( tp );
498         }
499     }
500
501     private void createNodes( final DefaultMutableTreeNode top, final PhylogenyNode phylogeny_node ) {
502         if ( !phylogeny_node.getNodeData().isHasTaxonomy() ) {
503             phylogeny_node.getNodeData().addTaxonomy( new Taxonomy() );
504         }
505         if ( !phylogeny_node.getNodeData().isHasSequence() ) {
506             phylogeny_node.getNodeData().addSequence( new Sequence() );
507         }
508         if ( !phylogeny_node.getNodeData().isHasDistribution() ) {
509             phylogeny_node.getNodeData().addDistribution( new Distribution( "" ) );
510         }
511         if ( !phylogeny_node.getNodeData().isHasReference() ) {
512             phylogeny_node.getNodeData().addReference( new Reference( "" ) );
513         }
514         addBasics( top, phylogeny_node, NodePanel.BASIC );
515         addTaxonomy( top, phylogeny_node.getNodeData().getTaxonomy(), NodePanel.TAXONOMY );
516         addSequence( top, phylogeny_node.getNodeData().getSequence(), NodePanel.SEQUENCE );
517         if ( !phylogeny_node.isExternal() ) {
518             addEvents( top, phylogeny_node.getNodeData().getEvent(), NodePanel.EVENTS );
519         }
520         addDate( top, phylogeny_node.getNodeData().getDate(), NodePanel.DATE );
521         addDistribution( top, phylogeny_node.getNodeData().getDistribution(), NodePanel.DISTRIBUTION );
522         addReference( top, phylogeny_node.getNodeData().getReference(), NodePanel.LIT_REFERENCE );
523         //  addProperties( top, phylogeny_node.getNodeData().getProperties(), "Properties" );
524     }
525
526     private void formatError( final DefaultMutableTreeNode mtn, final PhyloXmlDataFormatException e ) {
527         JOptionPane.showMessageDialog( this, e.getMessage(), "Format error", JOptionPane.ERROR_MESSAGE );
528         mtn.setUserObject( "" );
529         getJTree().repaint();
530     }
531
532     private JTree getJTree() {
533         return _tree;
534     }
535
536     private Map<DefaultMutableTreeNode, TagNumber> getMap() {
537         return _map;
538     }
539
540     private TagNumber getMapping( final DefaultMutableTreeNode mtn ) {
541         return getMap().get( mtn );
542     }
543
544     PhylogenyNode getMyNode() {
545         return _my_node;
546     }
547
548     private DefaultMutableTreeNode getSelectedTreeNode() {
549         final TreePath selectionPath = getJTree().getSelectionPath();
550         if ( selectionPath != null ) {
551             final Object[] path = selectionPath.getPath();
552             if ( path.length > 0 ) {
553                 return ( DefaultMutableTreeNode ) path[ path.length - 1 ]; // Last node
554             }
555         }
556         return null;
557     }
558
559     private TreePanel getTreePanel() {
560         return _tree_panel;
561     }
562
563     private void keyEvent( final KeyEvent e ) {
564         if ( e.getKeyCode() == KeyEvent.VK_ENTER ) {
565             writeBack( getSelectedTreeNode() );
566         }
567     }
568
569     private List<Point> obtainPoints() {
570         ForesterUtil.ensurePresenceOfDistribution( getMyNode() );
571         Distribution d = getMyNode().getNodeData().getDistribution();
572         if ( d.getPoints() == null ) {
573             d = new Distribution( d.getDesc(), new ArrayList<Point>(), d.getPolygons() );
574             getMyNode().getNodeData().setDistribution( d );
575         }
576         final List<Point> ps = d.getPoints();
577         if ( ps.isEmpty() ) {
578             ps.add( new Point() );
579         }
580         else if ( ps.get( 0 ) == null ) {
581             ps.set( 0, new Point() );
582         }
583         return ps;
584     }
585
586     private BigDecimal parseBigDecimal( final DefaultMutableTreeNode mtn, final String value ) {
587         if ( ForesterUtil.isEmpty( value ) ) {
588             return new BigDecimal( 0 );
589         }
590         BigDecimal i = null;
591         try {
592             i = new BigDecimal( value );
593         }
594         catch ( final NumberFormatException e ) {
595             JOptionPane.showMessageDialog( this, "illegal value: " + value, "Error", JOptionPane.ERROR_MESSAGE );
596             mtn.setUserObject( "" );
597         }
598         return i;
599     }
600
601     private int parsePositiveInt( final DefaultMutableTreeNode mtn, final String value ) {
602         if ( ForesterUtil.isEmpty( value ) ) {
603             return 0;
604         }
605         int i = -1;
606         try {
607             i = ForesterUtil.parseInt( value );
608         }
609         catch ( final ParseException e ) {
610             JOptionPane.showMessageDialog( this, "illegal value: " + value, "Error", JOptionPane.ERROR_MESSAGE );
611             mtn.setUserObject( "" );
612         }
613         if ( i < 0 ) {
614             JOptionPane.showMessageDialog( this, "illegal value: " + value, "Error", JOptionPane.ERROR_MESSAGE );
615             mtn.setUserObject( "" );
616         }
617         return i;
618     }
619
620     void writeAll() {
621         for( int i = 0; i < getJTree().getRowCount(); i++ ) {
622             final TreePath p = getJTree().getPathForRow( i );
623             writeBack( ( DefaultMutableTreeNode ) p.getLastPathComponent() );
624         }
625     }
626
627     private void writeBack( final DefaultMutableTreeNode mtn ) {
628         if ( !getMap().containsKey( mtn ) ) {
629             final DefaultMutableTreeNode parent = ( DefaultMutableTreeNode ) mtn.getParent();
630             if ( getMap().containsKey( parent ) ) {
631                 writeBack( mtn, getMapping( parent ) );
632             }
633         }
634     }
635
636     private void writeBack( final DefaultMutableTreeNode mtn, final TagNumber tag_number ) {
637         if ( tag_number == null ) {
638             return;
639         }
640         String value = mtn.toString();
641         if ( value == null ) {
642             value = "";
643         }
644         value = value.replaceAll( "\\s+", " " );
645         value = value.trim();
646         mtn.setUserObject( value );
647         getJTree().repaint();
648         final PHYLOXML_TAG tag = tag_number.getTag();
649         final int number = tag_number.getNumber();
650         switch ( tag ) {
651             case NODE_NAME:
652                 getMyNode().setName( value );
653                 break;
654             case NODE_BRANCH_LENGTH:
655                 if ( ForesterUtil.isEmpty( value ) ) {
656                     getMyNode().setDistanceToParent( PhylogenyDataUtil.BRANCH_LENGTH_DEFAULT );
657                 }
658                 else {
659                     try {
660                         getMyNode().setDistanceToParent( ForesterUtil.parseDouble( value ) );
661                     }
662                     catch ( final ParseException e ) {
663                         JOptionPane.showMessageDialog( this,
664                                                        "failed to parse branch length from: " + value,
665                                                        "Error",
666                                                        JOptionPane.ERROR_MESSAGE );
667                         mtn.setUserObject( "" );
668                     }
669                 }
670                 break;
671             case NODE_BRANCH_WIDTH:
672                 if ( ForesterUtil.isEmpty( value ) || value.equals( "1" ) ) {
673                     if ( getMyNode().getBranchData().getBranchWidth() != null ) {
674                         getMyNode().getBranchData().setBranchWidth( new BranchWidth() );
675                     }
676                 }
677                 else {
678                     try {
679                         final double bw = ForesterUtil.parseDouble( value );
680                         if ( bw >= 0 ) {
681                             getMyNode().getBranchData().setBranchWidth( new BranchWidth( bw ) );
682                         }
683                     }
684                     catch ( final ParseException e ) {
685                         JOptionPane.showMessageDialog( this,
686                                                        "failed to parse branch width from: " + value,
687                                                        "Error",
688                                                        JOptionPane.ERROR_MESSAGE );
689                         mtn.setUserObject( "" );
690                     }
691                 }
692                 break;
693             case CONFIDENCE_VALUE:
694                 double confidence = Confidence.CONFIDENCE_DEFAULT_VALUE;
695                 if ( !ForesterUtil.isEmpty( value ) ) {
696                     try {
697                         confidence = ForesterUtil.parseDouble( value );
698                     }
699                     catch ( final ParseException e ) {
700                         JOptionPane.showMessageDialog( this,
701                                                        "failed to parse confidence value from: " + value,
702                                                        "Error",
703                                                        JOptionPane.ERROR_MESSAGE );
704                         mtn.setUserObject( "" );
705                         break;
706                     }
707                 }
708                 if ( getMyNode().getBranchData().getConfidences().size() < number ) {
709                     throw new FailedConditionCheckException();
710                 }
711                 else if ( getMyNode().getBranchData().getConfidences().size() == number ) {
712                     if ( confidence >= 0 ) {
713                         getMyNode().getBranchData().getConfidences().add( new Confidence( confidence, "unknown" ) );
714                     }
715                 }
716                 else {
717                     final String type = getMyNode().getBranchData().getConfidences().get( number ).getType();
718                     final double sd = getMyNode().getBranchData().getConfidences().get( number ).getStandardDeviation();
719                     getMyNode().getBranchData().getConfidences().set( number, new Confidence( confidence, type, sd ) );
720                 }
721                 break;
722             case CONFIDENCE_TYPE:
723                 if ( getMyNode().getBranchData().getConfidences().size() < number ) {
724                     throw new FailedConditionCheckException();
725                 }
726                 else if ( getMyNode().getBranchData().getConfidences().size() == number ) {
727                     if ( !ForesterUtil.isEmpty( value ) ) {
728                         getMyNode().getBranchData().getConfidences().add( new Confidence( 0, value ) );
729                     }
730                 }
731                 else {
732                     final double v = getMyNode().getBranchData().getConfidences().get( number ).getValue();
733                     final double sd = getMyNode().getBranchData().getConfidences().get( number ).getStandardDeviation();
734                     getMyNode().getBranchData().getConfidences().set( number, new Confidence( v, value, sd ) );
735                 }
736                 break;
737             case TAXONOMY_CODE:
738                 ForesterUtil.ensurePresenceOfTaxonomy( getMyNode() );
739                 try {
740                     getMyNode().getNodeData().getTaxonomy().setTaxonomyCode( value );
741                 }
742                 catch ( final PhyloXmlDataFormatException e ) {
743                     formatError( mtn, e );
744                     break;
745                 }
746                 break;
747             case TAXONOMY_SCIENTIFIC_NAME:
748                 ForesterUtil.ensurePresenceOfTaxonomy( getMyNode() );
749                 getMyNode().getNodeData().getTaxonomy().setScientificName( value );
750                 break;
751             case TAXONOMY_COMMON_NAME:
752                 ForesterUtil.ensurePresenceOfTaxonomy( getMyNode() );
753                 getMyNode().getNodeData().getTaxonomy().setCommonName( value );
754                 break;
755             case TAXONOMY_RANK:
756                 ForesterUtil.ensurePresenceOfTaxonomy( getMyNode() );
757                 try {
758                     getMyNode().getNodeData().getTaxonomy().setRank( value.toLowerCase() );
759                 }
760                 catch ( final PhyloXmlDataFormatException e ) {
761                     formatError( mtn, e );
762                     break;
763                 }
764                 break;
765             case TAXONOMY_AUTHORITY:
766                 ForesterUtil.ensurePresenceOfTaxonomy( getMyNode() );
767                 getMyNode().getNodeData().getTaxonomy().setAuthority( value );
768                 break;
769             case TAXONOMY_URI: {
770                 Uri uri = null;
771                 if ( !ForesterUtil.isEmpty( value ) ) {
772                     try {
773                         uri = new Uri( new URL( value ).toURI() );
774                     }
775                     catch ( final Exception e ) {
776                         JOptionPane.showMessageDialog( this,
777                                                        "failed to parse URL from: " + value,
778                                                        "Error",
779                                                        JOptionPane.ERROR_MESSAGE );
780                         mtn.setUserObject( "" );
781                     }
782                 }
783                 if ( uri != null ) {
784                     ForesterUtil.ensurePresenceOfTaxonomy( getMyNode() );
785                 }
786                 addUri( mtn, uri, number, getMyNode().getNodeData().getTaxonomy() );
787                 break;
788             }
789             case TAXONOMY_SYNONYM:
790                 if ( getMyNode().getNodeData().getTaxonomy().getSynonyms().size() < number ) {
791                     throw new FailedConditionCheckException();
792                 }
793                 else if ( getMyNode().getNodeData().getTaxonomy().getSynonyms().size() == number ) {
794                     if ( !ForesterUtil.isEmpty( value ) ) {
795                         ForesterUtil.ensurePresenceOfTaxonomy( getMyNode() );
796                         getMyNode().getNodeData().getTaxonomy().getSynonyms().add( value );
797                     }
798                 }
799                 else {
800                     getMyNode().getNodeData().getTaxonomy().getSynonyms().set( number, value );
801                 }
802                 break;
803             case TAXONOMY_ID_VALUE:
804                 ForesterUtil.ensurePresenceOfTaxonomy( getMyNode() );
805                 if ( getMyNode().getNodeData().getTaxonomy().getIdentifier() == null ) {
806                     getMyNode().getNodeData().getTaxonomy().setIdentifier( new Identifier( value ) );
807                 }
808                 else {
809                     final String provider = getMyNode().getNodeData().getTaxonomy().getIdentifier().getProvider();
810                     getMyNode().getNodeData().getTaxonomy().setIdentifier( new Identifier( value, provider ) );
811                 }
812                 break;
813             case TAXONOMY_ID_PROVIDER:
814                 ForesterUtil.ensurePresenceOfTaxonomy( getMyNode() );
815                 if ( getMyNode().getNodeData().getTaxonomy().getIdentifier() == null ) {
816                     getMyNode().getNodeData().getTaxonomy().setIdentifier( new Identifier( "", value ) );
817                 }
818                 else {
819                     final String v = getMyNode().getNodeData().getTaxonomy().getIdentifier().getValue();
820                     getMyNode().getNodeData().getTaxonomy().setIdentifier( new Identifier( v, value ) );
821                 }
822                 break;
823             case SEQ_LOCATION:
824                 ForesterUtil.ensurePresenceOfSequence( getMyNode() );
825                 getMyNode().getNodeData().getSequence().setLocation( value );
826                 break;
827             case SEQ_MOL_SEQ:
828                 ForesterUtil.ensurePresenceOfSequence( getMyNode() );
829                 getMyNode().getNodeData().getSequence().setMolecularSequence( value.replaceAll( "[^a-zA-Z-]", "" ) );
830                 break;
831             case SEQ_NAME:
832                 ForesterUtil.ensurePresenceOfSequence( getMyNode() );
833                 getMyNode().getNodeData().getSequence().setName( value );
834                 break;
835             case SEQ_SYMBOL:
836                 ForesterUtil.ensurePresenceOfSequence( getMyNode() );
837                 try {
838                     getMyNode().getNodeData().getSequence().setSymbol( value );
839                 }
840                 catch ( final PhyloXmlDataFormatException e ) {
841                     formatError( mtn, e );
842                     break;
843                 }
844                 break;
845             case SEQ_GENE_NAME:
846                 ForesterUtil.ensurePresenceOfSequence( getMyNode() );
847                 getMyNode().getNodeData().getSequence().setGeneName( value );
848                 break;
849             case SEQ_TYPE:
850                 ForesterUtil.ensurePresenceOfSequence( getMyNode() );
851                 try {
852                     getMyNode().getNodeData().getSequence().setType( value.toLowerCase() );
853                 }
854                 catch ( final PhyloXmlDataFormatException e ) {
855                     formatError( mtn, e );
856                     break;
857                 }
858                 break;
859             case SEQ_ACC_SOURCE:
860                 ForesterUtil.ensurePresenceOfSequence( getMyNode() );
861                 if ( getMyNode().getNodeData().getSequence().getAccession() == null ) {
862                     getMyNode().getNodeData().getSequence().setAccession( new Accession( "", value ) );
863                 }
864                 else {
865                     final String v = getMyNode().getNodeData().getSequence().getAccession().getValue();
866                     getMyNode().getNodeData().getSequence().setAccession( new Accession( v, value ) );
867                 }
868                 break;
869             case SEQ_ACC_VALUE:
870                 ForesterUtil.ensurePresenceOfSequence( getMyNode() );
871                 if ( getMyNode().getNodeData().getSequence().getAccession() == null ) {
872                     getMyNode().getNodeData().getSequence().setAccession( new Accession( value, "" ) );
873                 }
874                 else {
875                     final String source = getMyNode().getNodeData().getSequence().getAccession().getSource();
876                     getMyNode().getNodeData().getSequence().setAccession( new Accession( value, source ) );
877                 }
878                 break;
879             case SEQ_URI: {
880                 Uri uri = null;
881                 if ( !ForesterUtil.isEmpty( value ) ) {
882                     try {
883                         uri = new Uri( new URL( value ).toURI() );
884                     }
885                     catch ( final Exception e ) {
886                         JOptionPane.showMessageDialog( this,
887                                                        "failed to parse URL from: " + value,
888                                                        "Error",
889                                                        JOptionPane.ERROR_MESSAGE );
890                         mtn.setUserObject( "" );
891                     }
892                 }
893                 if ( uri != null ) {
894                     ForesterUtil.ensurePresenceOfSequence( getMyNode() );
895                 }
896                 addUri( mtn, uri, number, getMyNode().getNodeData().getSequence() );
897                 break;
898             }
899             case LIT_REFERENCE_DESC:
900                 if ( !getMyNode().getNodeData().isHasReference() ) {
901                     getMyNode().getNodeData().setReference( new Reference( "" ) );
902                 }
903                 getMyNode().getNodeData().getReference().setValue( value );
904                 break;
905             case LIT_REFERENCE_DOI:
906                 if ( !getMyNode().getNodeData().isHasReference() ) {
907                     getMyNode().getNodeData().setReference( new Reference( "" ) );
908                 }
909                 try {
910                     getMyNode().getNodeData().getReference().setDoi( value );
911                 }
912                 catch ( final PhyloXmlDataFormatException e ) {
913                     formatError( mtn, e );
914                     break;
915                 }
916                 break;
917             case EVENTS_DUPLICATIONS:
918                 if ( !getMyNode().getNodeData().isHasEvent() ) {
919                     getMyNode().getNodeData().setEvent( new Event() );
920                 }
921                 getMyNode().getNodeData().getEvent().setDuplications( parsePositiveInt( mtn, value ) );
922                 break;
923             case EVENTS_SPECIATIONS:
924                 if ( !getMyNode().getNodeData().isHasEvent() ) {
925                     getMyNode().getNodeData().setEvent( new Event() );
926                 }
927                 getMyNode().getNodeData().getEvent().setSpeciations( parsePositiveInt( mtn, value ) );
928                 break;
929             case EVENTS_GENE_LOSSES:
930                 if ( !getMyNode().getNodeData().isHasEvent() ) {
931                     getMyNode().getNodeData().setEvent( new Event() );
932                 }
933                 getMyNode().getNodeData().getEvent().setGeneLosses( parsePositiveInt( mtn, value ) );
934                 break;
935             case DATE_DESCRIPTION:
936                 ForesterUtil.ensurePresenceOfDate( getMyNode() );
937                 getMyNode().getNodeData().getDate().setDesc( value );
938                 break;
939             case DATE_MAX:
940                 ForesterUtil.ensurePresenceOfDate( getMyNode() );
941                 getMyNode().getNodeData().getDate().setMax( parseBigDecimal( mtn, value ) );
942                 break;
943             case DATE_MIN:
944                 ForesterUtil.ensurePresenceOfDate( getMyNode() );
945                 getMyNode().getNodeData().getDate().setMin( parseBigDecimal( mtn, value ) );
946                 break;
947             case DATE_UNIT:
948                 ForesterUtil.ensurePresenceOfDate( getMyNode() );
949                 getMyNode().getNodeData().getDate().setUnit( value );
950                 break;
951             case DATE_VALUE:
952                 ForesterUtil.ensurePresenceOfDate( getMyNode() );
953                 getMyNode().getNodeData().getDate().setValue( parseBigDecimal( mtn, value ) );
954                 break;
955             case DIST_ALT: {
956                 final BigDecimal new_value = parseBigDecimal( mtn, value );
957                 if ( new_value != null ) {
958                     final List<Point> ps = obtainPoints();
959                     final Point p = ps.get( 0 );
960                     final Point p_new = new Point( p.getGeodeticDatum(),
961                                                    p.getLatitude(),
962                                                    p.getLongitude(),
963                                                    new_value,
964                                                    ForesterUtil.isEmpty( p.getAltiudeUnit() ) ? "?"
965                                                            : p.getAltiudeUnit() );
966                     ps.set( 0, p_new );
967                 }
968                 break;
969             }
970             case DIST_DESC: {
971                 ForesterUtil.ensurePresenceOfDistribution( getMyNode() );
972                 final Distribution d = getMyNode().getNodeData().getDistribution();
973                 getMyNode().getNodeData().setDistribution( new Distribution( value, d.getPoints(), d.getPolygons() ) );
974                 break;
975             }
976             case DIST_GEODETIC: {
977                 if ( !ForesterUtil.isEmpty( value ) ) {
978                     final List<Point> ps = obtainPoints();
979                     final Point p = ps.get( 0 );
980                     final Point p_new = new Point( value,
981                                                    p.getLatitude(),
982                                                    p.getLongitude(),
983                                                    p.getAltitude(),
984                                                    p.getAltiudeUnit() );
985                     ps.set( 0, p_new );
986                 }
987                 break;
988             }
989             case DIST_ALT_UNIT: {
990                 if ( !ForesterUtil.isEmpty( value ) ) {
991                     final List<Point> ps = obtainPoints();
992                     final Point p = ps.get( 0 );
993                     final Point p_new = new Point( p.getGeodeticDatum(),
994                                                    p.getLatitude(),
995                                                    p.getLongitude(),
996                                                    p.getAltitude(),
997                                                    value );
998                     ps.set( 0, p_new );
999                 }
1000                 break;
1001             }
1002             case DIST_LAT: {
1003                 final BigDecimal new_value = parseBigDecimal( mtn, value );
1004                 if ( new_value != null ) {
1005                     final List<Point> ps = obtainPoints();
1006                     final Point p = ps.get( 0 );
1007                     final Point p_new = new Point( p.getGeodeticDatum(),
1008                                                    new_value,
1009                                                    p.getLongitude(),
1010                                                    p.getAltitude(),
1011                                                    p.getAltiudeUnit() );
1012                     ps.set( 0, p_new );
1013                 }
1014                 break;
1015             }
1016             case DIST_LONG: {
1017                 final BigDecimal new_value = parseBigDecimal( mtn, value );
1018                 if ( new_value != null ) {
1019                     final List<Point> ps = obtainPoints();
1020                     final Point p = ps.get( 0 );
1021                     final Point p_new = new Point( p.getGeodeticDatum(),
1022                                                    p.getLatitude(),
1023                                                    new_value,
1024                                                    p.getAltitude(),
1025                                                    p.getAltiudeUnit() );
1026                     ps.set( 0, p_new );
1027                 }
1028                 break;
1029             }
1030             default:
1031                 throw new IllegalArgumentException( "unknown: " + tag );
1032         }
1033         getJTree().repaint();
1034         getTreePanel().setEdited( true );
1035         getTreePanel().repaint();
1036     }
1037
1038     private void addUri( final DefaultMutableTreeNode mtn, final Uri uri, final int number, final MultipleUris mu ) {
1039         if ( uri != null ) {
1040             if ( mu.getUris() == null ) {
1041                 mu.setUris( new ArrayList<Uri>() );
1042             }
1043         }
1044         if ( ( uri != null ) && ( mu.getUris() == null ) ) {
1045             mu.setUris( new ArrayList<Uri>() );
1046         }
1047         if ( ( uri != null ) && ( mu.getUris().size() == number ) ) {
1048             mu.getUris().add( uri );
1049         }
1050         if ( ( mu.getUris() != null ) && ( mu.getUris().size() != number ) ) {
1051             mu.getUris().set( number, uri );
1052         }
1053         final ImageLoader il = new ImageLoader( getTreePanel() );
1054         new Thread( il ).start();
1055     }
1056
1057     private enum PHYLOXML_TAG {
1058         NODE_NAME,
1059         NODE_BRANCH_LENGTH,
1060         NODE_BRANCH_WIDTH,
1061         TAXONOMY_CODE,
1062         TAXONOMY_SCIENTIFIC_NAME,
1063         TAXONOMY_AUTHORITY,
1064         TAXONOMY_COMMON_NAME,
1065         TAXONOMY_SYNONYM,
1066         TAXONOMY_RANK,
1067         TAXONOMY_URI,
1068         SEQ_SYMBOL,
1069         SEQ_NAME,
1070         SEQ_GENE_NAME,
1071         SEQ_LOCATION,
1072         SEQ_TYPE,
1073         SEQ_MOL_SEQ,
1074         SEQ_URI,
1075         DATE_DESCRIPTION,
1076         DATE_VALUE,
1077         DATE_MIN,
1078         DATE_MAX,
1079         DATE_UNIT,
1080         TAXONOMY_ID_VALUE,
1081         TAXONOMY_ID_PROVIDER,
1082         SEQ_ACC_VALUE,
1083         SEQ_ACC_SOURCE,
1084         CONFIDENCE_VALUE,
1085         CONFIDENCE_TYPE,
1086         LIT_REFERENCE_DESC,
1087         LIT_REFERENCE_DOI,
1088         EVENTS_DUPLICATIONS,
1089         EVENTS_SPECIATIONS,
1090         EVENTS_GENE_LOSSES,
1091         DIST_DESC,
1092         DIST_GEODETIC,
1093         DIST_LAT,
1094         DIST_LONG,
1095         DIST_ALT,
1096         DIST_ALT_UNIT
1097     }
1098
1099     private class TagNumber {
1100
1101         final private PHYLOXML_TAG _tag;
1102         final private int          _number;
1103
1104         TagNumber( final PHYLOXML_TAG tag, final int number ) {
1105             _tag = tag;
1106             _number = number;
1107         }
1108
1109         int getNumber() {
1110             return _number;
1111         }
1112
1113         PHYLOXML_TAG getTag() {
1114             return _tag;
1115         }
1116
1117         @Override
1118         public String toString() {
1119             return getTag() + "_" + getNumber();
1120         }
1121     }
1122 }