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