package scriptbuilder.structures; import java.awt.Color; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Observable; import java.util.Vector; import javax.xml.parsers.SAXParserFactory; import scriptbuilder.structures.ScriptIncident.IncidentFocusedEvent; import scriptbuilder.structures.ScriptIncident.SliceChangedEvent; import scriptbuilder.structures.events.*; import scriptbuilder.structures.units.Unit; import java.nio.file.Path; import java.nio.file.Paths; import java.util.TreeMap; import javax.swing.JOptionPane; import static scriptbuilder.structures.XMLBuilder.prettyPrintXML; /** * Representation of the script to be run by the TMC Simulator. Holds a list of * incidents, which have start and end times and contain events. * * @author Greg Eddington * * @author Bryan McGuffin * @author Sebastien Danthinne * @version 2017/06/22 */ public class SimulationScript extends Observable implements I_XML_Writable { /** * Strings to show in the incident color combo box. Last item should be black, * which will be used if invalid color is provided to lookupColor(). */ public static final String[] colorNames = {"BLUE", "RED", "CYAN", "GREEN", "ORANGE", "MAGENTA", "YELLOW", "BLACK"}; /** * Allowed color choices for incident display. * These colors must match the items in colorNames. */ public static final Color[] incidentColors = {Color.BLUE, Color.RED, Color.CYAN, Color.GREEN, Color.ORANGE, Color.MAGENTA, Color.YELLOW, Color.BLACK}; /** * The file to which this script will be saved. */ public File saveFile = null; /** * The name of this script. */ public String title = ""; /** * The incidents displayed by the GUI. */ public List incidents; /** * The units which participate in Unit events. */ public List units; //Somewhere in the code, something assumes that the list of incidents //contains exactly 10 items. Until I can find and un-break that, this will do. //todo: this incident fill count error private final int INCIDENT_FILL_COUNT = 10; /** * Number of incidents currently displayed. */ public int numberOfIncidents; /** * Script handler for parsing incoming XML files. */ private MyScriptHandler sh; public final String audioDirectory = "Audio"; public boolean saved; //TODO: Pretty much everything in this constructor is dummy data. //Replace all of it. /** * Constructor. Backfill incident list with null objects, to be replaced * later. */ public SimulationScript() { sh = new MyScriptHandler(this); incidents = new ArrayList(); units = new ArrayList(); numberOfIncidents = 0; saved = true; //Backfill with null incidents for (int i = numberOfIncidents; i < INCIDENT_FILL_COUNT; i++) { incidents.add(null); } } /** * checks and sees if this object has the unit passed. * @param unitID * @return does this SimulationScript have unitnum? */ public boolean hasUnit(String unitID){ boolean indicator = false; if(units.size()!=0){ for(Unit u : units){ if(unitID.equals(u.UnitNum)){ indicator = true; } } } return indicator; } /** * creates a dummy unit that only has unit number * @param unitNum */ public void addDummyUnit(String unitNum){ Unit dummy = new Unit(); dummy.UnitNum = unitNum; units.add(dummy); } /** * Update the script's observers. * */ public void update() { // The script has changed, notify observers //use to rewrite the save indicator //System.out.println("Script changed"); setChanged(); notifyObservers(this); saved = false; } /** * Tell this script's observers that there is a new slice event. * * @param e the slice focus event */ public void broadcastEvent(SliceChangedEvent e) { // The slice focus has changed; pass the message setChanged(); notifyObservers(e); } /** * Tell this script's observers that there is a new slice event. * * @param e the incident focus event */ public void broadcastEvent(IncidentFocusedEvent e) { // The slice focus has changed; pass the message setChanged(); notifyObservers(e); } /** * Load in an existing script from an XML file. * * @param infile the file containing the script */ public void loadScriptFromFile(File infile) { try { SAXParserFactory.newInstance().newSAXParser().parse(infile, sh); Vector inc = sh.getIncidents(); units = sh.getUnits(); for (ScriptIncident sci : inc) { addIncident(sci); } saveFile = infile; } catch (Exception ex) { String errMsg = ex.getMessage(); if (ex.getMessage().endsWith(".dtd (No such file or directory)")) { errMsg += "\nThe required external DTD file must be placed in\n" + " the same folder as the xml file."; } JOptionPane.showMessageDialog(null, errMsg, "Unable to load xml file", JOptionPane.INFORMATION_MESSAGE); } this.update(); } /** * Load in an existing list of units from an XML file. * @param inStream the input stream for the file containing the units */ public void loadUnitsFromFile(java.io.InputStream inStream){ try { SAXParserFactory.newInstance().newSAXParser().parse(inStream, sh); units.addAll(sh.getUnits()); } catch (Exception ex) { System.out.println("ERROR LOADING UNITS"); ex.printStackTrace(); } this.update(); } /** * Add a new incident to the script. * * @param sci the incident to be added. * @return true if there was enough room to add this incident. */ public boolean addIncident(ScriptIncident sci) { if (numberOfIncidents < INCIDENT_FILL_COUNT) { incidents.set(numberOfIncidents++, sci); return true; } return false; } /** * Write this script, in proper XML format, to the file in question. * * @param f the destination savefile to be written. */ public void saveScriptToFile(File f) { try { f.createNewFile(); BufferedWriter bw = new BufferedWriter(new FileWriter(f)); // convert to XML and remove newlines String xmlOut = (this.toXML()).replace("\n",""); // pretty print and save to file bw.write(prettyPrintXML(xmlOut)); bw.flush(); bw.close(); } catch (Exception ex) { System.out.println("ERROR SAVING SCRIPT"); ex.printStackTrace(); } try { createAudioDirectory(Paths.get(f.getCanonicalPath()).getParent()); } catch(IOException ex) { System.err.println("there was a problem creating the audio directories."); } saved = true; } /** * Creates the proper audio directory using the I_AudioEvent types. * @param path of audio directory root */ private void createAudioDirectory(Path path){ String separator = System.getProperty("file.separator"); File f = new File(path.toString()+separator+audioDirectory); if(!f.exists()) { f.mkdir(); //scan through to see what is already there and if there is no existing audio directory, create it. } for(ScriptIncident i : incidents) { if(i!=null) { String name = ((Integer) i.number).toString(); File incidentFolder = new File(path.toString()+separator+audioDirectory+separator+name); if(!incidentFolder.exists()) { //create an incidentfolder since one does not already exist incidentFolder.mkdir(); } for(TimeSlice slice : i.getSlices()) { for(I_ScriptEvent event : slice.events) { if(event instanceof I_AudioEvent) { //if the event is a chp radio event //then add the dummy file to the subdirectory created //CURRENTLY this ONLY is implemented for CHPRadioEvent, so it will need to be changed later. CHPRadioEvent radioEvent = (CHPRadioEvent) event; String output = radioEvent.toScriptFile(); //optimally, this line should use the ID, but to account for files being read in, it needs to be the radiofile name System.out.println("Attempting to create file: "+ path.toString()+ separator+ audioDirectory+ separator+ name+ separator+ radioEvent.radioFile.replaceAll(".mp3","")+ ".txt"); File newAudioScript = new File( path.toString()+ separator+ audioDirectory+ separator+ name+ separator+ radioEvent.radioFile.replaceAll(".mp3","")+ ".txt"); try { newAudioScript.createNewFile(); BufferedWriter bw = new BufferedWriter(new FileWriter(newAudioScript)); bw.write(output); bw.flush(); bw.close(); }catch(Exception e) { //to find the bug that triggers this Exception, we need to print out the lines that System.err.println("there was a problem creating your text files for: " + radioEvent.radioFile + "\n" + e.getMessage()); } } } } } } } @Override public String toXML() { ArrayList slices = arrangeAllSlices(); String output = ""; output += XMLBuilder.openTag(ELEMENT.TMC_SCRIPT.tag + " title=\"" + this.title + "\""); if (units.size() > 0) { output += XMLBuilder.openTag(ELEMENT.SCRIPT_DATA.tag); for (Unit unit : units) { output += unit.toXML(); } output += XMLBuilder.closeTag(ELEMENT.SCRIPT_DATA.tag); } for (TimeSlice slice : slices) { output += slice.toXML(); } output += XMLBuilder.closeTag(ELEMENT.TMC_SCRIPT.tag); return output; } /** * Arranges all timeslices in this script in chronological order, then by * incident number. * * @return a list of all timeslices in the simulation script */ public ArrayList arrangeAllSlices() { ArrayList list = new ArrayList(); int length = absoluteLength(); for (int i = 0; i < length; i++) { for (ScriptIncident inc : incidents) { if (inc != null && inc.slices.get(i) != null) { list.add(inc.slices.get(i)); } } } return list; } /** * Gets the total length of the simulation in seconds. * * @return */ public int absoluteLength() { int length = 0; for (ScriptIncident inc : incidents) { if (inc != null) { inc.updateLength(); int currentLength = inc.length + inc.offset; if (currentLength > length) { length = currentLength; } } } return length; } /** * Counts the number of incidents currently running. * * @return the number of non-null incidents currently in the script. A * number between 0 and INCIDENT_FILL_COUNT, inclusive. */ public int incidentCount() { int count = 0; for (ScriptIncident inc : incidents) { if (inc != null) { count++; } } return count; } /** Given a color, find its index in the incidentColors. * @param color a java color * @return the index of color in incidentColors, or last index if color isn't * in incidentColors. The last item in incidentColors should be black. */ public static int lookupColor(Color color) { int idx = 0; // search color array for target while(idx < incidentColors.length && !incidentColors[idx].equals(color)) { idx++; } // if color not found, return index of last item. if (idx == incidentColors.length) { return idx-1; } else return idx; } }