commit af7c39c37419be459cd2f6e7aa797d3dcc616b4f Author: Josh Larson Date: Tue Apr 11 15:01:35 2017 -0500 Initial Commit diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..fceb480 --- /dev/null +++ b/.classpath @@ -0,0 +1,6 @@ + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..543ab98 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.settings +bin diff --git a/.project b/.project new file mode 100644 index 0000000..69d73dc --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + PSWGCommon + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/PSWGCommon.jar b/PSWGCommon.jar new file mode 100644 index 0000000..2b18b1b Binary files /dev/null and b/PSWGCommon.jar differ diff --git a/src/com/projectswg/common/callback/CallbackManager.java b/src/com/projectswg/common/callback/CallbackManager.java new file mode 100644 index 0000000..0996133 --- /dev/null +++ b/src/com/projectswg/common/callback/CallbackManager.java @@ -0,0 +1,120 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.callback; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import com.projectswg.common.concurrency.PswgThreadPool; +import com.projectswg.common.debug.Log; + + +public class CallbackManager { + + private final PswgThreadPool executor; + private final List callbacks; + private final AtomicInteger runningTasks; + + public CallbackManager(String name) { + this(name, 1); + } + + public CallbackManager(String name, int threadCount) { + this.executor = new PswgThreadPool(threadCount, name); + this.callbacks = new ArrayList<>(); + this.runningTasks = new AtomicInteger(0); + } + + public void addCallback(T callback) { + synchronized (callbacks) { + callbacks.add(callback); + } + } + + public void removeCallback(T callback) { + synchronized (callbacks) { + callbacks.remove(callback); + } + } + + public void setCallback(T callback) { + synchronized (callbacks) { + callbacks.clear(); + callbacks.add(callback); + } + } + + public void clearCallbacks() { + synchronized (callbacks) { + callbacks.clear(); + } + } + + public void start() { + executor.start(); + } + + public void stop() { + executor.stop(); + } + + public boolean awaitTermination(long timeout, TimeUnit unit) { + return executor.awaitTermination(unit.toMillis(timeout)); + } + + public boolean isRunning() { + return executor.isRunning(); + } + + public boolean isQueueEmpty() { + return runningTasks.get() == 0; + } + + public boolean callOnEach(CallCallback call) { + runningTasks.incrementAndGet(); + return executor.execute(() -> { + synchronized (callbacks) { + for (T callback : callbacks) { + try { + call.run(callback); + } catch (Throwable t) { + Log.e(t); + } + } + } + runningTasks.decrementAndGet(); + }); + } + + + public interface CallCallback { + void run(T callback); + } +} diff --git a/src/com/projectswg/common/concurrency/Delay.java b/src/com/projectswg/common/concurrency/Delay.java new file mode 100644 index 0000000..23ff005 --- /dev/null +++ b/src/com/projectswg/common/concurrency/Delay.java @@ -0,0 +1,64 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.concurrency; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; + +public class Delay { + + public static boolean sleepNano(long nanos) { + LockSupport.parkNanos(nanos); + return isInterrupted(); + } + + public static boolean sleepMicro(long micro) { + return sleepNano(micro * 1000); + } + + public static boolean sleepMilli(long milli) { + return sleepNano(milli * 1000000); + } + + public static boolean sleepSeconds(long sec) { + return sleepNano(sec * 1000000000); + } + + public static boolean sleep(long time, TimeUnit unit) { + return sleepNano(unit.toNanos(time)); + } + + public static boolean isInterrupted() { + return Thread.currentThread().isInterrupted(); + } + + public static void clearInterrupted() { + Thread.interrupted(); + } + +} diff --git a/src/com/projectswg/common/concurrency/PswgBasicScheduledThread.java b/src/com/projectswg/common/concurrency/PswgBasicScheduledThread.java new file mode 100644 index 0000000..18f9f23 --- /dev/null +++ b/src/com/projectswg/common/concurrency/PswgBasicScheduledThread.java @@ -0,0 +1,68 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.concurrency; + +public class PswgBasicScheduledThread extends PswgScheduledThreadPool { + + private final Runnable runnable; + + public PswgBasicScheduledThread(String name, Runnable runnable) { + super(1, name); + this.runnable = runnable; + } + + @Override + public void start() { + throw new UnsupportedOperationException("Cannot use this function. Must use startX(initialDelay, periodicDelay)"); + } + + public void startWithFixedRate(long initialDelay, long periodicDelay) { + super.start(); + super.executeWithFixedRate(initialDelay, periodicDelay, runnable); + } + + public void startWithFixedDelay(long initialDelay, long periodicDelay) { + super.start(); + super.executeWithFixedDelay(initialDelay, periodicDelay, runnable); + } + + public void execute(long delay, Runnable runnable) { + throw new UnsupportedOperationException("Runnable is defined in the constructor!"); + } + + @Override + public void executeWithFixedRate(long initialDelay, long periodicDelay, Runnable runnable) { + throw new UnsupportedOperationException("Runnable is defined in the constructor!"); + } + + @Override + public void executeWithFixedDelay(long initialDelay, long periodicDelay, Runnable runnable) { + throw new UnsupportedOperationException("Runnable is defined in the constructor!"); + } + +} diff --git a/src/com/projectswg/common/concurrency/PswgBasicThread.java b/src/com/projectswg/common/concurrency/PswgBasicThread.java new file mode 100644 index 0000000..886f7fd --- /dev/null +++ b/src/com/projectswg/common/concurrency/PswgBasicThread.java @@ -0,0 +1,65 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.concurrency; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class PswgBasicThread extends PswgThreadPool { + + private final AtomicBoolean executing; + private final Runnable runnable; + + public PswgBasicThread(String name, Runnable runnable) { + super(1, name); + this.executing = new AtomicBoolean(false); + this.runnable = runnable; + } + + @Override + public void start() { + super.start(); + super.execute(() -> { + executing.set(true); + try { + runnable.run(); + } finally { + executing.set(false); + } + }); + } + + public boolean isExecuting() { + return executing.get(); + } + + @Override + public boolean execute(Runnable runnable) { + throw new UnsupportedOperationException("Runnable is defined in the constructor!"); + } + +} diff --git a/src/com/projectswg/common/concurrency/PswgScheduledThreadPool.java b/src/com/projectswg/common/concurrency/PswgScheduledThreadPool.java new file mode 100644 index 0000000..c3577ab --- /dev/null +++ b/src/com/projectswg/common/concurrency/PswgScheduledThreadPool.java @@ -0,0 +1,127 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.concurrency; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.projectswg.common.debug.Assert; +import com.projectswg.common.debug.Log; + +public class PswgScheduledThreadPool { + + private final AtomicBoolean running; + private final int nThreads; + private final ThreadFactory threadFactory; + private ScheduledExecutorService executor; + + public PswgScheduledThreadPool(int nThreads, String nameFormat) { + this.running = new AtomicBoolean(false); + this.nThreads = nThreads; + this.threadFactory = new CustomThreadFactory(nameFormat); + this.executor = null; + } + + public void start() { + Assert.test(!running.getAndSet(true)); + executor = Executors.newScheduledThreadPool(nThreads, threadFactory); + } + + public void stop() { + Assert.test(running.getAndSet(false)); + executor.shutdownNow(); + } + + public void executeWithFixedRate(long initialDelay, long time, Runnable runnable) { + Assert.test(running.get()); + executor.scheduleAtFixedRate(() -> { + try { + runnable.run(); + } catch (Throwable t) { + Log.e(t); + } + }, initialDelay, time, TimeUnit.MILLISECONDS); + } + + public void executeWithFixedDelay(long initialDelay, long time, Runnable runnable) { + Assert.test(running.get()); + executor.scheduleWithFixedDelay(() -> { + try { + runnable.run(); + } catch (Throwable t) { + Log.e(t); + } + }, initialDelay, time, TimeUnit.MILLISECONDS); + } + + public void execute(long delay, Runnable runnable) { + Assert.test(running.get()); + executor.schedule(() -> { + try { + runnable.run(); + } catch (Throwable t) { + Log.e(t); + } + }, delay, TimeUnit.MILLISECONDS); + } + + public boolean awaitTermination(long time) { + Assert.notNull(executor); + try { + return executor.awaitTermination(time, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + return false; + } + } + + private static class CustomThreadFactory implements ThreadFactory { + + private final String pattern; + private int counter; + + public CustomThreadFactory(String pattern) { + this.pattern = pattern; + this.counter = 0; + } + + @Override + public Thread newThread(Runnable r) { + String name; + if (pattern.contains("%d")) + name = String.format(pattern, counter++); + else + name = pattern; + return new Thread(r, name); + } + + } + +} diff --git a/src/com/projectswg/common/concurrency/PswgTaskThreadPool.java b/src/com/projectswg/common/concurrency/PswgTaskThreadPool.java new file mode 100644 index 0000000..f7b2649 --- /dev/null +++ b/src/com/projectswg/common/concurrency/PswgTaskThreadPool.java @@ -0,0 +1,67 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.concurrency; + +import java.util.ArrayDeque; +import java.util.Queue; + +public class PswgTaskThreadPool extends PswgThreadPool { + + private final Queue tasks; + private final Runnable runner; + + public PswgTaskThreadPool(int nThreads, String namePattern, TaskExecutor executor) { + super(nThreads, namePattern); + this.tasks = new ArrayDeque<>(); + this.runner = () -> { + T t = null; + synchronized (tasks) { + t = tasks.poll(); + } + if (t != null) + executor.run(t); + }; + } + + @Override + public boolean execute(Runnable runnable) { + throw new UnsupportedOperationException("Runnable are posted automatically by addTask!"); + } + + public void addTask(T t) { + synchronized (tasks) { + tasks.add(t); + } + super.execute(runner); + } + + public interface TaskExecutor { + void run(T t); + } + +} diff --git a/src/com/projectswg/common/concurrency/PswgThreadPool.java b/src/com/projectswg/common/concurrency/PswgThreadPool.java new file mode 100644 index 0000000..7094b2d --- /dev/null +++ b/src/com/projectswg/common/concurrency/PswgThreadPool.java @@ -0,0 +1,120 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.concurrency; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.projectswg.common.debug.Assert; +import com.projectswg.common.debug.Log; + +public class PswgThreadPool { + + private final AtomicBoolean running; + private final int nThreads; + private final ThreadFactory threadFactory; + private ExecutorService executor; + private volatile boolean interruptOnStop; + + public PswgThreadPool(int nThreads, String nameFormat) { + this.running = new AtomicBoolean(false); + this.nThreads = nThreads; + this.threadFactory = new CustomThreadFactory(nameFormat); + this.executor = null; + this.interruptOnStop = false; + } + + public void setInterruptOnStop(boolean interrupt) { + this.interruptOnStop = interrupt; + } + + public void start() { + Assert.test(!running.getAndSet(true)); + executor = Executors.newFixedThreadPool(nThreads, threadFactory); + } + + public void stop() { + Assert.test(running.getAndSet(false)); + if (interruptOnStop) + executor.shutdownNow(); + else + executor.shutdown(); + } + + public boolean execute(Runnable runnable) { + if (!running.get()) + return false; + executor.execute(() -> { + try { + runnable.run(); + } catch (Throwable t) { + Log.e(t); + } + }); + return true; + } + + public boolean awaitTermination(long time) { + Assert.notNull(executor); + try { + return executor.awaitTermination(time, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + return false; + } + } + + public boolean isRunning() { + return running.get(); + } + + private static class CustomThreadFactory implements ThreadFactory { + + private final String pattern; + private int counter; + + public CustomThreadFactory(String pattern) { + this.pattern = pattern; + this.counter = 0; + } + + @Override + public Thread newThread(Runnable r) { + String name; + if (pattern.contains("%d")) + name = String.format(pattern, counter++); + else + name = pattern; + return new Thread(r, name); + } + + } + +} diff --git a/src/com/projectswg/common/concurrency/SmartLock.java b/src/com/projectswg/common/concurrency/SmartLock.java new file mode 100644 index 0000000..0eea30c --- /dev/null +++ b/src/com/projectswg/common/concurrency/SmartLock.java @@ -0,0 +1,129 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.concurrency; + +import java.util.Date; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +public class SmartLock { + + private final Lock lock; + private final Condition condition; + + public SmartLock() { + this.lock = new ReentrantLock(true); + this.condition = lock.newCondition(); + } + + public void lock() { + lock.lock(); + } + + public void lockInterruptibly() throws InterruptedException { + lock.lockInterruptibly(); + } + + public boolean tryLock() { + return lock.tryLock(); + } + + public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { + return lock.tryLock(time, unit); + } + + public void unlock() { + lock.unlock(); + } + + public void await() throws InterruptedException { + lock(); + try { + condition.await(); + } finally { + unlock(); + } + } + + public void awaitUninterruptibly() { + lock(); + try { + condition.awaitUninterruptibly(); + } finally { + unlock(); + } + } + + public long awaitNanos(long nanosTimeout) throws InterruptedException { + lock(); + try { + return condition.awaitNanos(nanosTimeout); + } finally { + unlock(); + } + } + + public boolean await(long time, TimeUnit unit) throws InterruptedException { + lock(); + try { + return condition.await(time, unit); + } finally { + unlock(); + } + } + + public boolean awaitUntil(Date deadline) throws InterruptedException { + lock(); + try { + return condition.awaitUntil(deadline); + } finally { + unlock(); + } + } + + public void signal() { + lock(); + try { + condition.signal(); + } finally { + unlock(); + } + } + + public void signalAll() { + lock(); + try { + condition.signalAll(); + } finally { + unlock(); + } + } + +} diff --git a/src/com/projectswg/common/concurrency/SynchronizedList.java b/src/com/projectswg/common/concurrency/SynchronizedList.java new file mode 100644 index 0000000..6382c79 --- /dev/null +++ b/src/com/projectswg/common/concurrency/SynchronizedList.java @@ -0,0 +1,182 @@ +/************************************************************************************ + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * -------------------------------------------------------------------------------- * + * * + * Holocore is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General public synchronized License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * Holocore 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 Affero General public synchronized License for more details. * + * * + * You should have received a copy of the GNU Affero General public synchronized License * + * along with Holocore. If not, see . * + * * + ***********************************************************************************/ +package com.projectswg.common.concurrency; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; +import java.util.stream.Stream; + +public class SynchronizedList implements List { + + private final List list; + + public SynchronizedList() { + this.list = new ArrayList<>(); + } + + public SynchronizedList(List list) { + this.list = list; + } + + public synchronized void forEach(Consumer action) { + list.forEach(action); + } + + public synchronized int size() { + return list.size(); + } + + public synchronized boolean isEmpty() { + return list.isEmpty(); + } + + public synchronized boolean contains(Object o) { + return list.contains(o); + } + + public synchronized Iterator iterator() { + return list.iterator(); + } + + public synchronized Object[] toArray() { + return list.toArray(); + } + + public synchronized T[] toArray(T[] a) { + return list.toArray(a); + } + + public synchronized boolean add(E e) { + return list.add(e); + } + + public synchronized boolean remove(Object o) { + return list.remove(o); + } + + public synchronized boolean containsAll(Collection c) { + return list.containsAll(c); + } + + public synchronized boolean addAll(Collection c) { + return list.addAll(c); + } + + public synchronized boolean addAll(int index, Collection c) { + return list.addAll(index, c); + } + + public synchronized boolean removeAll(Collection c) { + return list.removeAll(c); + } + + public synchronized boolean retainAll(Collection c) { + return list.retainAll(c); + } + + public synchronized void replaceAll(UnaryOperator operator) { + list.replaceAll(operator); + } + + public synchronized boolean removeIf(Predicate filter) { + return list.removeIf(filter); + } + + public synchronized void sort(Comparator c) { + list.sort(c); + } + + public synchronized void clear() { + list.clear(); + } + + public synchronized boolean equals(Object o) { + return list.equals(o); + } + + public synchronized int hashCode() { + return list.hashCode(); + } + + public synchronized E get(int index) { + return list.get(index); + } + + public synchronized E set(int index, E element) { + return list.set(index, element); + } + + public synchronized void add(int index, E element) { + list.add(index, element); + } + + public synchronized Stream stream() { + return list.stream(); + } + + public synchronized E remove(int index) { + return list.remove(index); + } + + public synchronized Stream parallelStream() { + return list.parallelStream(); + } + + public synchronized int indexOf(Object o) { + return list.indexOf(o); + } + + public synchronized int lastIndexOf(Object o) { + return list.lastIndexOf(o); + } + + public synchronized ListIterator listIterator() { + return list.listIterator(); + } + + public synchronized ListIterator listIterator(int index) { + return list.listIterator(index); + } + + public synchronized List subList(int fromIndex, int toIndex) { + return list.subList(fromIndex, toIndex); + } + + public synchronized Spliterator spliterator() { + return list.spliterator(); + } + +} diff --git a/src/com/projectswg/common/concurrency/SynchronizedMap.java b/src/com/projectswg/common/concurrency/SynchronizedMap.java new file mode 100644 index 0000000..7794429 --- /dev/null +++ b/src/com/projectswg/common/concurrency/SynchronizedMap.java @@ -0,0 +1,111 @@ +/************************************************************************************ + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * -------------------------------------------------------------------------------- * + * * + * Holocore is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General public synchronized License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * Holocore 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 Affero General public synchronized License for more details. * + * * + * You should have received a copy of the GNU Affero General public synchronized License * + * along with Holocore. If not, see . * + * * + ***********************************************************************************/ +package com.projectswg.common.concurrency; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class SynchronizedMap implements Map { + + private final Map map; + + public SynchronizedMap() { + this.map = new HashMap<>(); + } + + public SynchronizedMap(Map map) { + this.map = map; + } + + public synchronized int hashCode() { + return map.hashCode(); + } + + public synchronized boolean equals(Object o) { + return map.equals(o); + } + + public synchronized String toString() { + return map.toString(); + } + + public synchronized int size() { + return map.size(); + } + + public synchronized boolean isEmpty() { + return map.isEmpty(); + } + + public synchronized V get(Object key) { + return map.get(key); + } + + public synchronized boolean containsKey(Object key) { + return map.containsKey(key); + } + + public synchronized V put(K key, V value) { + return map.put(key, value); + } + + public synchronized void putAll(Map m) { + map.putAll(m); + } + + public synchronized V remove(Object key) { + return map.remove(key); + } + + public synchronized void clear() { + map.clear(); + } + + public synchronized boolean containsValue(Object value) { + return map.containsValue(value); + } + + public synchronized Set keySet() { + return map.keySet(); + } + + public synchronized Collection values() { + return map.values(); + } + + public synchronized Set> entrySet() { + return map.entrySet(); + } + + public synchronized V replace(K key, V value) { + return map.replace(key, value); + } + +} diff --git a/src/com/projectswg/common/concurrency/SynchronizedQueue.java b/src/com/projectswg/common/concurrency/SynchronizedQueue.java new file mode 100644 index 0000000..30198b7 --- /dev/null +++ b/src/com/projectswg/common/concurrency/SynchronizedQueue.java @@ -0,0 +1,151 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.concurrency; + +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.Iterator; +import java.util.Queue; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Stream; + +public class SynchronizedQueue implements Queue { + + private final Queue queue; + + public SynchronizedQueue() { + this(new ArrayDeque<>()); + } + + public SynchronizedQueue(Queue queue) { + this.queue = queue; + } + + public synchronized void forEach(Consumer action) { + queue.forEach(action); + } + + public synchronized boolean add(T e) { + return queue.add(e); + } + + public synchronized boolean offer(T e) { + return queue.offer(e); + } + + public synchronized int size() { + return queue.size(); + } + + public synchronized boolean isEmpty() { + return queue.isEmpty(); + } + + public synchronized boolean contains(Object o) { + return queue.contains(o); + } + + public synchronized T remove() { + return queue.remove(); + } + + public synchronized T poll() { + return queue.poll(); + } + + public synchronized T element() { + return queue.element(); + } + + public synchronized Iterator iterator() { + return queue.iterator(); + } + + public synchronized T peek() { + return queue.peek(); + } + + public synchronized Object[] toArray() { + return queue.toArray(); + } + + public synchronized E[] toArray(E[] a) { + return queue.toArray(a); + } + + public synchronized boolean remove(Object o) { + return queue.remove(o); + } + + public synchronized boolean containsAll(Collection c) { + return queue.containsAll(c); + } + + public synchronized boolean addAll(Collection c) { + return queue.addAll(c); + } + + public synchronized boolean removeAll(Collection c) { + return queue.removeAll(c); + } + + public synchronized boolean removeIf(Predicate filter) { + return queue.removeIf(filter); + } + + public synchronized boolean retainAll(Collection c) { + return queue.retainAll(c); + } + + public synchronized void clear() { + queue.clear(); + } + + public synchronized boolean equals(Object o) { + return queue.equals(o); + } + + public synchronized int hashCode() { + return queue.hashCode(); + } + + public synchronized Spliterator spliterator() { + return queue.spliterator(); + } + + public synchronized Stream stream() { + return queue.stream(); + } + + public synchronized Stream parallelStream() { + return queue.parallelStream(); + } + +} diff --git a/src/com/projectswg/common/concurrency/SynchronizedSet.java b/src/com/projectswg/common/concurrency/SynchronizedSet.java new file mode 100644 index 0000000..2ddffec --- /dev/null +++ b/src/com/projectswg/common/concurrency/SynchronizedSet.java @@ -0,0 +1,131 @@ +/************************************************************************************ + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * -------------------------------------------------------------------------------- * + * * + * Holocore is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General public synchronized License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * Holocore 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 Affero General public synchronized License for more details. * + * * + * You should have received a copy of the GNU Affero General public synchronized License * + * along with Holocore. If not, see . * + * * + ***********************************************************************************/ +package com.projectswg.common.concurrency; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Stream; + +public class SynchronizedSet implements Set { + + private final Set set; + + public SynchronizedSet() { + this.set = new HashSet<>(); + } + + public SynchronizedSet(Set set) { + this.set = set; + } + + public synchronized void forEach(Consumer action) { + set.forEach(action); + } + + public synchronized int size() { + return set.size(); + } + + public synchronized boolean isEmpty() { + return set.isEmpty(); + } + + public synchronized boolean contains(Object o) { + return set.contains(o); + } + + public synchronized Iterator iterator() { + return set.iterator(); + } + + public synchronized Object[] toArray() { + return set.toArray(); + } + + public synchronized T[] toArray(T[] a) { + return set.toArray(a); + } + + public synchronized boolean add(E e) { + return set.add(e); + } + + public synchronized boolean remove(Object o) { + return set.remove(o); + } + + public synchronized boolean containsAll(Collection c) { + return set.containsAll(c); + } + + public synchronized boolean addAll(Collection c) { + return set.addAll(c); + } + + public synchronized boolean retainAll(Collection c) { + return set.retainAll(c); + } + + public synchronized boolean removeAll(Collection c) { + return set.removeAll(c); + } + + public synchronized void clear() { + set.clear(); + } + + public synchronized boolean equals(Object o) { + return set.equals(o); + } + + public synchronized int hashCode() { + return set.hashCode(); + } + + public synchronized Spliterator spliterator() { + return set.spliterator(); + } + + public synchronized boolean removeIf(Predicate filter) { + return set.removeIf(filter); + } + + public synchronized Stream stream() { + return set.stream(); + } + + public synchronized Stream parallelStream() { + return set.parallelStream(); + } + +} diff --git a/src/com/projectswg/common/control/Intent.java b/src/com/projectswg/common/control/Intent.java new file mode 100644 index 0000000..0c0864b --- /dev/null +++ b/src/com/projectswg/common/control/Intent.java @@ -0,0 +1,168 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.control; + +public abstract class Intent { + + private boolean broadcasted; + private boolean complete; + private Intent parallel; + private Intent sequential; + + protected Intent() { + this.broadcasted = false; + this.complete = false; + this.parallel = null; + this.sequential = null; + } + + /** + * Called when the intent has been completed + */ + protected synchronized void markAsComplete(IntentManager intentManager) { + this.complete = true; + if (sequential != null) + sequential.broadcast(intentManager); + sequential = null; + parallel = null; + } + + /** + * Determines whether or not the intent has been broadcasted and processed + * by the system + * @return TRUE if the intent has been broadcasted and processed, FALSE + * otherwise + */ + public synchronized boolean isComplete() { + return complete; + } + + /** + * Determines whether or not the intent has been broadcasted to the system + * @return TRUE if the intent has been broadcasted, FALSE otherwise + */ + public synchronized boolean isBroadcasted() { + return broadcasted; + } + + /** + * Waits for the intent as the parameter to finish before this intent + * starts + * @param i the intent to execute after + */ + public synchronized void broadcastAfterIntent(Intent i) { + broadcastAfterIntent(i, IntentManager.getInstance()); + } + + /** + * Waits for the intent as the parameter to finish before this intent + * starts + * @param i the intent to execute after + * @param intentManager the intent manager to broadcast this intent on + */ + public synchronized void broadcastAfterIntent(Intent i, IntentManager intentManager) { + if (i == null) { + broadcast(intentManager); + return; + } + synchronized (i) { + if (i.isComplete()) + broadcast(intentManager); + else + i.setAsSequential(this); + } + } + + /** + * Waits for the intent as the parameter to start before this intent starts + * @param i the intent to execute with + */ + public synchronized void broadcastWithIntent(Intent i) { + broadcastWithIntent(i, IntentManager.getInstance()); + } + + /** + * Waits for the intent as the parameter to start before this intent starts + * @param i the intent to execute with + * @param intentManager the intent manager to broadcast this intent on + */ + public synchronized void broadcastWithIntent(Intent i, IntentManager intentManager) { + if (i == null) { + broadcast(intentManager); + return; + } + synchronized (i) { + if (!isComplete()) { + setAsParallel(i); + } + broadcast(intentManager); + } + } + + /** + * Broadcasts this node to the system + */ + public synchronized void broadcast() { + broadcast(IntentManager.getInstance()); + } + + /** + * Broadcasts this node to the system with the specified intent manager + * + * @param intentManager the intent manager to broadcast this intent on + */ + public synchronized void broadcast(IntentManager intentManager) { + if (broadcasted) + throw new IllegalStateException("Intent has already been broadcasted!"); + broadcasted = true; + intentManager.broadcastIntent(this); + if (parallel != null) + parallel.broadcast(intentManager); + parallel = null; + } + + @Override + public synchronized String toString() { + return getClass().getSimpleName(); + } + + private synchronized void setAsParallel(Intent i) { + if (parallel == null) + parallel = i; + else + parallel.setAsParallel(i); + } + + private synchronized void setAsSequential(Intent i) { + if (sequential == null) + sequential = i; + else + sequential.setAsParallel(i); + } + +} diff --git a/src/com/projectswg/common/control/IntentChain.java b/src/com/projectswg/common/control/IntentChain.java new file mode 100644 index 0000000..0f92222 --- /dev/null +++ b/src/com/projectswg/common/control/IntentChain.java @@ -0,0 +1,67 @@ +/************************************************************************************ + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * -------------------------------------------------------------------------------- * + * * + * Holocore is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * Holocore 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 Affero General Public License for more details. * + * * + * You should have received a copy of the GNU Affero General Public License * + * along with Holocore. If not, see . * + * * + ***********************************************************************************/ +package com.projectswg.common.control; + +public class IntentChain { + + private final IntentManager intentManager; + private final Object mutex; + private Intent i; + + public IntentChain() { + this(IntentManager.getInstance()); + } + + public IntentChain(IntentManager intentManager) { + this(intentManager, null); + } + + public IntentChain(Intent i) { + this(IntentManager.getInstance(), i); + } + + public IntentChain(IntentManager intentManager, Intent i) { + this.intentManager = intentManager; + this.mutex = new Object(); + this.i = i; + } + + public void reset() { + synchronized (mutex) { + i = null; + } + } + + public void broadcastAfter(Intent i) { + synchronized (mutex) { + i.broadcastAfterIntent(this.i, intentManager); + this.i = i; + } + } + +} diff --git a/src/com/projectswg/common/control/IntentManager.java b/src/com/projectswg/common/control/IntentManager.java new file mode 100644 index 0000000..2bd8f37 --- /dev/null +++ b/src/com/projectswg/common/control/IntentManager.java @@ -0,0 +1,134 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.control; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +import com.projectswg.common.concurrency.PswgTaskThreadPool; +import com.projectswg.common.debug.Log; + +public class IntentManager { + + private static final IntentManager INSTANCE = new IntentManager(); + + private final Map , List>> intentRegistrations; + private final PswgTaskThreadPool broadcastThreads; + private final AtomicBoolean initialized; + + public IntentManager() { + this.intentRegistrations = new HashMap<>(); + int threadCount = Runtime.getRuntime().availableProcessors() * 10; + this.broadcastThreads = new PswgTaskThreadPool<>(threadCount, "intent-processor-%d", i -> broadcast(i)); + this.broadcastThreads.setInterruptOnStop(true); + this.initialized = new AtomicBoolean(false); + initialize(); + } + + public void initialize() { + if (initialized.getAndSet(true)) + return; + broadcastThreads.start(); + } + + public void terminate() { + if (!initialized.getAndSet(false)) + return; + broadcastThreads.stop(); + } + + public void broadcastIntent(Intent i) { + if (i == null) + throw new NullPointerException("Intent cannot be null!"); + broadcastThreads.addTask(i); + } + + @SuppressWarnings("unchecked") + public void registerForIntent(Class c, Consumer r) { + if (r == null) + throw new NullPointerException("Cannot register a null consumer for an intent"); + synchronized (intentRegistrations) { + List > intents = intentRegistrations.get(c); + if (intents == null) { + intents = new CopyOnWriteArrayList<>(); + intentRegistrations.put((Class) c, intents); + } + synchronized (intents) { + intents.add((Consumer) r); + } + } + } + + public void unregisterForIntent(Class c, Consumer r) { + if (r == null) + throw new NullPointerException("Cannot register a null consumer for an intent"); + synchronized (intentRegistrations) { + List > intents = intentRegistrations.get(c); + if (intents == null) + return; + synchronized (intents) { + intents.remove(r); + } + } + } + + private void broadcast(Intent i) { + List > receivers; + synchronized (intentRegistrations) { + receivers = intentRegistrations.get(i.getClass()); + } + try { + if (receivers == null) + return; + + for (Consumer r : receivers) { + broadcast(r, i); + } + } finally { + i.markAsComplete(this); + } + } + + private void broadcast(Consumer r, Intent i) { + try { + r.accept(i); + } catch (Throwable t) { + Log.e("Fatal Exception while processing intent: " + i); + Log.e(t); + } + } + + public static IntentManager getInstance() { + return INSTANCE; + } + +} diff --git a/src/com/projectswg/common/control/IntentQueue.java b/src/com/projectswg/common/control/IntentQueue.java new file mode 100644 index 0000000..515d1fa --- /dev/null +++ b/src/com/projectswg/common/control/IntentQueue.java @@ -0,0 +1,235 @@ +/************************************************************************************ + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * -------------------------------------------------------------------------------- * + * * + * Holocore is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * Holocore 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 Affero General Public License for more details. * + * * + * You should have received a copy of the GNU Affero General Public License * + * along with Holocore. If not, see . * + * * + ***********************************************************************************/ +package com.projectswg.common.control; + +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.NoSuchElementException; +import java.util.Queue; + +class IntentQueue implements Queue { + + private final Node head; + private int size; + private int modificationCount; + + public IntentQueue() { + head = new Node(null, null, null); // Left = Forward, Right = Reverse + head.left = head; + head.right = head; + modificationCount = 0; + } + + @Override + public int size() { + return size; + } + + @Override + public boolean isEmpty() { + return size == 0; + } + + @Override + public boolean contains(Object o) { + Node n = head; + while (n.left != head) { + if (n.left.value == o) + return true; + } + return false; + } + + @Override + public Iterator iterator() { + return new Iterator(); + } + + @Override + public Object [] toArray() { + return null; + } + + @Override + public T [] toArray(T [] a) { + return null; + } + + @Override + public boolean remove(Object o) { + modificationCount++; + Node n = head; + while (n.left != head) { + if (n.left.value == o) { + n.left = n.left.left; + n.left.right = n; + } + } + return false; + } + + @Override + public boolean containsAll(Collection c) { + for (Object o : c) + if (!contains(o)) + return false; + return true; + } + + @Override + public boolean addAll(Collection c) { + boolean added = false; + for (E i : c) + added = add(i) || added; + return added; + } + + @Override + public boolean removeAll(Collection c) { + boolean changed = false; + for (Object o : c) + changed = remove(o) || changed; + return changed; + } + + @Override + public boolean retainAll(Collection c) { + boolean changed = false; + Node n = head; + while (n.left != head) { + if (!c.contains(n.left.value)) { + n.left.left.right = n; + n.left = n.left.left; + } + } + return changed; + } + + @Override + public void clear() { + modificationCount++; + size = 0; + head.left = head; + head.right = head; + } + + @Override + public boolean add(E e) { + head.right.left = new Node(e, head, head.right); + head.right = head.right.left; + modificationCount++; + size++; + return true; + } + + @Override + public boolean offer(E e) { + return add(e); + } + + @Override + public E remove() { + if (isEmpty()) + throw new NoSuchElementException("Queue is empty!"); + modificationCount++; + E i = head.left.value; + head.left = head.left.left; + head.left.right = head; + size--; + return i; + } + + @Override + public E poll() { + if (isEmpty()) + return null; + modificationCount++; + E i = head.left.value; + head.left = head.left.left; + head.left.right = head; + size--; + return i; + } + + @Override + public E element() { + if (isEmpty()) + throw new NoSuchElementException("Queue is empty!"); + return head.left.value; + } + + @Override + public E peek() { + if (isEmpty()) + return null; + return head.left.value; + } + + private class Iterator implements java.util.Iterator { + + private final int modificationCount; + private Node currentNode; + + public Iterator() { + this.modificationCount = IntentQueue.this.modificationCount; + this.currentNode = IntentQueue.this.head; + } + + @Override + public boolean hasNext() { + if (this.modificationCount != IntentQueue.this.modificationCount) + throw new ConcurrentModificationException(); + return currentNode.left != IntentQueue.this.head; + } + + @Override + public E next() { + if (!hasNext()) + throw new NoSuchElementException("Iterator has reached the end of the queue!"); + E i = currentNode.left.value; + currentNode = currentNode.left; + return i; + } + + } + + private class Node { + + public final E value; + public Node left; + public Node right; + + public Node(E value, Node left, Node right) { + this.value = value; + this.left = left; + this.right = right; + } + + } + +} diff --git a/src/com/projectswg/common/control/IntentReceiver.java b/src/com/projectswg/common/control/IntentReceiver.java new file mode 100644 index 0000000..3839e4c --- /dev/null +++ b/src/com/projectswg/common/control/IntentReceiver.java @@ -0,0 +1,41 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.control; + + +public interface IntentReceiver { + + /** + * This function will be called if an intent is broadcasted and this + * manager is listening for it, or if this manager is specifically given + * this intent. + * @param i the intent received + */ + public void onIntentReceived(Intent i); + +} diff --git a/src/com/projectswg/common/control/Manager.java b/src/com/projectswg/common/control/Manager.java new file mode 100644 index 0000000..6a7bf7b --- /dev/null +++ b/src/com/projectswg/common/control/Manager.java @@ -0,0 +1,198 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.control; + +import java.util.ArrayList; +import java.util.List; + +import com.projectswg.common.debug.Log; + + + +/** + * A Manager is a class that will manage services, and generally controls the + * program as a whole + */ +public abstract class Manager extends Service { + + private List children; + + public Manager() { + children = new ArrayList(); + } + + /** + * Initializes this manager. If the manager returns false on this method + * then the initialization failed and may not work as intended. This will + * initialize all children automatically. + * @return TRUE if initialization was successful, FALSE otherwise + */ + @Override + public boolean initialize() { + boolean success = super.initialize(); + synchronized (children) { + for (Service child : children) { + if (!child.initialize()) { + Log.e(child.getClass().getSimpleName() + " failed to initialize!"); + success = false; + break; + } + } + } + return success; + } + + /** + * Starts this manager. If the manager returns false on this method then + * the manger failed to start and may not work as intended. This will start + * all children automatically. + * @return TRUE if starting was successful, FALSE otherwise + */ + @Override + public boolean start() { + boolean success = super.start(); + synchronized (children) { + for (Service child : children) { + if (!child.start()) { + Log.e(child.getClass().getSimpleName() + " failed to start!"); + success = false; + break; + } + } + } + return success; + } + + /** + * Stops this manager. If the manager returns false on this method then + * the manger failed to stop and may not have fully locked down. This will + * start all children automatically. + * @return TRUE if stopping was successful, FALSE otherwise + */ + @Override + public boolean stop() { + boolean success = super.stop(), cSuccess = true; + synchronized (children) { + for (Service child : children) { + if (!success) + break; + cSuccess = child.stop(); + if (!cSuccess) { + Log.e(child.getClass().getSimpleName() + " failed to stop!"); + success = false; + } + } + } + return success; + } + + /** + * Terminates this manager. If the manager returns false on this method + * then the manager failed to shut down and resources may not have been + * cleaned up. This will terminate all children automatically. + * @return TRUE if termination was successful, FALSE otherwise + */ + @Override + public boolean terminate() { + boolean success = super.terminate(); + synchronized (children) { + for (Service child : children) { + if (!child.terminate()) + success = false; + } + } + return success; + } + + /** + * Determines whether or not this manager is operational + * @return TRUE if this manager is operational, FALSE otherwise + */ + @Override + public boolean isOperational() { + boolean success = true; + synchronized (children) { + for (Service child : children) { + if (!child.isOperational()) + success = false; + } + } + return success; + } + + /** + * Adds a child to the manager's list of children. This creates a tree of + * managers that allows information to propogate freely through the network + * in an easy way. + * @param m the manager to add as a child. + */ + public void addChildService(Service s) { + if (s == null) + throw new NullPointerException("Child service cannot be null!"); + synchronized (children) { + for (Service child : children) { + if (s == child || s.equals(child)) + return; + } + children.add(s); + s.setIntentManager(getIntentManager()); + } + } + + /** + * Removes the sub-manager from the list of children + * @param m the sub-manager to remove + */ + public void removeChildService(Service s) { + if (s == null) + return; + synchronized (children) { + children.remove(s); + } + } + + /** + * Returns a copied ArrayList of the children of this manager + * @return a copied ArrayList of the children of this manager + */ + public List getManagerChildren() { + synchronized (children) { + return new ArrayList(children); + } + } + + public void setIntentManager(IntentManager intentManager) { + super.setIntentManager(intentManager); + synchronized (children) { + for (Service s : children) { + s.setIntentManager(intentManager); + } + } + } + +} diff --git a/src/com/projectswg/common/control/Service.java b/src/com/projectswg/common/control/Service.java new file mode 100644 index 0000000..9c04568 --- /dev/null +++ b/src/com/projectswg/common/control/Service.java @@ -0,0 +1,117 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.control; + +import java.util.function.Consumer; + +/** + * A Service is a class that does a specific job for the application + */ +public abstract class Service { + + private IntentManager intentManager; + + public Service() { + this.intentManager = IntentManager.getInstance(); + } + + /** + * Initializes this service. If the service returns false on this method + * then the initialization failed and may not work as intended. + * @return TRUE if initialization was successful, FALSE otherwise + */ + public boolean initialize() { + return true; + } + + /** + * Starts this service. If the service returns false on this method then + * the service failed to start and may not work as intended. + * @return TRUE if starting was successful, FALSE otherwise + */ + public boolean start() { + return true; + } + + /** + * Stops the service. If the service returns false on this method then the + * service failed to stop and may not have fully locked down. + * @return TRUE if stopping was successful, FALSe otherwise + */ + public boolean stop() { + return true; + } + + /** + * Terminates this service. If the service returns false on this method + * then the service failed to shut down and resources may not have been + * cleaned up. + * @return TRUE if termination was successful, FALSE otherwise + */ + public boolean terminate() { + IntentManager.getInstance().terminate(); + return true; + } + + /** + * Determines whether or not this service is operational + * @return TRUE if this service is operational, FALSE otherwise + */ + public boolean isOperational() { + return true; + } + + /** + * Registers for the intent using the specified consumer + * @param c the class of intent to register for + * @param consumer the consumer to run when the intent is fired + */ + protected void registerForIntent(Class c, Consumer consumer) { + intentManager.registerForIntent(c, consumer); + } + + /** + * Unregisters for the intent using the specified consumer + * @param c the class of intent to unregister + * @param consumer the consumer that was previous registered + */ + protected void unregisterForIntent(Class c, Consumer consumer) { + intentManager.unregisterForIntent(c, consumer); + } + + public void setIntentManager(IntentManager intentManager) { + if (intentManager == null) + throw new NullPointerException("IntentManager cannot be null!"); + this.intentManager = intentManager; + } + + public IntentManager getIntentManager() { + return intentManager; + } + +} diff --git a/src/com/projectswg/common/debug/Assert.java b/src/com/projectswg/common/debug/Assert.java new file mode 100644 index 0000000..d9e0ae7 --- /dev/null +++ b/src/com/projectswg/common/debug/Assert.java @@ -0,0 +1,111 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.debug; + +public class Assert { + + private static volatile AssertLevel level = AssertLevel.ASSERT; + + public static void setLevel(AssertLevel level) { + Assert.level = level; + } + + public static boolean debug() { + return level != AssertLevel.IGNORE; + } + + public static void notNull(Object o) { + notNull(o, ""); + } + + public static void notNull(Object o, String message) { + if (debug() && o == null) + handle(new NullPointerException(message)); + } + + public static void isNull(Object o) { + isNull(o, ""); + } + + public static void isNull(Object o, String message) { + if (debug() && o != null) + handle(new AssertionException(message)); + } + + public static void test(boolean expr) { + test(expr, ""); + } + + public static void test(boolean expr, String message) { + if (debug() && !expr) + handle(new AssertionException(message)); + } + + public static void fail() { + fail(""); + } + + public static void fail(String message) { + if (debug()) + handle(new AssertionException(message)); + } + + private static void handle(RuntimeException e) { + AssertLevel level = Assert.level; + switch (level) { + case WARN: + warn(e); + break; + case ASSERT: + throw e; + default: + break; + } + } + + private static void warn(Exception e) { + Log.e(e); + } + + private static class AssertionException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public AssertionException(String message) { + super(message); + } + + } + + public enum AssertLevel { + IGNORE, + WARN, + ASSERT + } + +} diff --git a/src/com/projectswg/common/debug/Log.java b/src/com/projectswg/common/debug/Log.java new file mode 100644 index 0000000..b62958d --- /dev/null +++ b/src/com/projectswg/common/debug/Log.java @@ -0,0 +1,225 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.debug; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +public class Log { + + private static Log INSTANCE = null; + + private final List wrappers; + private final Lock logLock; + private final DateFormat timeFormat; + + private Log() { + this.wrappers = new ArrayList<>(); + this.logLock = new ReentrantLock(true); + this.timeFormat = new SimpleDateFormat("dd-MM-yy HH:mm:ss.SSS"); + } + + private void logAddWrapper(LogWrapper wrapper) { + wrappers.add(wrapper); + } + + private void logImplementation(LogLevel level, String str, Object ... args) { + String date = timeFormat.format(System.currentTimeMillis()); + String logStr; + if (args.length == 0) + logStr = date + ' ' + level.getChar() + ": " + str; + else + logStr = date + ' ' + level.getChar() + ": " + String.format(str, args); + for (LogWrapper wrapper : wrappers) { + wrapper.onLog(level, logStr); + } + } + + private void lock() { + logLock.lock(); + } + + private void unlock() { + logLock.unlock(); + } + + private static synchronized final Log getInstance() { + if (INSTANCE == null) + INSTANCE = new Log(); + return INSTANCE; + } + + public static final void addWrapper(LogWrapper wrapper) { + getInstance().logAddWrapper(wrapper); + } + + /** + * Logs the string to the server log file, formatted to display the log + * severity, time and message. + * @param level the log level of this message between VERBOSE and ASSERT + * @param tag the tag to use for the log + * @param str the format string for the log + * @param args the string format arguments, if specified + */ + public static final void log(LogLevel level, String str, Object ... args) { + try { + getInstance().lock(); + getInstance().logImplementation(level, str, args); + } finally { + getInstance().unlock(); + } + } + + /** + * Logs the string to the server log file, formatted to display the log + * severity as VERBOSE, as well as the time and message. + * @param message the format string for the log + * @param args the string format arguments, if specified + */ + public static final void v(String message, Object ... args) { + log(LogLevel.VERBOSE, message, args); + } + + /** + * Logs the string to the server log file, formatted to display the log + * severity as DEBUG, as well as the time and message. + * @param message the format string for the log + * @param args the string format arguments, if specified + */ + public static final void d(String message, Object ... args) { + log(LogLevel.DEBUG, message, args); + } + + /** + * Logs the string to the server log file, formatted to display the log + * severity as INFO, as well as the time and message. + * @param message the format string for the log + * @param args the string format arguments, if specified + */ + public static final void i(String message, Object ... args) { + log(LogLevel.INFO, message, args); + } + + /** + * Logs the string to the server log file, formatted to display the log + * severity as WARN, as well as the time and message. + * @param message the format string for the log + * @param args the string format arguments, if specified + */ + public static final void w(String message, Object ... args) { + log(LogLevel.WARN, message, args); + } + + + /** + * Logs the exception to the server log file, formatted to display the log + * severity as WARN, as well as the time, and tag. + * @param exception the exception to print + */ + public static final void w(Throwable exception) { + printException(LogLevel.WARN, exception); + } + + /** + * Logs the string to the server log file, formatted to display the log + * severity as ERROR, as well as the time and message. + * @param tag the tag to use for the log + * @param message the format string for the log + * @param args the string format arguments, if specified + */ + public static final void e(String message, Object ... args) { + log(LogLevel.ERROR, message, args); + } + + /** + * Logs the exception to the server log file, formatted to display the log + * severity as ERROR, as well as the time, and tag. + * @param exception the exception to print + */ + public static final void e(Throwable exception) { + printException(LogLevel.ERROR, exception); + } + + /** + * Logs the string to the server log file, formatted to display the log + * severity as ASSERT, as well as the time and message. + * @param message the format string for the log + * @param args the string format arguments, if specified + */ + public static final void a(String message, Object ... args) { + log(LogLevel.ASSERT, message, args); + } + + /** + * Logs the exception to the server log file, formatted to display the log + * severity as ASSERT, as well as the time, and tag. + * @param exception the exception to print + */ + public static final void a(Throwable exception) { + printException(LogLevel.ASSERT, exception); + } + + private static final void printException(LogLevel level, Throwable exception) { + Log instance = getInstance(); + try { + String header1 = String.format("Exception in thread \"%s\" %s: %s", Thread.currentThread().getName(), exception.getClass().getName(), exception.getMessage()); + String header2 = String.format("Caused by: %s: %s", exception.getClass().getCanonicalName(), exception.getMessage()); + StackTraceElement [] elements = exception.getStackTrace(); + instance.lock(); + instance.logImplementation(level, header1); + instance.logImplementation(level, header2); + for (StackTraceElement e : elements) { + instance.logImplementation(level, " " + e.toString()); + } + } finally { + instance.unlock(); + } + } + + public static enum LogLevel { + VERBOSE ('V'), + DEBUG ('D'), + INFO ('I'), + WARN ('W'), + ERROR ('E'), + ASSERT ('A'); + + private char c; + + LogLevel(char c) { + this.c = c; + } + + public char getChar() { return c; } + } + +} diff --git a/src/com/projectswg/common/debug/LogWrapper.java b/src/com/projectswg/common/debug/LogWrapper.java new file mode 100644 index 0000000..52d0328 --- /dev/null +++ b/src/com/projectswg/common/debug/LogWrapper.java @@ -0,0 +1,36 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.debug; + +import com.projectswg.common.debug.Log.LogLevel; + +public interface LogWrapper { + + void onLog(LogLevel level, String str); + +} diff --git a/src/com/projectswg/common/debug/log_wrapper/ConsoleLogWrapper.java b/src/com/projectswg/common/debug/log_wrapper/ConsoleLogWrapper.java new file mode 100644 index 0000000..596f7b4 --- /dev/null +++ b/src/com/projectswg/common/debug/log_wrapper/ConsoleLogWrapper.java @@ -0,0 +1,24 @@ +package com.projectswg.common.debug.log_wrapper; + +import com.projectswg.common.debug.Log.LogLevel; +import com.projectswg.common.debug.LogWrapper; + +public class ConsoleLogWrapper implements LogWrapper { + + private final LogLevel level; + + public ConsoleLogWrapper(LogLevel level) { + this.level = level; + } + + @Override + public void onLog(LogLevel level, String str) { + if (this.level.compareTo(level) > 0) + return; + if (level.compareTo(LogLevel.WARN) >= 0) + System.err.println(str); + else + System.out.println(str); + } + +} diff --git a/src/com/projectswg/common/debug/log_wrapper/FileLogWrapper.java b/src/com/projectswg/common/debug/log_wrapper/FileLogWrapper.java new file mode 100644 index 0000000..c762927 --- /dev/null +++ b/src/com/projectswg/common/debug/log_wrapper/FileLogWrapper.java @@ -0,0 +1,37 @@ +package com.projectswg.common.debug.log_wrapper; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; + +import com.projectswg.common.debug.LogWrapper; +import com.projectswg.common.debug.Log.LogLevel; + +public class FileLogWrapper implements LogWrapper { + + private final BufferedWriter writer; + + public FileLogWrapper(File file) throws IOException { + writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)); + } + + @Override + protected void finalize() throws IOException { + writer.close(); + } + + @Override + public void onLog(LogLevel level, String str) { + try { + writer.write(str); + writer.newLine(); + writer.flush(); + } catch (IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/src/com/projectswg/common/info/Config.java b/src/com/projectswg/common/info/Config.java new file mode 100644 index 0000000..4faa89e --- /dev/null +++ b/src/com/projectswg/common/info/Config.java @@ -0,0 +1,183 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.info; + +import java.io.File; +import java.util.Map; + +/** + * Reads and stores configuration data from a file + */ +public class Config { + + private final ConfigData configData; + + /** + * Initilizes the Config and loads the data in the file + * @param filename the file to load + */ + public Config(String filename) { + this(new File(filename)); + } + + /** + * Initilizes the Config and loads the data in the file + * @param file the file to load + */ + public Config(File file) { + if (!file.exists() || !file.isFile()) + throw new IllegalArgumentException("Filepath does not point to a valid file!"); + configData = new ConfigData(file); + load(); + save(); + } + + /** + * Determines whether or not the key-value pair exists in the config + * @param key the key to check + * @return TRUE if the key-value pair exists, FALSE otherwise + */ + public boolean containsKey(String key) { + return configData.containsKey(key); + } + + /** + * Gets the parameter with the specified key. If no such parameter exists, + * it returns the default + * @param key the key to get the value for + * @param def the default value + * @return the value represented by the key, or the default value + */ + public String getString(String key, String def) { + if (!containsKey(key)) { + setProperty(key, def); + return def; + } + return configData.get(key); + } + + /** + * Gets the parameter with the specified key. If no such parameter exists, + * or if the value isn't an integer then it returns the default + * @param key the key to get the value for + * @param def the default value + * @return the value represented by the key, or the default value + */ + public int getInt(String key, int def) { + try { + return Integer.parseInt(getString(key, Integer.toString(def))); + } catch (NumberFormatException e) { + return def; + } + } + + /** + * Gets the parameter with the specified key. If no such parameter exists, + * or if the value isn't a double then it returns the default + * @param key the key to get the value for + * @param def the default value + * @return the value represented by the key, or the default value + */ + public double getDouble(String key, double def) { + try { + return Double.parseDouble(getString(key, Double.toString(def))); + } catch (NumberFormatException e) { + return def; + } + } + + /** + * Gets the parameter with the specified key. If no such parameter exists, + * or if the value isn't a boolean then it returns the default + * @param key the key to get the value for + * @param def the default value + * @return the value represented by the key, or the default value + */ + public boolean getBoolean(String key, boolean def) { + String val = getString(key, def?"true":"false"); + if (val.equalsIgnoreCase("true") || val.equals("1")) + return true; + if (val.equalsIgnoreCase("false") || val.equals("0")) + return false; + return def; + } + + /** + * Sets the property value for the specified key + * @param key the key of the value to set + * @param value the value to set + */ + public void setProperty(String key, String value) { + configData.put(key, value); + save(); + } + + /** + * Sets the property value for the specified key + * @param key the key of the value to set + * @param value the value to set + */ + public void setProperty(String key, int value) { + setProperty(key, Integer.toString(value)); + } + + /** + * Sets the property value for the specified key + * @param key the key of the value to set + * @param value the value to set + */ + public void setProperty(String key, boolean value) { + setProperty(key, value ? "true" : "false"); + } + + /** + * Sets the property value for the specified key + * @param key the key of the value to set + * @param value the value to set + */ + public void setProperty(String key, double value) { + setProperty(key, Double.toString(value)); + } + + /** + * Reloads the config data from the file + * @return TRUE if the data was successfully loaded, FALSE otherwise + */ + public Map load() { + return configData.load(); + } + + /** + * Saves the config data to the file + * @return TRUE if the data was successfully saved, FALSE otherwise + */ + public boolean save() { + return configData.save(); + } + +} diff --git a/src/com/projectswg/common/info/ConfigData.java b/src/com/projectswg/common/info/ConfigData.java new file mode 100644 index 0000000..bb587bd --- /dev/null +++ b/src/com/projectswg/common/info/ConfigData.java @@ -0,0 +1,154 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.info; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TreeMap; + +import com.projectswg.common.debug.Log; + +class ConfigData { + + private final DateFormat FORMAT = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss zzz"); + private final Map data; + private final File file; + + public ConfigData(File file) { + this.data = new TreeMap<>(); + this.file = file; + } + + public boolean containsKey(String key) { + synchronized (data) { + return data.containsKey(key); + } + } + + public String get(String key) { + synchronized (data) { + return data.get(key); + } + } + + public String put(String key, String value) { + synchronized (data) { + return data.put(key, value); + } + } + + /** + * @return null on an I/O failure, an empty {@code Map} on the first load and + * a populated {@code Map} when called afterwards + */ + public Map load() { + Map delta = new HashMap<>(); + BufferedReader reader = null; + + synchronized (data) { + delta.putAll(data); // Copy the current data + + try { + reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)); + String line = reader.readLine(); + while (line != null) { + loadLine(line); + line = reader.readLine(); + } + } catch (IOException e) { + Log.e(e); + return null; + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + return null; + } + } + } + for(Entry entry : data.entrySet()) + delta.remove(entry.getKey(), entry.getValue()); + } + + return delta; + } + + public boolean save() { + BufferedWriter writer = null; + synchronized (data) { + try { + writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)); + writer.write("# "+FORMAT.format(System.currentTimeMillis())); + writer.newLine(); + for (Entry e : data.entrySet()) { + writer.write(e.getKey() + "=" + e.getValue()); + writer.newLine(); + } + return true; + } catch (IOException e) { + Log.e(e); + return false; + } finally { + if (writer != null) { + try { + writer.close(); + } catch (IOException e) { + + } + } + } + } + } + + private void loadLine(String line) { + String beforeComment = line; + if (line.contains("#")) + beforeComment = line.substring(0, line.indexOf('#')); + if (!beforeComment.contains("=")) + return; + String key = beforeComment.substring(0, beforeComment.indexOf('=')); + String val = beforeComment.substring(key.length()+1); + synchronized (data) { + data.put(key, val); + } + } + +} diff --git a/src/com/projectswg/common/info/PostgresqlDatabase.java b/src/com/projectswg/common/info/PostgresqlDatabase.java new file mode 100644 index 0000000..4113db5 --- /dev/null +++ b/src/com/projectswg/common/info/PostgresqlDatabase.java @@ -0,0 +1,36 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.info; + +public class PostgresqlDatabase extends RelationalDatabase { + + public PostgresqlDatabase(String host, String db, String user, String pass) { + super("org.postgresql.Driver", "postgresql", host, db, user, pass, ""); + } + +} diff --git a/src/com/projectswg/common/info/RelationalDatabase.java b/src/com/projectswg/common/info/RelationalDatabase.java new file mode 100644 index 0000000..c9bacf3 --- /dev/null +++ b/src/com/projectswg/common/info/RelationalDatabase.java @@ -0,0 +1,192 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.info; + +import java.io.Closeable; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.Statement; + +import com.projectswg.common.debug.Log; + +public abstract class RelationalDatabase implements Closeable { + + private DatabaseMetaData metaData; + private Connection connection; + private boolean online; + + protected RelationalDatabase(String jdbcClass, String url) { + try { + Class.forName(jdbcClass); + initialize(url); + } catch (ClassNotFoundException e) { + Log.e(e); + online = false; + } + } + + protected RelationalDatabase(String jdbcClass, String url, String user, String pass) { + try { + Class.forName(jdbcClass); + initialize(url, user, pass); + } catch (ClassNotFoundException e) { + Log.e(e); + online = false; + } + } + + public RelationalDatabase(String jdbcClass, String url, String user, String pass, String params) { + try { + Class.forName(jdbcClass); + if (params != null && params.length() > 0) + url += "?" + params; + initialize(url, user, pass); + } catch (ClassNotFoundException e) { + Log.e(e); + online = false; + } + } + + public RelationalDatabase(String jdbcClass, String type, String host, String db, String user, String pass, String params) { + try { + Class.forName(jdbcClass); + String url = "jdbc:" + type + "://" + host + "/" + db; + if (params != null && params.length() > 0) + url += "?" + params; + initialize(url, user, pass); + } catch (ClassNotFoundException e) { + Log.e(e); + online = false; + } + } + + private void initialize(String url) { + try { + connection = DriverManager.getConnection(url); + metaData = connection.getMetaData(); + online = true; + } catch (SQLException e) { + Log.e("RelationalDatabase", "Failed to initialize relational database! %s - %s", e.getClass().getSimpleName(), e.getMessage()); + online = false; + } + } + + private void initialize(String url, String user, String pass) { + try { + connection = DriverManager.getConnection(url, user, pass); + metaData = connection.getMetaData(); + online = true; + } catch (SQLException e) { + Log.e("RelationalDatabase", "Failed to initialize relational database! %s - %s", e.getClass().getSimpleName(), e.getMessage()); + online = false; + } + } + + public void close() { + try { + connection.close(); + online = false; + } catch (SQLException e) { + Log.e(e); + } + } + + public boolean isOnline() { + if (connection == null) + return false; + try { + return online && !connection.isClosed(); + } catch (SQLException e) { + return online; + } + } + + public PreparedStatement prepareStatement(String sql) { + if (connection == null) { + Log.e("RelationalDatabase", "Cannot prepare statement! Connection is null"); + return null; + } + try { + return connection.prepareStatement(sql); + } catch (SQLException e) { + Log.e(e); + return null; + } + } + + public ResultSet executeQuery(String query) { + if (connection == null) + return null; + Statement s = null; + try { + s = connection.createStatement(); + s.execute(query); + try { + s.closeOnCompletion(); + } catch (SQLFeatureNotSupportedException e) { + // It was worth a shot + } + return s.getResultSet(); + } catch (SQLException e) { + Log.e(e); + if (s != null) { + try { s.close(); } catch (SQLException ex) { } + } + return null; + } + } + + public int updateQuery(String query) { + if (connection == null) + return 0; + try { + try (Statement s = connection.createStatement()) { + return s.executeUpdate(query); + } + } catch (SQLException e) { + Log.e(e); + return 0; + } + } + + public boolean isTable(String name) { + if (metaData == null) + return false; + try { + return metaData.getTables(null, null, name, null).next(); + } catch (SQLException e) { + return false; + } + } + +} diff --git a/src/com/projectswg/common/info/RelationalServerData.java b/src/com/projectswg/common/info/RelationalServerData.java new file mode 100644 index 0000000..8a27326 --- /dev/null +++ b/src/com/projectswg/common/info/RelationalServerData.java @@ -0,0 +1,447 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.info; + +import java.io.BufferedReader; +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import com.projectswg.common.debug.Log; + +public class RelationalServerData extends RelationalDatabase { + + private static final Charset ASCII = Charset.forName("ASCII"); + + private static final String META_TABLE = "__server_table_metadata__"; + private final PreparedStatement getTableMetadata; + private final PreparedStatement updateTableMetadata; + private final PreparedStatement insertTableMetadata; + + public RelationalServerData(String file) { + super("org.sqlite.JDBC", "jdbc:sqlite:" + file); + /* + * GREATLY speeds up INSERT speeds. This makes it so SQLite will not + * wait until the previous write finishes before starting to write the + * next INSERT. All imports will only take milliseconds. A caveat of + * this is that if the power ever cuts or the application is force + * killed it may result in database corruption. + */ + updateQuery("PRAGMA synchronous=OFF"); + updateQuery("CREATE TABLE IF NOT EXISTS "+META_TABLE+" (table_name TEXT, last_imported INTEGER)"); + getTableMetadata = prepareStatement("SELECT * FROM "+META_TABLE+" WHERE table_name=?"); + updateTableMetadata = prepareStatement("UPDATE "+META_TABLE+" SET last_imported=? WHERE table_name=?"); + insertTableMetadata = prepareStatement("INSERT INTO "+META_TABLE+" (table_name, last_imported) VALUES (?, ?)"); + } + + public boolean linkTableWithSdb(String table, String filepath) { + File sdb = new File(filepath); + if (!sdb.isFile()) + return false; + if (testLastModified(table, sdb) || testMasterSdbModified(table, sdb)) { + updateQuery("DROP TABLE IF EXISTS " + table); + importFromSdb(table, sdb); + updateLastImported(table, System.currentTimeMillis()); + } + return true; + } + + private boolean testLastModified(String table, File sdb) { + return sdb.lastModified() > getLastImported(table); + } + + private boolean testMasterSdbModified(String table, File sdb) { + if (!sdb.getName().endsWith(".msdb")) + return false; + try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(sdb), ASCII))) { + reader.readLine(); + reader.readLine(); + String line; + while ((line = reader.readLine()) != null) { + if (testLastModified(table, new File(sdb.getParent(), line.substring(0, line.indexOf('\t'))))) + return true; + } + } catch (IOException e) { + Log.e(e); + } + return false; + } + + /** + * Inserts a record with the specified information into the database + * @param table the table to insert into + * @param columns the columns to insert into + * @param params the parameters to insert + * @return TRUE on success, FALSE on failure + * @throws SQLException upon error + */ + public boolean insert(String table, String [] columns, Object ... params) throws SQLException { + StringBuilder columnStr = new StringBuilder(""); + StringBuilder valuesStr = new StringBuilder(""); + if (columns == null) + columns = getColumnsForTable(table); + for (int i = 0; i < columns.length; i++) { + columnStr.append(columns[i]); + valuesStr.append('?'); + if (i+1 < columns.length) { + columnStr.append(','); + valuesStr.append(','); + } + } + final String sql = String.format("INSERT INTO %s (%s) VALUES (%s)", table, columnStr, valuesStr); + try (PreparedStatement statement = prepareStatement(sql)) { + if (statement == null || params.length < getSqlParameterCount(sql)) + return false; + assignParameters(statement, params); + return statement.executeUpdate() > 0; + } + } + + /** + * Selects from the specified table with the supplied where clause, given + * the supplied parameters + * @param tables the tables to query, separated by commas + * @param columns the columns to select, null to select all + * @param where the where clause + * @param params the parameters to put into the ?s in the where clause + * @return the result set + * @throws SQLException upon error + */ + public ResultSet selectFromTable(String tables, String [] columns, String where, Object ... params) throws SQLException { + final String sql = createSelectQuery(tables, columns, where, params); + PreparedStatement statement = prepareStatement(sql); + if (statement == null || params.length < getSqlParameterCount(sql)) + return null; + assignParameters(statement, params); + return statement.executeQuery(); + } + + private void assignParameters(PreparedStatement statement, Object ... params) throws SQLException { + for (int i = 0; i < params.length; i++) { + if (params[i] instanceof Integer || params[i] instanceof Long) + statement.setLong(i+1, ((Number) params[i]).longValue()); + else if (params[i] instanceof Float || params[i] instanceof Double) + statement.setDouble(i+1, ((Number) params[i]).doubleValue()); + else if (params[i] instanceof String) + statement.setString(i+1, (String) params[i]); + else if (params[i] != null) + throw new IllegalArgumentException("Unknown object type: " + params[i].getClass().getSimpleName()); + else + throw new NullPointerException("Parameters cannot have null elements!"); + } + } + + private int getSqlParameterCount(String sql) { + int ret = 0; + for (int i = 0; i < sql.length(); i++) { + if (sql.charAt(i) == '?') + ret++; + } + return ret; + } + + private String createSelectQuery(String tables, String [] columns, String where, Object ... params) { + String columnStr = "*"; + if (columns == null && tables.contains(",")) { + StringBuilder bldr = new StringBuilder(""); + for (String table : tables.split(",")) { + bldr.append(table.trim()); + bldr.append(".*, "); + } + columnStr = bldr.substring(0, columnStr.length()-2); + } else if (columns != null) { + StringBuilder bldr = new StringBuilder(""); + for (String column : columns) { + bldr.append(column); + bldr.append(", "); + } + columnStr = bldr.substring(0, bldr.length()-2); + } + return "SELECT " + columnStr + " FROM " + tables + " WHERE " + where; + } + + private String [] getColumnsForTable(String table) throws SQLException { + List columns = new ArrayList<>(); + try (ResultSet set = executeQuery("PRAGMA table_info('"+table+"')")) { + int colInd = set.findColumn("name"); + while (set.next()) { + columns.add(set.getString(colInd)); + } + } + return columns.toArray(new String[columns.size()]); + } + + private long getLastImported(String table) { + synchronized (getTableMetadata) { + ResultSet set = null; + try { + getTableMetadata.setString(1, table); + set = getTableMetadata.executeQuery(); + if (set.next()) + return set.getLong("last_imported"); + } catch (SQLException e) { + Log.e(e); + } finally { + if (set != null) { + try { + set.close(); + } catch (SQLException e) { + Log.e(e); + } + } + } + return -1; + } + } + + private void updateLastImported(String table, long lastImported) { + synchronized (updateTableMetadata) { + try { + updateTableMetadata.setLong(1, lastImported); + updateTableMetadata.setString(2, table); + if (updateTableMetadata.executeUpdate() > 0) + return; + } catch (SQLException e) { + Log.e(e); + return; + } + } + synchronized (insertTableMetadata) { + try { + insertTableMetadata.setString(1, table); + insertTableMetadata.setLong(2, lastImported); + insertTableMetadata.executeUpdate(); + } catch (SQLException e) { + Log.e(e); + } + } + } + + private boolean importFromSdb(String table, File sdb) { + try (TableReader reader = new TableReader(table, sdb)) { + Log.i("RelationalServerData", "Importing sdb... '" + sdb + "'"); + if (sdb.getName().endsWith(".msdb")) + reader.readMaster(); + else + reader.readNormal(); + return true; + } catch (IOException e) { + Log.e(e); + } catch (SQLException e) { + Log.e(e); + } catch (IllegalArgumentException e) { + Log.e("RelationalServerData", "Invalid file format. Aborting read of %s! Message: %s", sdb, e.getMessage()); + } + return false; + } + + private class TableReader implements Closeable { + + private final String table; + private final File file; + private String [] columnNames; + private String [] columnTypes; + private PreparedStatement insert; + + public TableReader(String table, File file) { + this.table = table; + this.file = file; + this.columnNames = null; + this.columnTypes = null; + this.insert = null; + } + + @Override + public void close() { + if (insert != null) { + try { + insert.close(); + } catch (SQLException e) { + Log.e(e); + } + } + } + + public void readNormal() throws IOException, SQLException { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), ASCII))) { + readNormalRemainder(reader, 1); + } + } + + public void readMaster() throws SQLException, IOException { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), ASCII))) { + int lineNum = 1; + readLine(reader.readLine(), lineNum++); + readLine(reader.readLine(), lineNum++); + readMasterRemainder(reader, lineNum); + } + } + + private void readSlave() throws IOException, SQLException { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), ASCII))) { + int lineNum = 3; + reader.readLine(); + reader.readLine(); // Skip column names and types + readNormalRemainder(reader, lineNum); + } + } + + private void readMasterRemainder(BufferedReader reader, int lineNum) throws IOException, SQLException { + String line; + while ((line = reader.readLine()) != null) { + String [] parts = line.split("\t"); + if (parts.length != 2) { + Log.e("RelationalServerData", "Invalid line [%d]: %s", lineNum, line); + continue; + } + boolean load = Boolean.parseBoolean(parts[1]); + if (load) { + File sdb = new File(file.getParent(), parts[0]); + Log.i("RelationalServerData", " Importing sdb... '" + sdb + "'"); + if (!sdb.isFile()) { + Log.e("RelationalServerData", " Failed to import sdb! File is not file or does not exist"); + continue; + } + @SuppressWarnings("resource") // This closes the database.. we don't want to do that yet + TableReader slave = new TableReader(table, sdb); + slave.columnNames = columnNames; + slave.columnTypes = columnTypes; + slave.insert = insert; + slave.readSlave(); + } + } + } + + private void readNormalRemainder(BufferedReader reader, int lineNum) throws IOException, SQLException { + String line; + while ((line = reader.readLine()) != null) { + readLine(line, lineNum++); + } + if (insert != null) + insert.executeBatch(); + } + + private void readLine(String line, int lineNum) throws SQLException { + if (line == null) + return; + switch (lineNum) { + case 1: + columnNames = fastTabSplit(line); + break; + case 2: + columnTypes = fastTabSplit(line); + createTable(); + createPreparedStatement(); + break; + default: + generateInsert(fastTabSplit(line), lineNum); + break; + } + } + + private String [] fastTabSplit(String str) { + int count = 1; + for (int i = 0; i < str.length(); i++) { + if (str.charAt(i) == '\t') { + count++; + } + } + String [] tabs = new String[count]; + int tabInd = 0; + int prevInd = 0; + for (int i = 0; i < str.length(); i++) { + if (str.charAt(i) == '\t') { + tabs[tabInd++] = str.substring(prevInd, i); + prevInd = i+1; + } + } + if (prevInd < str.length()) + tabs[tabInd++] = str.substring(prevInd); + return tabs; + } + + private void createTable() { + if (columnNames.length != columnTypes.length) + throw new IllegalArgumentException("Names length and types length mismatch"); + StringBuilder sql = new StringBuilder("CREATE TABLE "+table+" ("); + for (int i = 0; i < columnNames.length; i++) { + if (i > 0) + sql.append(", "); + sql.append(columnNames[i]); + sql.append(' '); + sql.append(columnTypes[i]); + } + sql.append(')'); + updateQuery(sql.toString()); + } + + private void createPreparedStatement() { + StringBuilder sql = new StringBuilder("INSERT INTO " + table + " VALUES ("); + for (int i = 0; i < columnNames.length; i++) { + if (i > 0) + sql.append(", "); + sql.append('?'); + } + sql.append(')'); + insert = prepareStatement(sql.toString()); + } + + private void generateInsert(String [] data, int line) throws SQLException { + if (columnTypes.length != data.length) { + Log.e("RelationalServerData", "Could not load record: Types length and data length mismatch. Line: " + line); + return; + } + int column = 0; + try { + for (int i = 0; i < data.length; i++, column++) { + if (columnTypes[i].startsWith("TEXT")) + insert.setString(i+1, data[i]); + else if (columnTypes[i].startsWith("REAL")) + insert.setDouble(i+1, Double.parseDouble(data[i])); + else if (columnTypes[i].startsWith("INTEGER")) + insert.setLong(i+1, Long.parseLong(data[i])); + else + throw new SQLException("Data type unsupported by sdb/sqlite! Type: " + columnTypes[i]); + } + insert.addBatch(); + } catch (NumberFormatException e) { + Log.e("RelationalServerData", "Could not load record: Record has invalid data. Line: " + line + " Column: " + column); + } + } + + } + +} diff --git a/src/com/projectswg/common/info/RelationalServerFactory.java b/src/com/projectswg/common/info/RelationalServerFactory.java new file mode 100644 index 0000000..314d149 --- /dev/null +++ b/src/com/projectswg/common/info/RelationalServerFactory.java @@ -0,0 +1,278 @@ +/************************************************************************************ + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * -------------------------------------------------------------------------------- * + * * + * Holocore is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * Holocore 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 Affero General Public License for more details. * + * * + * You should have received a copy of the GNU Affero General Public License * + * along with Holocore. If not, see . * + * * + ***********************************************************************************/ +package com.projectswg.common.info; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import com.projectswg.common.debug.Log; + +public class RelationalServerFactory { + + private static final RelationalServerFactory INSTANCE = new RelationalServerFactory(); + private static final AtomicReference BASE_PATH = new AtomicReference<>(""); + private static final Map FILE_LOAD_LOCKING = new HashMap<>(); + + public static RelationalServerData getServerData(String file, String ... tables) { + return INSTANCE.getData(file, tables); + } + + public static RelationalServerData getServerDatabase(String file) { + return INSTANCE.getDatabase(file); + } + + public static void setBasePath(String basePath) { + BASE_PATH.set(basePath); + } + + private RelationalServerData getData(String file, String ... tables) { + if (!file.endsWith(".db")) + throw new IllegalArgumentException("File path for database must end in .db!"); + file = file.replace('/', File.separatorChar); + final Object lock = getFileLocking(file); + synchronized (lock) { + File f = new File(BASE_PATH + file); + RelationalServerData data = new RelationalServerData(BASE_PATH + file); + if (loadServerData(data, f, tables)) + return data; + return null; + } + } + + private boolean loadServerData(RelationalServerData data, File file, String ... tables) { + File parent = file.getParentFile(); + try { + if (loadTables(data, parent, tables)) + return true; + data.close(); + } catch (Exception e) { + Log.e(e); + data.close(); + } + return false; + } + + private RelationalServerData getDatabase(String file) { + if (!file.endsWith(".db")) + throw new IllegalArgumentException("File path for database must end in .db!"); + final Object lock = getFileLocking(file); + synchronized (lock) { + File f = new File(BASE_PATH + file); + RelationalServerData data = new RelationalServerData(BASE_PATH + file); + try { + String [] commands = getCommandsFromSchema(f.getPath().substring(0, f.getPath().length()-3) + ".sql"); + ParserData parserData = new ParserData(); + for (String command : commands) + executeCommand(data, command, parserData); + return data; + } catch (Exception e) { + Log.e(e); + } + data.close(); + return null; + } + } + + private boolean loadTables(RelationalServerData data, File parent, String [] tables) { + for (String table : tables) { + table = table.replace('/', File.separatorChar); + String path = generatePath(parent, table); + table = path.substring(path.lastIndexOf(File.separatorChar)+1, path.lastIndexOf('.')); + if (!data.linkTableWithSdb(table, path)) + return false; + } + return true; + } + + private String generatePath(File parent, String table) { + String base; + if (table.contains(File.separator)) + base = BASE_PATH + table; + else + base = parent.getPath() + File.separator + table; + if (new File(base + ".msdb").isFile()) + return base + ".msdb"; + return base + ".sdb"; + } + + private void executeCommand(RelationalServerData data, String command, ParserData parserData) { + command = command.trim(); + if (command.startsWith("SELECT") && parserData.getConditional()) { + try (ResultSet set = data.executeQuery(command)) { + + } catch (SQLException e) { + Log.e(e); + } + } else if (command.startsWith("IF")) { // VERY SIMPLE 'if' logic, no parenthesis and no AND/OR's - expects 3 arguments: + parserData.addConditional(evaluateIf(data, command.substring(2).trim())); + } else if (command.startsWith("ENDIF")) { + parserData.unwrapLastConditional(); + } else if (parserData.getConditional()) { + data.updateQuery(command); + } + } + + private boolean evaluateIf(RelationalServerData data, String statement) { + String [] args = statement.split(" ", 3); + if (args.length != 3) { + Log.e("Invalid IF statement: %s", statement); + return false; + } + double num1 = parseToNumber(data, args[0]); + String comparator = args[1]; + double num2 = parseToNumber(data, args[2]); + switch (comparator) { + case "<": return num1 < num2; + case ">": return num1 > num2; + case "=": return num1 == num2; + case "==": return num1 == num2; + case "<=": return num1 <= num2; + case ">=": return num1 >= num2; + default: + Log.e("Invalid comparator: %s", comparator); + return false; + } + } + + private double parseToNumber(RelationalServerData data, String str) { + if (str.equals("user_version")) { + ResultSet set = data.executeQuery("PRAGMA user_version"); + try { + if (set.next()) + return set.getDouble(1); + else + Log.e("Variable 'user_version' has not been set!"); + } catch (SQLException e) { + Log.e(e); + } + } else { + try { + return Double.parseDouble(str); + } catch (NumberFormatException e) { + Log.e("Number '%s' is not a valid number!", str); + } + } + return Double.NaN; + } + + private String [] getCommandsFromSchema(String schema) throws IOException { + String command; + try (InputStream input = new FileInputStream(new File(schema))) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(input.available()); + byte [] block = new byte[1024]; + while (input.available() > 0) { + int size = input.read(block); + baos.write(block, 0, size); + } + command = new String(baos.toByteArray(), Charset.forName("ASCII")); + } + return command.split(";"); + } + + private Object getFileLocking(String file) { + synchronized (FILE_LOAD_LOCKING) { + Object o = FILE_LOAD_LOCKING.get(file); + if (o == null) + FILE_LOAD_LOCKING.put(file, o = new Object()); + return o; + } + } + + private static class ParserData { + + private RecursiveBoolean conditionalValue; + + public ParserData() { + conditionalValue = null; + } + + public void addConditional(boolean val) { + if (conditionalValue == null) + conditionalValue = new RecursiveBoolean(val); + else + conditionalValue.createRecursive(val); + } + + public void unwrapLastConditional() { + if (conditionalValue != null && conditionalValue.unwrapLast()) + conditionalValue = null; + } + + public boolean getConditional() { + if (conditionalValue == null) + return true; + return conditionalValue.get(); + } + + } + + private static class RecursiveBoolean { + + private final boolean val; + private RecursiveBoolean recursive; + + public RecursiveBoolean(boolean val) { + this.val = val; + this.recursive = null; + } + + public boolean get() { + if (recursive != null) + return val && recursive.get(); + return val; + } + + public RecursiveBoolean createRecursive(boolean val) { + if (recursive != null) + return recursive.createRecursive(val); + return (recursive = new RecursiveBoolean(val)); + } + + public boolean unwrapLast() { + RecursiveBoolean recur = this; + while (recur.recursive != null && recur.recursive.recursive != null) + recur = recur.recursive; + if (recur != null && recur.recursive != null) { + recur.recursive = null; + return true; + } + return true; + } + + } + +} diff --git a/src/com/projectswg/common/javafx/FXMLController.java b/src/com/projectswg/common/javafx/FXMLController.java new file mode 100644 index 0000000..c3b5911 --- /dev/null +++ b/src/com/projectswg/common/javafx/FXMLController.java @@ -0,0 +1,40 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.javafx; + +import javafx.fxml.Initializable; +import javafx.scene.Parent; + +public interface FXMLController extends Initializable { + + Parent getRoot(); + default void terminate() { + + } + +} diff --git a/src/com/projectswg/common/javafx/FXMLUtilities.java b/src/com/projectswg/common/javafx/FXMLUtilities.java new file mode 100644 index 0000000..817986d --- /dev/null +++ b/src/com/projectswg/common/javafx/FXMLUtilities.java @@ -0,0 +1,88 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.javafx; + +import java.io.File; +import java.io.IOException; +import java.util.HashSet; +import java.util.Locale; +import java.util.ResourceBundle; +import java.util.Set; + +import com.projectswg.common.debug.Log; + +import javafx.fxml.FXMLLoader; + +public class FXMLUtilities { + + private static final Set CONTROLLERS = new HashSet<>(); + + public static void terminate() { + synchronized (CONTROLLERS) { + Log.i("Terminating FXML controllers"); + for (FXMLController controller : CONTROLLERS) { + controller.terminate(); + } + CONTROLLERS.clear(); + } + } + + public static ResourceBundle getResourceBundle(Locale locale) { + return ResourceBundle.getBundle("bundles.strings.strings", locale); + } + + public static void onFxmlLoaded(FXMLController controller) { + synchronized (CONTROLLERS) { + CONTROLLERS.add(controller); + } + } + + public static FXMLController loadFxml(String fxml, Locale locale) { + try { + File file = ResourceUtilities.getResource("fxml/" + fxml); + if (file == null || !file.isFile()) { + Log.e("Unable to load fxml - doesn't exist: %s", file); + return null; + } + Log.i("Loading fxml: %s", file); + FXMLLoader fxmlLoader = new FXMLLoader(file.toURI().toURL()); + fxmlLoader.setResources(getResourceBundle(locale)); + fxmlLoader.load(); + onFxmlLoaded(fxmlLoader.getController()); + synchronized (CONTROLLERS) { + CONTROLLERS.add(fxmlLoader.getController()); + } + return fxmlLoader.getController(); + } catch (IOException e) { + Log.e("Error loading fmxl: %s", fxml); + Log.e(e); + return null; + } + } + +} diff --git a/src/com/projectswg/common/javafx/PSWGBooleanProperty.java b/src/com/projectswg/common/javafx/PSWGBooleanProperty.java new file mode 100644 index 0000000..4bb794e --- /dev/null +++ b/src/com/projectswg/common/javafx/PSWGBooleanProperty.java @@ -0,0 +1,85 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.javafx; + +import java.util.HashSet; +import java.util.Set; + +import javafx.beans.property.Property; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.value.ChangeListener; + +public class PSWGBooleanProperty extends SimpleBooleanProperty { + + private final Set> listeners; + private final Set> properties; + + public PSWGBooleanProperty() { + super(); + listeners = new HashSet<>(); + properties = new HashSet<>(); + } + + public PSWGBooleanProperty(boolean initialValue) { + super(initialValue); + listeners = new HashSet<>(); + properties = new HashSet<>(); + } + + @Override + public void addListener(ChangeListener listener) { + listeners.add(listener); + super.addListener(listener); + } + + @Override + public void removeListener(ChangeListener listener) { + listeners.remove(listener); + super.removeListener(listener); + } + + public void removeAllListeners() { + for (ChangeListener listener : listeners) { + super.removeListener(listener); + } + } + + @Override + public void bindBidirectional(Property property) { + super.bindBidirectional(property); + properties.add(property); + } + + public void unbindAll() { + unbind(); + for (Property property : properties) { + super.unbindBidirectional(property); + } + } + +} diff --git a/src/com/projectswg/common/javafx/PSWGDoubleProperty.java b/src/com/projectswg/common/javafx/PSWGDoubleProperty.java new file mode 100644 index 0000000..dbeb51c --- /dev/null +++ b/src/com/projectswg/common/javafx/PSWGDoubleProperty.java @@ -0,0 +1,85 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.javafx; + +import java.util.HashSet; +import java.util.Set; + +import javafx.beans.property.Property; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.value.ChangeListener; + +public class PSWGDoubleProperty extends SimpleDoubleProperty { + + private final Set> listeners; + private final Set> properties; + + public PSWGDoubleProperty() { + super(); + listeners = new HashSet<>(); + properties = new HashSet<>(); + } + + public PSWGDoubleProperty(double initialValue) { + super(initialValue); + listeners = new HashSet<>(); + properties = new HashSet<>(); + } + + @Override + public void addListener(ChangeListener listener) { + listeners.add(listener); + super.addListener(listener); + } + + @Override + public void removeListener(ChangeListener listener) { + listeners.remove(listener); + super.removeListener(listener); + } + + public void removeAllListeners() { + for (ChangeListener listener : listeners) { + super.removeListener(listener); + } + } + + @Override + public void bindBidirectional(Property property) { + super.bindBidirectional(property); + properties.add(property); + } + + public void unbindAll() { + unbind(); + for (Property property : properties) { + super.unbindBidirectional(property); + } + } + +} diff --git a/src/com/projectswg/common/javafx/PSWGIntegerProperty.java b/src/com/projectswg/common/javafx/PSWGIntegerProperty.java new file mode 100644 index 0000000..cd19e11 --- /dev/null +++ b/src/com/projectswg/common/javafx/PSWGIntegerProperty.java @@ -0,0 +1,85 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.javafx; + +import java.util.HashSet; +import java.util.Set; + +import javafx.beans.property.Property; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.value.ChangeListener; + +public class PSWGIntegerProperty extends SimpleIntegerProperty { + + private final Set> listeners; + private final Set> properties; + + public PSWGIntegerProperty() { + super(); + listeners = new HashSet<>(); + properties = new HashSet<>(); + } + + public PSWGIntegerProperty(int initialValue) { + super(initialValue); + listeners = new HashSet<>(); + properties = new HashSet<>(); + } + + @Override + public void addListener(ChangeListener listener) { + listeners.add(listener); + super.addListener(listener); + } + + @Override + public void removeListener(ChangeListener listener) { + listeners.remove(listener); + super.removeListener(listener); + } + + public void removeAllListeners() { + for (ChangeListener listener : listeners) { + super.removeListener(listener); + } + } + + @Override + public void bindBidirectional(Property property) { + super.bindBidirectional(property); + properties.add(property); + } + + public void unbindAll() { + unbind(); + for (Property property : properties) { + super.unbindBidirectional(property); + } + } + +} diff --git a/src/com/projectswg/common/javafx/PSWGObjectProperty.java b/src/com/projectswg/common/javafx/PSWGObjectProperty.java new file mode 100644 index 0000000..6128268 --- /dev/null +++ b/src/com/projectswg/common/javafx/PSWGObjectProperty.java @@ -0,0 +1,104 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.javafx; + +import java.util.HashSet; +import java.util.Set; + +import javafx.beans.InvalidationListener; +import javafx.beans.property.Property; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ChangeListener; + +public class PSWGObjectProperty extends SimpleObjectProperty { + + private final Set> listeners; + private final Set invalidationListeners; + private final Set> properties; + + public PSWGObjectProperty() { + super(); + listeners = new HashSet<>(); + invalidationListeners = new HashSet<>(); + properties = new HashSet<>(); + } + + public PSWGObjectProperty(T initialValue) { + super(initialValue); + listeners = new HashSet<>(); + invalidationListeners = new HashSet<>(); + properties = new HashSet<>(); + } + + @Override + public void addListener(ChangeListener listener) { + super.addListener(listener); + listeners.add(listener); + } + + @Override + public void addListener(InvalidationListener listener) { + super.addListener(listener); + invalidationListeners.add(listener); + } + + @Override + public void removeListener(ChangeListener listener) { + super.removeListener(listener); + listeners.remove(listener); + } + + @Override + public void removeListener(InvalidationListener listener) { + super.removeListener(listener); + invalidationListeners.remove(listener); + } + + public void removeAllListeners() { + for (ChangeListener listener : listeners) { + super.removeListener(listener); + } + for (InvalidationListener listener : invalidationListeners) { + super.removeListener(listener); + } + } + + @Override + public void bindBidirectional(Property property) { + super.bindBidirectional(property); + properties.add(property); + } + + public void unbindAll() { + unbind(); + for (Property property : properties) { + super.unbindBidirectional(property); + } + } + +} diff --git a/src/com/projectswg/common/javafx/PSWGStringProperty.java b/src/com/projectswg/common/javafx/PSWGStringProperty.java new file mode 100644 index 0000000..44f73f5 --- /dev/null +++ b/src/com/projectswg/common/javafx/PSWGStringProperty.java @@ -0,0 +1,109 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.javafx; + +import java.text.Format; +import java.util.HashSet; +import java.util.Set; + +import javafx.beans.InvalidationListener; +import javafx.beans.property.Property; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.value.ChangeListener; +import javafx.util.StringConverter; + +public class PSWGStringProperty extends SimpleStringProperty { + + private final Set> listeners; + private final Set invalidationListeners; + private final Set> properties; + + public PSWGStringProperty() { + super(); + listeners = new HashSet<>(); + invalidationListeners = new HashSet<>(); + properties = new HashSet<>(); + } + + public PSWGStringProperty(String initialValue) { + super(initialValue); + listeners = new HashSet<>(); + invalidationListeners = new HashSet<>(); + properties = new HashSet<>(); + } + + @Override + public void addListener(ChangeListener listener) { + super.addListener(listener); + listeners.add(listener); + } + + @Override + public void addListener(InvalidationListener listener) { + super.addListener(listener); + invalidationListeners.add(listener); + } + + @Override + public void removeListener(ChangeListener listener) { + super.removeListener(listener); + listeners.remove(listener); + } + + public void removeAllListeners() { + for (ChangeListener listener : listeners) { + super.removeListener(listener); + } + for (InvalidationListener listener : invalidationListeners) { + super.removeListener(listener); + } + } + + public void bindBidirectional(Property property) { + super.bindBidirectional(property); + properties.add(property); + } + + public void bindBidirectional(Property property, Format format) { + super.bindBidirectional(property, format); + properties.add(property); + } + + public void bindBidirectional(Property property, StringConverter converter) { + super.bindBidirectional(property, converter); + properties.add(property); + } + + public void unbindAll() { + unbind(); + for (Property property : properties) { + super.unbindBidirectional(property); + } + } + +} diff --git a/src/com/projectswg/common/javafx/ResourceUtilities.java b/src/com/projectswg/common/javafx/ResourceUtilities.java new file mode 100644 index 0000000..4cf5e8d --- /dev/null +++ b/src/com/projectswg/common/javafx/ResourceUtilities.java @@ -0,0 +1,93 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.javafx; + +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.concurrent.atomic.AtomicReference; + +public class ResourceUtilities { + + private static final AtomicReference THEME = new AtomicReference<>("."); + private static final AtomicReference> SRC = new AtomicReference<>(ResourceUtilities.class); + private static final String RESOURCES_PATH = "/res/"; + + public static void setTheme(String theme) { + THEME.set(theme); + } + + public static void setPrimarySource(Class c) { + SRC.set(c); + } + + public static File getSourceDirectory() { + try { + return new File(SRC.get().getProtectionDomain().getCodeSource().getLocation().toURI()).getParentFile(); + } catch (URISyntaxException e) { + return null; + } + } + + public static File getResourcesDirectory() { + File source = getSourceDirectory(); + if (source == null) + return null; + return new File(source, RESOURCES_PATH); + } + + public static URI getResourceURI(String path) { + File file = getResource(path); + if (file == null) + return null; + return file.toURI(); + } + + public static File getResource(String path) { + try { + String parent = new File(SRC.get().getProtectionDomain().getCodeSource().getLocation().toURI()).getParent(); + return new File(parent, cleanupPath(RESOURCES_PATH + THEME.get() + '/' + path)); + } catch (URISyntaxException e) { + return null; + } + } + + public static File getGeneralResource(String path) { + try { + String parent = new File(SRC.get().getProtectionDomain().getCodeSource().getLocation().toURI()).getParent(); + return new File(parent, cleanupPath(RESOURCES_PATH + "common/" + path)); + } catch (URISyntaxException e) { + return null; + } + } + + private static String cleanupPath(String path) { + return path.replace('/', File.separatorChar); + } + +} diff --git a/src/com/projectswg/common/network/NetBuffer.java b/src/com/projectswg/common/network/NetBuffer.java new file mode 100644 index 0000000..d724652 --- /dev/null +++ b/src/com/projectswg/common/network/NetBuffer.java @@ -0,0 +1,241 @@ +/************************************************************************************ + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * -------------------------------------------------------------------------------- * + * * + * Holocore is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * Holocore 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 Affero General Public License for more details. * + * * + * You should have received a copy of the GNU Affero General Public License * + * along with Holocore. If not, see . * + * * + ***********************************************************************************/ +package com.projectswg.common.network; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.CharBuffer; +import java.nio.charset.Charset; + + +public class NetBuffer { + + public static final Charset ASCII = Charset.forName("UTF-8"); + public static final Charset UNICODE = Charset.forName("UTF-16LE"); + + private final ByteBuffer data; + private final int size; + + private NetBuffer(ByteBuffer data) { + this.data = data; + this.size = data.array().length; + } + + public static final NetBuffer allocate(int size) { + return new NetBuffer(ByteBuffer.allocate(size)); + } + + public static final NetBuffer wrap(byte [] data) { + return new NetBuffer(ByteBuffer.wrap(data)); + } + + public static final NetBuffer wrap(ByteBuffer data) { + return new NetBuffer(data); + } + + public boolean hasRemaining() { + return data.hasRemaining(); + } + + public int remaining() { + return data.remaining(); + } + + public int position() { + return data.position(); + } + + public void position(int position) { + data.position(position); + } + + public void seek(int relative) { + data.position(data.position()+relative); + } + + public ByteBuffer getBuffer() { + return data; + } + + public void addBoolean(boolean b) { + data.put(b ? (byte)1 : (byte)0); + } + + public void addAscii(String s) { + data.order(ByteOrder.LITTLE_ENDIAN); + data.putShort((short)s.length()); + data.put(s.getBytes(ASCII)); + } + + public void addAscii(char [] s) { + data.order(ByteOrder.LITTLE_ENDIAN); + data.putShort((short)s.length); + ByteBuffer bb = ASCII.encode(CharBuffer.wrap(s)); + byte [] bData = new byte[bb.limit()]; + bb.get(bData); + data.put(bData); + } + + public void addUnicode(String s) { + data.order(ByteOrder.LITTLE_ENDIAN); + data.putInt(s.length()); + data.put(s.getBytes(UNICODE)); + } + + public void addLong(long l) { + data.order(ByteOrder.LITTLE_ENDIAN).putLong(l); + } + + public void addInt(int i) { + data.order(ByteOrder.LITTLE_ENDIAN).putInt(i); + } + + public void addFloat(float f) { + data.putFloat(f); + } + + public void addShort(int i) { + data.order(ByteOrder.LITTLE_ENDIAN).putShort((short)i); + } + + public void addNetLong(long l) { + data.order(ByteOrder.BIG_ENDIAN).putLong(l); + } + + public void addNetInt(int i) { + data.order(ByteOrder.BIG_ENDIAN).putInt(i); + } + + public void addNetShort(int i) { + data.order(ByteOrder.BIG_ENDIAN).putShort((short)i); + } + + public void addByte(int b) { + data.put((byte)b); + } + + public void addArray(byte [] b) { + addShort(b.length); + data.put(b); + } + + public void addRawArray(byte [] b) { + data.put(b); + } + + public boolean getBoolean() { + return getByte() == 1 ? true : false; + } + + public String getAscii() { + data.order(ByteOrder.LITTLE_ENDIAN); + short length = data.getShort(); + if (length > data.remaining()) + return ""; + byte [] str = new byte[length]; + data.get(str); + return new String(str, ASCII); + } + + public String getUnicode() { + data.order(ByteOrder.LITTLE_ENDIAN); + int length = data.getInt() * 2; + if (length > data.remaining()) + return ""; + byte [] str = new byte[length]; + data.get(str); + return new String(str, UNICODE); + } + + public byte getByte() { + return data.get(); + } + + public short getShort() { + return data.order(ByteOrder.LITTLE_ENDIAN).getShort(); + } + + public int getInt() { + return data.order(ByteOrder.LITTLE_ENDIAN).getInt(); + } + + public float getFloat() { + return data.getFloat(); + } + + public long getLong() { + return data.order(ByteOrder.LITTLE_ENDIAN).getLong(); + } + + public short getNetShort() { + return data.order(ByteOrder.BIG_ENDIAN).getShort(); + } + + public int getNetInt() { + return data.order(ByteOrder.BIG_ENDIAN).getInt(); + } + + public long getNetLong() { + return data.order(ByteOrder.BIG_ENDIAN).getLong(); + } + + public byte [] getArray() { + byte [] bData = new byte[getShort()]; + data.get(bData); + return bData; + } + + public byte [] getArray(int size) { + byte [] bData = new byte[size]; + data.get(bData); + return bData; + } + + public byte [] array() { + return data.array(); + } + + public int size() { + return size; + } + + public byte [] copyArray() { + return copyArray(0, size); + } + + public byte [] copyArray(int offset, int length) { + if (length < 0) + throw new IllegalArgumentException("Length cannot be less than 0!"); + if (offset+length > size) + throw new IllegalArgumentException("Length extends past the end of the array!"); + byte [] ret = new byte[length]; + System.arraycopy(array(), offset, ret, 0, length); + return ret; + } + +} \ No newline at end of file diff --git a/src/com/projectswg/common/network/NetBufferStream.java b/src/com/projectswg/common/network/NetBufferStream.java new file mode 100644 index 0000000..e8e6438 --- /dev/null +++ b/src/com/projectswg/common/network/NetBufferStream.java @@ -0,0 +1,435 @@ +/************************************************************************************ + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * -------------------------------------------------------------------------------- * + * * + * Holocore is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * Holocore 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 Affero General Public License for more details. * + * * + * You should have received a copy of the GNU Affero General Public License * + * along with Holocore. If not, see . * + * * + ***********************************************************************************/ +package com.projectswg.common.network; + +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +public class NetBufferStream extends OutputStream { + + private final Object expansionMutex; + private final Object bufferMutex; + private NetBuffer buffer; + private int capacity; + private int size; + private int mark; + + public NetBufferStream() { + this(1024); + } + + public NetBufferStream(int size) { + if (size <= 0) + throw new NegativeArraySizeException("Size cannot be less than or equal to 0!"); + this.expansionMutex = new Object(); + this.bufferMutex = new Object(); + this.buffer = NetBuffer.allocate(size); + this.capacity = size; + this.size = 0; + this.mark = 0; + } + + @Override + public void close() { + reset(); + } + + @Override + public void flush() { + + } + + /** + * Sets the mark to the buffer's current position + */ + public void mark() { + synchronized (bufferMutex) { + mark = buffer.position(); + } + } + + /** + * Rewinds the buffer to the previously set mark + */ + public void rewind() { + synchronized (bufferMutex) { + buffer.position(mark); + mark = 0; + } + } + + /** + * Resets the buffer to the default capacity and clears all data + */ + public void reset() { + synchronized (expansionMutex) { + synchronized (bufferMutex) { + buffer = NetBuffer.allocate(1024); + capacity = 1024; + size = 0; + mark = 0; + } + } + } + + @Override + public void write(int b) { + ensureCapacity(size + 1); + synchronized (bufferMutex) { + buffer.array()[size] = (byte) b; + size++; + } + } + + public void write(byte [] data) { + write(data, 0, data.length); + } + + public void write(byte [] data, int offset, int length) { + ensureCapacity(size + length); + synchronized (bufferMutex) { + System.arraycopy(data, offset, buffer.array(), size, length); + size += length; + } + } + + public void write(ByteBuffer data) { + ensureCapacity(size + data.remaining()); + synchronized (bufferMutex) { + while (data.hasRemaining()) { + buffer.array()[size++] = data.get(); + } + } + } + + /** + * Moves all data from the buffer's current position to position 0. This + * method also adjusts the mark to be pointing to the same data + */ + public void compact() { + synchronized (bufferMutex) { + byte [] data = buffer.array(); + for (int i = buffer.position(), j = 0; i < size; ++i, ++j) { + data[j] = data[i]; + } + size -= buffer.position(); + mark -= buffer.position(); + buffer.position(0); + } + } + + public int remaining() { + synchronized (bufferMutex) { + return size - buffer.position(); + } + } + + public boolean hasRemaining() { + return remaining() > 0; + } + + public int position() { + synchronized (bufferMutex) { + return buffer.position(); + } + } + + public void position(int position) { + synchronized (bufferMutex) { + buffer.position(position); + } + } + + public void seek(int relative) { + synchronized (bufferMutex) { + buffer.seek(relative); + } + } + + public ByteBuffer getBuffer() { + synchronized (bufferMutex) { + return buffer.getBuffer(); + } + } + + public boolean getBoolean() { + synchronized (bufferMutex) { + return buffer.getBoolean(); + } + } + + public String getAscii() { + synchronized (bufferMutex) { + return buffer.getAscii(); + } + } + + public String getUnicode() { + synchronized (bufferMutex) { + return buffer.getUnicode(); + } + } + + public byte getByte() { + synchronized (bufferMutex) { + return buffer.getByte(); + } + } + + public short getShort() { + synchronized (bufferMutex) { + return buffer.getShort(); + } + } + + public int getInt() { + synchronized (bufferMutex) { + return buffer.getInt(); + } + } + + public float getFloat() { + synchronized (bufferMutex) { + return buffer.getFloat(); + } + } + + public long getLong() { + synchronized (bufferMutex) { + return buffer.getLong(); + } + } + + public short getNetShort() { + synchronized (bufferMutex) { + return buffer.getNetShort(); + } + } + + public int getNetInt() { + synchronized (bufferMutex) { + return buffer.getNetInt(); + } + } + + public long getNetLong() { + synchronized (bufferMutex) { + return buffer.getNetLong(); + } + } + + public byte[] getArray() { + synchronized (bufferMutex) { + return buffer.getArray(); + } + } + + public byte[] getArray(int size) { + synchronized (bufferMutex) { + return buffer.getArray(size); + } + } + + public void getList(NumericalIterator ni) { + int size = getInt(); + for (int i = 0; i < size; i++) + ni.onItem(i); + } + + public void addBoolean(boolean b) { + ensureCapacity(size+1); + synchronized (bufferMutex) { + buffer.addBoolean(b); + size++; + } + } + + public void addAscii(String s) { + ensureCapacity(size+2+s.length()); + synchronized (bufferMutex) { + buffer.addAscii(s); + size += 2 + s.length(); + } + } + + public void addAscii(char[] s) { + ensureCapacity(size+2+s.length); + synchronized (bufferMutex) { + buffer.addAscii(s); + size += 2 + s.length; + } + } + + public void addUnicode(String s) { + ensureCapacity(size+4+s.length()*2); + synchronized (bufferMutex) { + buffer.addUnicode(s); + size += 4 + s.length()*2; + } + } + + public void addLong(long l) { + ensureCapacity(size+8); + synchronized (bufferMutex) { + buffer.addLong(l); + size += 8; + } + } + + public void addInt(int i) { + ensureCapacity(size+4); + synchronized (bufferMutex) { + buffer.addInt(i); + size += 4; + } + } + + public void addFloat(float f) { + ensureCapacity(size+4); + synchronized (bufferMutex) { + buffer.addFloat(f); + size += 4; + } + } + + public void addShort(int i) { + ensureCapacity(size+2); + synchronized (bufferMutex) { + buffer.addShort(i); + size += 2; + } + } + + public void addNetLong(long l) { + ensureCapacity(size+8); + synchronized (bufferMutex) { + buffer.addNetLong(l); + size += 8; + } + } + + public void addNetInt(int i) { + ensureCapacity(size+4); + synchronized (bufferMutex) { + buffer.addNetInt(i); + size += 4; + } + } + + public void addNetShort(int i) { + ensureCapacity(size+2); + synchronized (bufferMutex) { + buffer.addNetShort(i); + size += 2; + } + } + + public void addByte(int b) { + ensureCapacity(size+1); + synchronized (bufferMutex) { + buffer.addByte(b); + size++; + } + } + + public void addArray(byte[] b) { + ensureCapacity(size+2+b.length); + synchronized (bufferMutex) { + buffer.addArray(b); + size += 2 + b.length; + } + } + + public void addList(List l, ListIterable r) { + addInt(l.size()); + for (T t : l) + r.onItem(t); + } + + public void addList(Set s, ListIterable r) { + addInt(s.size()); + for (T t : s) + r.onItem(t); + } + + public void addMap(Map m, MapIterable r) { + addInt(m.size()); + for (Entry e : m.entrySet()) + r.onItem(e); + } + + public void addRawArray(byte[] b) { + write(b); + } + + public byte [] array() { + synchronized (bufferMutex) { + return buffer.array(); + } + } + + public int size() { + return size; + } + + public int capacity() { + return capacity; + } + + private void ensureCapacity(int size) { + if (size <= capacity) + return; + synchronized (expansionMutex) { + while (size > capacity) + capacity <<= 2; + synchronized (bufferMutex) { + NetBuffer buf = NetBuffer.allocate(capacity); + System.arraycopy(buffer.array(), 0, buf.array(), 0, this.size); + buf.position(buffer.position()); + this.buffer = buf; + } + } + } + + public static interface ListIterable { + void onItem(T item); + } + + public static interface MapIterable { + void onItem(Entry item); + } + + public static interface NumericalIterator { + void onItem(int index); + } + +} diff --git a/src/com/projectswg/common/network/TCPServer.java b/src/com/projectswg/common/network/TCPServer.java new file mode 100644 index 0000000..6b01076 --- /dev/null +++ b/src/com/projectswg/common/network/TCPServer.java @@ -0,0 +1,278 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.network; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.ClosedByInterruptException; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.projectswg.common.callback.CallbackManager; +import com.projectswg.common.debug.Assert; +import com.projectswg.common.debug.Log; + +public class TCPServer { + + private final CallbackManager callbackManager; + private final Map sockets; + private final AtomicBoolean running; + private final InetAddress addr; + private final int port; + private final int bufferSize; + private final TCPServerListener listener; + private ServerSocketChannel channel; + + public TCPServer(int port, int bufferSize) { + this(null, port, bufferSize); + } + + public TCPServer(InetAddress addr, int port, int bufferSize) { + this.callbackManager = new CallbackManager<>("tcpserver-"+port, 1); + this.sockets = new HashMap<>(); + this.running = new AtomicBoolean(false); + this.addr = addr; + this.port = port; + this.bufferSize = bufferSize; + this.channel = null; + listener = new TCPServerListener(); + } + + public int getPort() { + return channel.socket().getLocalPort(); + } + + public void bind() throws IOException { + if (running.getAndSet(true)) { + Assert.fail(); + return; + } + callbackManager.start(); + channel = ServerSocketChannel.open(); + channel.socket().bind(new InetSocketAddress(addr, port)); + channel.configureBlocking(false); + listener.start(); + } + + public boolean disconnect(SocketAddress sock) { + Assert.notNull(sock); + synchronized (sockets) { + SocketChannel sc = sockets.get(sock); + Assert.notNull(sc); + sockets.remove(sock); + try { + Socket s = sc.socket(); + sc.close(); + callbackManager.callOnEach((callback) -> callback.onConnectionDisconnect(s, sock)); + return true; + } catch (IOException e) { + Log.e(e); + return false; + } + } + } + + private boolean disconnect(SocketChannel sc) { + try { + return disconnect(sc.getRemoteAddress()); + } catch (IOException e) { + return false; + } + } + + public boolean close() { + if (!running.getAndSet(false)) { + Assert.fail(); + return false; + } + callbackManager.stop(); + listener.stop(); + try { + channel.close(); + return true; + } catch (IOException e) { + Log.e(e); + } + return false; + } + + public boolean send(InetSocketAddress sock, ByteBuffer data) { + synchronized (sockets) { + SocketChannel sc = sockets.get(sock); + Assert.notNull(sc); + Assert.test(sc.isConnected()); + Assert.test(data.hasRemaining()); + try { + while (data.hasRemaining()) + sc.write(data); + return true; + } catch (IOException e) { + Log.e("TCPServer", "Terminated connection with %s. Error: %s", sock.toString(), e.getMessage()); + disconnect(sc); + } + } + return false; + } + + public void setCallback(TCPCallback callback) { + callbackManager.setCallback(callback); + } + + public interface TCPCallback { + void onIncomingConnection(Socket s); + void onConnectionDisconnect(Socket s, SocketAddress addr); + void onIncomingData(Socket s, byte [] data); + } + + private class TCPServerListener implements Runnable { + + private final ByteBuffer buffer; + private Thread thread; + private boolean running; + + public TCPServerListener() { + buffer = ByteBuffer.allocateDirect(bufferSize); + running = false; + thread = null; + } + + public void start() { + running = true; + thread = new Thread(this, "TCPServerListener-" + channel.socket().getLocalPort()); + thread.start(); + } + + public void stop() { + running = false; + if (thread != null) + thread.interrupt(); + thread = null; + } + + public void run() { + try (Selector selector = setupSelector()) { + while (running) { + try { + selector.select(); + processSelectionKeys(selector); + } catch (Exception e) { + Log.e(e); + try { + Thread.sleep(100); + } catch (InterruptedException e1) { + break; + } + } + } + } catch (IOException e) { + Log.e(e); + } + } + + private Selector setupSelector() throws IOException { + Selector selector = Selector.open(); + channel.register(selector, SelectionKey.OP_ACCEPT); + return selector; + } + + private void processSelectionKeys(Selector selector) throws ClosedChannelException { + for (SelectionKey key : selector.selectedKeys()) { + if (!key.isValid()) + continue; + if (key.isAcceptable()) { + accept(selector); + } else if (key.isReadable()) { + SelectableChannel selectable = key.channel(); + if (selectable instanceof SocketChannel) { + boolean canRead = true; + while (canRead) + canRead = read(key, (SocketChannel) selectable); + } + } + } + } + + private void accept(Selector selector) { + try { + while (channel.isOpen()) { + SocketChannel sc = channel.accept(); + if (sc == null) + break; + SocketChannel old = sockets.get(sc.getRemoteAddress()); + if (old != null) + disconnect(old); + sockets.put(sc.getRemoteAddress(), sc); + sc.configureBlocking(false); + sc.register(selector, SelectionKey.OP_READ); + callbackManager.callOnEach((callback) -> callback.onIncomingConnection(sc.socket())); + } + } catch (IOException e) { + Log.e(e); + } + } + + private boolean read(SelectionKey key, SocketChannel s) { + try { + buffer.position(0); + buffer.limit(bufferSize); + int n = s.read(buffer); + buffer.flip(); + if (n < 0) { + key.cancel(); + disconnect(s); + } else if (n > 0) { + ByteBuffer smaller = ByteBuffer.allocate(n); + smaller.put(buffer); + callbackManager.callOnEach((callback) -> callback.onIncomingData(s.socket(), smaller.array())); + return true; + } + } catch (IOException e) { + if (e.getMessage() != null && e.getMessage().toLowerCase(Locale.US).contains("connection reset")) + Log.e("TCPServer", "Connection Reset with %s", s.socket().getRemoteSocketAddress()); + else if (!(e instanceof ClosedByInterruptException)) + Log.e("TCPServer", e); + key.cancel(); + disconnect(s); + } + return false; + } + } + +} diff --git a/src/com/projectswg/common/network/TCPSocket.java b/src/com/projectswg/common/network/TCPSocket.java new file mode 100644 index 0000000..700fa5e --- /dev/null +++ b/src/com/projectswg/common/network/TCPSocket.java @@ -0,0 +1,222 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.network; + +import java.io.EOFException; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.projectswg.common.callback.CallbackManager; +import com.projectswg.common.concurrency.Delay; +import com.projectswg.common.debug.Assert; +import com.projectswg.common.debug.Log; + +public class TCPSocket { + + private final CallbackManager callbackManager; + private final TCPSocketListener listener; + private final InetSocketAddress address; + private final int bufferSize; + private SocketChannel socket; + + public TCPSocket(InetSocketAddress address, int bufferSize) { + this.callbackManager = new CallbackManager<>("tcpsocket-"+address, 1); + this.listener = new TCPSocketListener(); + this.address = address; + this.bufferSize = bufferSize; + this.socket = null; + } + + public int getBufferSize() { + return bufferSize; + } + + public InetSocketAddress getRemoteAddress() { + return address; + } + + public SocketChannel getSocket() { + return socket; + } + + public boolean isAlive() { + return socket != null && listener.isAlive(); + } + + public boolean isConnected() { + return socket != null && socket.isConnected(); + } + + public void setCallback(TCPSocketCallback callback) { + callbackManager.setCallback(callback); + } + + public void removeCallback() { + callbackManager.clearCallbacks(); + } + + public boolean connect() { + Assert.isNull(socket, "Socket must be null! Cannot connect twice!"); + Assert.test(!listener.isAlive(), "Listener must not be alive! Cannot connect twice!"); + try { + callbackManager.start(); + socket = SocketChannel.open(address); + if (socket.finishConnect()) { + listener.start(); + callbackManager.callOnEach((callback) -> callback.onConnected(this)); + return true; + } + } catch (IOException e) { + Log.e(e); + } + callbackManager.stop(); + return false; + } + + public boolean disconnect() { + if (socket == null) + return true; + try { + socket.close(); + if (listener.isAlive()) { + listener.stop(); + listener.awaitTermination(); + } + if (callbackManager.isRunning()) { + callbackManager.callOnEach((callback) -> callback.onDisconnected(this)); + callbackManager.stop(); + } + socket = null; + return true; + } catch (IOException e) { + Log.e(e); + } + return false; + } + + public boolean send(ByteBuffer data) { + try { + while (data.hasRemaining()) { + if (socket == null || socket.write(data) <= 0) + return false; + } + return true; + } catch (IOException e) { + Log.e(e); + } + return false; + } + + public interface TCPSocketCallback { + void onConnected(TCPSocket socket); + void onDisconnected(TCPSocket socket); + void onIncomingData(TCPSocket socket, byte [] data); + } + + private class TCPSocketListener implements Runnable { + + private final AtomicBoolean running; + private final AtomicBoolean alive; + + private Thread thread; + + public TCPSocketListener() { + this.running = new AtomicBoolean(false); + this.alive = new AtomicBoolean(false); + this.thread = null; + } + + public void start() { + Assert.test(!running.get(), "Cannot start listener! Already started!"); + Assert.isNull(thread, "Cannot start listener! Already started!"); + thread = new Thread(this); + running.set(true); + thread.start(); + } + + public void stop() { + Assert.test(running.get(), "Cannot stop listener! Already stopped!"); + Assert.notNull(thread, "Cannot stop listener! Already stopped!"); + running.set(false); + if (thread != null) + thread.interrupt(); + thread = null; + } + + public void awaitTermination() { + while (isAlive()) { + if (!Delay.sleepMicro(5)) + break; + } + } + + public boolean isAlive() { + return alive.get(); + } + + public void run() { + try { + alive.set(true); + SocketChannel sc = TCPSocket.this.socket; + int bufferSize = TCPSocket.this.bufferSize; + Assert.notNull(sc, "SocketChannel is null at start of listener run()!"); + Assert.test(bufferSize > 0, "Buffer size is <= 0 at start of listener run()!"); + ByteBuffer buf = ByteBuffer.allocateDirect(bufferSize); + while (running.get()) { + waitIncoming(sc, buf, bufferSize); + } + } catch (Throwable t) { + + } finally { + running.set(false); + alive.set(false); + thread = null; + disconnect(); + } + } + + private void waitIncoming(SocketChannel sc, ByteBuffer buf, int bufferSize) throws IOException { + buf.position(0); + buf.limit(bufferSize); + int n = sc.read(buf); + buf.flip(); + if (n == 0) + return; + if (n < 0) + throw new EOFException(); + byte [] data = new byte[n]; + buf.position(0); + buf.get(data, 0, n); + callbackManager.callOnEach((callback) -> callback.onIncomingData(TCPSocket.this, data)); + } + + } +} diff --git a/src/com/projectswg/common/utilities/ThreadUtilities.java b/src/com/projectswg/common/utilities/ThreadUtilities.java new file mode 100644 index 0000000..638ac6d --- /dev/null +++ b/src/com/projectswg/common/utilities/ThreadUtilities.java @@ -0,0 +1,60 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.utilities; + +import java.util.concurrent.ThreadFactory; + +public class ThreadUtilities { + + public static ThreadFactory newThreadFactory(String pattern) { + return new CustomThreadFactory(pattern); + } + + private static class CustomThreadFactory implements ThreadFactory { + + private final String pattern; + private int counter; + + public CustomThreadFactory(String pattern) { + this.pattern = pattern; + this.counter = 0; + } + + @Override + public Thread newThread(Runnable r) { + String name; + if (pattern.contains("%d")) + name = String.format(pattern, counter++); + else + name = pattern; + return new Thread(r, name); + } + + } + +} diff --git a/src/com/projectswg/common/utilities/TimeUtilities.java b/src/com/projectswg/common/utilities/TimeUtilities.java new file mode 100644 index 0000000..791df54 --- /dev/null +++ b/src/com/projectswg/common/utilities/TimeUtilities.java @@ -0,0 +1,98 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore 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 Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.common.utilities; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Locale; +import java.util.TimeZone; +import java.util.concurrent.TimeUnit; + +import com.projectswg.common.debug.Log; + + +public class TimeUtilities { + + private static final String DATE_TIME_FORMAT = "EEE, dd MMM yyyy HH:mm:ss z"; + private static final TimeZone CURRENT_TIMEZONE = TimeZone.getDefault(); + private static final TimeZone UTC = TimeZone.getTimeZone("UTC"); + private static final DateFormat DATE_TIME_FORMATTER_CUR = new SimpleDateFormat(DATE_TIME_FORMAT, Locale.US); + private static final DateFormat DATE_TIME_FORMATTER_UTC = new SimpleDateFormat(DATE_TIME_FORMAT, Locale.US); + + static { + DATE_TIME_FORMATTER_UTC.setTimeZone(UTC); + } + + public static long getTime() { + return System.currentTimeMillis() - CURRENT_TIMEZONE.getOffset(System.currentTimeMillis()); + } + + public static long getTime(long offset, TimeUnit unit) { + return getTime() + unit.toMillis(offset); + } + + public static String getDateStringUtc(long time) { + synchronized (DATE_TIME_FORMATTER_UTC) { + return DATE_TIME_FORMATTER_UTC.format(time); + } + } + + public static String getDateString(long time) { + synchronized (DATE_TIME_FORMATTER_CUR) { + return DATE_TIME_FORMATTER_CUR.format(time); + } + } + + public static long getTimeFromStringUtc(String str) { + synchronized (DATE_TIME_FORMATTER_UTC) { + try { + return DATE_TIME_FORMATTER_UTC.parse(str).getTime(); + } catch (ParseException e) { + Log.e(e); + return -1; + } + } + } + + public static long getTimeFromString(String str) { + synchronized (DATE_TIME_FORMATTER_CUR) { + try { + return DATE_TIME_FORMATTER_CUR.parse(str).getTime(); + } catch (ParseException e) { + Log.e(e); + return -1; + } + } + } + + public static long convertUtcToLocal(long time) { + return time + CURRENT_TIMEZONE.getOffset(time); + } + +}