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<Alphabet> alphabets;
private static volatile List<Colors> 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<String> 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<SquareWorldView> 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<SquareWorldView> 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<Alphabet> parseAlphabetsDirectory(String directoryName) {
List<Alphabet> 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<String> 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<Colors> parseColorsDirectory(String directoryName) {
List<Colors> 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<String> 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<foundColors.size(); i++) {
Colors c = foundColors.get(i);
List<Colors> 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<foundColors.size(); i++) {
Colors c = foundColors.get(i);
if (!c.isComplete()) {
showErrorMessage("Removed incomplete color palette \""+c.getName()+"\"");
foundColors.remove(i);
i--;
}
}
NamedObject.sortCaseInsensitive(foundColors); //sort alphabetically
return foundColors;
}
private static List<String> parseSettingsDirectory(String directoryName) {
List<String> 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.<String, String>emptyMap())) {
Path jarPath = fileSystem.getPath(sourceDirectoryName);
if (!Files.exists(jarPath)) {
throw new IOException("Couldn't read resource directory \""+sourceDirectoryName
+"\" inside JAR file");
}
try (Stream<Path> 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<Integer, Integer> 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;
}
}
}