001         package com.croftsoft.apps.infant;
002         
003         import java.io.*;     
004         import java.util.*;
005         
006         import com.croftsoft.core.io.FileLib;
007         import com.croftsoft.core.lang.*;
008         import com.croftsoft.core.lang.lifecycle.*;
009         import com.croftsoft.core.math.MathConstants;
010         import com.croftsoft.core.util.ArrayLib;
011         
012         /**********************************************************************
013         * Maintains state.
014         * 
015         * @version
016         *   $Id: InfantModel.java,v 1.37 2007/02/25 03:25:29 croft Exp $
017         * @since
018         *   2006-01-03
019         * @author
020         *   <a href="https://www.croftsoft.com/">David Wallace Croft</a>
021         **********************************************************************/
022    
023         public final class  InfantModel
024           implements Destroyable, InfantAccessor, Updatable
025         ///////////////////////////////////////////////////////////////////////
026         ///////////////////////////////////////////////////////////////////////
027         {
028           
029         // private final instance variables
030           
031         private final InfantConfig  infantConfig;
032         
033         private final Queue<InfantMessage>
034           eventQueue,
035           requestQueue;
036           
037         // model state instance variables
038         
039         private int     imageIndex;
040         
041         private String [ ]  imagePaths;
042         
043         private String  imagePath;
044         
045         private long
046           imageDisplayTime,
047           interStimulusInterval;
048         
049         private long
050           imageStartTime,
051           originalImageStartTime,
052           cycle,
053           contemplationNanos,
054           contemplationStartTimeNanos,
055           durationNanos;
056         
057         private InfantData  infantData;
058         
059         private boolean     experimentIsRunning;
060         
061         ///////////////////////////////////////////////////////////////////////
062         ///////////////////////////////////////////////////////////////////////
063         
064         public  InfantModel (
065           final InfantConfig          infantConfig,
066           final Queue<InfantMessage>  requestQueue,
067           final Queue<InfantMessage>  eventQueue )
068         ///////////////////////////////////////////////////////////////////////
069         {
070           NullArgumentException.checkArgs (
071             this.infantConfig = infantConfig,
072             this.requestQueue = requestQueue,
073             this.eventQueue   = eventQueue );
074           
075           imagePaths = infantConfig.getImagePaths ( );
076           
077           imageDisplayTime = infantConfig.getImageDisplayTime ( );
078           
079           interStimulusInterval = infantConfig.getInterStimulusInterval ( );
080           
081           imageIndex = 0;
082           
083           if ( ( imagePaths != null    )
084             && ( imagePaths.length > 0 ) )
085           {
086             imagePath = imagePaths [ 0 ];
087           }
088           
089           infantData = new InfantData ( );
090         }
091         
092         ///////////////////////////////////////////////////////////////////////
093         // interface InfantAccessor methods
094         ///////////////////////////////////////////////////////////////////////
095         
096         public long    getImageDisplayTime ( ) { return imageDisplayTime; }
097         
098         public String  getImagePath ( ) { return imagePath; }
099         
100         public long    getInterStimulusInterval ( )
101           { return interStimulusInterval; }
102         
103         public double  getScale ( )
104         ///////////////////////////////////////////////////////////////////////
105         {
106           final double  scale = infantConfig.getScale ( );
107           
108           return scale <= 0.0 ? 1.0 : scale;
109         }
110         
111         ///////////////////////////////////////////////////////////////////////
112         // lifecycle methods
113         ///////////////////////////////////////////////////////////////////////
114         
115         public void  update ( )
116         ///////////////////////////////////////////////////////////////////////
117         {
118           processRequests ( );
119           
120           updateExperiment ( );
121         }
122         
123         public void  destroy ( )
124         ///////////////////////////////////////////////////////////////////////
125         {
126           try
127           {
128             infantConfig.saveIfChanged ( this );
129           }
130           catch ( Exception  ex )
131           {
132             ex.printStackTrace ( );
133           }
134         }
135         
136         ///////////////////////////////////////////////////////////////////////
137         // private methods
138         ///////////////////////////////////////////////////////////////////////
139         
140         private void  addImage ( final InfantMessage  infantRequest )
141         ///////////////////////////////////////////////////////////////////////
142         {
143           final String  addedImagePath
144             = ( String ) infantRequest.getValue ( );
145           
146           try
147           {
148             final File  file = new File (
149               InfantConfig.getInfantImagesDirPath ( ), addedImagePath );
150           
151             final String  canonicalPath = file.getCanonicalPath ( );
152           
153             imagePaths = ( String [ ] ) ArrayLib.append (
154               imagePaths, canonicalPath );
155           
156             infantConfig.getLog ( ).record (
157               "Added image \"" + canonicalPath + "\"" );
158           }
159           catch ( final Exception  ex )
160           {
161             ex.printStackTrace ( );
162             
163             infantConfig.getLog ( ).record (
164               "Unable to add image \"" + addedImagePath + "\"" );
165           }
166         }
167         
168         private void  experimentBegin ( )
169         ///////////////////////////////////////////////////////////////////////
170         {
171           experimentIsRunning = true;
172    
173           infantConfig.getLog ( ).record ( "Experiment started." );
174           
175           infantData = new InfantData ( );
176           
177           imageIndex = 0;
178           
179           setImagePath ( imagePaths [ 0 ] );
180           
181           durationNanos = contemplationNanos = cycle = 0;
182           
183           eventQueue.offer (
184             new InfantMessage ( InfantMessage.Type.STIMULUS_WINDOW_OPEN ) );
185         }     
186         
187         private void  experimentEnd ( )
188         ///////////////////////////////////////////////////////////////////////
189         {
190           if ( !experimentIsRunning )
191           {
192             infantConfig.getLog ( ).record ( "Cannot end experiment "
193               + "because experiment is not currently running." );
194             
195             return;
196           }
197           
198           experimentIsRunning = false;
199           
200           recordData ( );
201           
202           infantConfig.getLog ( ).record ( "Experiment ended." );
203    
204           infantConfig.getLog ( ).record (
205             "\nReport (cycle, stimulus, duration, contemplation)\n---\n"
206             + infantData.report ( ) + "---\n" );
207           
208           eventQueue.offer (
209             new InfantMessage ( InfantMessage.Type.STIMULUS_WINDOW_CLOSE ) );
210         }
211         
212         private void  loadSetup ( final File  file )
213         ///////////////////////////////////////////////////////////////////////
214         {
215           try
216           {
217             infantConfig.getLog ( ).record (
218               "\nLoading setup file \"" + file + "\"..." );
219             
220             imagePaths = new String [ 0 ];
221             
222             final String  text = FileLib.loadTextFile ( file.getPath ( ) );
223             
224             final String [ ]  array = StringLib.toStringArray ( text );
225             
226             for ( String  s : array )
227             {
228               s = s.trim ( ).toLowerCase ( );
229               
230               if ( "".equals ( s ) )
231               {
232                 continue;
233               }
234               
235               int  index = s.indexOf ( ' ' );
236               
237               if ( index < 1 )
238               {
239                 continue;
240               }
241               
242               final String  name = s.substring ( 0, index );
243               
244               String  value = s.substring ( index );
245               
246               value = value.trim ( );
247               
248               if ( "".equals ( value ) )
249               {
250                 continue;
251               }
252               
253               if ( name.equals ( "duration" ) )
254               {
255                 final Double  d = new Double ( value );
256                 
257                 final long  l = Math.round ( d.doubleValue ( )
258                   * MathConstants.NANOSECONDS_PER_SECOND );
259                 
260                 requestQueue.offer (
261                   new InfantMessage (
262                     InfantMessage.Type.DURATION,
263                     new Long ( l ) ) );
264               }
265               else if ( name.equals ( "isi" ) )
266               {
267                 final Double  d = new Double ( value );
268                 
269                 final long  l = Math.round ( d.doubleValue ( )
270                   * MathConstants.NANOSECONDS_PER_SECOND );
271                 
272                 requestQueue.offer (
273                   new InfantMessage (
274                     InfantMessage.Type.ISI,
275                     new Long ( l ) ) );
276               }
277               else if ( name.equals ( "image" ) )
278               {
279                 requestQueue.offer (
280                   new InfantMessage (
281                     InfantMessage.Type.IMAGE,
282                     value ) );
283               }
284               else if ( name.equals ( "scale" ) )
285               {
286                 requestQueue.offer (
287                   new InfantMessage (
288                     InfantMessage.Type.SET_SCALE_REQUEST,
289                     value ) );
290               }
291               else
292               {
293                 System.out.println ( "Ignoring " + s );
294               }
295             }
296           }
297           catch ( Exception  ex )
298           {
299             ex.printStackTrace ( );
300           }
301         }
302         
303         private void  processRequests ( )
304         ///////////////////////////////////////////////////////////////////////
305         {
306           InfantMessage  infantRequest = null;
307           
308           while ( ( infantRequest = requestQueue.poll ( ) ) != null )
309           {
310             final InfantMessage.Type  type = infantRequest.getType ( );
311             
312             final Object  value = infantRequest.getValue ( );
313             
314             switch ( type )
315             {
316               case DURATION:
317                 
318                 setImageDisplayTime ( ( ( Long ) value ).longValue ( ) );
319                 
320                 break;
321                 
322               case EXPERIMENT_BEGIN:
323                 
324                 experimentBegin ( );
325                 
326                 break;
327                 
328               case EXPERIMENT_END:
329                 
330                 experimentEnd ( );
331                 
332                 break;
333                 
334               case IMAGE:
335                 
336                 addImage ( infantRequest );
337                 
338                 break;
339                 
340               case ISI:
341                 
342                 setInterStimulusInterval ( ( ( Long ) value ).longValue ( ) );
343                 
344                 break;
345                 
346               case LOAD_SETUP:
347                 
348                 final File  file = ( File ) value;
349                 
350                 if ( file == null )
351                 {             
352                   eventQueue.offer (
353                     new InfantMessage ( InfantMessage.Type.LOAD_SETUP ) );
354                 }
355                 else
356                 {
357                   loadSetup ( file );
358                 }
359                 
360                 break;
361                 
362               case PACIFIER:
363                 
364                 final long  currentTime = System.nanoTime ( );
365           
366                 if ( imagePath == imagePaths [ 0 ] )
367                 {
368                   final long  deltaTimeNanos = currentTime - imageStartTime;
369           
370                   if ( deltaTimeNanos >= interStimulusInterval )
371                   {
372                     contemplationNanos
373                       = currentTime - contemplationStartTimeNanos;
374                     
375                     if ( imageIndex != 0 )
376                     {
377                       recordData ( );
378                     }
379                     
380                     imageIndex = ( imageIndex + 1 ) % imagePaths.length;
381                     
382                     if ( imageIndex == 0 )
383                     {
384                       imageIndex = ( imageIndex + 1 ) % imagePaths.length;
385                       
386                       cycle++;
387                     }
388                     
389                     durationNanos = contemplationNanos = 0;
390                   }               
391                   
392                   originalImageStartTime = currentTime;
393                   
394                   setImagePath ( imagePaths [ imageIndex ] );
395                 }
396                 
397                 imageStartTime = currentTime;
398                 
399                 break;
400                 
401               case REQUEST_SAVE_FILENAME:
402                 
403                 requestSaveFilename ( );             
404                 
405                 break;
406                 
407               case SAVE_DATA:
408                 
409                 saveData ( infantRequest );
410                 
411                 break;
412                 
413               case SET_SCALE_REQUEST:
414                 
415                 setScale ( ( ( Double ) value ).doubleValue ( ) );
416                 
417                 break;
418                 
419               case SET_CONTROLLER_INDEX:
420                 
421                 setControllerIndex ( infantRequest );
422                 
423                 break;
424                 
425               case STIMULUS_WINDOW_CLOSING:
426                 
427                 experimentEnd ( );
428                 
429                 break;
430                 
431               default:
432                 
433                 infantConfig.getLog ( ).record (
434                   "InfantModel:  unknown message type:  " + type );
435             }
436           }
437         }
438         
439         private void  saveData ( final InfantMessage  infantMessage )
440         ///////////////////////////////////////////////////////////////////////
441         {
442           final Object [ ]  value = ( Object [ ] ) infantMessage.getValue ( );
443           
444           final File  file = ( File ) value [ 0 ];
445           
446           final InfantData  infantData2 = ( InfantData ) value [ 1 ];
447           
448           try
449           {
450             final BufferedWriter  bufferedWriter
451               = new BufferedWriter ( new FileWriter ( file ) );
452             
453             bufferedWriter.write ( infantData2.report ( ) );
454             
455             bufferedWriter.close ( );
456    
457             infantConfig.getLog ( ).record (
458               "Data saved to " + file.getCanonicalPath ( ) );
459           }
460           catch ( final IOException  ex )
461           {
462             ex.printStackTrace ( );
463    
464             infantConfig.getLog ( ).record ( "Saving data failed." );
465             
466             infantConfig.getLog ( ).record ( ex );
467           }
468         }
469         
470         private void  setControllerIndex (
471           final InfantMessage  infantMessage )
472         ///////////////////////////////////////////////////////////////////////
473         {
474           final Integer  value = ( Integer ) infantMessage.getValue ( );
475           
476           final int  newControllerIndex = value.intValue ( );
477           
478           final int  oldControllerIndex = infantConfig.getControllerIndex ( );
479           
480           if ( newControllerIndex == oldControllerIndex )
481           {
482             return;
483           }
484           
485           infantConfig.setControllerIndex ( newControllerIndex );
486           
487           infantConfig.getLog ( ).record (
488             "Controller index set to " + newControllerIndex );
489         }       
490         
491         private void  setImageDisplayTime ( final long  imageDisplayTime )
492         ///////////////////////////////////////////////////////////////////////
493         {
494           if ( imageDisplayTime != this.imageDisplayTime )
495           {
496             this.imageDisplayTime = imageDisplayTime;
497           
498             eventQueue.offer (
499               new InfantMessage ( InfantMessage.Type.DURATION_CHANGED ) );
500           }
501           
502           infantConfig.getLog ( ).record (
503             "Image display time set to "
504             + imageDisplayTime * MathConstants.SECONDS_PER_NANOSECOND
505             + " s" );         
506         }
507         
508         private void  setImagePath ( final String  imagePath )
509         ///////////////////////////////////////////////////////////////////////
510         {
511           this.imagePath = imagePath;
512           
513           eventQueue.offer (
514             new InfantMessage ( InfantMessage.Type.IMAGE ) );
515         }
516         
517         private void  setInterStimulusInterval (
518           final long  interStimulusInterval )
519         ///////////////////////////////////////////////////////////////////////
520         {
521           if ( interStimulusInterval != this.interStimulusInterval )
522           {
523             this.interStimulusInterval = interStimulusInterval;
524           
525             eventQueue.offer (
526               new InfantMessage ( InfantMessage.Type.ISI_CHANGED ) );
527           }
528           
529           infantConfig.getLog ( ).record (
530             "Interstimulus interval (ISI) set to "
531             + interStimulusInterval * MathConstants.SECONDS_PER_NANOSECOND
532             + " s" );         
533         }
534         
535         private void  requestSaveFilename ( )
536         ///////////////////////////////////////////////////////////////////////
537         {
538           if ( experimentIsRunning )
539           {
540             experimentEnd ( );
541           }
542           
543           eventQueue.offer (
544             new InfantMessage (
545               InfantMessage.Type.GET_SAVE_REPORT_FILENAME,
546               infantData ) );
547         }
548         
549         private void  setScale ( final double  scale )
550         ///////////////////////////////////////////////////////////////////////
551         {
552           if ( scale <= 0 )
553           {
554             infantConfig.getLog ( ).record ( "Scale must be > 0.0" );
555             
556             return;
557           }
558           
559           infantConfig.setScale ( scale );
560           
561           infantConfig.getLog ( ).record ( "Scale set to " + scale );
562         }
563         
564         private void  updateExperiment ( )
565         ///////////////////////////////////////////////////////////////////////
566         {
567           if ( imagePaths.length < 1 )
568           {
569             return;
570           }
571           
572           if ( imagePath != imagePaths [ 0 ] )
573           {
574             final long  currentTime = System.nanoTime ( );
575           
576             final long  deltaTimeNanos = currentTime - imageStartTime;
577           
578             if ( deltaTimeNanos >= imageDisplayTime )
579             {
580               setImagePath ( imagePaths [ 0 ] );
581               
582               durationNanos += ( currentTime - originalImageStartTime );
583               
584               contemplationStartTimeNanos = currentTime;
585             }
586           }
587         }
588         
589         private void  recordData ( )
590         ///////////////////////////////////////////////////////////////////////
591         {
592           final double  duration
593             = durationNanos * MathConstants.SECONDS_PER_NANOSECOND;
594           
595           final double  contemplation
596             = contemplationNanos * MathConstants.SECONDS_PER_NANOSECOND;
597                     
598           infantData.record (
599             cycle + 1,
600             imageIndex,
601             duration,
602             contemplation );
603                     
604           infantConfig.getLog ( ).record (
605             String.format (
606               "%1$d %2$d %3$1.3f %4$1.3f",
607               new Long    ( cycle + 1     ),
608               new Integer ( imageIndex    ),
609               new Double  ( duration      ),
610               new Double  ( contemplation ) ) );
611         }
612         
613         ///////////////////////////////////////////////////////////////////////
614         ///////////////////////////////////////////////////////////////////////
615         }