();
+ files.addAll(cached.values());
+ while (files.size() > 0) {
+ final File file = files.poll();
+ if (!file.exists()) {
+ Verboser.verbose("Generating directory: " + file.getAbsolutePath());
+ file.mkdirs();
+ if (!file.exists()) {
+ System.err.println("Failed to make directory: " + file.getAbsolutePath());
+ }
+ }
+ }
+ }
+
+ public static File getTempDirectory() {
+ if (temp != null) {
+ return temp;
+ }
+ int randomNum = new Random().nextInt(999999999);
+ temp = new File(getResourcesPath(), randomNum + "/");
+ temp.mkdirs();
+ temp.deleteOnExit();
+ return temp;
+ }
+
+ /**
+ * Clears the cache based on the latest modification
+ *
+ * @param remove A long that represents the amount of seconds that a file may have since the latest modification
+ * @param force Defines if the cache folder, within user.home, should also be removed
+ */
+ public static void clearCache(int remove, boolean force) {
+ File[] cache = getCachePath().listFiles();
+ if (cache != null) {
+ for (File f : cache) {
+ if (f != null && System.currentTimeMillis() / 1000 - f.lastModified() / 1000 > remove) {
+ Verboser.verbose("Clearing " + (f.isDirectory() ? "directory " : "file ") + f.getName() + " from cache (" + (force ? "Force-cleared" : "Expired") + ")...");
+
+ try {
+ FileUtil.delete(f);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+ }
+
+ private static void clearCache(int remove) {
+ clearCache(remove, false);
+ }
+
+ public static void clearCache() {
+ clearCache(0, true);
+ }
+
+ /**
+ * @param file Directory to be removed
+ */
+ private static void removeDirectory(File file, boolean root) {
+ if (file.isDirectory()) {
+ if (file.list().length == 0) {
+ file.delete();
+ Verboser.verbose("Directory is deleted : "
+ + file.getAbsolutePath());
+ } else {
+ String files[] = file.list();
+ for (String temp : files) {
+ File fileDelete = new File(file, temp);
+ removeDirectory(fileDelete, false);
+ }
+
+ if (file.list().length == 0) {
+ file.delete();
+ Verboser.verbose("Directory is deleted: "
+ + file.getAbsolutePath());
+ }
+ }
+ if (root) {
+ file.delete();
+ }
+ } else {
+ file.delete();
+ Verboser.verbose("File is deleted : " + file.getAbsolutePath());
+ }
+ }
+
+ /**
+ * Returns an array of files with from a given directory and a given extension
+ *
+ * @param directory The directory where should be searched
+ * @param extension The extension to be searched for, including the dot (like .json)
+ * @return An array of of files that match the request
+ */
+ public static File[] listFilesWithExtension(File directory, final String extension) {
+ return directory.listFiles(new FilenameFilter() {
+ public boolean accept(File dir, String filename) {
+ return filename.endsWith(extension);
+ }
+ });
+ }
+
+ public static File[] listJSONFiles(File directory) {
+ return listFilesWithExtension(directory, ".json");
+ }
+}
diff --git a/src/main/java/org/parabot/api/io/FileUtil.java b/src/main/java/org/parabot/api/io/FileUtil.java
new file mode 100644
index 0000000..423698e
--- /dev/null
+++ b/src/main/java/org/parabot/api/io/FileUtil.java
@@ -0,0 +1,123 @@
+package org.parabot.api.io;
+
+import org.parabot.api.output.Verboser;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.channels.FileChannel;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * @author JKetelaar
+ */
+public class FileUtil {
+
+ public static String getChecksum(File file) {
+ if (file.isFile()) {
+ try {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ if (file.exists()) {
+ FileInputStream fis = new FileInputStream(file);
+ byte[] dataBytes = new byte[1024];
+
+ int nread;
+
+ while ((nread = fis.read(dataBytes)) != -1) {
+ md.update(dataBytes, 0, nread);
+ }
+
+ byte[] mdbytes = md.digest();
+
+ StringBuilder sb = new StringBuilder("");
+ for (int i = 0; i < mdbytes.length; i++) {
+ sb.append(Integer.toString((mdbytes[i] & 0xff) + 0x100, 16).substring(1));
+ }
+
+ return sb.toString();
+ }
+ } catch (NoSuchAlgorithmException | IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ return null;
+ }
+
+ public static byte[] getChecksumBytes(File file) {
+ if (file.isFile()) {
+ try {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ if (file.exists()) {
+ FileInputStream fis = new FileInputStream(file);
+ byte[] dataBytes = new byte[1024];
+
+ int nread;
+
+ while ((nread = fis.read(dataBytes)) != -1) {
+ md.update(dataBytes, 0, nread);
+ }
+
+ return md.digest();
+ }
+ } catch (NoSuchAlgorithmException | IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ return null;
+ }
+
+ public static void copyFile(File sourceFile, File destFile)
+ throws IOException {
+ if (!sourceFile.exists()) {
+ return;
+ }
+ if (!destFile.exists()) {
+ destFile.createNewFile();
+ }
+ FileChannel source = null;
+ FileChannel destination = null;
+ source = new FileInputStream(sourceFile).getChannel();
+ destination = new FileOutputStream(destFile).getChannel();
+ if (source != null) {
+ destination.transferFrom(source, 0, source.size());
+ }
+ if (source != null) {
+ source.close();
+ }
+ destination.close();
+ }
+
+ public static void delete(File file) throws IOException {
+ if (file.isDirectory()) {
+ if (file.list().length == 0) {
+
+ boolean success = file.delete();
+ Verboser.verbose(success ? "Deleted " + file.getAbsolutePath() + " OK" :
+ "FAILED to delete " + file.getAbsolutePath());
+ } else {
+
+ String files[] = file.list();
+
+ for (String temp : files) {
+ File fileDelete = new File(file, temp);
+ delete(fileDelete);
+ }
+
+ if (file.list().length == 0) {
+ boolean success = file.delete();
+ Verboser.verbose(success ? "Deleted " + file.getAbsolutePath() + " OK" :
+ "FAILED to delete " + file.getAbsolutePath());
+ }
+ }
+
+ } else {
+ boolean success = file.delete();
+ Verboser.verbose(success ? "Deleted " + file.getAbsolutePath() + " OK" :
+ "FAILED to delete " + file.getAbsolutePath());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/parabot/api/io/ImageUtil.java b/src/main/java/org/parabot/api/io/ImageUtil.java
new file mode 100644
index 0000000..3b41156
--- /dev/null
+++ b/src/main/java/org/parabot/api/io/ImageUtil.java
@@ -0,0 +1,43 @@
+package org.parabot.api.io;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+
+/**
+ * @author Fryslan
+ */
+public class ImageUtil {
+
+ /**
+ * Gets an image from local device
+ *
+ * @param location Location of the image
+ * @return image from location.
+ */
+ public Image getLocalImage(String location) {
+ try {
+ File sourceimage = new File(location);
+ return ImageIO.read(sourceimage);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Gets an image from the given URL.
+ *
+ * @param url Url of th image.
+ * @return image from location.
+ */
+ public Image getURLImage(String url) {
+ try {
+ URL sourceimage = new URL(url);
+ return ImageIO.read(sourceimage);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/org/parabot/api/io/ProgressListener.java b/src/main/java/org/parabot/api/io/ProgressListener.java
new file mode 100644
index 0000000..b23c1d5
--- /dev/null
+++ b/src/main/java/org/parabot/api/io/ProgressListener.java
@@ -0,0 +1,44 @@
+package org.parabot.api.io;
+
+/**
+ * Keeps track of a progress
+ *
+ * @author Everel
+ */
+public interface ProgressListener {
+
+ /**
+ * Called when progress increased
+ *
+ * @param value
+ */
+ void onProgressUpdate(double value);
+
+ /**
+ * Updates upload speed
+ *
+ * @param mbPerSecond
+ */
+ void updateDownloadSpeed(double mbPerSecond);
+
+ /**
+ * Sets the message displayed
+ * @param message The Text to show
+ */
+ void updateMessage(String message);
+
+ /**
+ * Combination of the two
+ * @param message
+ * @param progress
+ */
+ void updateMessageAndProgress(String message, double progress);
+
+ /**
+ * Usage would be
+ * double before = getCurrentProgress()
setCurrent(0)
for 0 .. 100: setCurrent(x)
done!
setCurrent(before)
+ * @return
+ */
+ double getCurrentProgress();
+
+}
diff --git a/src/main/java/org/parabot/api/io/SizeInputStream.java b/src/main/java/org/parabot/api/io/SizeInputStream.java
new file mode 100644
index 0000000..7d0bace
--- /dev/null
+++ b/src/main/java/org/parabot/api/io/SizeInputStream.java
@@ -0,0 +1,62 @@
+package org.parabot.api.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @author Everel
+ */
+public class SizeInputStream extends InputStream {
+ public int bytesRead;
+ private ProgressListener l;
+ private InputStream in;
+ private long startTime;
+ private double size;
+
+ public SizeInputStream(InputStream in, int size, ProgressListener l) {
+ this.in = in;
+ this.size = size;
+ this.l = l;
+ this.startTime = System.currentTimeMillis();
+ }
+
+ public int available() {
+ return ((int) size - bytesRead);
+ }
+
+ public int read() throws IOException {
+ int b = in.read();
+ if (b != -1) {
+ bytesRead++;
+ }
+ updateListener();
+ return b;
+ }
+
+ public int read(byte[] b) throws IOException {
+ int read = in.read(b);
+ bytesRead += read;
+ updateListener();
+ return read;
+ }
+
+ public int read(byte[] b, int off, int len) throws IOException {
+ int read = in.read(b, off, len);
+ bytesRead += read;
+ updateListener();
+ return read;
+ }
+
+ private void updateListener() {
+ if (l != null) {
+ double percent = (bytesRead / size) * 100.0D;
+ l.onProgressUpdate(percent);
+
+ long curTime = System.currentTimeMillis();
+ double timeSeconds = (curTime - startTime) / 1000.0D;
+ double speed = bytesRead / (1024.0D * 1024.0D) / timeSeconds;
+ l.updateDownloadSpeed(speed);
+ }
+ }
+}
+
diff --git a/src/main/java/org/parabot/api/io/WebUtil.java b/src/main/java/org/parabot/api/io/WebUtil.java
new file mode 100644
index 0000000..6eaa746
--- /dev/null
+++ b/src/main/java/org/parabot/api/io/WebUtil.java
@@ -0,0 +1,330 @@
+package org.parabot.api.io;
+
+import org.json.simple.parser.JSONParser;
+import org.parabot.api.output.Verboser;
+
+import java.io.*;
+import java.net.*;
+
+/**
+ * A WebUtil class fetches data from an URL
+ *
+ * @author Everel
+ */
+public class WebUtil {
+
+ private static JSONParser jsonParser;
+
+ private static String agent = "Mozilla/5.0 (Wind0ws NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.112 Safari/535.1";
+
+ /**
+ * Gets useragent
+ *
+ * @return useragent
+ */
+ public static String getUserAgent() {
+ return agent;
+ }
+
+ /**
+ * Agent to set at a URL connection
+ *
+ * @param userAgent
+ */
+ public static void setUserAgent(final String userAgent) {
+ agent = userAgent;
+ }
+
+ /**
+ * Fetches content of a page
+ *
+ * @param location
+ * @return contents of page
+ * @throws MalformedURLException
+ */
+ public static String getContents(final String location)
+ throws MalformedURLException {
+ return getContents(new URL(location));
+ }
+
+ public static String getContents(final String location, String parameters) throws MalformedURLException {
+ return getContents(new URL(location), parameters);
+ }
+
+ /**
+ * Get contents from URL
+ *
+ * @param url
+ * @return page contents
+ */
+ public static String getContents(final URL url) {
+ return getContents(getConnection(url));
+ }
+
+ public static String getContents(final URL url, final String parameters) {
+ return getContents(getConnection(url), parameters);
+ }
+
+ /**
+ * Gets contents from URLConnection
+ *
+ * @param urlConnection
+ * @return page contents
+ */
+ public static String getContents(URLConnection urlConnection) {
+ try {
+ final BufferedReader in = getReader(urlConnection);
+ final StringBuilder builder = new StringBuilder();
+ String line;
+ if (in != null) {
+ while ((line = in.readLine()) != null) {
+ builder.append(line);
+ }
+ in.close();
+ }
+ return builder.toString();
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ return null;
+ }
+
+ public static String getContents(URLConnection urlConnection, String parameters) {
+ try {
+ urlConnection.setDoOutput(true);
+ OutputStreamWriter wr = new OutputStreamWriter(urlConnection.getOutputStream());
+ wr.write(parameters);
+ wr.flush();
+ wr.close();
+
+ final BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
+ final StringBuilder builder = new StringBuilder();
+ String line;
+ while ((line = in.readLine()) != null) {
+ builder.append(line);
+ }
+ return builder.toString();
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * Gets buffered reader from string url
+ *
+ * @param url
+ * @return bufferedreader
+ */
+ public static BufferedReader getReader(final String url) {
+ try {
+ return getReader(new URL(url));
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * Gets BufferedReader from URL
+ *
+ * @param url
+ * @return BufferedReader from URL
+ */
+ public static BufferedReader getReader(final URL url) {
+ return getReader(getConnection(url));
+ }
+
+ public static BufferedReader getReader(final URLConnection urlConnection) {
+ try {
+ return new BufferedReader(new InputStreamReader(
+ urlConnection.getInputStream()));
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * Gets inputstream from url
+ *
+ * @param url
+ * @return inputstream from url
+ */
+ public static InputStream getInputStream(final URL url) {
+ final URLConnection con = getConnection(url);
+ try {
+ return con.getInputStream();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * Opens a connection
+ *
+ * @param url
+ * @return URLConnection to URL
+ */
+ public static URLConnection getConnection(final URL url) {
+ try {
+ final URLConnection con = url.openConnection();
+ con.setRequestProperty("User-Agent", agent);
+ return con;
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ return null;
+ }
+
+ public static URLConnection getConnection(final URL url, String parameters) {
+ try {
+ final URLConnection con = getConnection(url);
+ con.setDoOutput(true);
+ OutputStreamWriter wr = new OutputStreamWriter(con.getOutputStream());
+ wr.write(parameters);
+ wr.flush();
+ wr.close();
+
+ return con;
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ return null;
+ }
+
+ public static BufferedReader getReader(final URL url, String username, String password) {
+ try {
+ String data = URLEncoder.encode("username", "UTF-8") + "=" + username;
+ data += "&" + URLEncoder.encode("password", "UTF-8") + "=" + password;
+
+ URLConnection connection = url.openConnection();
+
+ connection.setDoOutput(true);
+ OutputStreamWriter wr = new OutputStreamWriter(connection.getOutputStream());
+ wr.write(data);
+ wr.flush();
+ wr.close();
+
+ return new BufferedReader(new InputStreamReader(connection.getInputStream()));
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ return null;
+ }
+
+ public static URLConnection getConnection(final URL url, String username, String password) {
+ try {
+ String data = URLEncoder.encode("username", "UTF-8") + "=" + username;
+ data += "&" + URLEncoder.encode("password", "UTF-8") + "=" + password;
+
+ URLConnection connection = url.openConnection();
+
+ connection.setDoOutput(true);
+ OutputStreamWriter wr = new OutputStreamWriter(connection.getOutputStream());
+ wr.write(data);
+ wr.flush();
+ wr.close();
+
+ return connection;
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * Downloads a file on the internet
+ *
+ * @param url
+ * @param destination
+ */
+ public static void downloadFile(final URL url, final File destination) {
+ downloadFile(url, destination, null, null, null);
+ }
+
+ /**
+ * Downloads a file on the internet
+ *
+ * @param url
+ * @param destination
+ * @param listener
+ */
+ public static void downloadFile(final URL url, final File destination,
+ final ProgressListener listener) {
+ downloadFile(url, destination, listener, null, null);
+ }
+
+ /**
+ * Downloads a file on the internet
+ *
+ * @param url
+ * @param destination
+ * @param listener
+ */
+ public static void downloadFile(final URL url, final File destination,
+ final ProgressListener listener, String username, String password) {
+ try {
+ final URLConnection connection;
+ if (username == null || password == null) {
+ connection = getConnection(url);
+ } else {
+ connection = getConnection(url, username, password);
+ }
+ final int size = connection.getContentLength();
+ SizeInputStream sizeInputStream = new SizeInputStream(
+ connection.getInputStream(), size, listener);
+ BufferedInputStream in = new BufferedInputStream(sizeInputStream);
+ FileOutputStream fileOut = new FileOutputStream(destination);
+ final double before = listener == null ? 0d : listener.getCurrentProgress();
+ final long startTime = System.currentTimeMillis();
+ int totalRead = 0;
+ try {
+ byte data[] = new byte[1024];
+ int count;
+ while ((count = in.read(data, 0, 1024)) != -1) {
+ fileOut.write(data, 0, count);
+ totalRead += count;
+ if (listener != null) {
+ double progress = (((double)totalRead / (double)size) * 100d);
+ String rate = (totalRead / Math.max(1, ((System.currentTimeMillis()-startTime)/1000))) + "/s";
+ listener.updateMessageAndProgress(String.format("[%s] Downloading... %02f%% (%s of %s bytes) %s from %s to %s", WebUtil.class.getName(), progress, totalRead, size, rate, url.getPath(), destination.getAbsolutePath()),
+ progress);
+ }
+ }
+ } finally {
+ in.close();
+ fileOut.close();
+ }
+ if (listener != null) {
+ listener.onProgressUpdate(before);
+ }
+ Verboser.verbose("[WebUtil] Downloaded " + totalRead + " bytes from " + url + " -> " + destination.getAbsolutePath());
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ }
+
+ /**
+ * Converts file format to url format
+ *
+ * @param file
+ * @return url to file
+ */
+ public static URL toURL(File file) {
+ try {
+ return file.toURI().toURL();
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ public static JSONParser getJsonParser() {
+ if (jsonParser == null) {
+ jsonParser = new JSONParser();
+ }
+ return jsonParser;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/parabot/api/io/build/BuildPath.java b/src/main/java/org/parabot/api/io/build/BuildPath.java
new file mode 100644
index 0000000..bafe195
--- /dev/null
+++ b/src/main/java/org/parabot/api/io/build/BuildPath.java
@@ -0,0 +1,19 @@
+package org.parabot.api.io.build;
+
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+public class BuildPath {
+
+ public static void add(final URL url) {
+ try {
+ Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
+ method.setAccessible(true);
+ method.invoke((URLClassLoader) ClassLoader.getSystemClassLoader(), url);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/parabot/api/io/images/Images.java b/src/main/java/org/parabot/api/io/images/Images.java
new file mode 100644
index 0000000..0028ac2
--- /dev/null
+++ b/src/main/java/org/parabot/api/io/images/Images.java
@@ -0,0 +1,30 @@
+package org.parabot.api.io.images;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.util.HashMap;
+
+/**
+ *
+ * Caches and loads images from resource
+ *
+ * @author Everel, JKetelaar
+ *
+ */
+public class Images {
+ private static final HashMap IMAGE_CACHE = new HashMap<>();
+
+ public static BufferedImage getResource(final String resource) {
+ if(IMAGE_CACHE.containsKey(resource)) {
+ return IMAGE_CACHE.get(resource);
+ }
+ try {
+ final BufferedImage img = ImageIO.read(Images.class.getResourceAsStream(resource));
+ IMAGE_CACHE.put(resource, img);
+ return img;
+ } catch (Throwable t) {
+ throw new RuntimeException("Failed to load image from resource. " + t.getMessage());
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/parabot/api/io/libraries/Environment.java b/src/main/java/org/parabot/api/io/libraries/Environment.java
new file mode 100644
index 0000000..092dc0c
--- /dev/null
+++ b/src/main/java/org/parabot/api/io/libraries/Environment.java
@@ -0,0 +1,23 @@
+package org.parabot.api.io.libraries;
+
+import org.parabot.api.io.WebUtil;
+
+/**
+ * @author JKetelaar
+ */
+public class Environment {
+
+ /**
+ * Loads library into environment
+ *
+ * @param library
+ */
+ public static void loadLibrary(Library library) {
+ if (library.requiresJar()) {
+ if (!library.hasJar()) {
+ WebUtil.downloadFile(library.getDownloadLink(), library.getJarFile());
+ }
+ library.init();
+ }
+ }
+}
diff --git a/src/main/java/org/parabot/api/io/libraries/Library.java b/src/main/java/org/parabot/api/io/libraries/Library.java
new file mode 100644
index 0000000..a72c8bf
--- /dev/null
+++ b/src/main/java/org/parabot/api/io/libraries/Library.java
@@ -0,0 +1,67 @@
+package org.parabot.api.io.libraries;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ *
+ * @author Everel
+ *
+ */
+public abstract class Library {
+
+ /**
+ * Determines if this library jar has already been downloaded.
+ * @return false if library jar has not been downloaded, otherwise true
+ */
+ public boolean hasJar() {
+ return getJarFile().exists();
+ }
+
+ /**
+ * Adds the library to the buildpath and validates if it has been added
+ */
+ public abstract void init();
+
+ /**
+ * Determines if library has been added to the buildpath
+ * @return true if library has been added to the buildpath, otherwise false.
+ */
+ public abstract boolean isAdded();
+
+ /**
+ * Gets the local file target/location of the jar file
+ * @return local file (target) to library
+ */
+ public abstract File getJarFile();
+
+ /**
+ * Gets download url to the library
+ * @return url
+ */
+ public abstract URL getDownloadLink();
+
+ /**
+ * Defines if the system requires a jar
+ * @return boolean
+ */
+ public abstract boolean requiresJar();
+
+
+ /**
+ * Fetches URL from {@link Library#getJarFile()}
+ * @return URL to local library jar file
+ */
+ public URL getJarFileURL() {
+ try {
+ return getJarFile().toURI().toURL();
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ public abstract String getLibraryName();
+
+}
diff --git a/src/main/java/org/parabot/api/io/libraries/jpushbullet/JPushBullet.java b/src/main/java/org/parabot/api/io/libraries/jpushbullet/JPushBullet.java
new file mode 100644
index 0000000..e4a3fa8
--- /dev/null
+++ b/src/main/java/org/parabot/api/io/libraries/jpushbullet/JPushBullet.java
@@ -0,0 +1,67 @@
+package org.parabot.api.io.libraries.jpushbullet;
+
+import org.parabot.api.Configuration;
+import org.parabot.api.io.Directories;
+import org.parabot.api.io.build.BuildPath;
+import org.parabot.api.io.libraries.Library;
+
+import java.io.File;
+import java.net.URL;
+
+/**
+ * @author EmmaStone
+ */
+public class JPushBullet extends Library {
+ private static boolean valid;
+
+ @Override
+ public void init() {
+ if (!hasJar()) {
+ System.err.println("Failed to load jpushbullet... [jar missing]");
+ return;
+ }
+ BuildPath.add(getJarFileURL());
+
+ try {
+ Class.forName("com.github.sheigutn.pushbullet.Pushbullet");
+ valid = true;
+ } catch (ClassNotFoundException e) {
+ System.err
+ .println("Failed to add jpushbullet to build path, or incorrupt download");
+ }
+ }
+
+ @Override
+ public boolean isAdded() {
+ return valid;
+ }
+
+ @Override
+ public File getJarFile() {
+ return new File(Directories.getCachePath(), "jpushbullet.jar");
+ }
+
+ @Override
+ public URL getDownloadLink() {
+ try {
+ return new URL(Configuration.LIBRARIES_DOWNLOAD + "/JPushBullet");
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ return null;
+ }
+
+ @Override
+ public boolean requiresJar() {
+ return true;
+ }
+
+ @Override
+ public String getLibraryName() {
+ return "JPushBullet";
+ }
+
+ public static boolean isValid() {
+ return valid;
+ }
+}
diff --git a/src/main/java/org/parabot/api/misc/JavaUtil.java b/src/main/java/org/parabot/api/misc/JavaUtil.java
new file mode 100644
index 0000000..964ee11
--- /dev/null
+++ b/src/main/java/org/parabot/api/misc/JavaUtil.java
@@ -0,0 +1,15 @@
+package org.parabot.api.misc;
+
+/**
+ * @author JKetelaar
+ */
+public class JavaUtil {
+ public static double JAVA_VERSION = getVersion();
+
+ static double getVersion() {
+ String version = System.getProperty("java.version");
+ int pos = version.indexOf('.');
+ pos = version.indexOf('.', pos + 1);
+ return Double.parseDouble(version.substring(0, pos));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/parabot/api/misc/OperatingSystem.java b/src/main/java/org/parabot/api/misc/OperatingSystem.java
new file mode 100644
index 0000000..2c57000
--- /dev/null
+++ b/src/main/java/org/parabot/api/misc/OperatingSystem.java
@@ -0,0 +1,26 @@
+package org.parabot.api.misc;
+
+/**
+ * This class is used for detecting the user's operating system
+ *
+ * @author Everel
+ */
+public enum OperatingSystem {
+
+ WINDOWS, LINUX, MAC, OTHER;
+
+ public static final OperatingSystem getOS() {
+ String str = System.getProperty("os.name").toLowerCase();
+ if (str.contains("win")) {
+ return OperatingSystem.WINDOWS;
+ }
+ if (str.contains("mac")) {
+ return OperatingSystem.MAC;
+ }
+ if (str.contains("nix") || str.contains("nux")) {
+ return OperatingSystem.LINUX;
+ }
+ return OperatingSystem.OTHER;
+ }
+
+}
diff --git a/src/main/java/org/parabot/api/misc/ProjectProperties.java b/src/main/java/org/parabot/api/misc/ProjectProperties.java
new file mode 100644
index 0000000..5edfc9a
--- /dev/null
+++ b/src/main/java/org/parabot/api/misc/ProjectProperties.java
@@ -0,0 +1,44 @@
+package org.parabot.api.misc;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+/**
+ * @author JKetelaar
+ */
+public class ProjectProperties {
+
+ private static ProjectProperties instance;
+ private Properties cached = new Properties();
+
+ private ProjectProperties() {
+ setProperties();
+ }
+
+ public static Version getProjectVersion() {
+ return new Version(getInstance().getCached().getProperty("application.version"));
+ }
+
+ public static ProjectProperties getInstance() {
+ return instance == null ? instance = new ProjectProperties() : instance;
+ }
+
+ private void setProperties() {
+ InputStream input;
+ try {
+ String propertiesFileName = "storage/internal.properties";
+
+ input = getClass().getClassLoader()
+ .getResourceAsStream(propertiesFileName);
+
+ cached.load(input);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private Properties getCached() {
+ return cached;
+ }
+}
diff --git a/src/main/java/org/parabot/api/misc/StringUtil.java b/src/main/java/org/parabot/api/misc/StringUtil.java
new file mode 100644
index 0000000..54bdc33
--- /dev/null
+++ b/src/main/java/org/parabot/api/misc/StringUtil.java
@@ -0,0 +1,117 @@
+package org.parabot.api.misc;
+
+/**
+ * @author mkyong, JKetelaar
+ */
+public class StringUtil {
+
+ private static java.util.Random random = new java.util.Random();
+ private static char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".toCharArray();
+
+ public static String implode(String separator, String... data) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < data.length - 1; i++) {
+ //data.length - 1 => to not add separator at the end
+ if (!data[i].matches(" *")) {//empty string are ""; " "; " "; and so on
+ sb.append(data[i]);
+ sb.append(separator);
+ }
+ }
+ sb.append(data[data.length - 1].trim());
+ return sb.toString();
+ }
+
+ public static String convertHexToString(String hex) {
+
+ StringBuilder sb = new StringBuilder();
+ StringBuilder temp = new StringBuilder();
+
+ for (int i = 0; i < hex.length() - 1; i += 2) {
+
+ // grab the hex in pairs
+ String output = hex.substring(i, (i + 2));
+ // convert hex to decimal
+ int decimal = Integer.parseInt(output, 16);
+ // convert the decimal to character
+ sb.append((char) decimal);
+
+ temp.append(decimal);
+ }
+
+ return sb.toString();
+ }
+
+ public static String randomString(final int length) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < 20; i++) {
+ char c = chars[random.nextInt(chars.length)];
+ sb.append(c);
+ }
+ return sb.toString();
+ }
+
+ public static long longForName(String name) {
+ long l = 0L;
+ for (int i = 0; i < name.length() && i < 12; i++) {
+ char c = name.charAt(i);
+ l *= 37L;
+ if (c >= 'A' && c <= 'Z') {
+ l += (1 + c) - 65;
+ } else if (c >= 'a' && c <= 'z') {
+ l += (1 + c) - 97;
+ } else if (c >= '0' && c <= '9') {
+ l += (27 + c) - 48;
+ }
+ }
+
+ while (l % 37L == 0L && l != 0L) {
+ l /= 37L;
+ }
+
+ return l;
+ }
+
+ public static String nameForLong(long name) {
+ try {
+ if (name <= 0L || name >= 0x5b5b57f8a98a5dd1L) {
+ return "invalid_name";
+ }
+ if (name % 37L == 0L) {
+ return "invalid_name";
+ }
+
+ int i = 0;
+ char ac[] = new char[12];
+ while (name != 0L) {
+ long l1 = name;
+ name /= 37L;
+ ac[11 - i++] = chars[(int) (l1 - name * 37L)];
+ }
+ return new String(ac, 12 - i, i);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ public static String fixName(String name) {
+ if (name.length() > 0) {
+ char ac[] = name.toCharArray();
+ for (int j = 0; j < ac.length; j++)
+ if (ac[j] == '_') {
+ ac[j] = ' ';
+ if (j + 1 < ac.length && ac[j + 1] >= 'a' && ac[j + 1] <= 'z') {
+ ac[j + 1] = (char) ((ac[j + 1] + 65) - 97);
+ }
+ }
+
+ if (ac[0] >= 'a' && ac[0] <= 'z') {
+ ac[0] = (char) ((ac[0] + 65) - 97);
+ }
+ return new String(ac);
+ } else {
+ return name;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/parabot/api/misc/Version.java b/src/main/java/org/parabot/api/misc/Version.java
new file mode 100644
index 0000000..b55bcc4
--- /dev/null
+++ b/src/main/java/org/parabot/api/misc/Version.java
@@ -0,0 +1,55 @@
+package org.parabot.api.misc;
+
+public class Version implements Comparable {
+
+ private String version;
+
+ public Version(String version) {
+ if (version == null) {
+ throw new IllegalArgumentException("Version can not be null");
+ }
+
+ if (!version.matches("[0-9]+(\\.[0-9]+)*") && !version.matches("[0-9]+(\\.[0-9]+)*-RC-([0-9]+)")) {
+ throw new IllegalArgumentException("Invalid version format");
+ }
+ this.version = version;
+ }
+
+ public final String get() {
+ return this.version;
+ }
+
+ public boolean isNightly() {
+ return this.version.contains("RC");
+ }
+
+ @Override
+ public int compareTo(Version that) {
+ if (that == null) {
+ return 1;
+ }
+
+ String[] thisParts = this.get().split("\\.");
+ String[] thatParts = that.get().split("\\.");
+ int length = Math.max(thisParts.length, thatParts.length);
+
+ for (int i = 0; i < length; i++) {
+ int thisPart = i < thisParts.length ?
+ Integer.parseInt(thisParts[i]) : 0;
+ int thatPart = i < thatParts.length ?
+ Integer.parseInt(thatParts[i]) : 0;
+ if (thisPart < thatPart) {
+ return -1;
+ }
+ if (thisPart > thatPart) {
+ return 1;
+ }
+ }
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object that) {
+ return this == that || that != null && this.getClass() == that.getClass() && this.compareTo((Version) that) == 0;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/parabot/api/notifications/NotificationManager.java b/src/main/java/org/parabot/api/notifications/NotificationManager.java
new file mode 100644
index 0000000..08d0b21
--- /dev/null
+++ b/src/main/java/org/parabot/api/notifications/NotificationManager.java
@@ -0,0 +1,113 @@
+package org.parabot.api.notifications;
+
+import org.parabot.api.notifications.types.MacNotificationType;
+import org.parabot.api.notifications.types.NotificationType;
+import org.parabot.api.notifications.types.PushBulletNotificationType;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * @author JKetelaar
+ */
+public class NotificationManager {
+
+ private static NotificationManager context;
+
+ private ArrayList notificationTypes;
+ private ArrayList enabledTypes;
+
+ public NotificationManager() {
+ this.notificationTypes = new ArrayList<>();
+ this.enabledTypes = new ArrayList<>();
+
+ this.fillNotificationTypes();
+ }
+
+ public ArrayList getNotificationTypes() {
+ return notificationTypes;
+ }
+
+ public void addNotificationType(NotificationType type) {
+ this.notificationTypes.add(type);
+ }
+
+ private void fillNotificationTypes() {
+ this.notificationTypes.add(new MacNotificationType());
+ this.notificationTypes.add(new PushBulletNotificationType());
+ }
+
+ public void enableNotificationType(NotificationType type) {
+ this.enabledTypes.add(type);
+ type.enable();
+ }
+
+ public void disableNotificationType(NotificationType type) {
+ Iterator iterator = this.enabledTypes.iterator();
+ while(iterator.hasNext()){
+ NotificationType t = (NotificationType) iterator.next();
+ if (t.getName().equalsIgnoreCase(type.getName())){
+ iterator.remove();
+ break;
+ }
+ }
+ }
+
+ public void sendNotification(String message) {
+ for (NotificationType notificationType : this.enabledTypes) {
+ notificationType.notify(message);
+ }
+ }
+
+ public void sendNotification(String header, String message) {
+ for (NotificationType notificationType : this.enabledTypes) {
+ notificationType.notify(header, message);
+ }
+ }
+
+ public void sendNotification(String title, String header, String message) {
+ for (NotificationType notificationType : this.enabledTypes) {
+ notificationType.notify(title, header, message);
+ }
+ }
+
+ public ArrayList getEnabledTypes() {
+ return enabledTypes;
+ }
+
+ public ArrayList getAvailableNotificationTypes() {
+ ArrayList types = new ArrayList<>();
+ for (NotificationType notificationType : this.notificationTypes) {
+ boolean inAdded = false;
+ for (NotificationType enabledType : this.enabledTypes) {
+ if (enabledType.getName().equalsIgnoreCase(notificationType.getName())) {
+ inAdded = true;
+ }
+ }
+
+ if (!inAdded) {
+ types.add(notificationType);
+ }
+ }
+
+ return types;
+ }
+
+ public NotificationType getNotificationType(String name) {
+ for (NotificationType notificationType : this.notificationTypes) {
+ if (notificationType.getName().equalsIgnoreCase(name)) {
+ return notificationType;
+ }
+ }
+
+ return null;
+ }
+
+ public static NotificationManager getContext() {
+ if (context == null) {
+ context = new NotificationManager();
+ }
+
+ return context;
+ }
+}
diff --git a/src/main/java/org/parabot/api/notifications/types/MacNotificationType.java b/src/main/java/org/parabot/api/notifications/types/MacNotificationType.java
new file mode 100644
index 0000000..8c23696
--- /dev/null
+++ b/src/main/java/org/parabot/api/notifications/types/MacNotificationType.java
@@ -0,0 +1,43 @@
+package org.parabot.api.notifications.types;
+
+import org.parabot.api.misc.OperatingSystem;
+
+import java.io.IOException;
+
+/**
+ * @author JKetelaar
+ */
+public class MacNotificationType extends NotificationType {
+
+ public MacNotificationType() {
+ super("Mac");
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return OperatingSystem.getOS().equals(OperatingSystem.MAC);
+ }
+
+ @Override
+ public void notify(String title, String header, String message) {
+ final StringBuilder src = new StringBuilder();
+
+ src.append("display notification")
+ .append(" \"").append(message).append("\"");
+ if (title != null && title.length() > 0) {
+ src.append(" with title ").append("\"").append(title).append("\"");
+ }
+ if (header != null && header.length() > 0) {
+ src.append(" subtitle ").append("\"").append(header).append("\"");
+ }
+ src.append(" sound name \"Ping\" ");
+
+ final Runtime rt = Runtime.getRuntime();
+ final String[] cmd = new String[]{"/usr/bin/osascript", "-e", src.toString()};
+ try {
+ rt.exec(cmd);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/src/main/java/org/parabot/api/notifications/types/NotificationType.java b/src/main/java/org/parabot/api/notifications/types/NotificationType.java
new file mode 100644
index 0000000..dbe1464
--- /dev/null
+++ b/src/main/java/org/parabot/api/notifications/types/NotificationType.java
@@ -0,0 +1,33 @@
+package org.parabot.api.notifications.types;
+
+import org.parabot.api.Configuration;
+
+/**
+ * @author JKetelaar
+ */
+public abstract class NotificationType {
+
+ private String name;
+
+ public NotificationType(String name) {
+ this.name = name;
+ }
+
+ public abstract boolean isAvailable();
+
+ public abstract void notify(String title, String header, String message);
+
+ public void notify(String header, String message){
+ notify(Configuration.BOT_TITLE, header, message);
+ }
+
+ public void notify(String message){
+ notify("Notification", message);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void enable(){}
+}
diff --git a/src/main/java/org/parabot/api/notifications/types/PushBulletNotificationType.java b/src/main/java/org/parabot/api/notifications/types/PushBulletNotificationType.java
new file mode 100644
index 0000000..24e20db
--- /dev/null
+++ b/src/main/java/org/parabot/api/notifications/types/PushBulletNotificationType.java
@@ -0,0 +1,55 @@
+package org.parabot.api.notifications.types;
+
+import org.parabot.api.io.libraries.Environment;
+import org.parabot.api.io.libraries.jpushbullet.JPushBullet;
+import org.parabot.api.notifications.types.pushbullet.PushBulletController;
+
+import javax.swing.*;
+
+/**
+ * @author JKetelaar
+ */
+public class PushBulletNotificationType extends NotificationType {
+
+ private boolean enabled = false;
+
+ public PushBulletNotificationType() {
+ super("PushBullet");
+ }
+
+ @Override
+ public boolean isAvailable() {
+ double version = 1.0;
+
+ try {
+ version = Double.parseDouble(System.getProperty("java.specification.version"));
+ } catch (NumberFormatException ignored) {
+ }
+
+ return version >= 1.8;
+ }
+
+ @Override
+ public void enable() {
+ final String message = "Please insert your PushBullet API key, so we could send notifications.";
+
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ String s = JOptionPane.showInputDialog(null, message, "PushBullet API key", JOptionPane.QUESTION_MESSAGE);
+
+ if (s != null) {
+ Environment.loadLibrary(new JPushBullet());
+ enabled = PushBulletController.pushNote("Parabot", "PushBullets notifications have been enabled for Parabot", s);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void notify(String title, String header, String message) {
+ if (this.enabled) {
+ PushBulletController.pushNote(title, message);
+ }
+ }
+}
diff --git a/src/main/java/org/parabot/api/notifications/types/pushbullet/PushBulletController.java b/src/main/java/org/parabot/api/notifications/types/pushbullet/PushBulletController.java
new file mode 100644
index 0000000..3471a3e
--- /dev/null
+++ b/src/main/java/org/parabot/api/notifications/types/pushbullet/PushBulletController.java
@@ -0,0 +1,52 @@
+package org.parabot.api.notifications.types.pushbullet;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * @author JKetelaar
+ */
+public class PushBulletController {
+
+ private static Object pushBulletInstance;
+
+ public static void setPushBulletInstance(String key){
+ ClassLoader classLoader = PushBulletController.class.getClassLoader();
+ try {
+ PushBulletController.pushBulletInstance = classLoader.loadClass("com.github.sheigutn.pushbullet.Pushbullet").getConstructors()[0].newInstance(key);
+ } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static Object getPushBulletInstance(String key){
+ PushBulletController.setPushBulletInstance(key);
+
+ return PushBulletController.pushBulletInstance;
+ }
+
+ public static boolean pushNote(String title, String message, String key){
+ PushBulletController.setPushBulletInstance(key);
+
+ return PushBulletController.pushNote(title, message);
+ }
+
+ public static boolean pushNote(String title, String message){
+ Class[] cArg = new Class[2];
+ cArg[0] = String.class;
+ cArg[1] = String.class;
+
+ if (PushBulletController.pushBulletInstance != null){
+ try {
+ Method method = PushBulletController.pushBulletInstance.getClass().getMethod("pushNote", cArg);
+ method.setAccessible(true);
+ method.invoke(PushBulletController.pushBulletInstance, title, message);
+
+ return true;
+ } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+ return false;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/org/parabot/api/output/Logger.java b/src/main/java/org/parabot/api/output/Logger.java
new file mode 100644
index 0000000..7a171b9
--- /dev/null
+++ b/src/main/java/org/parabot/api/output/Logger.java
@@ -0,0 +1,94 @@
+package org.parabot.api.output;
+
+/**
+ * @author Fryslan
+ */
+public class Logger {
+
+ public enum LoggerColor {
+ RESET("\u001B[0m"),
+ BLACK("\u001B[30m"),
+ WHITE("\u001B[37m"),
+ RED("\u001B[31m"),
+ GREEN("\u001B[32m"),
+ YELLOW("\u001B[33m"),
+ BLUE("\u001B[34m"),
+ PURPLE("\u001B[35m"),
+ CYAN("\u001B[36m");
+
+ private final String ansi;
+
+ LoggerColor(String ansi) {
+ this.ansi = ansi;
+ }
+
+ public String getAnsi() {
+ return ansi;
+ }
+ }
+
+
+ /**
+ * Prints Error data in Red.
+ *
+ * @param tag Reference to the location , I.E Core, UserInterface.
+ * @param data Error data.
+ */
+ public static void error(String tag, String data) {
+ System.out.println(LoggerColor.RED.getAnsi() + "[ERROR] : " + tag + " - " + data + LoggerColor.RESET.getAnsi());
+ }
+
+ /**
+ * Prints Error data in Red.
+ *
+ * @param tag Reference to the location , I.E Core, UserInterface.
+ * @param data Error data.
+ * @param throwable Error or Exception.
+ */
+ public static void error(String tag, String data, Throwable throwable) {
+ System.out.println(LoggerColor.RED.getAnsi() + "[ERROR] : " + tag + " - " + data);
+ throwable.printStackTrace();
+ System.out.println(LoggerColor.RESET.getAnsi());
+
+ }
+
+ /**
+ * Prints Information data in Blue.
+ *
+ * @param tag Reference to the location , I.E Core, UserInterface.
+ * @param data Information data.
+ */
+ public static void info(String tag, String data) {
+ System.out.println(LoggerColor.BLUE.getAnsi() + "[INFO] : " + tag + " - " + data + LoggerColor.RESET.getAnsi());
+ }
+
+ /**
+ * Prints Warning data in Yellow.
+ *
+ * @param tag Reference to the location , I.E Core, UserInterface.
+ * @param data Warning data.
+ */
+ public static void warning(String tag, String data) {
+ System.out.println(LoggerColor.YELLOW.getAnsi() + "[WARNING] : " + tag + " - " + data + LoggerColor.RESET.getAnsi());
+ }
+
+ /**
+ * Prints Debug data in Green, if debugging is enabled.
+ *
+ * @param tag Reference to the location , I.E Core, UserInterface.
+ * @param data Debug data.
+ */
+ public static void debug(String tag, String data) {
+ System.out.println(LoggerColor.GREEN.getAnsi() + "[DEBUG] : " + tag + " - " + data + LoggerColor.RESET.getAnsi());
+ }
+
+ /**
+ * @param tag Reference to the location , I.E Core, UserInterface.
+ * @param data Data to Print.
+ * @param color Color to print the the data in.
+ * @param header Reference to the reason this message is printed I.E Warning, Debug.
+ */
+ public static void custom(String tag, String data, LoggerColor color, String header) {
+ System.out.println(color.getAnsi() + "[" + header.toUpperCase() + "] : " + tag + " - " + data + LoggerColor.RESET.getAnsi());
+ }
+}
diff --git a/src/main/java/org/parabot/api/output/Verboser.java b/src/main/java/org/parabot/api/output/Verboser.java
new file mode 100644
index 0000000..538575f
--- /dev/null
+++ b/src/main/java/org/parabot/api/output/Verboser.java
@@ -0,0 +1,10 @@
+package org.parabot.api.output;
+
+/**
+ * @author JKetelaar
+ */
+public class Verboser {
+ public static void verbose(String s) {
+ System.out.println(s);
+ }
+}
diff --git a/src/main/java/org/parabot/api/ui/JavaFxUtil.java b/src/main/java/org/parabot/api/ui/JavaFxUtil.java
new file mode 100644
index 0000000..9097369
--- /dev/null
+++ b/src/main/java/org/parabot/api/ui/JavaFxUtil.java
@@ -0,0 +1,142 @@
+package org.parabot.api.ui;
+
+import javafx.application.Platform;
+import javafx.embed.swing.JFXPanel;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Scene;
+import javafx.scene.layout.Pane;
+import org.parabot.api.io.ProgressListener;
+import org.parabot.api.io.WebUtil;
+import org.parabot.api.output.Verboser;
+
+import javax.swing.*;
+import java.awt.event.WindowAdapter;
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+
+/**
+ * If a JavaFX program runs as an "Application" it can only be launched once due to
+ * how the UI threadding works. To solve this, JavaFX should be used by embedded the FX content inside an FXPanel,
+ * which itself should just fill the content of a empty JFrame.
+ *
+ * @author Shadowrs
+ */
+public abstract class JavaFxUtil {
+
+ private JFrame frame;
+ private JFXPanel jfxp;
+ private JavaFxUtil instance;
+ private final URL end;
+ private final File target;
+ private final Class> controller;
+ private final ProgressListener listener;
+
+ /**
+ * Constructor to use when stylesheet.fxml is in the Parabot Jar
+ *
+ * @param fxmlSheet Location to .fxml such as "/storage/ui/notifications.fxml"
+ */
+ public JavaFxUtil(final URL fxmlSheet, final Class> controller) {
+ this.end = fxmlSheet;
+ this.target = null;
+ this.listener = null;
+ this.controller = controller;
+ launchJFX();
+ }
+
+ /**
+ * Constructor to use when stylesheet.fxml requires downloading from a remote target.
+ *
+ * @param endpoint
+ * @param target
+ * @param listener
+ * @param controller
+ */
+ public JavaFxUtil(URL endpoint, final File target, final ProgressListener listener, final Class> controller) {
+ this.end = endpoint;
+ this.target = target;
+ this.listener = listener;
+ this.controller = controller;
+
+ if (!target.exists() || !target.canRead()) {
+ WebUtil.downloadFile(end, target, listener);
+ }
+ Verboser.verbose("ui from " + end);
+
+ launchJFX();
+ }
+
+ /**
+ * Kick off the GUI by creating a JFrame on the Swing Thread, then load the JavaFX on the Platform Thread
+ */
+ private void launchJFX() {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ createJFrame();
+ Platform.setImplicitExit(false);
+ Platform.runLater(new Runnable() {
+ @Override
+ public void run() {
+ addJavaFX();
+ }
+ });
+ }
+ });
+ }
+
+ /**
+ * Create the JFrame and attach the window listener
+ */
+ private void createJFrame() {
+ Verboser.verbose("Creating JFrame for JavaFXPanel");
+ if (getFrame() != null) {
+ System.err.println("frame exists");
+ return;
+ }
+ frame = new JFrame();
+ jfxp = new JFXPanel();
+ getFrame().add(jfxp);
+ getFrame().setSize(230, 300);
+ getFrame().setLocationRelativeTo(null);
+ getFrame().setVisible(true);
+
+ getFrame().setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
+ getFrame().addWindowListener(getWindowAdapter());
+ Verboser.verbose("frame created: " + getFrame());
+ }
+
+ protected abstract WindowAdapter getWindowAdapter();
+
+ /**
+ * Add the JavaFX based on the controller and stylesheet to the Scene of the JavaFxPanel
+ */
+ private void addJavaFX() {
+ Verboser.verbose("panel init");
+ instance = this;
+ try {
+ FXMLLoader loader = new FXMLLoader(end); // ui.fxml
+ if (loader.getController() == null) {
+ loader.setController(controller.newInstance());
+ }
+ Pane page = (Pane) loader.load();
+
+ Scene scene = new Scene(page);
+ jfxp.setScene(scene);
+ getFrame().pack();
+
+ onLaunched();
+ Verboser.verbose("UI showing");
+ } catch (IOException | InstantiationException | IllegalAccessException e) {
+ System.err.println("Error loading ui.fxml!");
+ e.printStackTrace();
+ }
+ }
+
+ protected abstract void onLaunched();
+
+ public JFrame getFrame() {
+ return frame;
+ }
+}
diff --git a/src/main/java/org/parabot/api/ui/SwingUtil.java b/src/main/java/org/parabot/api/ui/SwingUtil.java
new file mode 100644
index 0000000..f9e5659
--- /dev/null
+++ b/src/main/java/org/parabot/api/ui/SwingUtil.java
@@ -0,0 +1,66 @@
+package org.parabot.api.ui;
+
+import org.parabot.api.io.images.Images;
+import org.parabot.api.misc.JavaUtil;
+import org.parabot.api.misc.OperatingSystem;
+
+import javax.swing.*;
+import java.awt.*;
+import java.lang.reflect.Method;
+
+/**
+ * Holds various swing util based methods
+ *
+ * @author Dane, JKetelaar
+ */
+public class SwingUtil {
+
+ /**
+ * Packs, centers, and shows the frame.
+ *
+ * @param f
+ */
+ public static void finalize(JFrame f) {
+ f.pack();
+ f.setLocationRelativeTo(null);
+ f.setVisible(true);
+ }
+
+ /**
+ * Adds the dock icon to mac users
+ *
+ * @param f
+ */
+ public static void setParabotIcons(JFrame f) {
+ setParabotIcon(f);
+ }
+
+ /**
+ * Adds the dock icon to mac users
+ *
+ * @param f
+ */
+ public static boolean setParabotIcon(JFrame f) {
+ f.setIconImage(Images.getResource("/storage/images/icon.png"));
+
+ if (OperatingSystem.getOS() == OperatingSystem.MAC) {
+ Image image = Images.getResource("/storage/images/icon.png");
+ try {
+ if (JavaUtil.JAVA_VERSION >= 9) {
+ Class> taskbar = Class.forName("java.awt.Taskbar");
+ Object application = taskbar.getMethod("getTaskbar").invoke(null);
+ taskbar.getMethod("setIconImage", Image.class).invoke(application, image);
+ } else {
+ Class> util = Class.forName("com.apple.eawt.Application");
+ Object application = util.getMethod("getApplication").invoke(null);
+ Method setDockIconImage = util.getMethod("setDockIconImage", Image.class);
+ setDockIconImage.invoke(application, Images.getResource("/storage/images/icon.png"));
+ }
+ } catch (Throwable t) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/storage/images/icon.png b/src/main/resources/storage/images/icon.png
new file mode 100644
index 0000000..33ef49f
Binary files /dev/null and b/src/main/resources/storage/images/icon.png differ
diff --git a/src/main/resources/storage/internal.properties b/src/main/resources/storage/internal.properties
new file mode 100644
index 0000000..263d0ff
--- /dev/null
+++ b/src/main/resources/storage/internal.properties
@@ -0,0 +1 @@
+application.version=${project.version}${build.version}
\ No newline at end of file
diff --git a/src/test/java/org/parabot/MacOSTest.java b/src/test/java/org/parabot/MacOSTest.java
new file mode 100644
index 0000000..5be5eb8
--- /dev/null
+++ b/src/test/java/org/parabot/MacOSTest.java
@@ -0,0 +1,20 @@
+package org.parabot;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.parabot.api.misc.OperatingSystem;
+import org.parabot.api.ui.SwingUtil;
+
+import javax.swing.*;
+
+public class MacOSTest {
+
+ @Test
+ public void testMacOS() {
+ if (OperatingSystem.getOS().equals(OperatingSystem.MAC)) {
+ JFrame frame = new JFrame();
+ frame.setSize(500, 500);
+ Assert.assertTrue(SwingUtil.setParabotIcon(frame));
+ }
+ }
+}
diff --git a/src/test/java/org/parabot/NotificationTest.java b/src/test/java/org/parabot/NotificationTest.java
new file mode 100644
index 0000000..456fb60
--- /dev/null
+++ b/src/test/java/org/parabot/NotificationTest.java
@@ -0,0 +1,16 @@
+package org.parabot;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.parabot.api.notifications.NotificationManager;
+
+/**
+ * @author JKetelaar
+ */
+public class NotificationTest {
+ @Test
+ public void testAmount(){
+ NotificationManager manager = new NotificationManager();
+ Assert.assertTrue(manager.getNotificationTypes().size() > 0);
+ }
+}