package dasherJava; import java.awt.AWTException; import java.awt.Component; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.net.URISyntaxException; import java.net.URL; import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map.Entry; import java.util.stream.Stream; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import javax.xml.parsers.ParserConfigurationException; import dasherJava.core.alphabets.GUIColors; import dasherJava.core.alphabets.xml.Alphabet; import dasherJava.core.alphabets.xml.AlphabetFileParser; import dasherJava.core.alphabets.xml.Colors; import dasherJava.core.alphabets.xml.ColorsFileParser; import dasherJava.core.collections.NamedObject; import dasherJava.core.input.GlobalMouseInput; import dasherJava.core.input.InputProvider; import dasherJava.core.input.JoystickInput; import dasherJava.core.input.JoystickInput.JoystickNotFoundException; import dasherJava.core.input.SocketInput; import dasherJava.core.languageModeling.LanguageAlphabet; import dasherJava.core.languageModeling.LanguageModel; import dasherJava.core.languageModeling.LanguageModel.LanguageModelTrainingStats; import dasherJava.core.languageModeling.TrainingFileReader; import dasherJava.core.languageModeling.TrainingFileWriter; import dasherJava.core.output.ATSPIImplementation; import dasherJava.core.output.AccessibilityActionsListTarget; import dasherJava.core.output.AccessibilityInterface; import dasherJava.core.output.KeyboardOutput; import dasherJava.core.output.PauseDasherTarget; import dasherJava.core.output.RobotKeyboardOutput; import dasherJava.core.output.SocketOutput; import dasherJava.core.output.TextCharOutput; import dasherJava.core.settings.Settings; import dasherJava.core.settings.SettingsFileReader; import dasherJava.core.settings.SettingsFileWriter; import dasherJava.core.startStop.CircleStartStopHandler; import dasherJava.core.startStop.StartStopHandler; import dasherJava.core.startStop.TwoBoxesStartStopHandler; import dasherJava.core.world.SquareWorldView; import dasherJava.core.world.SquareWorldView.SquareWorldViewOrientation; import dasherJava.core.world.WorldModel; import dasherJava.gui.MainFrame; import dasherJava.gui.SettingsConfigurationChooser; import dasherJava.gui.SwingWorldGraphics; import org.xml.sax.SAXException; public class DasherJava { public static final String VERSION_STRING = "1.0.0"; public static final String DATE_STRING = "September 17, 2024"; public static final String USING_STRING = "Built with Java 17, using JInput 2.0.10 and JNA 5.14.0"; public static final String WEBSITE_STRING = "https://github.com/janschu99/DasherJava"; public static final String BASE_DIRECTORY = System.getProperty("user.home")+File.separator+".dasherJava"; private static SettingsConfigurationChooser settingsConfigurationChooser = null; private static volatile String configurationName; private static volatile Settings settings = Settings.DEFAULT_SETTINGS; private static volatile MainFrame mainFrame = null; private static volatile GUIColors guiColors = null; private static volatile SocketInput socketInput = null; private static volatile ATSPIImplementation atspiImplementation = null; private static volatile KeyboardOutput keyboardOutput = null; private static volatile SocketOutput socketOutput = null; private static boolean initializedAccessibilityInterfaceToListActions = false; private static volatile TrainingFileWriter userTrainingFileWriter; private static volatile List alphabets; private static volatile List colors; public static void main(String[] args) { System.out.println("DasherJava "+VERSION_STRING+" started"); System.out.println("Released "+DATE_STRING+". See "+WEBSITE_STRING+" for more information"); System.out.println(USING_STRING); installDefaultResources(); alphabets=parseAlphabetsDirectory(BASE_DIRECTORY+File.separator+"alphabets"); colors=parseColorsDirectory(BASE_DIRECTORY+File.separator+"colors"); List foundConfigurations = parseSettingsDirectory(BASE_DIRECTORY+File.separator+"settings"); SwingUtilities.invokeLater(() -> settingsConfigurationChooser=new SettingsConfigurationChooser(foundConfigurations)); boolean openConfigurationChooser = true; if (args.length>0) { try { initWithSettings(args[0]); openConfigurationChooser=false; } catch (IOException ex) { showErrorMessage(ex); } } else System.out.println("No configuration specified, opening configuration chooser"); if (openConfigurationChooser) SwingUtilities.invokeLater(() -> settingsConfigurationChooser.setVisible(true)); } public static void initWithSettings(String configurationName) throws IOException { //init using configuration name Settings settingsRead = readSettingsFile(configurationName); DasherJava.configurationName=configurationName; initWithSettings(settingsRead, false); } public static void initWithSettings(Settings settings, boolean save) { //all initialization code that uses settings if (save) { try { writeSettingsFile(configurationName, settings, false); } catch (IOException ex) { showErrorMessage(mainFrame.getSettingsDialog(), ex); } } String alphabetName = settings.getAlphabetName(); Alphabet selectedAlphabet = NamedObject.findObjectByName(alphabetName, alphabets); if (selectedAlphabet==null) { String message = alphabetName.isEmpty() ? "No alphabet selected, please select one" : "Alphabet \""+alphabetName+"\" not found, please select a different one"; System.out.println(message); SwingUtilities.invokeLater(() -> { if (mainFrame!=null) mainFrame.setVisible(false); String newAlphabetName = (String) JOptionPane.showInputDialog(null, message, "Select Alphabet", JOptionPane.QUESTION_MESSAGE, null, NamedObject.buildNamesArray(alphabets), null); if (newAlphabetName==null) settingsConfigurationChooser.setVisible(true); //canceled by user else { settings.insertIntoAlphabetHistory(newAlphabetName); initWithSettings(settings, true); } }); return; } String colorsName = settings.getColorPaletteNameOverride(); if (colorsName.isEmpty()) colorsName=selectedAlphabet.getColorsName(); Colors selectedColors = NamedObject.findObjectByName(colorsName, colors); if (selectedColors==null && !colorsName.equals(selectedAlphabet.getColorsName())) { showErrorMessage("Color palette \""+colorsName+"\" not found, switching to alphabet-default color " +"palette (\""+selectedAlphabet.getColorsName()+"\")"); settings.setColorPaletteNameOverride(""); //alphabet-default colorsName=selectedAlphabet.getColorsName(); selectedColors=NamedObject.findObjectByName(colorsName, colors); } if (selectedColors==null) { String message = colorsName.isEmpty() ? "No color palette selected, please select one" : "Color palette \""+colorsName+"\" not found, please select a different one"; System.out.println(message); SwingUtilities.invokeLater(() -> { if (mainFrame!=null) mainFrame.setVisible(false); String newColorsName = (String) JOptionPane.showInputDialog(null, message, "Select Color Palette", JOptionPane.QUESTION_MESSAGE, null, NamedObject.buildNamesArray(colors), null); if (newColorsName==null) settingsConfigurationChooser.setVisible(true); //canceled by user else { settings.setColorPaletteNameOverride(newColorsName); initWithSettings(settings, true); } }); return; } closeUserTrainingFileWriter(); if (atspiImplementation!=null) atspiImplementation.terminate(); terminateSockets(); DasherJava.settings=settings; if (selectedAlphabet.usesKeyboardOutput()) { try { keyboardOutput=new RobotKeyboardOutput(); } catch (AWTException ex) { showErrorMessage("DasherJava: Couldn't create RobotKeyboardOutput", ex); } } else keyboardOutput=null; if (settings.getTextOutputTarget().equals("external") || selectedAlphabet.usesAccessibilityInterface()) { try { atspiImplementation=new ATSPIImplementation(); } catch (UnsatisfiedLinkError error) { showErrorMessage("DasherJava: Couldn't create ATSPIImplementation", error); } } else atspiImplementation=null; if (selectedAlphabet.usesSocketOutput()) socketOutput=new SocketOutput(settings.getSocketOutputPort()); else socketOutput=null; InputProvider inputProvider = null; //use mouse input String inputProviderSetting = settings.getInputProvider(); switch (inputProviderSetting) { case "mouse": break; case "globalMouse": inputProvider=new GlobalMouseInput(); break; case "joystick": try { inputProvider=new JoystickInput(settings.getJoystickInputName(), settings.getJoystickInputComponentNameX(), settings.getJoystickInputComponentNameY(), settings.getJoystickInputComponentNameStartStop(), settings.getJoystickInputSensitivityX(), settings.getJoystickInputSensitivityY(), settings.getJoystickInputOffsetX(), settings.getJoystickInputOffsetY()); } catch (JoystickNotFoundException ex) { showErrorMessage("DasherJava: Couldn't create JoystickInput", ex); } break; case "socket": socketInput=new SocketInput(settings.getSocketInputPort()); inputProvider=socketInput; break; default: showErrorMessage("Invalid inputProvider setting: \""+inputProviderSetting+"\". Must be either " +"\"mouse\", \"globalMouse\", \"joystick\" or \"socket\", assuming \"mouse\""); break; } guiColors=selectedColors.getGUIColors(); StartStopHandler startStopHandler = null; String startStopHandlerSetting = settings.getStartStopHandler(); switch (startStopHandlerSetting) { case "none": break; case "circle": startStopHandler=new CircleStartStopHandler(); break; case "twoBoxes": startStopHandler=new TwoBoxesStartStopHandler(); break; default: showErrorMessage("Invalid startStopHandler setting: \""+startStopHandlerSetting +"\". Must be either \"none\", \"circle\" or \"twoBoxes\", assuming \"none\""); break; } WorldModel worldModel = new WorldModel(createAndTrainLanguageModel( selectedAlphabet.getLanguageAlphabet(selectedColors), selectedAlphabet.getTrainingFilename()), settings.getMaxNumberOfNodes(), settings.getMinGainForNodeTrade(), settings.getMaxNumberOfOldRootNodes()); SwingWorldGraphics worldGraphics = new SwingWorldGraphics(); SquareWorldViewOrientation orientation = SquareWorldViewOrientation.getResultingOrientation( selectedAlphabet.getOrientation(), settings.getOrientationOverride()); SquareWorldView worldView = new SquareWorldView(0, 0, settings.getSpaceBehindNodes(), orientation, worldModel, worldGraphics, startStopHandler); //no size known yet StartStopHandler finalStartStopHandler = startStopHandler; InputProvider finalInputProvider = inputProvider; SwingUtilities.invokeLater(() -> { if (mainFrame==null) mainFrame=new MainFrame(); mainFrame.setContent(worldView, worldGraphics, finalStartStopHandler, finalInputProvider); }); } public static GUIColors getGUIColors() { return guiColors; } public static Settings getSettings() { return settings; } public static ViewPanelBounds getViewPanelBoundsOnScreen() { if (mainFrame!=null) return mainFrame.getViewPanelBoundsOnScreen(); return null; } public static TextCharOutput getTextCharOutput() { if (settings.getTextOutputTarget().equals("external")) return atspiImplementation; return mainFrame.getInternalTextCharOutput(); } public static PauseDasherTarget getPauseDasherTarget() { return mainFrame.getPauseDasherTarget(); } public static KeyboardOutput getKeyboardOutput() { return keyboardOutput; } public static AccessibilityInterface getAccessibilityInterface() { return atspiImplementation; } public static SocketOutput getSocketOutput() { return socketOutput; } public static void setAccessibilityActionsListTarget(AccessibilityActionsListTarget target) { if (target==null) { if (atspiImplementation!=null) { atspiImplementation.setAccessibilityActionsListTarget(null); if (initializedAccessibilityInterfaceToListActions) { atspiImplementation.terminate(); atspiImplementation=null; initializedAccessibilityInterfaceToListActions=false; } } } else { if (atspiImplementation==null) { try { atspiImplementation=new ATSPIImplementation(); initializedAccessibilityInterfaceToListActions=true; } catch (UnsatisfiedLinkError error) { showErrorMessage("DasherJava: Couldn't create ATSPIImplementation", error); } } if (atspiImplementation!=null) atspiImplementation.setAccessibilityActionsListTarget(target); } } public static boolean isMovementBlocked() { return !mainFrame.isCurrentlyShowing() || mainFrame.getSettingsDialog().isCurrentlyShowing(); } public static void showConfigurationChooser() { mainFrame.setVisible(false); mainFrame.writeWindowParameters(settings); try { writeSettingsFile(configurationName, settings, false); } catch (IOException ex) { showErrorMessage("showConfigurationChooser()", ex); } settingsConfigurationChooser.setVisible(true); } public static void showSettingsDialog() { mainFrame.writeWindowParameters(settings); mainFrame.getSettingsDialog().showSettingsDialog(settings, alphabets, colors, configurationName); } public static void changeAlphabet(String newAlphabetName) { if (doesAlphabetExist(newAlphabetName)) { settings.insertIntoAlphabetHistory(newAlphabetName); if (SwingUtilities.isEventDispatchThread()) { if (mainFrame!=null) mainFrame.writeWindowParameters(settings); initWithSettings(settings, true); } else SwingUtilities.invokeLater(() -> { if (mainFrame!=null) mainFrame.writeWindowParameters(settings); initWithSettings(settings, true); }); } } public static void doExit(boolean save) { //May only be called on the EDT since it works with GUI components! boolean doExit = true; if (settings.getConfirmExit().equals("always") || settings.getConfirmExit().equals("whenTextEntered") && mainFrame!=null && mainFrame.doesTextAreaContainText()) { doExit=JOptionPane.showConfirmDialog(mainFrame, "Really exit?", "Confirm Exit", JOptionPane.OK_CANCEL_OPTION)==JOptionPane.OK_OPTION; } if (doExit) { if (save) { if (mainFrame!=null) mainFrame.writeWindowParameters(settings); try { writeSettingsFile(configurationName, settings, false); } catch (IOException ex) { System.out.println(ex.getMessage()); //Cannot show a GUI dialog here since we are about to exit. } } closeUserTrainingFileWriter(); if (atspiImplementation!=null) atspiImplementation.terminate(); ATSPIImplementation.exit(); terminateSockets(); System.out.println("DasherJava "+VERSION_STRING+" exiting"); System.exit(0); } } private static void closeUserTrainingFileWriter() { TrainingFileWriter writer = userTrainingFileWriter; //for atomicity if (writer!=null) { try { writer.close(); } catch (IOException ex) { showErrorMessage("DasherJava: Couldn't close user training file writer", ex); } userTrainingFileWriter=null; } } private static void terminateSockets() { if (socketInput!=null) socketInput.terminate(); if (socketOutput!=null) socketOutput.terminate(); } private static Settings readSettingsFile(String configurationName) throws IOException { String settingsFilename = BASE_DIRECTORY+File.separator+"settings"+File.separator+configurationName+".txt"; try { return SettingsFileReader.readSettingsFile(settingsFilename); } catch (IOException ex) { throw new IOException("Couldn't read settings file \""+settingsFilename+"\": "+ex.getMessage(), ex); } } public static void writeSettingsFile(String configurationName, Settings settingsToWrite, boolean failIfExisting) throws IOException { String settingsFilename = BASE_DIRECTORY+File.separator+"settings"+File.separator+configurationName+".txt"; try { SettingsFileWriter.writeSettingsFile(settingsFilename, settingsToWrite, failIfExisting); } catch (IOException ex) { throw new IOException("Couldn't write settings file \""+settingsFilename+"\": "+ex.getMessage(), ex); } } public static void duplicateSettingsFile(String configurationName, String newConfigurationName) throws IOException { String originalName = BASE_DIRECTORY+File.separator+"settings"+File.separator+configurationName+".txt"; String duplicatedName = BASE_DIRECTORY+File.separator+"settings"+File.separator+newConfigurationName+".txt"; Path original = Path.of(originalName); Path duplicated = Path.of(duplicatedName); try { Files.copy(original, duplicated, StandardCopyOption.COPY_ATTRIBUTES); } catch (IOException ex) { throw new IOException("Couldn't duplicate settings file \""+originalName+"\" to \""+duplicatedName+"\": " +ex.getMessage(), ex); } //Copying silently does nothing when the source and target file are the same, but we still want to inform the //user that no duplication has happened, so we manually check after copying. if (Files.isSameFile(original, duplicated)) throw new FileAlreadyExistsException("Couldn't duplicate settings " +"file \""+originalName+"\" to \""+duplicatedName+"\""); } public static void renameSettingsFile(String configurationName, String newConfigurationName) throws IOException { String originalName = BASE_DIRECTORY+File.separator+"settings"+File.separator+configurationName+".txt"; String newName = BASE_DIRECTORY+File.separator+"settings"+File.separator+newConfigurationName+".txt"; Path original = Path.of(originalName); Path newPath = Path.of(newName); try { Files.move(original, newPath); } catch (IOException ex) { throw new IOException("Couldn't rename settings file \""+originalName+"\" to \""+newName+"\": " +ex.getMessage(), ex); } } public static void deleteSettingsFile(String configurationName) throws IOException { String settingsFilename = BASE_DIRECTORY+File.separator+"settings"+File.separator+configurationName+".txt"; Path path = Path.of(settingsFilename); try { Files.delete(path); } catch (IOException ex) { throw new IOException("Couldn't delete settings file \""+settingsFilename+"\": "+ex.getMessage(), ex); } } private static List parseAlphabetsDirectory(String directoryName) { List foundAlphabets = new ArrayList<>(); File[] alphabetFiles = new File(directoryName).listFiles(); if (alphabetFiles==null) { showErrorMessage("Couldn't read alphabets directory \""+directoryName+"\""); return foundAlphabets; //empty list } for (File alphabetFile : alphabetFiles) { if (alphabetFile.isFile()) { try { foundAlphabets.add(new AlphabetFileParser(alphabetFile.getAbsolutePath()).getAlphabet()); } catch (ParserConfigurationException|SAXException|IOException ex) { showErrorMessage("Couldn't read alphabet file \""+alphabetFile.getAbsolutePath()+"\"", ex); } } } List duplicatedNames = NamedObject.removeDuplicates(foundAlphabets); for (String duplicatedName : duplicatedNames) { showErrorMessage("Found multiple alphabets with name \""+duplicatedName+"\", ignoring all of them"); } NamedObject.sortCaseInsensitive(foundAlphabets); //sort alphabetically return foundAlphabets; } private static List parseColorsDirectory(String directoryName) { List foundColors = new ArrayList<>(); File[] colorsFiles = new File(directoryName).listFiles(); if (colorsFiles==null) { showErrorMessage("Couldn't read colors directory \""+directoryName+"\""); return foundColors; //empty list } for (File colorsFile : colorsFiles) { if (colorsFile.isFile()) { try { foundColors.add(new ColorsFileParser(colorsFile.getAbsolutePath()).getColors()); } catch (ParserConfigurationException|SAXException|IOException ex) { showErrorMessage("Couldn't read colors file \""+colorsFile.getAbsolutePath()+"\"", ex); } } } List duplicatedNames = NamedObject.removeDuplicates(foundColors); for (String duplicatedName : duplicatedNames) { showErrorMessage("Found multiple color palettes with name \""+duplicatedName+"\", ignoring all of them"); } //now handle inheritance for (int i = 0; i usedColors = new ArrayList<>(foundColors.size()); //to detect cyclic inheritance usedColors.add(c); while (true) { String parentName = c.getParentName(); if (parentName.isEmpty()) break; Colors parent = NamedObject.findObjectByName(parentName, foundColors); if (parent==null) { showErrorMessage("Color palette \""+parentName+"\" (parent of \""+c.getName()+"\") not found"); break; } if (usedColors.contains(parent)) { showErrorMessage("Cyclic inheritance detected for color palette \""+foundColors.get(i).getName() +"\""); break; } usedColors.add(parent); c=c.combineWithParent(parent); } foundColors.set(i, c); } for (int i = 0; i parseSettingsDirectory(String directoryName) { List foundConfigurations = new ArrayList<>(); File[] settingsFiles = new File(directoryName).listFiles(); if (settingsFiles==null) { showErrorMessage("Couldn't read settings directory \""+directoryName+"\""); return foundConfigurations; //empty list } for (File settingsFile : settingsFiles) { if (settingsFile.isFile()) { String fileName = settingsFile.getName(); if (fileName.endsWith(".txt") && fileName.length()>4) //remove .txt extension fileName=fileName.substring(0, fileName.length()-4); foundConfigurations.add(fileName); } } return foundConfigurations; } private static void installDefaultResources() { try { //Note: For resources inside the JAR we always have to use a forward slash as path separator replaceDirectoryIfMissingOrEmpty("resources/alphabets", BASE_DIRECTORY+File.separator+"alphabets"); replaceDirectoryIfMissingOrEmpty("resources/colors", BASE_DIRECTORY+File.separator+"colors"); replaceDirectoryIfMissingOrEmpty("resources/libs", BASE_DIRECTORY+File.separator+"libs"); replaceDirectoryIfMissingOrEmpty("resources/settings", BASE_DIRECTORY+File.separator+"settings"); replaceDirectoryIfMissingOrEmpty("resources/systemTrainingTexts", BASE_DIRECTORY+File.separator +"systemTrainingTexts"); Files.createDirectories(Path.of(BASE_DIRECTORY+File.separator+"userTrainingTexts")); } catch (IOException|URISyntaxException ex) { showErrorMessage("installDefaultResources()", ex); } } private static void replaceDirectoryIfMissingOrEmpty(String sourceDirectoryName, String targetDirectoryName) throws IOException, URISyntaxException { Files.createDirectories(Path.of(targetDirectoryName)); File[] targetFiles = new File(targetDirectoryName).listFiles(); if (targetFiles==null) throw new IOException("Couldn't read target directory \""+targetDirectoryName+"\""); if (targetFiles.length>0) return; //target directory not empty, don't copy URL resourcesBase = DasherJava.class.getResource(""); if (resourcesBase==null) throw new IOException("Couldn't determine resources base"); if (resourcesBase.toString().startsWith("jar")) { //extract files from jar file //adapted from https://stackoverflow.com/a/24316335 try (FileSystem fileSystem = FileSystems.newFileSystem(resourcesBase.toURI(), Collections.emptyMap())) { Path jarPath = fileSystem.getPath(sourceDirectoryName); if (!Files.exists(jarPath)) { throw new IOException("Couldn't read resource directory \""+sourceDirectoryName +"\" inside JAR file"); } try (Stream sourceFiles = Files.list(jarPath)) { for (Path sourceFile : sourceFiles.toList()) { Files.copy(sourceFile, Path.of(targetDirectoryName+File.separator+sourceFile.getFileName()), StandardCopyOption.COPY_ATTRIBUTES); } } } } else { //copy files from resource directory File[] sourceFiles = new File(sourceDirectoryName).listFiles(); if (sourceFiles==null) throw new IOException("Couldn't read resource directory \""+sourceDirectoryName+"\""); for (File sourceFile : sourceFiles) { Files.copy(sourceFile.toPath(), Path.of(targetDirectoryName+File.separator+sourceFile.getName()), StandardCopyOption.COPY_ATTRIBUTES); } } } private static LanguageModel createAndTrainLanguageModel(LanguageAlphabet languageAlphabet, String trainingFilename) { LanguageModel languageModel = new LanguageModel(languageAlphabet, getSettings().getPPMMaxOrder(), getSettings().getPPMAlpha(), getSettings().getPPMBeta(), getSettings().getPPMUniform()); LanguageModelTrainingStats trainingStats = new LanguageModelTrainingStats(); trainingStats.incrementNumOfContextTrieNodes(); //language model root if (trainingFilename!=null && !trainingFilename.isEmpty()) { trainLanguageModel(languageModel, languageAlphabet, trainingFilename, false, trainingStats); trainLanguageModel(languageModel, languageAlphabet, trainingFilename, true, trainingStats); String userTrainingFilePath = BASE_DIRECTORY+File.separator+"userTrainingTexts"+File.separator +trainingFilename; try { userTrainingFileWriter=new TrainingFileWriter(userTrainingFilePath); } catch (IOException ex) { showErrorMessage("DasherJava: Couldn't open user training file \""+userTrainingFilePath +"\" for writing", ex); } } else if (languageAlphabet.getFixedProbabilityCharacters().size()!=languageAlphabet.getNumOfCharacters()) //don't need a training file if all characters have a fixed probability anyway showErrorMessage("No training file specified, assuming uniform distribution"); return languageModel; } private static void trainLanguageModel(LanguageModel languageModel, LanguageAlphabet languageAlphabet, String trainingFilename, boolean user, LanguageModelTrainingStats trainingStats) { String fullTrainingFilePath = BASE_DIRECTORY+File.separator+(user ? "userTrainingTexts" : "systemTrainingTexts") +File.separator+trainingFilename; try (TrainingFileReader trainingFileReader = new TrainingFileReader(fullTrainingFilePath, languageAlphabet)) { languageModel.train(trainingFileReader, trainingStats); System.out.println("Language model training with "+(user ? "user" : "system") +" training text done, read "+trainingStats.getNumOfSymbolsRead() +" symbols from the training text and created "+trainingStats.getNumOfContextTrieNodes() +" context trie nodes"+(user ? " (statistics accumulated with system training text)" : "") +". Skipped symbols are listed below, if any" +(user ? " (also accumulated with system training text)" : "")+"."); for (Entry entry : trainingStats.getSkippedSymbols().entrySet()) { System.out.println(" Skipped "+entry.getValue()+" occurrence(s) of Unicode value "+entry.getKey() +" because the current alphabet does not contain such character"); } } catch (IOException ex) { showErrorMessage("Couldn't read "+(user ? "user" : "system")+" training text", ex); } } public static void appendToUserTrainingFile(int unicode) { TrainingFileWriter writer = userTrainingFileWriter; //for atomicity if (writer!=null) { try { writer.writeCodePoint(unicode); } catch (IOException ex) { showErrorMessage("DasherJava: Couldn't write to user training file", ex); } } } public static boolean doesAlphabetExist(String alphabetName) { for (Alphabet alphabet : alphabets) { if (alphabet.getName().equals(alphabetName)) return true; } return false; } public static void showErrorMessage(String message) { showErrorMessage(mainFrame, message, null); } public static void showErrorMessage(Throwable throwable) { showErrorMessage(mainFrame, null, throwable); } public static void showErrorMessage(String message, Throwable throwable) { showErrorMessage(mainFrame, message, throwable); } public static void showErrorMessage(Component parent, Throwable throwable) { showErrorMessage(parent, null, throwable); } public static void showErrorMessage(Component parent, String message, Throwable throwable) { StringBuilder fullMessageBuilder = new StringBuilder(); if (message!=null) fullMessageBuilder.append(message); if (throwable!=null) { if (message!=null) fullMessageBuilder.append(": "); fullMessageBuilder.append(throwable.getClass()); fullMessageBuilder.append(": "); fullMessageBuilder.append(throwable.getMessage()); Throwable cause = throwable.getCause(); if (cause!=null) { fullMessageBuilder.append(": "); fullMessageBuilder.append(cause.getClass()); fullMessageBuilder.append(": "); fullMessageBuilder.append(cause.getMessage()); } } String fullMessage = fullMessageBuilder.toString(); System.out.println(fullMessage); if (SwingUtilities.isEventDispatchThread()) { JOptionPane.showMessageDialog(parent, fullMessage, "Error", JOptionPane.ERROR_MESSAGE); } else { try { SwingUtilities.invokeAndWait(() -> JOptionPane.showMessageDialog(parent, fullMessage, "Error", JOptionPane.ERROR_MESSAGE)); } catch (InterruptedException ex) { System.out.println("showError(): InterruptedException: "+ex.getMessage()); } catch (InvocationTargetException ex) { System.out.println("showError(): InvocationTargetException: "+ex.getMessage()); } } } public static class ViewPanelBounds { //simple rectangle class private final int x; private final int y; private final int width; private final int height; public ViewPanelBounds(int x, int y, int width, int height) { this.x=x; this.y=y; this.width=width; this.height=height; } public int getX() { return x; } public int getY() { return y; } public int getWidth() { return width; } public int getHeight() { return height; } } }