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 }