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

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

Implemented importing of classifications

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