in progress
[jalview.git] / forester / java / src / org / forester / archaeopteryx / NodePanel.java
1 // $Id:
2 // FORESTER -- software libraries and applications
3 // for evolutionary biology research and applications.
4 //
5 // Copyright (C) 2008-2009 Christian M. Zmasek
6 // Copyright (C) 2008-2009 Burnham Institute for Medical Research
7 // All rights reserved
8 //
9 // This library is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU Lesser General Public
11 // License as published by the Free Software Foundation; either
12 // version 2.1 of the License, or (at your option) any later version.
13 //
14 // This library is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 // Lesser General Public License for more details.
18 //
19 // You should have received a copy of the GNU Lesser General Public
20 // License along with this library; if not, write to the Free Software
21 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 //
23 // Contact: phylosoft @ gmail . com
24 // WWW: https://sites.google.com/site/cmzmasek/home/software/forester
25
26 package org.forester.archaeopteryx;
27
28
29 import java.awt.Color;
30 import java.awt.Component;
31 import java.awt.event.ActionEvent;
32 import java.awt.event.ActionListener;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.SortedSet;
36
37 import javax.swing.BoxLayout;
38 import javax.swing.JButton;
39 import javax.swing.JEditorPane;
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
50 import org.forester.phylogeny.PhylogenyMethods;
51 import org.forester.phylogeny.PhylogenyNode;
52 import org.forester.phylogeny.data.Accession;
53 import org.forester.phylogeny.data.Annotation;
54 import org.forester.phylogeny.data.BinaryCharacters;
55 import org.forester.phylogeny.data.BranchWidth;
56 import org.forester.phylogeny.data.Date;
57 import org.forester.phylogeny.data.Distribution;
58 import org.forester.phylogeny.data.Event;
59 import org.forester.phylogeny.data.PhylogenyData;
60 import org.forester.phylogeny.data.PhylogenyDataUtil;
61 import org.forester.phylogeny.data.Point;
62 import org.forester.phylogeny.data.PropertiesList;
63 import org.forester.phylogeny.data.Property;
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.ForesterUtil;
69
70 class NodePanel extends JPanel implements TreeSelectionListener {
71
72     static final String       BASIC                    = "Basic";
73     static final String       BINARY_CHARACTERS        = "Binary characters";
74     static final String       CONFIDENCE               = "Confidence";
75     static final String       CONFIDENCE_TYPE          = "type";
76     static final String       DATE                     = "Date";
77     static final String       DATE_DESCRIPTION         = "Description";
78     static final String       DATE_MAX                 = "Max";
79     static final String       DATE_MIN                 = "Min";
80     static final String       DATE_UNIT                = "Unit";
81     static final String       DATE_VALUE               = "Value";
82     static final String       DIST_ALT_UNIT            = "Altitude unit";
83     static final String       DIST_ALTITUDE            = "Altitude";
84     static final String       DIST_DESCRIPTION         = "Description";
85     static final String       DIST_GEODETIC_DATUM      = "Geodetic datum";
86     static final String       DIST_LATITUDE            = "Latitude";
87     static final String       DIST_LONGITUDE           = "Longitude";
88     static final String       DISTRIBUTION             = "Distribution";
89     static final String       EVENTS                   = "Events";
90     static final String       EVENTS_DUPLICATIONS      = "Duplications";
91     static final String       EVENTS_GENE_LOSSES       = "Gene losses";
92     static final String       EVENTS_SPECIATIONS       = "Speciations";
93     static final String       LIT_REFERENCE            = "Reference";
94     static final String       LIT_REFERENCE_DESC       = "Description";
95     static final String       LIT_REFERENCE_DOI        = "DOI";
96     static final String       NODE_BRANCH_COLOR        = "Branch color";
97     static final String       NODE_BRANCH_LENGTH       = "Branch length";
98     static final String       NODE_BRANCH_WIDTH        = "Branch width";
99     static final String       NODE_NAME                = "Name";
100     static final String       PROP                     = "Properties";
101     static final String       REFERENCE                = "Reference";
102     static final String       SEQ_ACCESSION            = "Accession";
103     static final String       SEQ_LOCATION             = "Location";
104     static final String       SEQ_MOL_SEQ              = "Mol seq";
105     static final String       SEQ_NAME                 = "Name";
106     static final String       SEQ_SYMBOL               = "Symbol";
107     static final String       SEQ_GENE_NAME            = "Gene name";
108     static final String       SEQ_TYPE                 = "Type";
109     static final String       SEQ_URI                  = "URI";
110     static final String       SEQUENCE                 = "Sequence";
111     static final String       TAXONOMY                 = "Taxonomy";
112     static final String       TAXONOMY_AUTHORITY       = "Authority";
113     static final String       TAXONOMY_CODE            = "Code";
114     static final String       TAXONOMY_COMMON_NAME     = "Common name";
115     static final String       TAXONOMY_IDENTIFIER      = "Identifier";
116     static final String       TAXONOMY_RANK            = "Rank";
117     static final String       TAXONOMY_SCIENTIFIC_NAME = "Scientific name";
118     static final String       TAXONOMY_SYNONYM         = "Synonym";
119     static final String       TAXONOMY_URI             = "URI";
120     private static final long serialVersionUID         = 5120159904388100771L;
121     private final JEditorPane _pane;
122     private final JTree       _tree;
123
124     public NodePanel( final PhylogenyNode phylogeny_node, final NodeFrame parent ) {
125         String node_name = "";
126         if ( !ForesterUtil.isEmpty( phylogeny_node.getName() ) ) {
127             node_name = phylogeny_node.getName() + " ";
128         }
129         final DefaultMutableTreeNode top = new DefaultMutableTreeNode( "Node " + node_name );
130         createNodes( top, phylogeny_node );
131         _tree = new JTree( top );
132         _tree.setEditable( false );
133         getJTree().setToggleClickCount( 1 );
134         expandPath( BASIC );
135         expandPath( TAXONOMY );
136         expandPath( SEQUENCE );
137         expandPath( EVENTS );
138         final JScrollPane tree_view = new JScrollPane( getJTree() );
139         
140         final JButton close_button = new JButton( "Close" );
141         close_button.setEnabled( true );
142        
143         _pane = new JEditorPane();
144         _pane.setEditable( false );
145         
146         final JScrollPane data_view = new JScrollPane( _pane );
147         final JSplitPane split_pane = new JSplitPane( JSplitPane.VERTICAL_SPLIT );
148         split_pane.setTopComponent( tree_view );
149         data_view.add( close_button );
150         split_pane.setBottomComponent( data_view );
151         data_view.setMinimumSize( AptxConstants.NODE_PANEL_SPLIT_MINIMUM_SIZE );
152         tree_view.setMinimumSize( AptxConstants.NODE_PANEL_SPLIT_MINIMUM_SIZE );
153         split_pane.setDividerLocation( 300 );
154         split_pane.setPreferredSize( AptxConstants.NODE_PANEL_SIZE );
155        
156         
157         close_button.addActionListener( new ActionListener() {
158             public void actionPerformed( final ActionEvent e ) {
159                 parent.close();
160            }
161         } );
162         
163       
164         close_button.setAlignmentX( Component.CENTER_ALIGNMENT );
165         split_pane.setAlignmentX( Component.CENTER_ALIGNMENT );
166         final JPanel panel = new JPanel();
167         panel.setLayout( new BoxLayout( panel, BoxLayout.Y_AXIS ) );
168         panel.add( split_pane );
169         panel.add( close_button );
170         add( panel );
171     }
172
173     @Override
174     public void valueChanged( final TreeSelectionEvent e ) {
175         // Do nothing.
176     }
177
178     private void expandPath( final String name ) {
179         final TreePath tp = getJTree().getNextMatch( name, 0, Position.Bias.Forward );
180         if ( tp != null ) {
181             getJTree().expandPath( tp );
182         }
183     }
184
185     private JTree getJTree() {
186         return _tree;
187     }
188
189     private static void addAnnotation( final DefaultMutableTreeNode top, final Annotation ann, final String name ) {
190         DefaultMutableTreeNode category;
191         category = new DefaultMutableTreeNode( name );
192         top.add( category );
193         addSubelement( category, "Source", ann.getSource() );
194         addSubelement( category, "Type", ann.getType() );
195         addSubelement( category, "Evidence", ann.getEvidence() );
196         if ( ann.getConfidence() != null ) {
197             addSubelement( category, CONFIDENCE, ann.getConfidence().asText().toString() );
198         }
199         if ( ann.getProperties() != null ) {
200             addProperties( category, ann.getProperties(), PROP );
201         }
202     }
203
204     private static void addAnnotations( final DefaultMutableTreeNode top,
205                                         final SortedSet<Annotation> annotations,
206                                         final DefaultMutableTreeNode category ) {
207         if ( ( annotations != null ) && ( annotations.size() > 0 ) ) {
208             category.add( new DefaultMutableTreeNode( "Annotations" ) );
209             final DefaultMutableTreeNode last = top.getLastLeaf();
210             for( final Annotation ann : annotations ) {
211                 addAnnotation( last, ann, ann.asText().toString() );
212             }
213         }
214     }
215
216     private static void addBasics( final DefaultMutableTreeNode top,
217                                    final PhylogenyNode phylogeny_node,
218                                    final String name ) {
219         final DefaultMutableTreeNode category = new DefaultMutableTreeNode( name );
220         top.add( category );
221         addSubelement( category, NODE_NAME, phylogeny_node.getName() );
222         if ( phylogeny_node.getDistanceToParent() != PhylogenyDataUtil.BRANCH_LENGTH_DEFAULT ) {
223             addSubelement( category,
224                            NODE_BRANCH_LENGTH,
225                            ForesterUtil.FORMATTER_6.format( phylogeny_node.getDistanceToParent() ) );
226         }
227         if ( phylogeny_node.getBranchData().isHasConfidences() ) {
228             for( final PhylogenyData conf : phylogeny_node.getBranchData().getConfidences() ) {
229                 addSubelement( category, CONFIDENCE, conf.asText().toString() );
230             }
231         }
232         if ( !phylogeny_node.isExternal() ) {
233             addSubelement( category, "Children", String.valueOf( phylogeny_node.getNumberOfDescendants() ) );
234             addSubelement( category,
235                            "External children",
236                            String.valueOf( phylogeny_node.getAllExternalDescendants().size() ) );
237             final Map<Taxonomy, Integer> distinct_tax = PhylogenyMethods.obtainDistinctTaxonomyCounts( phylogeny_node );
238             if ( distinct_tax != null ) {
239                 final int no_tax = PhylogenyMethods.calculateNumberOfExternalNodesWithoutTaxonomy( phylogeny_node );
240                 final int tax_count = distinct_tax.size();
241                 addSubelement( category, "Distinct external taxonomies", String.valueOf( tax_count ) );
242                 if ( no_tax > 0 ) {
243                     addSubelement( category, "External nodes without taxonomy", String.valueOf( no_tax ) );
244                 }
245             }
246         }
247         if ( !phylogeny_node.isRoot() ) {
248             addSubelement( category, "Depth", String.valueOf( phylogeny_node.calculateDepth() ) );
249             final double d = phylogeny_node.calculateDistanceToRoot();
250             if ( d > 0 ) {
251                 addSubelement( category, "Distance to root", String.valueOf( ForesterUtil.FORMATTER_6.format( d ) ) );
252             }
253         }
254         if ( ( phylogeny_node.getBranchData().getBranchWidth() != null )
255                 && ( phylogeny_node.getBranchData().getBranchWidth().getValue() != BranchWidth.BRANCH_WIDTH_DEFAULT_VALUE ) ) {
256             addSubelement( category,
257                            NODE_BRANCH_WIDTH,
258                            ForesterUtil.FORMATTER_3.format( phylogeny_node.getBranchData().getBranchWidth().getValue() ) );
259         }
260         if ( ( phylogeny_node.getBranchData().getBranchColor() != null ) ) {
261             final Color c = phylogeny_node.getBranchData().getBranchColor().getValue();
262             addSubelement( category, NODE_BRANCH_COLOR, c.getRed() + ", " + c.getGreen() + ", " + c.getBlue() );
263         }
264     }
265
266     private static void addBinaryCharacters( final DefaultMutableTreeNode top,
267                                              final BinaryCharacters bc,
268                                              final String name ) {
269         DefaultMutableTreeNode category;
270         category = new DefaultMutableTreeNode( name );
271         top.add( category );
272         addSubelement( category, "Gained", String.valueOf( bc.getGainedCount() ) );
273         addSubelement( category, "Lost", String.valueOf( bc.getLostCount() ) );
274         addSubelement( category, "Present", String.valueOf( bc.getPresentCount() ) );
275         final DefaultMutableTreeNode chars = new DefaultMutableTreeNode( "Lists" );
276         category.add( chars );
277         addSubelement( chars, "Gained", bc.getGainedCharactersAsStringBuffer().toString() );
278         addSubelement( chars, "Lost", bc.getLostCharactersAsStringBuffer().toString() );
279         addSubelement( chars, "Present", bc.getPresentCharactersAsStringBuffer().toString() );
280     }
281
282     private static void addCrossReference( final DefaultMutableTreeNode top, final Accession x, final String name ) {
283         DefaultMutableTreeNode category;
284         category = new DefaultMutableTreeNode( name );
285         top.add( category );
286     }
287
288     private static void addCrossReferences( final DefaultMutableTreeNode top,
289                                             final SortedSet<Accession> xs,
290                                             final DefaultMutableTreeNode category ) {
291         if ( ( xs != null ) && ( xs.size() > 0 ) ) {
292             category.add( new DefaultMutableTreeNode( "Cross references" ) );
293             final DefaultMutableTreeNode last = top.getLastLeaf();
294             for( final Accession x : xs ) {
295                 addCrossReference( last, x, x.asText().toString() );
296             }
297         }
298     }
299
300     private static void addDate( final DefaultMutableTreeNode top, final Date date, final String name ) {
301         DefaultMutableTreeNode category;
302         category = new DefaultMutableTreeNode( name );
303         top.add( category );
304         addSubelement( category, DATE_DESCRIPTION, date.getDesc() );
305         addSubelement( category, DATE_VALUE, String.valueOf( date.getValue() ) );
306         addSubelement( category, DATE_MIN, String.valueOf( date.getMin() ) );
307         addSubelement( category, DATE_MAX, String.valueOf( date.getMax() ) );
308         addSubelement( category, DATE_UNIT, date.getUnit() );
309     }
310
311     private static void addDistribution( final DefaultMutableTreeNode top, final Distribution dist, final String name ) {
312         DefaultMutableTreeNode category;
313         category = new DefaultMutableTreeNode( name );
314         top.add( category );
315         addSubelement( category, DIST_DESCRIPTION, dist.getDesc() );
316         if ( ( dist.getPoints() != null ) && ( dist.getPoints().size() > 0 ) ) {
317             final Point p0 = dist.getPoints().get( 0 );
318             if ( ( p0 != null ) && !Point.isSeemsEmpty( p0 ) ) {
319                 addSubelement( category, DIST_GEODETIC_DATUM, p0.getGeodeticDatum() );
320                 addSubelement( category, DIST_LATITUDE, String.valueOf( p0.getLatitude() ) );
321                 addSubelement( category, DIST_LONGITUDE, String.valueOf( p0.getLongitude() ) );
322                 String alt_unit = p0.getAltiudeUnit();
323                 if ( ForesterUtil.isEmpty( alt_unit ) ) {
324                     alt_unit = "?";
325                 }
326                 addSubelement( category, DIST_ALTITUDE, String.valueOf( p0.getAltitude() ) + alt_unit );
327             }
328         }
329     }
330
331     private static void addEvents( final DefaultMutableTreeNode top, final Event events, final String name ) {
332         DefaultMutableTreeNode category;
333         category = new DefaultMutableTreeNode( name );
334         top.add( category );
335         if ( events.getNumberOfDuplications() > 0 ) {
336             addSubelement( category, EVENTS_DUPLICATIONS, String.valueOf( events.getNumberOfDuplications() ) );
337         }
338         if ( events.getNumberOfSpeciations() > 0 ) {
339             addSubelement( category, EVENTS_SPECIATIONS, String.valueOf( events.getNumberOfSpeciations() ) );
340         }
341         if ( events.getNumberOfGeneLosses() > 0 ) {
342             addSubelement( category, EVENTS_GENE_LOSSES, String.valueOf( events.getNumberOfGeneLosses() ) );
343         }
344         addSubelement( category, "Type", events.getEventType().toString() );
345         if ( events.getConfidence() != null ) {
346             addSubelement( category, CONFIDENCE, events.getConfidence().asText().toString() );
347         }
348     }
349
350     private static void addLineage( final DefaultMutableTreeNode top,
351                                     final List<String> lineage,
352                                     final DefaultMutableTreeNode category ) {
353         if ( ( lineage != null ) && ( lineage.size() > 0 ) ) {
354             final StringBuilder sb = new StringBuilder();
355             for( final String lin : lineage ) {
356                 if ( !ForesterUtil.isEmpty( lin ) ) {
357                     sb.append( lin );
358                     sb.append( " > " );
359                 }
360             }
361             String str = null;
362             if ( sb.length() > 1 ) {
363                 str = sb.substring( 0, sb.length() - 3 );
364             }
365             if ( !ForesterUtil.isEmpty( str ) ) {
366                 addSubelement( category, "Lineage", str );
367             }
368         }
369     }
370
371     private static void addProperties( final DefaultMutableTreeNode top,
372                                        final PropertiesList properties,
373                                        final String string ) {
374         final List<Property> properties_map = properties.getProperties();
375         final DefaultMutableTreeNode category = new DefaultMutableTreeNode( "Properties " );
376         top.add( category );
377         for( final Property prop : properties_map ) {
378           
379             category.add( new DefaultMutableTreeNode( prop.getRef() + "=" + prop.getValue() + " " + prop.getUnit()
380                     + " [" + prop.getAppliesTo().toString() + "]" ) );
381         }
382     }
383
384     private static void addReference( final DefaultMutableTreeNode top, final Reference ref, final String name ) {
385         final DefaultMutableTreeNode category = new DefaultMutableTreeNode( name );
386         top.add( category );
387         addSubelement( category, LIT_REFERENCE_DOI, ref.getDoi() );
388         addSubelement( category, LIT_REFERENCE_DESC, ref.getDescription() );
389     }
390
391     private static void addSequence( final DefaultMutableTreeNode top, final Sequence seq, final String name ) {
392         final DefaultMutableTreeNode category = new DefaultMutableTreeNode( name );
393         top.add( category );
394         addSubelement( category, SEQ_NAME, seq.getName() );
395         addSubelement( category, SEQ_SYMBOL, seq.getSymbol() );
396         addSubelement( category, SEQ_GENE_NAME, seq.getGeneName() );
397         if ( seq.getAccession() != null ) {
398             addSubelement( category, SEQ_ACCESSION, seq.getAccession().asText().toString() );
399         }
400         addSubelement( category, SEQ_LOCATION, seq.getLocation() );
401         addSubelement( category, SEQ_TYPE, seq.getType() );
402         addSubelement( category, SEQ_MOL_SEQ, seq.getMolecularSequence() );
403         if ( ( seq.getAnnotations() != null ) && !seq.getAnnotations().isEmpty() ) {
404             addAnnotations( top, seq.getAnnotations(), category );
405         }
406         if ( ( seq.getCrossReferences() != null ) && !seq.getCrossReferences().isEmpty() ) {
407             addCrossReferences( top, seq.getCrossReferences(), category );
408         }
409         if ( ( seq.getUris() != null ) && !seq.getUris().isEmpty() ) {
410             addUris( top, seq.getUris(), category );
411         }
412     }
413
414     private static void addSubelement( final DefaultMutableTreeNode node, final String name, final String value ) {
415         if ( !ForesterUtil.isEmpty( value ) ) {
416             node.add( new DefaultMutableTreeNode( name + ": " + value ) );
417         }
418     }
419
420     private static void addTaxonomy( final DefaultMutableTreeNode top, final Taxonomy tax, final String name ) {
421         final DefaultMutableTreeNode category = new DefaultMutableTreeNode( name );
422         top.add( category );
423         if ( tax.getIdentifier() != null ) {
424             addSubelement( category, TAXONOMY_IDENTIFIER, tax.getIdentifier().asText().toString() );
425         }
426         addSubelement( category, TAXONOMY_CODE, tax.getTaxonomyCode() );
427         addSubelement( category, TAXONOMY_SCIENTIFIC_NAME, tax.getScientificName() );
428         addSubelement( category, TAXONOMY_AUTHORITY, tax.getAuthority() );
429         addSubelement( category, TAXONOMY_COMMON_NAME, tax.getCommonName() );
430         for( final String syn : tax.getSynonyms() ) {
431             addSubelement( category, TAXONOMY_SYNONYM, syn );
432         }
433         addSubelement( category, TAXONOMY_RANK, tax.getRank() );
434         if ( ( tax.getUris() != null ) && !tax.getUris().isEmpty() ) {
435             addUris( top, tax.getUris(), category );
436         }
437         if ( ( tax.getLineage() != null ) && !tax.getLineage().isEmpty() ) {
438             addLineage( top, tax.getLineage(), category );
439         }
440     }
441
442     private static void addUri( final DefaultMutableTreeNode top, final Uri uri, final String name ) {
443         DefaultMutableTreeNode category;
444         category = new DefaultMutableTreeNode( name );
445         top.add( category );
446         addSubelement( category, "Description", uri.getDescription() );
447         addSubelement( category, "Type", uri.getType() );
448         addSubelement( category, "URI", uri.getValue().toString() );
449     }
450
451     private static void addUris( final DefaultMutableTreeNode top,
452                                  final List<Uri> uris,
453                                  final DefaultMutableTreeNode category ) {
454         if ( ( uris != null ) && ( uris.size() > 0 ) ) {
455             category.add( new DefaultMutableTreeNode( "URIs" ) );
456             final DefaultMutableTreeNode last = top.getLastLeaf();
457             int i = 0;
458             for( final Uri uri : uris ) {
459                 if ( uri != null ) {
460                     addUri( last, uri, "URI " + ( i++ ) );
461                 }
462             }
463         }
464     }
465
466     private static void createNodes( final DefaultMutableTreeNode top, final PhylogenyNode phylogeny_node ) {
467         addBasics( top, phylogeny_node, BASIC );
468         // Taxonomy
469         if ( phylogeny_node.getNodeData().isHasTaxonomy() ) {
470             addTaxonomy( top, phylogeny_node.getNodeData().getTaxonomy(), TAXONOMY );
471         }
472         // Sequence
473         if ( phylogeny_node.getNodeData().isHasSequence() ) {
474             addSequence( top, phylogeny_node.getNodeData().getSequence(), SEQUENCE );
475         }
476         // Events
477         if ( phylogeny_node.getNodeData().isHasEvent() ) {
478             addEvents( top, phylogeny_node.getNodeData().getEvent(), EVENTS );
479         }
480         // Date
481         if ( phylogeny_node.getNodeData().isHasDate() ) {
482             addDate( top, phylogeny_node.getNodeData().getDate(), DATE );
483         }
484         // Distribution
485         if ( phylogeny_node.getNodeData().isHasDistribution() ) {
486             addDistribution( top, phylogeny_node.getNodeData().getDistribution(), DISTRIBUTION );
487         }
488         // Reference
489         if ( phylogeny_node.getNodeData().isHasReference() ) {
490             addReference( top, phylogeny_node.getNodeData().getReference(), LIT_REFERENCE );
491         }
492         // BinaryCharacters
493         if ( phylogeny_node.getNodeData().isHasBinaryCharacters() ) {
494             addBinaryCharacters( top, phylogeny_node.getNodeData().getBinaryCharacters(), BINARY_CHARACTERS );
495         }
496         // Properties
497         if ( phylogeny_node.getNodeData().isHasProperties() ) {
498             addProperties( top, phylogeny_node.getNodeData().getProperties(), PROP );
499         }
500     }
501 }