001/*
002 * Copyright (c) 2007-2010 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: crawl $'
006 * '$Date: 2015-04-20 20:58:30 +0000 (Mon, 20 Apr 2015) $' 
007 * '$Revision: 33352 $'
008 * 
009 * Permission is hereby granted, without written agreement and without
010 * license or royalty fees, to use, copy, modify, and distribute this
011 * software and its documentation for any purpose, provided that the above
012 * copyright notice and the following two paragraphs appear in all copies
013 * of this software.
014 *
015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
019 * SUCH DAMAGE.
020 *
021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
026 * ENHANCEMENTS, OR MODIFICATIONS.
027 *
028 */
029
030package org.kepler.provenance;
031
032import java.io.FileNotFoundException;
033import java.io.PrintStream;
034import java.text.SimpleDateFormat;
035import java.util.Date;
036
037import org.kepler.tagging.TagEvent;
038
039import ptolemy.actor.Actor;
040import ptolemy.actor.Director;
041import ptolemy.actor.FiringEvent;
042import ptolemy.actor.IOPortEvent;
043import ptolemy.actor.IORelation;
044import ptolemy.actor.LocalClock;
045import ptolemy.actor.TypedIOPort;
046import ptolemy.data.StringToken;
047import ptolemy.data.expr.FileParameter;
048import ptolemy.data.expr.Parameter;
049import ptolemy.kernel.Port;
050import ptolemy.kernel.util.AbstractSettableAttribute;
051import ptolemy.kernel.util.Attribute;
052import ptolemy.kernel.util.IllegalActionException;
053import ptolemy.kernel.util.NameDuplicationException;
054import ptolemy.kernel.util.Nameable;
055import ptolemy.kernel.util.NamedObj;
056
057/** A provenance Recording that writes to a text file.
058 *
059 * @author Daniel Crawl
060 * @version $Id: TextFileRecording.java 33352 2015-04-20 20:58:30Z crawl $
061 *
062 */
063    
064public class TextFileRecording extends Recording
065{
066
067    /** Create a new provenance recording. */
068    public TextFileRecording() throws RecordingException
069    {
070        super();
071
072        _dFormat = new SimpleDateFormat("HH:mm:ss");
073
074        _needWorkflowContents(true);
075    }
076
077    /** Stop recording. This is the last method called to a 
078     *  Recording instance.
079     */
080    @Override
081    public void disconnect() throws RecordingException
082    {
083        //_writeOutput("disconnect.");
084        _closeWriter();
085    }
086
087    // Specification
088    
089    /** Called before registering workflow contents. */
090    @Override
091    public void specificationStart() throws RecordingException
092    {
093        super.specificationStart();
094
095        if(_recordSpecVal)
096        {
097            _writeOutput("Specification started."); 
098        }
099    }
100
101    /** Called when finished registering workflow contents. */
102    @Override
103    public void specificationStop() throws RecordingException
104    {
105        super.specificationStop();
106
107        if(_recordSpecVal)
108        {
109            _writeOutput("Specification stopped."); 
110        }
111    }
112
113    /** Register an actor. */
114    @Override
115    public boolean regActor(Actor actor) throws RecordingException
116    {
117        if(_recordSpecVal)
118        {
119            _writeOutput("Actor: " + _getNameableFullName(actor));
120        }
121        return true;
122    }
123
124    /** Register a director. */
125    @Override
126    public boolean regDirector(Director director) throws RecordingException
127    {
128        if(_recordSpecVal)
129        {
130            _writeOutput("Director: " + _getNameableFullName(director));
131        } 
132        return true; 
133    }
134
135    /** Register a parameter. A parameter can be any <b>entity</b>
136     * stored in the MoML that does not have its own
137     * <code>regNNN()</code> method. This can be user-level 
138     * parameters (e.g., Parameter, StringParameter, etc.) or
139     * internal to Kepler (e.g., _location, semanticType000, etc.).
140     * (A "parameter" corresponds to a property in the MoML).
141     *
142     */
143    @Override
144    public boolean regParameter(NamedObj parameter) throws RecordingException
145    {
146        if(_recordSpecVal && !(parameter instanceof LocalClock))
147        {
148            if(parameter instanceof AbstractSettableAttribute)
149            {
150                String output = "Parameter: " + _getNameableFullName(parameter) +
151                    " = " + 
152                    ((AbstractSettableAttribute)parameter).getValueAsString();
153
154                if(parameter instanceof org.kepler.moml.NamedObjId)
155                {
156                    _writeOutput(output, false);
157
158                    _debugWrite("Parameter: " + _getNameableFullName(parameter) +
159                        " = lsid");
160                }
161                else
162                {
163                    _writeOutput(output);
164                }
165            }
166            else
167            {
168                _writeOutput("Parameter: " + _getNameableFullName(parameter));
169            }
170        }
171        return true; 
172    }
173
174    /** Register a link between two endpoints.  */
175    @Override
176    public boolean regLink(NamedObj endPoint1, NamedObj endPoint2)
177        throws RecordingException
178    {
179        if(_recordSpecVal)
180        {
181            _writeOutput("Link: " + _getNameableFullName(endPoint1) + " <---> " + 
182                _getNameableFullName(endPoint2));
183        }
184        return true; 
185    }
186
187    /** Register a port or portparameter.  */
188    @Override
189    public boolean regPort(TypedIOPort port) throws RecordingException
190    {
191        if(_recordSpecVal)
192        {
193            _writeOutput("Port: " + _getNameableFullName(port));
194        }
195        return true; 
196    }
197
198    /** Register a relation. */
199    @Override
200    public boolean regRelation(IORelation relation) throws RecordingException
201    {
202        if(_recordSpecVal)
203        {
204            _writeOutput("Relation: " + _getNameableFullName(relation));
205        }
206        return true; 
207    }
208    
209    // Evolution of workflow structure.
210   
211    @Override
212    public void evolutionStart() throws RecordingException
213    {
214        if(_recordSpecVal) 
215        {
216            _writeOutput("Evolution started.");
217        }
218    }
219
220    @Override
221    public void evolutionStop() throws RecordingException
222    {
223        if(_recordSpecVal) 
224        {
225            _writeOutput("Evolution stopped.");
226        }
227    }
228 
229    @Override
230    public void remove(String name) throws RecordingException
231    {
232        if(_recordSpecVal)
233        {
234            _writeOutput("Removed: " + name);
235        }
236    }
237
238    @Override
239    public void removeLink(String endPoint1, String endPoint2)
240        throws RecordingException
241    {
242        if(_recordSpecVal)
243        {
244            _writeOutput("Removed link: " + endPoint1 + " <---> " +
245                endPoint2);
246        }
247    }
248   
249
250    /** A NamedObj was renamed. */
251    @Override
252    public void rename(String oldName, String newName) 
253        throws RecordingException
254    {
255        if(_recordSpecVal) 
256        {
257            _writeOutput("Rename: " + oldName + " ---> " + newName);
258        }
259    }
260
261    // Execution of workflow
262    
263    /** Record the starting of workflow execution. */
264    @Override
265    public void executionStart() throws RecordingException
266    {
267        _needWorkflowContents(false);
268        _writeOutput("Execution started.");
269    }
270
271    /** Record the stopping of workflow execution. */
272    @Override
273    public void executionStop() throws RecordingException
274    {
275        _writeOutput("Execution stopped.");
276    }
277    
278    /** An actor threw an exception.  */
279    @Override
280    public void executionError(Nameable source, Throwable throwable)
281        throws RecordingException
282    {
283        if(source != null)
284        {
285            _writeOutput("Execution error from " + source.getFullName() +
286                ": " + throwable.getMessage());
287        }
288        else
289        {
290            _writeOutput("Execution error: " + throwable.getMessage());
291        }
292    }
293
294    /** Record starting an actor fire event. */
295    @Override
296    public void actorFire(FiringEvent event, Date timestamp) throws RecordingException
297    {
298        String outputStr;
299        if(_containerName != null)
300        {
301            String eventStr = event.toString();
302            Actor actor = event.getActor();
303            String fullName = actor.getFullName();
304            outputStr = eventStr.replaceAll(fullName, _getNameableFullName(actor));
305        }
306        else
307        {
308            outputStr = event.toString();
309        }
310        
311        _writeOutput(outputStr, timestamp);
312    }
313
314    /** Record a port event. */
315    @Override
316    public void portEvent(IOPortEvent event, Date timestamp) throws RecordingException
317    {
318        String outputStr;
319        if(_containerName != null)
320        {
321            String eventStr = event.toString();
322            Port port = event.getPort();
323            String fullName = port.getFullName();
324            outputStr = eventStr.replaceAll(fullName, _getNameableFullName(port));
325        }
326        else
327        {
328            outputStr = event.toString();
329        }
330        
331        _writeOutput(outputStr, timestamp);
332    }
333
334    /** Record a custom provenance event. */
335    @Override
336    public void customProvEvent(ProvenanceEvent event) throws RecordingException
337    {
338        _writeOutput(event.toString());
339    }
340
341    /** Add Parameters for ProvenanceListener. */
342    @Override
343    public RecordingParameters generateParameters(NamedObj no) 
344        throws IllegalActionException, NameDuplicationException
345    {
346        _params = new TextFileRecordingParameters(no);
347        return _params;
348    }
349
350    /** React to a change in an attribute. */
351    @Override
352    public void attributeChanged(Attribute attribute)
353        throws IllegalActionException
354    {
355        String name =  attribute.getName();
356
357        if(name.equals(TextFileRecordingParameters._recordSpecStr))
358        {
359            boolean oldVal = _recordSpecVal;
360            _recordSpecVal = _params.getRecordSpecValue();
361
362            // see if we now want the specfication
363            if(!oldVal && _recordSpecVal)
364            {
365                _needWorkflowContents(true);
366            }
367            else if(!_recordSpecVal)
368            {
369                _needWorkflowContents(false);
370            }
371        }
372        else if(name.equals(TextFileRecordingParameters._filenameStr))
373        {
374            _resetWriter();
375        }
376        else if(name.equals(TextFileRecordingParameters._alwaysFlushStr))
377        {
378            _alwaysFlushVal = _params.getAlwaysFlushValue();
379
380            // if should always flush and have existing writer,
381            // flush any pending output.
382            if(_alwaysFlushVal && _textWriter != null)
383            {
384                _textWriter.flush();
385            }
386
387        }
388        else if(name.equals(TextFileRecordingParameters._addTimestampStr))
389        {
390            _addTimestampVal = _params.getAddTimestampValue();
391        }
392        else
393        {
394            super.attributeChanged(attribute);
395        }
396    }
397    
398    /** A tag was added. */
399    @Override
400    public void tagAdded(TagEvent event) throws RecordingException
401    {
402        _writeOutput("Tag added: " + event.toString());   
403    }
404    
405    /** A tag was removed. */
406    @Override
407    public void tagRemoved(TagEvent event) throws RecordingException
408    {
409        _writeOutput("Tag removed: " + event.toString());
410    }
411   
412    /** Set if the output should have a timestamp. */ 
413    public void setAddTimestamp(boolean addTimestamp) {
414        _addTimestampVal = addTimestamp;
415    }
416    
417    ////////////////////////////////////////////////////////////////////////
418    //// protected methods                                              ////
419
420    /** Output a string. */
421    protected void _write(String str) throws RecordingException
422    {
423        _write(str, true);
424    }
425
426    /** Output a string. */
427    protected void _write(String str, boolean outputDebug) throws RecordingException
428    {
429        if(_textWriter != null)
430        {
431            _textWriter.println(str);
432            if(_alwaysFlushVal)
433            {
434                _textWriter.flush();
435            }
436        }
437
438        if(outputDebug && _debugWriter != null)
439        {
440            _debugWrite(str);
441        }
442    }
443
444    /** Output a string, optionally adding a timestamp. */
445    protected void _writeOutput(String str) throws RecordingException
446    {
447        _writeOutput(str, true, null);        
448    }
449
450    /** Output a string, optionally adding a timestamp. */
451    protected void _writeOutput(String str, Date timestamp) throws RecordingException
452    {
453        _writeOutput(str, true, timestamp);        
454    }
455
456    /** Output a string, optionally adding a timestamp. */
457    protected void _writeOutput(String str, boolean outputDebug)
458        throws RecordingException {
459        _writeOutput(str, outputDebug, null);
460    }
461
462    /** Output a string, optionally adding a timestamp. */
463    protected void _writeOutput(String str, boolean outputDebug, Date timestamp)
464        throws RecordingException
465    {
466        if(_addTimestampVal)
467        {
468            Date date;
469            if(timestamp == null) {
470                date = new Date();
471            } else {
472                date = timestamp;
473            }
474            String dateStr = _dFormat.format(date);
475            _write(dateStr + ": " + str, outputDebug);
476        }
477        else
478        {
479            _write(str, outputDebug);
480        }
481    }
482
483    ////////////////////////////////////////////////////////////////////////
484    //// protected classes                                              ////
485
486    /** Configuration Parameters for TextFileRecording. */
487    protected class TextFileRecordingParameters extends RecordingParameters
488    {
489        TextFileRecordingParameters(NamedObj no)
490            throws IllegalActionException, NameDuplicationException
491        {
492            super(no);
493            addBooleanParameter(_recordSpecStr, _recordSpecVal);
494            addFileParameter(_filenameStr, "System.out");
495            addBooleanParameter(_alwaysFlushStr, _alwaysFlushVal);
496            addBooleanParameter(_addTimestampStr, _addTimestampVal);
497        }
498
499        /** Get the "Record Specification" check-box value. */
500        boolean getRecordSpecValue() throws IllegalActionException
501        {
502            return getBooleanValue(_recordSpecStr);
503        }
504
505        /** Get the output FileParameter. */
506        FileParameter getFileParameter() throws IllegalActionException
507        {
508            return (FileParameter)_params.get(_filenameStr);
509        }
510
511        /** Get "Always Flush Output" check-box value. */
512        boolean getAlwaysFlushValue() throws IllegalActionException
513        {
514            return getBooleanValue(_alwaysFlushStr);
515        }
516
517        /** Get "Timestamp Output" check-box value. */
518        boolean getAddTimestampValue() throws IllegalActionException
519        {
520            return getBooleanValue(_addTimestampStr);
521        }
522
523        /** Replace a Parameter. */
524        @Override
525        public void replaceParameter(String name, Parameter parameter)
526            throws IllegalActionException
527        {   
528            // if replacing the filename, close the file.
529            if(name.equals(_filenameStr))
530            {
531                FileParameter p = (FileParameter)_params.get(_filenameStr);
532                p.close();
533            }
534
535            super.replaceParameter(name, parameter);
536        }
537            
538        private static final String _recordSpecStr = "Record Specification";
539        private static final String _filenameStr = "Filename";
540        private static final String _alwaysFlushStr = "Always Flush Output";
541        private static final String _addTimestampStr = "Timestamp Output";
542    }
543
544    ////////////////////////////////////////////////////////////////////////
545    //// protected variables                                            ////
546
547    /** Whether to record workflow specification. */
548    protected boolean _recordSpecVal = true;
549
550    /** Output writer. */
551    protected PrintStream _textWriter = null;
552    
553    /** If true, flush after writing to output. */
554    protected boolean _alwaysFlushVal = false;
555
556    /** If true, add timestamp to output. */
557    protected boolean _addTimestampVal = true;
558
559    /** Format timestamps. */
560    protected SimpleDateFormat _dFormat = null;
561
562    /** Parameters for TextFileRecording */
563    protected TextFileRecordingParameters _params = null;
564
565    /** Output name. */
566    protected String _outputName = null;
567
568    ////////////////////////////////////////////////////////////////////////
569    //// private methods                                                ////
570
571    /** Reset the output writer. */
572    private void _resetWriter() throws IllegalActionException
573    {
574        FileParameter fp = _params.getFileParameter();
575        String name = ((StringToken)fp.getToken()).stringValue();
576        
577        // see if the output name has changed or was never set
578        if(_outputName == null || !name.equals(_outputName))
579        {
580            _closeWriter();
581
582            // see if we should write to stdout
583            if(name.equals("System.out"))
584            {
585                _textWriter = System.out;
586            }
587            // see if we should write to stderr
588            else if(name.equals("System.err"))
589            {
590                _textWriter = System.err;
591            }
592            else if(name.equals("NULL"))
593            {
594                _textWriter = null;
595            }
596            else
597            {
598                // remove possible "file:" prefix
599                if(name.startsWith("file:"))
600                {
601                    name = name.substring("file:".length());
602                }
603
604                try
605                {
606                    _textWriter = new PrintStream(name); 
607                }
608                catch(FileNotFoundException e)
609                {
610                    throw new IllegalActionException("Could not find file " + 
611                        name + " : " + e.getMessage());
612                }
613            }
614            
615            // save the name
616            _outputName = name;
617        }
618
619        // if we're configured to recording workflow structure,
620        // tell listener to re-send the structure since the file
621        // changed.
622        _needWorkflowContents(_recordSpecVal);
623    }
624
625    /** Close the writer unless it is stdout or stderr. */
626    private void _closeWriter()
627    {
628        if(_textWriter != null)
629        {
630            // don't close stdout or stderr
631            if(_outputName == null ||
632                (!_outputName.equals("System.out") &&
633                !_outputName.equals("System.err")))
634            {
635                _textWriter.close();
636            }
637            else
638            {
639                _textWriter.flush(); 
640            }
641        }
642    }
643}