Statistics
| Revision:

gvsig-scripting / org.gvsig.scripting / trunk / org.gvsig.scripting / org.gvsig.scripting.lib / org.gvsig.scripting.lib.impl / src / main / java / org / gvsig / scripting / impl / DefaultScriptingScript.java @ 669

History | View | Annotate | Download (23.5 KB)

1
package org.gvsig.scripting.impl;
2

    
3
import java.io.File;
4
import java.io.IOException;
5
import java.io.InputStream;
6
import java.util.List;
7

    
8
import javax.script.Compilable;
9
import javax.script.CompiledScript;
10
import javax.script.Invocable;
11
import javax.script.ScriptEngine;
12
import javax.script.ScriptException;
13

    
14
import org.apache.commons.io.FileUtils;
15
import org.apache.commons.io.FilenameUtils;
16
import org.apache.commons.io.IOUtils;
17
import org.apache.commons.lang3.StringUtils;
18
import org.apache.commons.lang3.exception.ExceptionUtils;
19
import org.gvsig.scripting.CompileErrorException;
20
import org.gvsig.scripting.ExecuteErrorException;
21
import org.gvsig.scripting.Main;
22
import org.gvsig.scripting.ScriptingBaseScript;
23
import org.gvsig.scripting.ScriptingFolder;
24
import org.gvsig.scripting.ScriptingManager;
25
import org.gvsig.scripting.ScriptingScript;
26
import org.gvsig.scripting.ScriptingUnit;
27
import org.gvsig.tools.dispose.Disposable;
28
import org.gvsig.tools.observer.Observer;
29
import org.gvsig.tools.observer.impl.DelegateWeakReferencingObservable;
30
import org.gvsig.tools.task.AbstractMonitorableTask;
31
import org.ini4j.Ini;
32
import org.python.core.PyException;
33
import org.python.core.PyString;
34
import org.python.core.PyTraceback;
35
import org.slf4j.Logger;
36
import org.slf4j.LoggerFactory;
37

    
38
public class DefaultScriptingScript extends AbstractScript implements
39
        ScriptingScript {
40

    
41
    private static final Logger logger = LoggerFactory.getLogger(DefaultScriptingScript.class);
42
    protected String langName;
43
    protected String extension = null;
44
    protected String librarySuffix = null;
45
    protected ScriptEngine engine = null;
46
    protected CompiledScript compiledCode;
47

    
48
    private String code = null;
49
    private String mainName = "main";
50
    private boolean saved;
51
    private final DelegateWeakReferencingObservable delegatedObservable;
52

    
53
    protected DefaultScriptingScript(ScriptingFolder parent, String typename, ScriptingManager manager, String id) {
54
        super(parent, typename, manager, id);
55
        this.setLangName("python");
56
        this.setSaved(true);
57
        this.delegatedObservable = new DelegateWeakReferencingObservable(this);
58
    }
59

    
60
    public DefaultScriptingScript(ScriptingFolder parent, ScriptingManager manager, String id) {
61
        this(parent, ScriptingManager.UNIT_SCRIPT, manager, id);
62
    }
63

    
64
    public Object __getattr__(String name) {
65
        ScriptEngine engine = this.getEngine();
66
        this.compile();
67
        return engine.get(name);
68
    }
69

    
70
    public void __setattr__(String name, Object value) {
71
        ScriptEngine engine = this.getEngine();
72
        this.compile();
73
        engine.put(name, value);
74
    }
75

    
76
    public Object __call__() {
77
        return this.run();
78
    }
79

    
80
    public Object __call__(Object[] args) {
81
        return this.run(args);
82
    }
83

    
84
    protected void notifyErrors(Exception exception, String command) {
85
        this.delegatedObservable.notifyObservers(new BaseScriptingNotifycation(
86
                this, BaseScriptingNotifycation.RUNTIME_ERROR_NOTIFICATION,
87
                command, exception));
88
    }
89

    
90
    @Override
91
    public void setSaved(boolean saved) {
92
        this.saved = saved;
93
    }
94

    
95
    @Override
96
    public String getCode() {
97
        if (this.code == null) {
98
            File f = null;
99
            try {
100
                f = this.getFileResource(this.extension);
101
                this.code = FileUtils.readFileToString(f);
102
            } catch (IOException e) {
103
                String fname = (f == null) ? "(null)" : f.getAbsolutePath();
104
                logger.warn("Can't load code from file '" + fname + "'.");
105
            }
106
        }
107
        return this.code;
108
    }
109

    
110
    @Override
111
    public void setCode(String code) {
112
        this.code = code;
113
        this.engine = null;
114
        this.compiledCode = null;
115
        this.setSaved(false);
116
    }
117
    
118
    @Override
119
    public String getLibrarySuffix() {
120
        return this.librarySuffix;
121
    }
122

    
123
    @Override
124
    public void setLibrarySuffix(String librarySuffix) {
125
        this.librarySuffix = librarySuffix;
126
    }
127
    
128
    public List<File> getLibFolders() {
129
        List<File> folders = this.manager.getLibFolders();
130
        String suffix = this.getLibrarySuffix();
131
        if( suffix == null ) {
132
            return folders;
133
        }
134
        for( int i=0; i<folders.size(); i++) {
135
            File folder = folders.get(i);
136
            File f = new File(folder.getParentFile(),folder.getName()+suffix);
137
            if( f.exists() ) {
138
                folders.set(i, f);
139
            }
140
        }
141
        return folders;
142
    }
143
    
144
    protected String getCodeToInitializeEngine() {
145
        String name = "org/gvsig/scripting/langs/"+this.getLangName().toLowerCase()+"/init.txt";
146
        try {
147
            InputStream template = this.getClass().getClassLoader().getResourceAsStream(name);
148
            if( template == null ) {
149
                return null;
150
            }
151
            List<String> lines = IOUtils.readLines(template);
152
            return StringUtils.join(lines, "\n");
153
        } catch (Exception ex) {
154
            logger.warn("Can't load code to initialize the script from '"+name+".",ex);
155
            return null;
156
        }
157
    }
158

    
159
    public ScriptEngine getEngine() {
160
        if (this.engine == null) {
161
            ScriptEngine scriptEngine = this.manager.getEngineByLanguage(langName);
162
            if( "scala".equalsIgnoreCase(langName) ) {
163
                try {
164
                    // https://gist.github.com/takawitter/5479445
165
                    Object settings = scriptEngine.getClass().getMethod("settings", new Class[0]).invoke(scriptEngine, new Object[0]);
166
                    settings.getClass().getMethod("processArgumentString", new Class[] { String.class }).invoke(settings, new String[] { "-usejavacp"});
167
                } catch(Throwable th) {
168
                    logger.warn("Can't initialice scala setting -usejavacp",th);
169
                }
170
            }
171
            scriptEngine.put("script", this);
172
            scriptEngine.put("Main", Main.class);
173
            this.engine = scriptEngine;
174
            String code = this.getCodeToInitializeEngine();
175
            if (code != null) {
176
                try {
177
                    this.engine.eval(code);
178
                } catch (Exception ex) {
179
                    logger.warn("Can't initialize engine with the code:\n" + code, ex);
180
                }
181
            }
182
        }
183
        return this.engine;
184
    }
185

    
186
    @Override
187
    protected void loadInf(Ini prefs) {
188
        super.loadInf(prefs);
189

    
190
        this.setMainName((String) getInfValue(prefs, "Script", "main", "main"));
191
        this.setLangName((String) getInfValue(prefs, "Script", "Lang",this.getLangName()));
192
        this.setLibrarySuffix((String) getInfValue(prefs, "Script", "LibraryVersion",null));
193
    }
194

    
195
    @Override
196
    public void load(ScriptingFolder folder, String id) {
197
        this.setId(id);
198
        this.setParent(folder);
199

    
200
        String extension = FilenameUtils.getExtension(id);
201
        if( extension != null ) {
202
            String language = this.manager.getLanguageOfExtension(extension);
203
            if( language != null ) {
204
                this.setLangName(language);
205
            }
206
            this.setExtension(extension);
207
        }
208
        File f = getFileResource(".inf");
209
        if (f.isFile()) {
210
            Ini prefs = null;
211
            try {
212
                prefs = new Ini(f);
213
            } catch (Exception e) {
214
                logger.warn("Can't load 'inf' file '" + f.getAbsolutePath() + "'.", e);
215
            }
216
            loadInf(prefs);
217
        }
218
        this.setSaved(true);
219
    }
220

    
221
    @Override
222
    public void save() {
223
        this.saveInfo();
224
        // Guardo el codigo en el fichero
225
        File fcode = this.getFileResource(this.getExtension());
226
        try {
227
            FileUtils.write(fcode, this.getCode());
228
        } catch (Exception e) {
229
            logger.warn("Can't write code to file '" + fcode.getAbsolutePath() + "'.", e);
230
        }
231
        this.setSaved(true);
232
    }
233

    
234
    private void saveInfo() {
235
        File f = getFileResource(".inf");
236
        if (!f.isFile()) {
237
            try {
238
                f.createNewFile();
239
            } catch (Exception e) {
240
                logger.warn("Can't create 'inf' file '" + f.getAbsolutePath() + "'.", e);
241
            }
242
        }
243
        Ini prefs = null;
244
        try {
245
            prefs = new Ini(f);
246
        } catch (Exception e) {
247
            logger.warn("Can't load 'inf' file '" + f.getAbsolutePath() + "'.", e);
248
        }
249
        save(prefs);
250
    }
251
    
252
    @Override
253
    protected void save(Ini prefs) {
254
        super.save(prefs);
255
        prefs.put("Script", "main", this.getMainName());
256
        prefs.put("Script", "Lang", this.getLangName());
257
        try {
258
            prefs.store();
259
        } catch (IOException e) {
260
            String fname = (prefs.getFile() == null) ? "(null)" : prefs.getFile().getAbsolutePath();
261
            logger.warn("Can't save inf file (" + fname + ").", e);
262
        }
263

    
264
    }
265

    
266
    @Override
267
    public String getLangName() {
268
        return this.langName;
269
    }
270

    
271
    protected void setLangName(final String langName) {
272
        if( langName == null ) {
273
            return;
274
        }
275
        this.langName = langName;
276
        this.setExtension(this.manager.getExtensionOfLanguage(this.langName));
277
    }
278

    
279
    @Override
280
    public String[] getIconNames() {
281
        return new String[]{
282
            "scripting_" + this.getLangName().toLowerCase(),
283
            "scripting_" + this.getLangName().toLowerCase() + "_open"
284
        };
285
    }
286

    
287
    @Override
288
    public String getMainName() {
289
        return this.mainName;
290
    }
291

    
292
    @Override
293
    public void setMainName(final String mainName) {
294
        this.mainName = mainName;
295
    }
296

    
297
    public String getExtension() {
298
        return this.extension;
299
    }
300

    
301
    public void setExtension(final String extension) {
302
        if (!extension.startsWith(".")) {
303
            this.extension = "." + extension;
304
        } else {
305
            this.extension = extension;
306
        }
307
    }
308

    
309
    @Override
310
    public boolean isSaved() {
311
        return this.saved;
312
    }
313

    
314
    @Override
315
    public void addObserver(final Observer o) {
316
        this.delegatedObservable.addObserver(o);
317
    }
318

    
319
    @Override
320
    public void deleteObserver(final Observer o) {
321
        this.delegatedObservable.deleteObserver(o);
322
    }
323

    
324
    @Override
325
    public void deleteObservers() {
326
        this.delegatedObservable.deleteObservers();
327
    }
328

    
329
    @Override
330
    public void put(final String name, final Object value) {
331
        this.getEngine().put(name, value);
332
    }
333

    
334
    @Override
335
    public void compile() {
336
        if (this.compiledCode == null) {
337
            ScriptEngine engine = this.getEngine();
338
            if (engine instanceof Compilable) {
339
                try {
340
                    Compilable compilable = (Compilable) engine;
341
                    String code = this.getCode();
342
                    if( "python".equalsIgnoreCase(this.getLangName()) ) {
343
                        // If a Unicode string with a coding declaration is passed to compile(),
344
                        // a SyntaxError will be raised, but this is necessary to import from 
345
                        // another module, so we remove it.
346
                        // http://bugs.jython.org/issue1696
347
                        code = code.replaceFirst("^\\s*#([^:\\n]*)coding:","#$1 c-o-d-i-n-g:");
348
                    }
349
                    this.compiledCode = compilable.compile(code);
350
                    if( engine instanceof Invocable) {
351
                        this.compiledCode.eval();
352
                    }
353
                } catch (ScriptException e) {
354
                    Object[] location = this.getLocation(e);
355
                    CompileErrorException ce = new CompileErrorException(
356
                            e.getMessage(), 
357
                            (File) location[0], 
358
                            (int) location[1], 
359
                            (int) location[2], 
360
                            e
361
                    );
362
                    notifyErrors(ce, "compile");
363
                    throw ce;
364
                } catch (Throwable e) {
365
                    CompileErrorException ce = new CompileErrorException(e.getMessage(), this.getScriptFile(), e);
366
                    notifyErrors(new Exception(e), "compile");
367
                    throw ce;
368
                }
369
            } else {
370
                String code = this.getCode();
371
                try {
372
                    engine.eval(code);
373
                } catch (ScriptException e) {
374
                    Object[] location = this.getLocation(e);
375
                    CompileErrorException ce = new CompileErrorException(
376
                            e.getMessage(), 
377
                            (File) location[0], 
378
                            (int) location[1], 
379
                            (int) location[2], 
380
                            e
381
                    );                    
382
                    notifyErrors(ce, "compile");
383
                    throw ce;
384
                }
385
            }
386
        }
387
    }
388

    
389
    public void addDisposable(Disposable disposable) {
390
        //pass
391
    }
392

    
393
    /**
394
     * Run the main function of this script.
395
     * This method is created by familiarity when running the script from another script.
396
     * @return 
397
     */
398
    public Object main() {
399
        return this.run(null);
400
    }
401

    
402
    /**
403
     * Run the main function of this script.
404
     * This method is created by familiarity when running the script from another script.
405
     * @return 
406
     */
407
    public Object main(Object... args) {
408
        return this.run(args);
409
    }
410

    
411
        
412
    public Object run() {
413
        return this.run(null);
414
    }
415

    
416
    @Override
417
    public Object run(Object args[]) {
418
        if( !this.isEnabled() ) {
419
            System.err.printf("The script '"+this.getName()+"' is not enabled, see properties page to change.\n");
420
            return null;
421
        }
422
        if (args == null) {
423
            args = new Object[]{};
424
        }
425
        this.compile();
426
        return this.invokeFunction(this.getMainName(), args);
427
    }
428

    
429
    @Override
430
    public Object invokeFunction(final String name, Object args[]) {
431
        try {
432
            if (this.getEngine() instanceof Invocable) {
433
                Invocable invocable = (Invocable) this.getEngine();
434
                this.compile();
435
                if (args == null) {
436
                    args = new Object[]{};
437
                }
438
                return invocable.invokeFunction(name, args);
439
            } else {
440
                if (this.compiledCode != null) {
441
                    Object x = this.compiledCode.eval();
442
                    if( x instanceof Main ) {
443
                        return ((Main) x).main(args);
444
                    } else if(x instanceof Runnable) {
445
                        ((Runnable) x).run();
446
                    }
447
                }
448
                return null;
449
            }
450
        } catch (ScriptException e) {
451
            Object[] location = this.getLocation(e);
452
            ExecuteErrorException ee = new ExecuteErrorException(
453
                    e.getMessage(), 
454
                    (File) location[0], 
455
                    (int) location[1], 
456
                    (int) location[2], 
457
                    e
458
            );
459
            notifyErrors(ee, "invoke");
460
            throw ee;
461

    
462
        } catch (Error | Exception e) {
463
            ExecuteErrorException ee = new ExecuteErrorException(e.getMessage(), this.getScriptFile(), e);
464
            notifyErrors(ee, "invoke");
465
            throw ee;
466
        }
467
    }
468

    
469
    @Override
470
    public File getScriptFile() {
471
        return this.getFileResource(extension);
472
    }
473

    
474
    private Object[] getLocation(ScriptException e) {
475
        Throwable[] es = ExceptionUtils.getThrowables(e);
476
        Exception dbgex; // Para debug con mas comodidad
477
        for( Throwable t : es) {
478
            if( t instanceof PyException ) {
479
                try {
480
                    PyException pyex = (PyException)t;
481
                    PyTraceback tb = pyex.traceback;
482
                    if( tb!=null ) {
483
                        while( tb.tb_next!=null ) {
484
                            tb = (PyTraceback) tb.tb_next;
485
                        }
486
                        String s = tb.tb_frame.f_globals.__getitem__(new PyString("__file__")).asString();
487
                        if( s.endsWith("$py.class") ) {
488
                            s = s.substring(0, s.length()-9) + ".py";
489
                            File resource = new File(s);
490
                            return new Object[] { resource, tb.tb_lineno, 0};
491
                        }
492
                        return new Object[] { this.getScriptFile(), tb.tb_lineno, 0};
493
                    }
494
                } catch(Exception ex) {
495
                    // Pass
496
                    dbgex = ex;
497
                }
498
            }
499
        }        
500
        int column = e.getColumnNumber();
501
        if( column < 0 ) {
502
            column = 0;
503
        }
504
        return new Object[] { this.getScriptFile(), e.getLineNumber(), column };
505
    }
506
    
507
    
508
    @Override
509
    public Object invokeMethod(final Object obj, final String name, Object[] args)
510
            throws NoSuchMethodException {
511

    
512
        if (this.getEngine() instanceof Invocable) {
513
            Invocable invocable = (Invocable) this.getEngine();
514
            this.compile();
515
            if (args == null) {
516
                args = new Object[]{};
517
            }
518
            try {
519
                return invocable.invokeMethod(obj, name, args);
520
            } catch (ScriptException e) {
521
                ExecuteErrorException ee = new ExecuteErrorException(e.getMessage(), this.getScriptFile(), e.getLineNumber(), e.getColumnNumber(), e);
522
                notifyErrors(ee, "invoke");
523
                throw ee;
524
            } catch (Throwable e) {
525
                ExecuteErrorException ee = new ExecuteErrorException(e.getMessage(), this.getScriptFile(), e);
526
                notifyErrors(ee, "invoke");
527
                throw ee;
528
            }
529
        } else {
530
            return null;
531
        }
532
    }
533

    
534
    @Override
535
    public File getResource(String filename) {
536
        return new File(this.getParent().getFile(), filename);
537
    }
538

    
539
    @Override
540
    public String getMimeType() {
541
        return "text/"+ this.getLangName();
542
    }
543

    
544
    class ScriptTask extends AbstractMonitorableTask {
545

    
546
        ScriptingBaseScript script = null;
547
        Object[] args = null;
548

    
549
        protected ScriptTask(ScriptingBaseScript script, Object[] args) {
550
            super(script.getName(), false);
551
            this.args = args;
552
            this.script = script;
553
            this.script.put("task", this);
554
            this.script.put("taskStatus", this.getTaskStatus());
555
        }
556

    
557
        @Override
558
        public void run() {
559
            try {
560
                console_println("Running script " + this.script.getName() + ".");
561
                script.run(this.args);
562
                console_println("Script " + this.script.getName() + " terminated.");
563
            } catch (Throwable e) {
564
                console_println("Stript " + this.script.getName() + " aborted.");
565
            } finally {
566
                this.taskStatus.terminate();
567
                try {
568
                    Thread.sleep(3000);
569
                } catch (InterruptedException e) {
570
                    // Ignore
571
                }
572
                this.taskStatus.remove();
573
            }
574
        }
575

    
576
        public void showTaskStatus() {
577
            this.taskStatus.add();
578
        }
579
    }
580

    
581
    @Override
582
    public void runAsTask(Object[] args) {
583
        if( !this.isEnabled() ) {
584
            System.err.printf("The script '"+this.getName()+"' is not enabled, see properties page to change.\n");
585
            return;
586
        }
587
        ScriptTask task = new ScriptTask(this, args);
588
        task.start();
589
    }
590

    
591
    @Override
592
    public boolean remove() {
593
        boolean r = true;
594
        File folder = this.getParent().getFile();
595
        File f = new File(folder, this.getId() + ".inf");
596
        try {
597
            FileUtils.forceDelete(f);
598
        } catch (IOException e) {
599
            logger.warn("Can't remove inf file '" + f.getAbsolutePath() + "'.", e);
600
            r = false;
601
        }
602
        try {
603
            f = new File(folder, this.getId() + this.getExtension());
604
            FileUtils.forceDelete(f);
605
        } catch (IOException e) {
606
            logger.warn("Can't remove code file '" + f.getAbsolutePath() + "'.", e);
607
            r = false;
608
        }
609
        return r;
610
    }
611

    
612
    @Override
613
    public void create(ScriptingFolder folder, String id, String language) {
614
        this.setParent(folder);
615
        this.setId(id);
616
        if (language == null) {
617
            this.setLangName("python");
618
        } else {
619
            this.setLangName(language);
620
        }
621
        this.setExtension(this.manager.getExtensionOfLanguage(getLangName()));
622

    
623
        File file = new File(folder.getFile(), id + ".inf");
624
        try {
625
            file.createNewFile();
626
        } catch (IOException e) {
627
            logger.warn("Can't create file of the dialog in '" + file.getAbsolutePath() + "'.", e);
628
        }
629
        String template = this.getNewTemplate();
630
        if( template != null ) {
631
            this.setCode(template);
632
        }
633
        this.save();
634
    }
635

    
636
    public String getNewTemplate() {
637
        String name = "org/gvsig/scripting/langs/"+this.getLangName().toLowerCase()+"/new_template.txt";
638
        try {
639
            InputStream template = this.getClass().getClassLoader().getResourceAsStream(name);
640
            if( template == null ) {
641
                return null;
642
            }
643
            List<String> lines = IOUtils.readLines(template);
644
            return StringUtils.join(lines, "\n");
645
        } catch (Exception ex) {
646
            logger.warn("Can't load new-template from '"+name+"'.",ex);
647
            return null;
648
        }
649
    }
650
    
651
    public ScriptingUnit get(String name) {
652
        return this.manager.getScript(name);
653
    }
654

    
655
    public ScriptingUnit get(File file) {
656
        return this.manager.getScript(file);
657
    }
658

    
659
    @Override
660
    public boolean move(ScriptingFolder target) {
661
        if (! manager.validateUnitId(target, this.getId()) ) {
662
            logger.info("Can't move script '"+this.getId()+"' to '"+target.getFile().getAbsolutePath()+"', is not valid.");
663
            return false;
664
        }
665
        if( !this.isSaved() ) {
666
            logger.info("Can't move script '"+this.getId()+"', is not saved.");
667
            return false;
668
        }
669
        try {
670
            File codefile = this.getFileResource(this.extension);
671
            FileUtils.moveFileToDirectory(this.getFile(), target.getFile(),true);
672
            FileUtils.moveFileToDirectory(codefile, target.getFile(), true);
673
            this.parent = target;
674
            this.load(target, id);
675
        } catch (IOException ex) {
676
            logger.info("Can't move script '"+this.getId()+"' to '"+target.getFile().getAbsolutePath()+"', "+ex.getMessage(),ex);
677
            return false;
678
        }
679
        return true;
680
    }
681

    
682
    @Override
683
    public boolean rename(String newId) {
684
        if (! manager.validateUnitId(this.getParent(), newId) ) {
685
            logger.info("Can't rename script '"+this.getId()+"', target id '"+newId+"' is not valid.");
686
            return false;
687
        }
688
        if( !this.isSaved() ) {
689
            logger.info("Can't rename script '"+this.getId()+"', is not saved.");
690
            return false;
691
        }
692
        try {
693
            ScriptingFolder target = this.getParent();
694
            File codefile = this.getFileResource(this.extension);
695
            FileUtils.moveFile(this.getFile(),  new File(target.getFile(),newId+".inf") );
696
            FileUtils.moveFile(codefile, new File(target.getFile(),newId+this.extension));
697
            this.setId(newId);
698
            this.saveInfo();
699
            this.load(target, id);
700
        } catch (IOException ex) {
701
            logger.info("Can't rename script '"+this.getId()+"' to '"+newId+"', "+ex.getMessage(),ex);
702
            return false;
703
        }        
704
        return true;
705
    }
706

    
707
}