/*
Copyright (C) 2013 Isak Eriksson, Linus Lindgren
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package host;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import bluetooth.BluetoothClient;
import bluetooth.BluetoothServer;
/**
*
* This is a singleton class which will handle the mapping between different
* {@link BluetoothClient}s buttons and the key codes. A configuration file
* folder will be created and read from. Configuration files, containing
* different setups of key code mappings, will be created and parsed.
*
* @author Isak Eriksson (isak.eriksson@mail.com) & Linus Lindren
* (linlind@student.chalmers.se)
*
*/
public class Configuration {
private int numberOfClients;
private int numberOfButtons;
private File configFile;
private String configuration;
private ArrayList<Integer> keyCodes;
private static Configuration instance = null;
private Configuration() {
loadConfig();
}
public synchronized static Configuration getInstance() {
if (instance == null) {
instance = new Configuration();
}
return instance;
}
/**
* @param clientID
* The id of the client
* @return A {@link Set} of existing key codes for the given client
* @throws IllegalArgumentException
*/
public synchronized Set<Integer> getClientKeyCodes(int clientID) throws IllegalArgumentException {
int offset = clientID * numberOfButtons;
HashSet<Integer> keycodeSet = new HashSet<>();
for (int i = offset; i < (offset + numberOfButtons); i++) {
try {
keycodeSet.add(this.keyCodes.get(i));
} catch (ArrayIndexOutOfBoundsException e) {
throw new IllegalArgumentException();
}
}
return keycodeSet;
}
/**
* Gets the configured key code for a specific client and a button.
*
* @param clientID
* the client on which a button was pressed/released
* @param buttonID
* the button which was pressed/released
* @return the configured key code
* @throws IllegalArgumentException
* If the clientID or the buttonID exceeds the allowed values,
* an {@link IllegalArgumentException} is thrown.
*/
public synchronized int getKeyCode(int clientID, int buttonID) throws IllegalArgumentException {
int index = clientID * numberOfButtons + buttonID;
if (index < keyCodes.size() && buttonID < numberOfButtons) {
return keyCodes.get(clientID * numberOfButtons + buttonID);
} else {
throw new IllegalArgumentException(
"Too high button id! Ignoring button event.\n(Edit your config file or run the command 'reloadConfiguration' and set a higher number of buttons to prevent this error.)");
}
}
/**
*
* Takes input from the user about number of clients and number of buttons.
* Then parses the matching configuration file (Creates a new one if
* needed). The configuration files is located at
* '~/.config/VirtualGamepad'.
*
*/
public void loadConfig() {
numberOfClients = BluetoothServer.getNumberOfClients();
numberOfButtons = BluetoothServer.getNumberOfButtons();
keyCodes = new ArrayList<Integer>();
// Allocating space for array
for (int i = 0; i < (numberOfClients * numberOfClients) + numberOfButtons; i++) {
keyCodes.add(0);
}
determineConfigFileLocation(); // different path on different operating
// systems
if (!configFile.exists()) {
generateConfiguration();
writeConfigFile();
}
parseConfigFile();
}
public int getNumberOfClients() {
return numberOfClients;
}
private void addDefaultKeyCodes() {
keyCodes = new ArrayList<Integer>();
for (int i = 65; i <= 249; i++) {
keyCodes.add(i);
}
while (keyCodes.size() <= numberOfClients * numberOfButtons + numberOfButtons) {
keyCodes.add(0);
}
}
private void determineConfigFileLocation() {
String os = System.getProperty("os.name");
if (os.startsWith("Linux")) {
System.out.println("you are running on a Linux machine, setting config location to ~/.config/VirtualGamepad");
configFile = new File(System.getProperty("user.home") + File.separatorChar + ".config/VirtualGamepad" + File.separatorChar + "virtual-gamepad-" + numberOfClients + "c"
+ numberOfButtons + "b.conf");
System.out.println("config file path \"" + configFile.getAbsolutePath() + "\"");
} else if (os.startsWith("Windows")) {
System.out.println("Windoze is not yet supported. Get a Linux based OS now!");
System.exit(1);
}
}
private void generateConfiguration() {
addDefaultKeyCodes();
StringBuilder buttonKeyCodes = new StringBuilder();
for (int client = 0; client < numberOfClients; client++) {
for (int button = 0; button < numberOfButtons; button++) {
int code = client * numberOfButtons + button;
buttonKeyCodes.append("client" + client + ":button" + button + "=" + keyCodes.get(code) + "\n");
if (keyCodes.get(code) == 249) {
System.out.println();
System.out.println("---------------------------------------------------------");
System.out.println("NOTIFICATION: THE NUMBER OF DEFAULT KEYCODES HAS EXCEEDED!" + "\nIn \"" + configFile.getAbsolutePath() + "\"\nat [client" + client
+ ", button" + button + "]" + "\nPLEASE MANUALLY REPLACE ALL ZEROES IN THE CONFIG FILE");
System.out.println("---------------------------------------------------------");
}
}
}
this.configuration = "[KeyCodes]\n" + buttonKeyCodes.toString();
}
private void writeConfigFile() {
try {
configFile.getParentFile().mkdirs();
configFile.createNewFile();
FileWriter fw = new FileWriter(configFile);
fw.write(configuration);
fw.close();
} catch (IOException e) {
System.out.println("error: " + e.getMessage());
System.exit(1);
}
}
private void parseLine(String line) {
line = line.split("#")[0];
System.out.println("Parsed: " + line);
if (line.contains(":")) {
int client, button, code;
client = getClient(line);
button = getButton(line);
code = getCode(line);
/*
* System.out.println("client " + client + " : button " + button +
* " : code " + code);
*/
keyCodes.add(client * numberOfButtons + button, code);
}
}
private void parseConfigFile() {
String line = "";
if (configFile.exists()) {
int lineCounter = 1;
try {
FileReader fr = new FileReader(configFile);
BufferedReader br = new BufferedReader(fr);
while ((line = br.readLine()) != null) {
parseLine(line);
lineCounter++;
}
br.close();
} catch (FileNotFoundException e) {
parsingError("cannot open config file: " + configFile.getAbsolutePath() + "\n");
} catch (IOException e) {
parsingError("ERROR reading config file: " + configFile.getAbsolutePath() + "\n");
} catch (NumberFormatException e) {
parsingError("FAILED parsing integer at: '" + line + "' [line "+ lineCounter +"]\nin file: " + configFile.getAbsolutePath() + "\n");
}
} else {
parsingError("config file at path" + configFile.getAbsolutePath() +"\n could not be found!");
}
}
private int getClient(String line) {
line = line.split(":")[0];
line = line.split("client")[1];
int number = Integer.parseInt(line);
return number;
}
private void parsingError(String s){
System.out.println();
System.out.println(s);
System.out.println("Please fix the problems in the config file and start the server again!");
System.exit(1);
}
private int getButton(String line) {
line = line.split(":")[1];
line = line.split("button")[1];
line = line.split("=")[0];
int number = Integer.parseInt(line);
return number;
}
private int getCode(String line) {
line = line.split("=")[1];
int number = Integer.parseInt(line);
return number;
}
}