source: trunk/grails-app/domain/nl/tno/massSequencing/classification/Taxon.groovy @ 67

Last change on this file since 67 was 67, checked in by robert@…, 8 years ago

Removed needless debug printlns

File size: 10.3 KB
Line 
1package nl.tno.massSequencing.classification
2
3class Taxon {
4        def sessionFactory
5       
6        /**
7        * Available taxonomy levels. These levels are only here for reference, so you don't have
8        * to use magic numbers when referencing a specific level
9        * @author robert
10        */
11        enum Level {
12                DOMAIN(0, "Domain"), KINGDOM(1, "Kingdom"), PHYLUM(2, "Phylum"), CLASS(3, "Class"), ORDER(4, "Order"), FAMILY(5, "Family"), GENUS(6, "Genus"), SPECIES(7, "Species")
13               
14                private int levelNumber;
15                private String description;
16               
17                private Level( int number, String description ) { 
18                        levelNumber = number
19                        this.description = description;
20                }
21               
22                public int number() { return levelNumber; }
23                public String description() { return description; }
24        }
25       
26        // Level of this taxon in the taxonomy. We use an int instead of a Level enum because the level
27        // can be up to 12 in the future. This implementation is capable of handling all numbers
28        int level = -1;
29       
30        // Name of the taxon
31        String name;
32       
33        // Indices to handle hierarchy
34        // @see http://dev.mysql.com/tech-resources/articles/hierarchical-data.html
35        int lft;
36        int rgt;
37       
38    static constraints = {
39    }
40       
41        static mapping = {
42                table 'taxonomy'
43                lft index: 'lft_idx'
44                rgt index: 'rgt_idx'
45                level index: 'level_name_idx'
46                name index: 'level_name_idx'
47        }
48       
49        /**
50         * Retrieves the parent of this taxon
51         * @return      The parent taxon for the current taxon, or null if no parent exists
52         */
53        public Taxon giveParent() {
54                // Root taxon doesn't have a parent
55                if( level == 0 ) 
56                        return null
57                       
58                return Taxon.find( "FROM Taxon t WHERE t.lft < :currentLeft AND t.rgt > :currentRgt AND t.level = :parentLevel", [ 'currentLeft': lft, 'currentRgt': rgt, 'parentLevel': level - 1 ] )
59        }
60       
61        /**
62         * Returns the path of taxa in the tree, leading up to this one. The list
63         * includes the current taxon.
64         *
65         * @return
66         */
67        public List givePath() {
68                if( level == 0 )
69                        return [ this ];
70               
71                return Taxon.findAll( "FROM Taxon parent WHERE parent.lft <= :nodeLft AND parent.rgt >= :nodeLft ORDER BY parent.lft", [ 'nodeLft' : this.lft ] )
72        }
73       
74        /**
75         * Returns the names of taxa in the tree, leading up to this one. The list
76         * includes the current taxon.
77         *
78         * @return
79         */
80        public List givePathNames() {
81                if( level == 0 )
82                        return [ this.name ];
83               
84                return Taxon.executeQuery( "SELECT parent.name FROM Taxon parent WHERE parent.lft <= :nodeLft AND parent.rgt >= :nodeLft ORDER BY parent.lft", [ 'nodeLft' : this.lft ] )
85        }
86       
87        /**
88         * Append the current taxon to the tree as a child of the parent taxon. The current taxon will be saved immediately.
89         *
90         * The lft and rgt properties of this taxon will be set. Also, if no level is set, the level will be set to the parent level + 1
91         *
92         * Make sure to refresh all taxon objects after adding 
93         *
94         * @param parentTaxon
95         * @return      true if the taxon has been added correctly, false otherwise
96         */
97        public boolean appendTo( Taxon parentTaxon ) {
98                // Without parentTaxon, a root taxon must be added
99                if( !parentTaxon )
100                        return addAsRoot();
101                       
102                // Perform the changes within a transaction, in order to rollback all changes if something fails
103                Taxon.withTransaction { status ->
104                        def parentRgt = parentTaxon.rgt;
105                       
106                        try {
107                                // Update the tree by shifting all numbers 2 to the right
108                                Taxon.executeUpdate( "UPDATE Taxon t SET t.rgt = t.rgt + 2 WHERE t.rgt >= ?", [ parentRgt ] )
109                                Taxon.executeUpdate( "UPDATE Taxon t SET t.lft = t.lft + 2 WHERE t.lft >= ?", [ parentRgt ] )
110                               
111                                // Update the current taxon by putting the lft and rgt number correct
112                                this.lft = parentRgt
113                                this.rgt = parentRgt + 1
114                               
115                                // Set the current taxon level, if needed
116                                this.level = this.level > -1 ? this.level : parentTaxon.level + 1;
117                               
118                                // Save this taxon in the database in order to prevent
119                                this.save( failOnError: true );
120                               
121                                // The session must be cleared each time the update statements are executed
122                                // since otherwise the objects don't match anymore
123                                sessionFactory.getCurrentSession().flush();
124                                sessionFactory.getCurrentSession().clear();
125                        } catch( Exception e ) {
126                                // If an error occurs, rollback whole transaction
127                                status.setRollbackOnly();
128                               
129                                throw new Exception( "Error occurred while adding taxon " + this, e )
130                        }
131                }
132               
133                return true;
134        }
135       
136        /**
137         * Append the current taxon to the tree as root taxon. The taxon will be saved.
138         *
139         * The lft and rgt properties of this taxon will be set. Also, if no level is set, the level will be set to 0
140         *
141         * @return      true if the taxon has been added correctly, false otherwise
142         */
143        public boolean addAsRoot() {
144                // Determine the maximum 'right' value
145                def results = Taxon.executeQuery( "SELECT max(t.rgt) FROM Taxon t" )
146                def previousRgt = results && results[ 0 ] ? results[ 0 ] : 0
147               
148                try {
149                        // Update the current taxon by putting the lft and rgt number correct
150                        this.lft = previousRgt + 1
151                        this.rgt = previousRgt + 2
152                       
153                        // Set the current taxon level, if needed
154                        this.level = this.level > -1 ? this.level : 0;
155
156                        // Save this taxon in the database in order to prevent
157                        this.save( failOnError: true );
158                } catch( Exception e ) {
159                        throw new Exception( "Error occurred while adding taxon " + this, e )
160                }
161
162        }
163       
164        /**
165         * Retrieves a taxon from the database based on the path of names given.
166         * @param path                  An array or list with the names of the taxa in the path, ordered by level
167         * @param startLevel    Which level is the first entry in the list. Defaults to zero. Can be used in order to find taxa
168         *                                              without the whole tree being specified (e.g. without the root element)
169         * @return                              First taxon that is found and matches the criteria or null if nothing is found
170         */
171        public static Taxon findTaxonByPath( def path, int startLevel = 0 ) {
172                if( path.size() == 0 )
173                        return null;
174                       
175                def leafLevel = path.size() - 1 + startLevel;
176                def leafName = path[ -1 ];
177               
178                // Find all taxa that match the given leafnode
179                def leafs = Taxon.findAll(
180                        "FROM Taxon t WHERE t.level = :level AND t.name = :name",
181                        [ 'name': leafName, 'level': leafLevel ]
182                )
183               
184                // If none is found, return null
185                if( !leafs || !leafs[0] )
186                        return null
187               
188                // If one or more leafs are found, return the one with the correct path
189                def leafPath
190                def numLeafs;
191                def i
192               
193                findLeafs:      // Label in order to continue this loop
194                for( leaf in leafs ) {
195                        leafPath = leaf.givePathNames();
196                       
197                        println "Path for leaf " + leaf.name + " (" + leaf.level + "): " + leafPath
198                        numLeafs = leafPath.size();
199                       
200                        for( i = 0; i < numLeafs; i++ ) {
201                                if( leafPath[ i ] != path[ i ] ) {
202                                        continue findLeafs
203                                }
204                        }
205                       
206                        return leaf;
207                }
208               
209                return null;
210        }
211       
212        /**
213         * Retrieves a taxon from the database based on the path of names given. If no taxon matches the criteria,
214         * a new taxon is created (and the other parts of the tree are created as well.
215         *
216         * @param path                  An array or list with the names of the taxa in the path, ordered by level
217         * @param startLevel    Which level is the first entry in the list. Defaults to zero. Can be used in order to find taxa
218         *                                              without the whole tree being specified (e.g. without the root element)
219         * @return                              First taxon that is found and matches the criteria or a new taxon if it didn't exist
220         */
221        static Taxon findOrCreateTaxonByPath( def path, int startLevel = 0 ) {
222                def taxon = findTaxonByPath( path, startLevel );
223               
224                if( taxon )
225                        return taxon;
226                       
227                def depth = path.size();
228                def found = false
229                def levelFound = -1;
230                def parentNode;
231
232                // The taxon doesn't exist yet. Find the highest level in the path that does exist
233                // level contains the index of the taxon in the path. So the highest level is depth - 1.
234                // However, we don't have to check the highest level, so we start at depth - 2
235                for( def level = depth - 2; level >= 0 && !found ; level-- ) {
236                        parentNode = findTaxonByPath( path[ 0 .. level ], startLevel );
237                       
238                        // If this taxon is found, it is the highest level
239                        if( parentNode ) {
240                                found = true;
241                                levelFound = level;
242                        }
243                }
244               
245                // Create taxons from the levelFound up to the leaf
246                for( def level = levelFound + 1; level < depth; level++ ) {
247                        // Create a new taxon for this level
248                        parentNode = Taxon.createTaxon( path[ level ], level + startLevel, parentNode );
249                }
250               
251                // Return the leaf node
252                return parentNode
253        }
254       
255        /**
256         * Searches the database for a taxon with the given name, leven and parent. If it exists, it is returned.
257         * Otherwise it will be created and the newly created object is returned.
258         *
259         * @param name          Name of the taxon to find or create
260         * @param level         Level of the taxon
261         * @param parent        Parent of the taxon
262         * @return                      The taxon object
263         */
264        public static Taxon findOrCreateTaxon( String name, int level, Taxon parent ) {
265                def existingTaxon = findTaxon( name, level, parent );
266               
267                if( existingTaxon )
268                        return existingTaxon
269                       
270                return createTaxon( name, level, parent );
271        }
272       
273        /**
274         * Searches the database for a taxon with the given name, leven and parent. If it doesn't exist,
275         * null is returned
276         *
277         * @param name          Name of the taxon to find or create
278         * @param level         Level of the taxon
279         * @param parent        Parent of the taxon. If not given or null, the first taxon with the given name and level is returned.
280         * @return                      The taxon object or null if it doesn't exist
281         */
282        public static Taxon findTaxon( String name, int level, Taxon parent = null ) {
283                if( parent ) {
284                        return Taxon.find( 
285                                "FROM Taxon t WHERE t.level = :level AND t.name = :name AND t.lft > :parentLeft AND t.rgt < :parentRgt", 
286                                [ 'name': name, 'level': level, 'parentLeft': parent.lft, 'parentRgt': parent.rgt ] 
287                        )
288                } else {
289                        return Taxon.find(
290                                "FROM Taxon t WHERE t.level = :level AND t.name = :name",
291                                [ 'name': name, 'level': level ]
292                        )
293                }
294        }
295
296        /**
297        * Creates a taxon with the given name, leven and parent. The method creates a new taxon
298        * without checking whether it already exists.
299        *
300        * @param name           Name of the taxon to create
301        * @param level          Level of the taxon
302        * @param parent         Parent of the taxon
303        * @return                       The newly created taxon object
304        */
305   public static Taxon createTaxon( String name, int level, Taxon parent = null ) {
306           def t = new Taxon( name: name, level: level );
307           t.appendTo( parent );
308           
309           return t
310   }
311   
312   public static Map retrieveLevelNames( def minLevel = 1, def maxLevel = 6 ) {
313                def levels = [:];
314                minLevel.upto( maxLevel ) { level ->
315                        def levelEnum = Taxon.Level.find { it.number() == level }
316                        levels[ level ] = levelEnum?.description();
317                }
318               
319                return levels;
320   }
321
322}
Note: See TracBrowser for help on using the repository browser.