Statistics
| Revision:

root / tmp / org.txm.treetagger.core / src / org / txm / importer / xmltxm / Annotate.groovy @ 3051

History | View | Annotate | Download (18.5 kB)

1
// Copyright © 2010-2013 ENS de Lyon.
2
// Copyright © 2007-2010 ENS de Lyon, CNRS, INRP, University of
3
// Lyon 2, University of Franche-Comté, University of Nice
4
// Sophia Antipolis, University of Paris 3.
5
//
6
// The TXM platform is free software: you can redistribute it
7
// and/or modify it under the terms of the GNU General Public
8
// License as published by the Free Software Foundation,
9
// either version 2 of the License, or (at your option) any
10
// later version.
11
//
12
// The TXM platform is distributed in the hope that it will be
13
// useful, but WITHOUT ANY WARRANTY; without even the implied
14
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15
// PURPOSE. See the GNU General Public License for more
16
// details.
17
//
18
// You should have received a copy of the GNU General
19
// Public License along with the TXM platform. If not, see
20
// http://www.gnu.org/licenses.
21
//
22
//
23
//
24
// $LastChangedDate: 2016-10-03 15:30:36 +0200 (lun. 03 oct. 2016) $
25
// $LastChangedRevision: 3313 $
26
// $LastChangedBy: mdecorde $
27
//
28
package org.txm.importer.xmltxm
29

    
30
import java.io.File
31
import java.text.DateFormat
32
import java.util.Date
33
import java.util.concurrent.*
34

    
35
import org.txm.Toolbox
36
import org.txm.importer.*
37
import org.txm.scripts.importer.*
38
import org.txm.importer.scripts.xmltxm.AnnotationInjection
39
import org.txm.importer.cwb.*
40
import org.txm.objects.*
41
import org.txm.treetagger.core.preferences.TreeTaggerPreferences
42
import org.txm.utils.ConsoleProgressBar
43
import org.txm.utils.LangDetector;
44
import org.txm.utils.io.IOUtils
45
import org.txm.utils.logger.Log;
46
import org.txm.utils.treetagger.TreeTagger
47

    
48
/**
49
 * Annotate and replace the TEI-TXM files of the folder $rootDirFile/txm with TreeTagger.
50
 * creates $rootDirFile/interp and $rootDirFile/treetagger
51
 *
52
 */
