<commit_before>package net.sf.jaer;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.prefs.Preferences;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import net.sf.jaer.aemonitor.AEPacketRaw;
import net.sf.jaer.eventio.AEDataFile;
import net.sf.jaer.eventio.AEFileInputStream;
import net.sf.jaer.eventio.AEFileInputStreamInterface;
import net.sf.jaer.graphics.AEViewer;
import net.sf.jaer.graphics.AbstractAEPlayer;
import net.sf.jaer.util.IndexFileFilter;
/**
 * Synchronized playback and control of such playback is not
 * totally straightforward because of the bursty nature of
 * AER - there are no frames to synchronize
 * on and you obviously cannot sync on event number.
 * <p>
 * This class synchronizes multiple viewer players.
 * It assumes one is the master (whichever the user controls)
 * and coordinates viewers synchronously
 * so that all viewers can present a consistent view.
 * <p>
 * To achieve this, each viewer encapsulates its playback functionality on an AEPlayer
 *  class instance that is controlled either by the Viewer GUI (the user)
 * or by JAERViewer through its own SyncPlayer.
 * <p>
 * There is a single SyncPlayer and multiple AEViwer.AEPlayers, one for each viewer.
 * The user opens an index file to play back multiple files. The files each play in their own AEViewer.
 * <p>
 *
 * The Players share a common interface so this is achieved by
 * returning the correct object within AEViewer depending on
 * whether the views are synchronized.
 *
 * <p>
 * The individual threads doing the rendering in each AEViewer are
 * barricaded by the CyclicBarrier here. Each time an AEViewer asks
 * for synchronized events using getNextPacket, the call
 * here to SyncPlayer blocks until all threads asking for events have
 * gotten them. Then rendering in each thread happens normally.
 */
public class SyncPlayer extends AbstractAEPlayer implements PropertyChangeListener{
    JAERViewer outer;
    JFileChooser fileChooser;
    int currentTime = 0;
//        boolean paused=false;
    File lastFile;
    volatile CyclicBarrier barrier;
    // used to sync up viewers for playback
    int numPlayers = 0;
    ArrayList<AEViewer> playingViewers = new ArrayList<AEViewer>();
    static Preferences prefs=Preferences.userNodeForPackage(SyncPlayer.class);

    public SyncPlayer (AEViewer viewer,JAERViewer outer){
        super(viewer);
        this.outer = outer;
    }

    public boolean isChoosingFile (){
        return fileChooser != null && fileChooser.isVisible();
    }

    /** this call shows a file chooser for index files: files containing
     * information on which AE data files go together. This method is only called when an index file is opened.
     */
    public void openAEInputFileDialog (){
        fileChooser = new JFileChooser();
        IndexFileFilter filter = new IndexFileFilter();
        String lastFilePath = prefs.get("JAERViewer.lastFile","");
        // get the last folder
        lastFile = new File(lastFilePath);
        fileChooser.setFileFilter(filter);
        fileChooser.setCurrentDirectory(lastFile);
        // sets the working directory of the chooser
//            boolean wasPaused=isPaused();
        setPaused(true);
        int retValue = fileChooser.showOpenDialog(null);
        if ( retValue == JFileChooser.APPROVE_OPTION ){
            try{
                lastFile = fileChooser.getSelectedFile();
//                    if(lastFile!=null) recentFiles.addFile(lastFile);
                startPlayback(lastFile);
                lastFilePath = lastFile.getPath();
                prefs.put("JAERViewer.lastFile",lastFilePath);
            } catch ( IOException fnf ){
                fnf.printStackTrace();
            }
        }
        fileChooser = null;
        setPaused(false);
    }

    /** @return a simple class name (no package header) parsed from a .dat filename as the part before the "-"
     */
    String parseClassnameFromFilename (String filename){
        StringBuilder className = new StringBuilder();
        int i = 0;
        while ( i < filename.length() && filename.charAt(i) != '-' ){
            className.append(filename.charAt(i));
            i++;
        }
        log.info("filename "+filename+" parses to chip class name "+className.toString());
        return className.toString();
    }

