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