53
class Annotate {
54
        boolean cancelNow = false;
55
        
56
        /** The report file. */
57
        File reportFile;//contains the txm:application tag content
58
        
59
        /** The resp person. */
60
        String respPerson;
61
        
62
        /** The resp id. */
63
        String respId;
64
        
65
        /** The resp desc. */
66
        String respDesc;
67
        
68
        /** The resp date. */
69
        String respDate;
70
        
71
        /** The resp when. */
72
        String respWhen;
73
        
74
        /** The app ident. */
75
        String appIdent;
76
        
77
        /** The app version. */
78
        String appVersion;
79
        
80
        /** The distributor. */
81
        String distributor;
82
        
83
        /** The publi stmt. */
84
        String publiStmt;
85
        
86
        /** The source stmt. */
87
        String sourceStmt;
88
        
89
        /** The types. */
90
        def types;
91
        
92
        /** The types title. */
93
        def typesTITLE;
94
        
95
        /** The types desc. */
96
        def typesDesc;
97
        
98
        /** The types tagset. */
99
        def typesTAGSET;
100
        
101
        /** The types web. */
102
        def typesWEB;
103
        
104
        /** The idform. */
105
        String idform;
106
        
107
        /** The debug. */
108
        boolean debug = false;
109
        
110
        File modelsDirectory;
111
        
112
        public Annotate() {
113
                modelsDirectory = new File(TreeTaggerPreferences.getInstance().getString(TreeTaggerPreferences.MODELS_PATH)); // default models directory is set in the Toolbox
114
        }
115
        
116
        /**
117
         * Sets the debug.
118
         */
119
        public void setDebug() {
120
                debug = true;
121
        }
122
        
123
        String id;
124
        /**
125
         * Inits the tt outfile infos.
126
         *
127
         * @param rootDirFile the root dir file
128
         * @param modelfile the modelfile
129
         */
130
        public void initTTOutfileInfos(File rootDirFile, File modelfile, String modelfilename) {
131
                initTTOutfileInfos(rootDirFile, modelfile, modelfilename, null);
132
        }
133
        
134
        /**
135
         * Inits the tt outfile infos.
136
         *
137
         * @param rootDirFile the root dir file
138
         * @param modelfile the modelfile
139
         * @param properties : 2 element array that contains the word properties to create. It can be null (the modelfilename will be used)
140
         */
141
        public void initTTOutfileInfos(File rootDirFile, File modelfile, String modelfilename, String[] properties) {
142
                
143
                id = modelfilename;
144
                String[] split = id.split("\\.");
145
                if (split.length > 0) id = split[0];
146
                if (id.equals("??")) id = "xx"
147
                
148
                reportFile = new File(rootDirFile,"NLPToolsParameters.xml");
149
                
150
                respPerson = System.getProperty("user.name");
151
                respId = "txm";
152
                respDesc = "NLP annotation tool";
153
                respDate = DateFormat.getDateInstance(DateFormat.SHORT, Locale.UK).format(new Date());
154
                respWhen = DateFormat.getDateInstance(DateFormat.FULL, Locale.UK).format(new Date());
155
                
156
                appIdent = "TreeTagger";
157
                appVersion = "3.2";
158
                
159
                distributor = "";
160
                publiStmt = """""";
161
                sourceStmt = """""";
162
                
163
                if (properties != null && properties.length == 2) {
164
                        types = [properties[0], properties[1]];
165
                        typesTITLE = [properties[0], properties[1]];
166
                } else {
167
                        types = [id+"pos", id+"lemma"];
168
                        typesTITLE = [id+"pos", id+"lemma"];
169
                }
170
                
171
                //TODO: the tagset, website and description should be referenced in the model catalog
172
                if (modelfile.getName() == "rgaqcj.par") {
173
                        typesDesc = [
174
                                "CATTEX pos tagset built with BFM texts",
175
                                "fr lemma of the model "+modelfile+" - "
176
                        ]
177
                        typesTAGSET = [
178
                                "http://bfm.ens-lyon.fr/IMG/pdf/Cattex2009_Manuel.pdf",
179
                                ""
180
                        ]
181
                        typesWEB = [
182
                                "http://bfm.ens-lyon.fr/",
183
                                ""
184
                        ]
185
                } else {
186
                        typesDesc = [
187
                                "pos tagset built from model "+modelfile,
188
                                id+" lemma of the model "+modelfile+" - "
189
                        ]
190
                        typesTAGSET = ["", ""]
191
                        typesWEB = ["", ""]
192
                }
193
                
194
                idform ="w";
195
        }
196
        
197
        /**
198
         * Apply tt.
199
         *
200
         * @param ttsrcfile the ttsrcfile
201
         * @param ttoutfile the ttoutfile
202
         * @param modelfile the modelfile
203
         * @return true, if successful
204
         */
205
        public boolean applyTT(File ttsrcfile, File ttoutfile, File modelfile) {
206
                return applyTT(ttsrcfile, ttoutfile, modelfile, null)
207
        }
208
        
209
        /**
210
         * Apply tt.
211
         *
212
         * @param ttsrcfile the ttsrcfile
213
         * @param ttoutfile the ttoutfile
214
         * @param modelfile the modelfile
215
         * @param options, if null use value set in Toolbox preferences
216
         * @return true, if successful
217
         */
218
        public boolean applyTT(File ttsrcfile, File ttoutfile, File modelfile, String[] options) {
219
                
220
                try {
221
                        File infile = ttsrcfile;
222
                        File outfile = ttoutfile;
223
                        
224
                        def tt = new TreeTagger(TreeTaggerPreferences.getInstance().getString(TreeTaggerPreferences.INSTALL_PATH)+"/bin/", options);
225
                        tt.settoken();
226
                        tt.setlemma();
227
                        tt.setsgml();
228
                        if (TreeTaggerPreferences.getInstance().getBoolean(TreeTaggerPreferences.OPTIONS_UNKNOWN)) {
229
                                tt.setnounknown();
230
                        }
231
                        tt.seteostag("<s>");
232
                        if (TreeTaggerPreferences.getInstance().getBoolean(TreeTaggerPreferences.OPTIONS_DEBUG)) {
233
                                tt.debug(true);
234
                        } else {
235
                                tt.setquiet();
236
                        }
237
                        if (TreeTaggerPreferences.getInstance().getBoolean(TreeTaggerPreferences.OPTIONS_CAPHEURISTIC)) {
238
                                tt.setcapheuristics();
239
                        }
240
                        if (TreeTaggerPreferences.getInstance().getBoolean(TreeTaggerPreferences.OPTIONS_HYPHENHEURISTIC)) {
241
                                tt.sethyphenheuristics();
242
                        }
243
                        if (TreeTaggerPreferences.getInstance().getBoolean(TreeTaggerPreferences.OPTIONS_PROB)) {
244
                                tt.setprob();
245
                        }
246
                        
247
                        String lex = TreeTaggerPreferences.getInstance().getString(TreeTaggerPreferences.OPTIONS_LEX);
248
                        if (lex !=null && lex.length() > 0) {
249
                                tt.setlex(lex);
250
                        }
251
                        String wc = TreeTaggerPreferences.getInstance().getString(TreeTaggerPreferences.OPTIONS_WC);
252
                        if (wc !=null && wc.length() > 0) {
253
                                tt.setwc(wc);
254
                        }
255
                        tt.treetagger(modelfile.getAbsolutePath(), infile.getAbsolutePath(), outfile.getAbsolutePath())
256
                        infile.delete();
257
                } catch(Exception e) {
258
                        Log.printStackTrace(e);
259
                        System.out.println("Failed to apply TreeTagger on $ttsrcfile input file with the $modelfile model file.");
260
                        return false;
261
                }
262
                return true;
263
        }
264
        
265
        /**
266
         * Write standoff file.
267
         *
268
         * @param ttoutfile the ttoutfile
269
         * @param posfile the posfile
270
         * @return true, if successful
271
         */
272
        public boolean writeStandoffFile(File ttoutfile, File posfile)
273
        {
274
                def encoding ="UTF-8";
275
                def transfo = new CSV2W_ANA();
276
                //println("build w-interp "+ttfile.getName()+ ">>"+posfile.getName())
277
                transfo.setAnnotationTypes( types, typesDesc, typesTAGSET, typesWEB, idform);
278
                transfo.setResp(respId, respDesc, respDate, respPerson, respWhen);
279
                transfo.setApp(appIdent, appVersion);
280
                transfo.setTarget(ttoutfile.getAbsolutePath(), reportFile);
281
                transfo.setInfos(distributor,  publiStmt, sourceStmt);
282
                return transfo.process( ttoutfile, posfile, encoding );
283
        }
284
        
285
        /**
286
         * Run step by step : build TT src files, run TT, build xml-standoff files, inject standoff annotations
287
         *
288
         * @param rootDirFile the root dir file
289
         * @param modelfilename the modelfilename
290
         * @return true, if successful
291
         */
292
        public boolean run(File binDir, File txmDir,  String modelfilename)
293
        {
294
                //test if modelfile exists
295
                if (debug) {
296
                        println "rootDirFile "+binDir
297
                        println "txmDir "+txmDir
298
                        println "TREETAGGER INSTALL PATH : "+TreeTaggerPreferences.getInstance().getString(TreeTaggerPreferences.INSTALL_PATH);
299
                        println "TREETAGGER MODELS PATH : "+TreeTaggerPreferences.getInstance().getString(TreeTaggerPreferences.MODELS_PATH)
300
                }
301
                
302
                //test if the Toolbox know TreeTagger
303
                if (!new File(TreeTaggerPreferences.getInstance().getString(TreeTaggerPreferences.INSTALL_PATH)+"/bin/").exists()) {
304
                        println("Could not find TreeTagger binaries in "+TreeTaggerPreferences.getInstance().getString(TreeTaggerPreferences.INSTALL_PATH)+"/bin/")
305
                        return false;
306
                }
307
                String langAll = null
308
                String lang;
309
                if (modelfilename.startsWith("??")) {
310
                        langAll = new LangDetector(binDir).getLang();
311
                        println "General lang $langAll"
312
                }
313
                
314
                //cleaning
315
                new File(binDir, "annotations").deleteDir();
316
                new File(binDir, "annotations").mkdir();
317
                new File(binDir, "treetagger").deleteDir();
318
                new File(binDir, "treetagger").mkdir();
319
                
320
                ArrayList<String> milestones = [];
321
                
322
                //BUILD TT FILE READY TO BE TAGGED
323
                List<File> files = txmDir.listFiles(IOUtils.HIDDENFILE_FILTER)
324
                
325
                println("Building TT source files ("+files.size()+") from directory "+txmDir)
326
                ConsoleProgressBar cpb = new ConsoleProgressBar(files.size())
327
                for (File f : files) {
328
                        cpb.tick()
329
                        File srcfile = f;
330
                        File resultfile = new File(binDir, "treetagger/"+f.getName()+".tt");
331
                        if(debug)
332
                                println "build tt src : "+srcfile+" >> "+resultfile
333
                        def ttsrcbuilder = new BuildTTSrc(srcfile.toURI().toURL())
334
                        if (!ttsrcbuilder.process(resultfile, null))
335
                                System.out.println("Failed to build tt src file of "+srcfile);
336
                }
337
                
338
                if (cancelNow) {
339
                        return false;
340
                }
341
                
342
                File modelDirectory = new File(TreeTaggerPreferences.getInstance().getString(TreeTaggerPreferences.MODELS_PATH));
343
                if (!modelDirectory.exists()) {
344
                        println "Skipping ANNOTATE: TreeTagger language model file directory not found: "+modelDirectory.getAbsolutePath();
345
                        return false;
346
                } else         if (!modelDirectory.canRead()) {
347
                        println "Skipping ANNOTATE: impossible to access the TreeTagger language model file directory: "+modelDirectory.getAbsolutePath();
348
                        return false;
349
                }
350
                println("")
351
                //Convert encoding if needed
352
                
353
                //APPLY TREETAGGER
354
                files = new File(binDir, "treetagger").listFiles(IOUtils.HIDDENFILE_FILTER)
355
                println("Applying $modelfilename TreeTagger model on dir: "+new File(binDir, "treetagger")+ " ("+files.size()+" files)")
356
                if (files == null || files.size() == 0) {
357
                        return false;
358
                }
359
                File modelfile;
360
                cpb = new ConsoleProgressBar(files.size())
361
                for (File f : files) {
362
                        String tmpModelFileName = modelfilename
363
                        if (modelfilename.startsWith("??")) {
364
                                lang = langAll;
365
                                if (f.length() > LangDetector.MINIMALSIZE) {
366
                                        lang = new LangDetector(f).getLang();
367
                                        //println "guessing lang $f : $lang"
368
                                }
369
                                tmpModelFileName = lang+".par"
370
                        }
371
                        modelfile = new File(modelsDirectory, tmpModelFileName);
372
                        if (debug)
373
                                println "model file : "+modelfile;
374
                        
375
                        File
376
                        
377
                        if (!modelfile.exists()) {
378
                                println "Skipping ANNOTATE: '$modelfile' TreeTagger language model file not found."
379
                                if(System.getProperty("os.name").startsWith("Windows") || System.getProperty("os.name").startsWith("Mac"))
380
                                        println "Windows&Mac users: the operating system might be hiding file extensions. Use your file explorer to check the file name."
381
                                return false;
382
                        } else if (!modelfile.canRead()) {
383
                                println "Skipping ANNOTATE: impossible to access the '$modelfile' TreeTagger language model file."
384
                                return false;
385
                        }
386
                        
387
                        //                        if (modelfile.getName().equals("sp.par")) {//UTF >> Latin1
388
                        //                                if(debug)
389
                        //                                        println "fix encoding for model "+modelfile
390
                        //                                new EncodingConverter(f, "UTF-8", "ISO-8859-1")
391
                        //                        }
392
                        
393
                        cpb.tick()
394
                        File infile = f;
395
                        File outfile = new File(f.getParent(),f.getName()+"-out.tt");
396
                        if (!applyTT(infile, outfile, modelfile)) {
397
                                return false;
398
                        }
399
                        
400
                        //                        //Reconvert encoding if needed
401
                        //                        if (modelfile.getName().equals("sp.par")) {
402
                        //                                if(debug)
403
                        //                                        println "convert "+f+" latin1 >> UTF-8"
404
                        //                                new EncodingConverter(f, "ISO-8859-1", "UTF-8")
405
                        //                        }
406
                        
407
                        
408
                        
409
                        initTTOutfileInfos(binDir, modelfile, modelfilename);
410
                        
411
                        File annotfile = new File(binDir, "annotations/"+outfile.getName()+"-STOFF.xml");
412
                        if (!writeStandoffFile(outfile, annotfile)) {
413
                                println("Failed to build standoff file of "+outfile);
414
                        }
415
                        if (cancelNow) return;
416
                }
417
                println("")
418
                
419
                if (cancelNow) {
420
                        return false;
421
                }
422
                
423
                //INJECT ANNOTATIONS
424
                List<File> interpfiles = new File(binDir, "annotations").listFiles(IOUtils.HIDDENFILE_FILTER);
425
                List<File> txmfiles = txmDir.listFiles(IOUtils.HIDDENFILE_FILTER);
426
                if (txmfiles == null) {
427
                        println "No file to annotate in "+txmDir.getAbsolutePath()
428
                        return false;
429
                }
430
                interpfiles.sort(); // same order
431
                txmfiles.sort(); //same order
432
                println "Injecting stdoff files ("+interpfiles.size()+") data from "+new File(binDir, "annotations")+ " to xml-txm files of "+txmDir;
433
                if (interpfiles == null || interpfiles.size() == 0)
434
                        return false;
435
                cpb = new ConsoleProgressBar(interpfiles.size())
436
                for (int i = 0 ; i < interpfiles.size() ; i++) {
437
                        cpb.tick()
438
                        File srcfile = txmfiles.get(i);
439
                        File pos1file = interpfiles.get(i);
440
                        File temp = File.createTempFile("Annotate", "temp", srcfile.getParentFile());
441
                        def builder = new AnnotationInjection(srcfile.toURI().toURL(), pos1file.toURI().toURL());
442
                        if (!builder.process(temp)) {
443
                                return false;
444
                        }
445
                        builder = null;
446
                        
447
                        //println "renaming files..."
448
                        if (!(srcfile.delete() && temp.renameTo(srcfile)))
449
                                println "Warning can't rename file "+temp+" to "+srcfile
450
                }
451
                
452
                println("")
453
                return true;
454
        }
455
        
456
        public void setModelsDirectory(File modelsDirectory) {
457
                this.modelsDirectory = modelsDirectory;
458
        }
459
        
460
        /**
461
         * Run file by file. Allow to have one different lang per file. Default behavior add new word properties
462
         *
463
         * @param binDir
464
         * @param txmDir
465
         * @param lang associate a file name with a model filename
466
         * @return true, if successful
467
         */
468
        public boolean run(File binDir, File txmDir, HashMap<String, String> langs) {
469
                return run(binDir, txmDir, langs, false, new String[0], new String[0]);
470
        }
471
        
472
        /**
473
         * Run file by file. Allow to have one different lang per file
474
         *
475
         * @param binDir 
476
         * @param txmDir
477
         * @param lang associate a file name with a model filename
478
         * @param replace, replace or create a word property
479
         * @return true, if successful
480
         */
481
        public boolean run(File binDir, File txmDir, HashMap<String, String> langs, boolean replace, String[] properties, String[] options) {
482
                
483
                if (!new File(TreeTaggerPreferences.getInstance().getString(TreeTaggerPreferences.INSTALL_PATH)+"/bin/").exists()) {
484
                        println("Path to TreeTagger is wrong "+TreeTaggerPreferences.getInstance().getString(TreeTaggerPreferences.INSTALL_PATH)+"/bin/")
485
                        return true;
486
                }
487
                
488
                List<File> listfiles = txmDir.listFiles(IOUtils.HIDDENFILE_FILTER);
489
                
490
                //cleaning
491
                File annotDir = new File(binDir,"annotations");
492
                annotDir.deleteDir();
493
                annotDir.mkdir();
494
                File ptreetaggerDir = new File(binDir,"ptreetagger");
495
                ptreetaggerDir.deleteDir();
496
                ptreetaggerDir.mkdir();
497
                File treetaggerDir = new File(binDir,"treetagger");
498
                treetaggerDir.deleteDir();
499
                treetaggerDir.mkdir();
500
                
501
                int cores = Runtime.getRuntime().availableProcessors()
502
                int coresToUse = Math.max(1.0, cores * 0.7)
503
                ExecutorService pool = Executors.newFixedThreadPool(coresToUse)
504
                
505
                def files = txmDir.listFiles(IOUtils.HIDDENFILE_FILTER)
506
                ConsoleProgressBar cpb = new ConsoleProgressBar(files.size())
507
                for (File teiFile : files) {
508
                        int counter = 1;
509
                        ThreadFile t = new ThreadFile("TT_"+counter++, teiFile) {
510
                                                
511
                                                public void run() {
512
                                                        
513
                                                        if (cancelNow) return;
514
                                                        if (langs.get(f.getName()) == null) {
515
                                                                println "Error: no lang defined for file $f"
516
                                                                return;
517
                                                        }
518
                                                        
519
                                                        String lang = langs.get(f.getName());
520
                                                        run(f, lang, binDir, txmDir, replace, properties, options, annotDir, ptreetaggerDir, treetaggerDir)
521
                                                        
522
                                                        cpb.tick();
523
                                                }
524
                                        };
525
                        
526
                        pool.execute(t)
527
                }
528
                
529
                pool.shutdown()
530
                pool.awaitTermination(10, TimeUnit.HOURS)
531
                println ""
532
                return true;
533
        }
534
        
535
        public boolean run(File f, String lang, boolean fixExistingValues, File binDir, File txmDir) {
536
                
537
                File annotDir = new File(binDir,"annotations");
538
                annotDir.mkdir();
539
                File ptreetaggerDir = new File(binDir,"ptreetagger");
540
                ptreetaggerDir.mkdir();
541
                File treetaggerDir = new File(binDir,"treetagger");
542
                treetaggerDir.mkdir();
543
                
544
                return run(f, lang, binDir, txmDir, fixExistingValues, new String[0], new String[0], annotDir, ptreetaggerDir, treetaggerDir)
545
        }
546
        
547
        public boolean run(File f, String lang, File binDir, File txmDir, boolean fixExistingValues, String[] properties, String[] options, File annotDir, File ptreetaggerDir, File treetaggerDir) {
548
                
549
                File modelfile = new File(modelsDirectory, lang+".par");
550
                if (!"??".equals(lang) && !modelfile.exists()) {
551
                        println "Error: No Modelfile available for lang "+modelfile+". Continue import process ";
552
                        return false;
553
                }
554
                File annotfile = new File(annotDir, f.getName()+"-STDOFF.xml");
555
                File ttsrcfile = new File(ptreetaggerDir, f.getName()+"-src.tt");
556
                File ttrezfile = new File(treetaggerDir, f.getName()+"-out.tt");
557
                //println ("TT with $model "+f+"+"+annotfile+" > "+ttsrcfile+" > "+ttrezfile);
558
                
559
                //BUILD TT FILE READY TO BE TAGGED
560
                def builder = new BuildTTSrc(f.toURL());
561
                builder.process(ttsrcfile, null);
562
                
563
                String tmpModelFileName = modelfile.getName()
564
                if (tmpModelFileName.startsWith("??")) {
565
                        if (f.length() > LangDetector.MINIMALSIZE) {
566
                                String dlang = new LangDetector(f).getLang();
567
                                //println "$f detected lang is $dlang"
568
                                tmpModelFileName = dlang+".par"
569
                        }
570
                }
571
                modelfile = new File(modelsDirectory, tmpModelFileName);
572
                
573
                //Apply TT
574
                applyTT(ttsrcfile, ttrezfile, modelfile, options);
575
                
576
                //CREATE STANDOFF FILES
577
                initTTOutfileInfos(binDir, modelfile, lang, properties);
578
                writeStandoffFile(ttrezfile, annotfile)
579
                
580
                //INJECT ANNOTATIONS
581
                File tmpFile = new File(txmDir, "temp_"+f.getName())
582
                builder = new AnnotationInjection(f.toURL(), annotfile.toURL(), fixExistingValues);
583
                builder.process(tmpFile);
584
                if (!(f.delete() && tmpFile.renameTo(f))) println "Warning can't rename file "+tmpFile+" to "+f
585
                
586
                return f.exists();
587
        }
588
        
589
        public void setCancelNow() {
590
                cancelNow = true;
591
        }
592
        
593
        public class ThreadFile extends Thread {
594
                File f;
595
                public ThreadFile(String name, File f) {
596
                        this.setName(name)
597
                        this.f = f;
598
                }
599
        }
600
}