    /** Returns AEChip class from simple name. If chip classes have not
     * yet been cached, waits until they exist.
     *
     * @param className, e.g. DVS128.
     * @return class for AEChip.
     */
    private Class getChipClassFromSimpleName (String className){
        Class deviceClass = null;
        do{
            if ( JAERViewer.getChipClassNames()== null ){
                try{
                    log.warning("AEChip class names have not yet been cached, waiting 1s to try again");
                    Thread.sleep(1000);
                } catch ( InterruptedException ex ){
                }
            }
        } while ( JAERViewer.getChipClassNames()== null );
        for ( String s:JAERViewer.getChipClassNames()){
            int ind=s.lastIndexOf('.');
            String s2=s.substring(ind+1);
            if ( s2.equals(className) ){ 
                try{
                    deviceClass = Class.forName(s);
                    log.info("found class " + deviceClass + " for className " + className);
                    break;
                } catch ( ClassNotFoundException e ){
                    log.warning(e.getMessage());
                }
            }
        }
        if ( deviceClass == null ){
            log.warning("no chip class for chip className=" + className);
        }
        return deviceClass;
    }

    synchronized void makeBarrier (){
        if ( numPlayers < 1 ){
            log.warning("cannot make barrier for " + numPlayers + " viewers - something is wrong");
            log.warning("disabling sychronized playback because probably multiple viewers are active but we are not playing set of sychronized files");
            outer.getToggleSyncEnabledAction().actionPerformed(null);
            // toggle all the viewers syncenabled menu item
//               JOptionPane.showMessageDialog(null,"Disabled sychronized playback because files are not part of sychronized set");
            return;
        }
        log.info("making barrier for "+this);
        barrier = new CyclicBarrier(numPlayers,new Runnable(){
            public void run (){
                // this is run after await synchronization; it updates the time to read events from each AEInputStream
//                        log.info(Thread.currentThread()+" resetting barrier");
                barrier.reset();
                setTime(getTime() + getTimesliceUs());
            }
        });
    }

    /** this call starts playback on the supplied index file, starting playback in each viewer appropriately.
    If the file is not an index file, then the first available viewer is called to start playback of the data file.
     * @param indexFile the .index file containing the filenames to play
     */
    @Override
    public void startPlayback (File indexFile) throws IOException{
        super.startPlayback(indexFile);  // just sets the file
        log.info("indexFile=" + indexFile);
        stopPlayback();
        // first check to make sure that index file is really an index file, in case a viewer called it
        if ( !indexFile.getName().endsWith(AEDataFile.INDEX_FILE_EXTENSION) &&!indexFile.getName().endsWith(AEDataFile.OLD_INDEX_FILE_EXTENSION) ){
            log.warning(indexFile + " doesn\'t appear to be an .index file, opening it in the first viewer and setting sync enabled false");
            AEViewer v = outer.getViewers().get(0);
            if ( outer.isSyncEnabled() ){
                JOptionPane.showMessageDialog(v,"<html>You are opening a single data file so synchronization has been disabled<br>To reenable, use File/Synchronization enabled</html>");
//                    setSyncEnabled(false);
                outer.getToggleSyncEnabledAction().actionPerformed(null);
            }
            v.aePlayer.startPlayback(indexFile);
            return;
        }
        playingViewers.clear();
        // this map will map from the data files to the viewer windows
        HashMap<File,AEViewer> map = new HashMap<File,AEViewer>();
        setTime(0);
        BufferedReader reader;
        // files are in same folder as index file
        try{
            reader = new BufferedReader(new FileReader(indexFile));
            String filename;
            ArrayList<AEViewer> dontUseAgain = new ArrayList<AEViewer>();
            // for each line in index file, get the data file, class of chip (from filename) and find or make a viewer window for it
            while ( ( filename = reader.readLine() ) != null ){
//                    log.info("JAERViewer.startPlayback(): trying to open AE file "+filename);
                // find chip classname from leading part of e.g. Tmpdiff128-2006-02-16T11-51-13+0100-0.dat up to '-'
//                    log.info("***********JAERViewer.SyncPlayer.startPlayback(): filename "+filename+" indicates chip class is "+className.toString());
                // now get the data file
                File file = new File(indexFile.getParentFile(),filename);
                // this is File object for the data file
                if ( !file.isFile() ){
                    JOptionPane.showMessageDialog(null,file + " from index file doesn\'t exist","Missing data file", JOptionPane.WARNING_MESSAGE);
                    reader.close();
                    return;
                }
                // for each filename in the index file, find the right viewer window.
                String className = parseClassnameFromFilename(filename);
                AEViewer vToUse = null;
                for ( AEViewer v:outer.getViewers() ){
                    // a viewer is acceptable if its chip class is the same as the parsed filename chip class or if it is a virgin AEViewer window
                    if(v.getAeChipClass().getSimpleName().equals(className)){
                        // just last component of viewer chip class has to match parsed chip class name
                        vToUse=v;
                        dontUseAgain.add(v);
                        break;
                    }

//                    //  a viewer is acceptable if its window title name starts with the same classname as the filename
//                    // or if it is a virgin window named "AEViewer"
//                    // AND if it hasn't already been assigned to some file
//                    String windowTitle = v.getTitle();
////                        log.info("...AEViewer has window title "+windowTitle);
//                    if ( ( windowTitle.startsWith(className) || windowTitle.startsWith("AEViewer") ) && !dontUseAgain.contains(v) ){
//                        vToUse = v;
//                        // always gets first one...
//                        dontUseAgain.add(v);
//                        // don't use this one again
////                            log.info("... viewer "+v.getTitle()+" can be used for "+file);
//                        break;
//                    }
                }
                // if there is no acceptable window, create a new AEViewer for this file
                if ( vToUse == null ){
                    log.info("JAERViewer.SyncPlayer.startPlayback(): no window found for " + filename + ", making new one");
                    vToUse = new AEViewer(outer);
                    dontUseAgain.add(vToUse);
                    vToUse.setVisible(true);
                }
                map.put(file,vToUse);
                log.info("JAERViewer.SyncPlayer.startPlayback(): mapped " + file + " to viewer " + vToUse);
            }
            // foreach data file
            if ( reader != null ){
                reader.close();
            }
            // now make a cyclic barrier to synchronize the players
            numPlayers = map.size();
            log.info(Thread.currentThread() + " constructing barrier for " + numPlayers + " synchronized players");
            makeBarrier();
            // now for each file, start playback in the correct window
            // also set the chip class for the viewer as parsed from the filename
            for ( Entry<File,AEViewer> e:map.entrySet() ){
                AEViewer v = e.getValue();
                File f = e.getKey();
                log.info("Starting playback of File " + f + " in viewer " + v.getTitle());
                String className = parseClassnameFromFilename(f.getName());
                Class chipClass = getChipClassFromSimpleName(className);
//                    AEPlayerInterface p=v.getAePlayer(); // this resolves to this play (SyncPlayer), but we want the viewer local player
                v.setAeChipClass(chipClass);
                v.aePlayer.stopPlayback();
                v.aePlayer.startPlayback(e.getKey());
                v.aePlayer.getAEInputStream().getSupport().addPropertyChangeListener("rewind",this);
                playingViewers.add(v);
            }
            initTime();<commit_msg>fixed bug in playing back index file sets where an existing AEViewer was being used for all the files of a set of files. Now playback seems to work.<commit_after>package net.sf.jaer;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.prefs.Preferences;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import net.sf.jaer.aemonitor.AEPacketRaw;
import net.sf.jaer.eventio.AEDataFile;
import net.sf.jaer.eventio.AEFileInputStream;
import net.sf.jaer.eventio.AEFileInputStreamInterface;
import net.sf.jaer.graphics.AEViewer;
import net.sf.jaer.graphics.AbstractAEPlayer;
import net.sf.jaer.util.IndexFileFilter;
/**
 * Synchronized playback and control of such playback is not
 * totally straightforward because of the bursty nature of
 * AER - there are no frames to synchronize
 * on and you obviously cannot sync on event number.
 * <p>
 * This class synchronizes multiple viewer players.
 * It assumes one is the master (whichever the user controls)
 * and coordinates viewers synchronously
 * so that all viewers can present a consistent view.
 * <p>
 * To achieve this, each viewer encapsulates its playback functionality on an AEPlayer
 *  class instance that is controlled either by the Viewer GUI (the user)
 * or by JAERViewer through its own SyncPlayer.
 * <p>
 * There is a single SyncPlayer and multiple AEViwer.AEPlayers, one for each viewer.
 * The user opens an index file to play back multiple files. The files each play in their own AEViewer.
 * <p>
 *
 * The Players share a common interface so this is achieved by
 * returning the correct object within AEViewer depending on
 * whether the views are synchronized.
 *
 * <p>
 * The individual threads doing the rendering in each AEViewer are
 * barricaded by the CyclicBarrier here. Each time an AEViewer asks
 * for synchronized events using getNextPacket, the call
 * here to SyncPlayer blocks until all threads asking for events have
 * gotten them. Then rendering in each thread happens normally.
 */
public class SyncPlayer extends AbstractAEPlayer implements PropertyChangeListener{
    JAERViewer outer;
    JFileChooser fileChooser;
    int currentTime = 0;
//        boolean paused=false;
    File lastFile;
    volatile CyclicBarrier barrier;
    // used to sync up viewers for playback
    int numPlayers = 0;
    ArrayList<AEViewer> playingViewers = new ArrayList<AEViewer>();
    static Preferences prefs=Preferences.userNodeForPackage(SyncPlayer.class);

    public SyncPlayer (AEViewer viewer,JAERViewer outer){
        super(viewer);
        this.outer = outer;
    }

    public boolean isChoosingFile (){
        return fileChooser != null && fileChooser.isVisible();
    }

    /** this call shows a file chooser for index files: files containing
     * information on which AE data files go together. This method is only called when an index file is opened.
     */
    public void openAEInputFileDialog (){
        fileChooser = new JFileChooser();
        IndexFileFilter filter = new IndexFileFilter();
        String lastFilePath = prefs.get("JAERViewer.lastFile","");
        // get the last folder
        lastFile = new File(lastFilePath);
        fileChooser.setFileFilter(filter);
        fileChooser.setCurrentDirectory(lastFile);
        // sets the working directory of the chooser
//            boolean wasPaused=isPaused();
        setPaused(true);
        int retValue = fileChooser.showOpenDialog(null);
        if ( retValue == JFileChooser.APPROVE_OPTION ){
            try{
                lastFile = fileChooser.getSelectedFile();
//                    if(lastFile!=null) recentFiles.addFile(lastFile);
                startPlayback(lastFile);
                lastFilePath = lastFile.getPath();
                prefs.put("JAERViewer.lastFile",lastFilePath);
            } catch ( IOException fnf ){
                fnf.printStackTrace();
            }
        }
        fileChooser = null;
        setPaused(false);
    }

    /** @return a simple class name (no package header) parsed from a .dat filename as the part before the "-"
     */
    String parseClassnameFromFilename (String filename){
        StringBuilder className = new StringBuilder();
        int i = 0;
        while ( i < filename.length() && filename.charAt(i) != '-' ){
            className.append(filename.charAt(i));
            i++;
        }
        log.info("filename "+filename+" parses to chip class name "+className.toString());
        return className.toString();
    }

    /** Returns AEChip class from simple name. If chip classes have not
     * yet been cached, waits until they exist.
     *
     * @param className, e.g. DVS128.
     * @return class for AEChip.
     */
    private Class getChipClassFromSimpleName (String className){
        Class deviceClass = null;
        do{
            if ( JAERViewer.getChipClassNames()== null ){
                try{
                    log.warning("AEChip class names have not yet been cached, waiting 1s to try again");
                    Thread.sleep(1000);
                } catch ( InterruptedException ex ){
                }
            }
        } while ( JAERViewer.getChipClassNames()== null );
        for ( String s:JAERViewer.getChipClassNames()){
            int ind=s.lastIndexOf('.');
            String s2=s.substring(ind+1);
            if ( s2.equals(className) ){ 
                try{
                    deviceClass = Class.forName(s);
                    log.info("found class " + deviceClass + " for className " + className);
                    break;
                } catch ( ClassNotFoundException e ){
                    log.warning(e.getMessage());
                }
            }
        }
        if ( deviceClass == null ){
            log.warning("no chip class for chip className=" + className);
        }
        return deviceClass;
    }

    synchronized void makeBarrier (){
        if ( numPlayers < 1 ){
            log.warning("cannot make barrier for " + numPlayers + " viewers - something is wrong");
            log.warning("disabling sychronized playback because probably multiple viewers are active but we are not playing set of sychronized files");
            outer.getToggleSyncEnabledAction().actionPerformed(null);
            // toggle all the viewers syncenabled menu item
//               JOptionPane.showMessageDialog(null,"Disabled sychronized playback because files are not part of sychronized set");
            return;
        }
        log.info("making barrier for "+this);
        barrier = new CyclicBarrier(numPlayers,new Runnable(){
            public void run (){
                // this is run after await synchronization; it updates the time to read events from each AEInputStream
//                        log.info(Thread.currentThread()+" resetting barrier");
                barrier.reset();
                setTime(getTime() + getTimesliceUs());
            }
        });
    }

    /** this call starts playback on the supplied index file, starting playback in each viewer appropriately.
    If the file is not an index file, then the first available viewer is called to start playback of the data file.
     * @param indexFile the .index file containing the filenames to play
     */
    @Override
    public void startPlayback (File indexFile) throws IOException{
        super.startPlayback(indexFile);  // just sets the file
        log.info("indexFile=" + indexFile);
        stopPlayback();
        // first check to make sure that index file is really an index file, in case a viewer called it
        if ( !indexFile.getName().endsWith(AEDataFile.INDEX_FILE_EXTENSION) &&!indexFile.getName().endsWith(AEDataFile.OLD_INDEX_FILE_EXTENSION) ){
            log.warning(indexFile + " doesn\'t appear to be an index file pointing to a set of data files because it does't end with the correct extension (.aeidx or .index), opening it in the first viewer and setting sync enabled false");
            AEViewer v = outer.getViewers().get(0);
            if ( outer.isSyncEnabled() ){
                JOptionPane.showMessageDialog(v,"<html>You are opening a single data file so synchronization has been disabled<br>To reenable, use File/Synchronization enabled</html>");
//                    setSyncEnabled(false);
                outer.getToggleSyncEnabledAction().actionPerformed(null);
            }
            v.aePlayer.startPlayback(indexFile);
            return;
        }
        playingViewers.clear();
        // this map will map from the data files to the viewer windows
        HashMap<File,AEViewer> map = new HashMap<File,AEViewer>();
        setTime(0);
        BufferedReader reader;
        // files are in same folder as index file
        try{
            reader = new BufferedReader(new FileReader(indexFile));
            String filename;
            ArrayList<AEViewer> dontUseAgain = new ArrayList<AEViewer>();
            // for each line in index file, get the data file, class of chip (from filename) and find or make a viewer window for it
            while ( ( filename = reader.readLine() ) != null ){
//                    log.info("JAERViewer.startPlayback(): trying to open AE file "+filename);
                // find chip classname from leading part of e.g. Tmpdiff128-2006-02-16T11-51-13+0100-0.dat up to '-'
//                    log.info(" filename "+filename+" indicates chip class is "+className.toString());
                // now get the data file
                File file = new File(indexFile.getParentFile(),filename);
                // this is File object for the data file
                if ( !file.isFile() ){
                    JOptionPane.showMessageDialog(null,file + " from index file doesn\'t exist","Missing data file", JOptionPane.WARNING_MESSAGE);
                    reader.close();
                    return;
                }
                // for each filename in the index file, find the right viewer window.
                String className = parseClassnameFromFilename(filename);
                AEViewer vToUse = null;
                for ( AEViewer v:outer.getViewers() ){
                    // a viewer is acceptable if it hasn't been mapped yet and its chip class is the same as the parsed filename chip class or if it is a virgin AEViewer window
                    if(!dontUseAgain.contains(v) && v.getAeChipClass().getSimpleName().equals(className) ){
                        vToUse=v;
                        dontUseAgain.add(v);
                        break;
                    }

//                    //  a viewer is acceptable if its window title name starts with the same classname as the filename
//                    // or if it is a virgin window named "AEViewer"
//                    // AND if it hasn't already been assigned to some file
//                    String windowTitle = v.getTitle();
////                        log.info("...AEViewer has window title "+windowTitle);
//                    if ( ( windowTitle.startsWith(className) || windowTitle.startsWith("AEViewer") ) && !dontUseAgain.contains(v) ){
//                        vToUse = v;
//                        // always gets first one...
//                        dontUseAgain.add(v);
//                        // don't use this one again
////                            log.info("... viewer "+v.getTitle()+" can be used for "+file);
//                        break;
//                    }
                }
                // if there is no acceptable window, create a new AEViewer for this file
                if ( vToUse == null ){
                    log.info("no AEViewer found for " + filename + ", making new one");
                    vToUse = new AEViewer(outer);
                    dontUseAgain.add(vToUse);
                    vToUse.setVisible(true);
                }
                map.put(file,vToUse);
                log.info("mapped " + file + " to viewer " + vToUse);
            }
            // foreach data file
            if ( reader != null ){
                reader.close();
            }
            // now make a cyclic barrier to synchronize the players
            numPlayers = map.size();
            log.info(Thread.currentThread() + " constructing barrier for " + numPlayers + " synchronized players");
            makeBarrier();
            // now for each file, start playback in the correct window
            // also set the chip class for the viewer as parsed from the filename
            for ( Entry<File,AEViewer> e:map.entrySet() ){
                AEViewer v = e.getValue();
                File f = e.getKey();
                log.info("Starting playback of File " + f + " in viewer " + v.getTitle());
                String className = parseClassnameFromFilename(f.getName());
                Class chipClass = getChipClassFromSimpleName(className);
//                    AEPlayerInterface p=v.getAePlayer(); // this resolves to this play (SyncPlayer), but we want the viewer local player
                v.setAeChipClass(chipClass);
                v.aePlayer.stopPlayback();
                v.aePlayer.startPlayback(e.getKey());
                v.aePlayer.getAEInputStream().getSupport().addPropertyChangeListener("rewind",this);
                playingViewers.add(v);
            }
            initTime();
{'fraction_non_alphanumeric': 0.09021323127392017, 'fraction_numerical': 0.0028430836522689994, 'mean_word_length': 2.120209257363812, 'pattern_counts': {'":': 0, '<': 37, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6696210', 'n_tokens_mistral': 7201, 'n_tokens_neox': 6786, 'n_words': 2296}