mirror of
https://github.com/ProjectSWGCore/pswgcommon.git
synced 2026-01-17 00:04:25 -05:00
Merge branch 'master' of bitbucket.org:projectswg/pswgcommon
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-9"/>
|
||||
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
|
||||
<classpathentry kind="output" path="bin"/>
|
||||
</classpath>
|
||||
|
||||
11
.gitignore
vendored
11
.gitignore
vendored
@@ -1,4 +1,13 @@
|
||||
.settings
|
||||
.gradle
|
||||
|
||||
# IDE-based
|
||||
PSWGCommon.iml
|
||||
out
|
||||
.idea
|
||||
|
||||
# Gradle
|
||||
bin
|
||||
build
|
||||
.gradle
|
||||
gradle
|
||||
gradlew*
|
||||
|
||||
14
build.gradle
14
build.gradle
@@ -1,12 +1,12 @@
|
||||
apply plugin: 'java'
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'idea'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDirs = ['src']
|
||||
}
|
||||
resources {
|
||||
srcDirs = ['src']
|
||||
srcDir 'src'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,3 +15,7 @@ jar {
|
||||
from sourceSets.main.allSource
|
||||
archiveName = "PSWGCommon.jar"
|
||||
}
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
gradleVersion = "4.4"
|
||||
}
|
||||
|
||||
3
module-info.java
Normal file
3
module-info.java
Normal file
@@ -0,0 +1,3 @@
|
||||
module com.projectswg.launcher {
|
||||
requires java.base;
|
||||
}
|
||||
@@ -27,6 +27,9 @@
|
||||
***********************************************************************************/
|
||||
package com.projectswg.common.control;
|
||||
|
||||
import com.projectswg.common.concurrency.PswgThreadPool;
|
||||
import com.projectswg.common.debug.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -39,9 +42,6 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.projectswg.common.concurrency.PswgThreadPool;
|
||||
import com.projectswg.common.debug.Log;
|
||||
|
||||
public class IntentManager {
|
||||
|
||||
private static final AtomicReference<IntentManager> INSTANCE = new AtomicReference<>(null);
|
||||
@@ -70,6 +70,7 @@ public class IntentManager {
|
||||
if (!initialized.getAndSet(false))
|
||||
return;
|
||||
processThreads.stop(true);
|
||||
processThreads.awaitTermination(1000);
|
||||
}
|
||||
|
||||
public int getIntentCount() {
|
||||
|
||||
@@ -160,7 +160,9 @@ public abstract class Manager extends Service {
|
||||
return;
|
||||
}
|
||||
children.add(s);
|
||||
s.setIntentManager(getIntentManager());
|
||||
IntentManager manager = getIntentManager();
|
||||
if (manager != null)
|
||||
s.setIntentManager(manager);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
73
src/com/projectswg/common/control/PrimaryManager.java
Normal file
73
src/com/projectswg/common/control/PrimaryManager.java
Normal file
@@ -0,0 +1,73 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (c) 2018 /// 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 <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
package com.projectswg.common.control;
|
||||
|
||||
public class PrimaryManager {
|
||||
|
||||
private final Manager manager;
|
||||
|
||||
public PrimaryManager() {
|
||||
this(new DefaultManager());
|
||||
}
|
||||
|
||||
public PrimaryManager(Manager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
public void addChildService(Service service) {
|
||||
manager.addChildService(service);
|
||||
}
|
||||
|
||||
public boolean initialize() {
|
||||
return manager.initialize();
|
||||
}
|
||||
|
||||
public boolean start() {
|
||||
boolean ret = manager.start();
|
||||
if (!ret)
|
||||
terminate();
|
||||
return ret;
|
||||
}
|
||||
|
||||
public boolean isOperational() {
|
||||
return manager.isOperational();
|
||||
}
|
||||
|
||||
public boolean stop() {
|
||||
return manager.stop();
|
||||
}
|
||||
|
||||
public boolean terminate() {
|
||||
return manager.terminate();
|
||||
}
|
||||
|
||||
private static class DefaultManager extends Manager {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -74,7 +74,9 @@ public abstract class Service {
|
||||
* @return TRUE if termination was successful, FALSE otherwise
|
||||
*/
|
||||
public boolean terminate() {
|
||||
IntentManager.getInstance().terminate();
|
||||
IntentManager im = IntentManager.getInstance();
|
||||
if (im != null)
|
||||
im.terminate();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
/***********************************************************************************
|
||||
* 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 <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
* Copyright (c) 2017 /// 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 <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
package com.projectswg.common.data;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import com.projectswg.common.encoding.Encodable;
|
||||
import com.projectswg.common.network.NetBuffer;
|
||||
import com.projectswg.common.network.NetBufferStream;
|
||||
import com.projectswg.common.persistable.Persistable;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class CRC implements Encodable, Persistable {
|
||||
|
||||
private static final int CRC_TABLE[] = {
|
||||
@@ -101,7 +101,7 @@ public class CRC implements Encodable, Persistable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte [] encode() {
|
||||
public byte[] encode() {
|
||||
NetBuffer buffer = NetBuffer.allocate(4);
|
||||
buffer.addInt(crc);
|
||||
return buffer.array();
|
||||
@@ -127,45 +127,35 @@ public class CRC implements Encodable, Persistable {
|
||||
public void read(NetBufferStream stream) {
|
||||
crc = stream.getInt();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.crc;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (!(obj instanceof CRC)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return crc == ((CRC) obj).crc;
|
||||
}
|
||||
|
||||
public static int getCrc(String input) {
|
||||
return getCrc(input.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public static int getCrc(byte [] data) {
|
||||
int crc = 0xFFFFFFFF;
|
||||
for (int i = 0; i < data.length; i++)
|
||||
crc = CRC_TABLE[((crc>>>24) ^ data[i]) & 0xFF] ^ (crc << 8);
|
||||
return ~crc;
|
||||
return this == obj || (obj instanceof CRC && crc == ((CRC) obj).crc);
|
||||
}
|
||||
|
||||
public static String getString(int crc) {
|
||||
return CrcDatabase.getInstance().getString(crc);
|
||||
}
|
||||
|
||||
public static int getCrc(String input) {
|
||||
return getCrc(input.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public static int getCrc(byte[] data) {
|
||||
int crc = 0xFFFFFFFF;
|
||||
for (byte b : data)
|
||||
crc = CRC_TABLE[((crc >>> 24) ^ b) & 0xFF] ^ (crc << 8);
|
||||
return ~crc;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
192
src/com/projectswg/common/data/Pair.java
Normal file
192
src/com/projectswg/common/data/Pair.java
Normal file
@@ -0,0 +1,192 @@
|
||||
package com.projectswg.common.data;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.projectswg.common.debug.Assert;
|
||||
import com.projectswg.common.debug.Log;
|
||||
import com.projectswg.common.encoding.CachedEncode;
|
||||
import com.projectswg.common.encoding.Encodable;
|
||||
import com.projectswg.common.encoding.Encoder;
|
||||
import com.projectswg.common.encoding.StringType;
|
||||
import com.projectswg.common.network.NetBuffer;
|
||||
import com.projectswg.common.network.NetBufferStream;
|
||||
import com.projectswg.common.persistable.Persistable;
|
||||
|
||||
public class Pair<T, S> implements Encodable, Persistable {
|
||||
|
||||
private final StringType leftType;
|
||||
private final StringType rightType;
|
||||
private final Class<T> leftClass;
|
||||
private final Class<S> rightClass;
|
||||
private final CachedEncode cache;
|
||||
|
||||
private T left;
|
||||
private S right;
|
||||
|
||||
private Pair(T left, S right, Class<T> leftClass, Class<S> rightClass, StringType leftType, StringType rightType) {
|
||||
// Final checks - has to be either one or the other. These ensure other assumptions in the class will succeed
|
||||
Assert.test((leftType == null && !(left instanceof String) && !leftClass.equals(String.class)) ^ (leftType != null && (left instanceof String) && leftClass.equals(String.class)));
|
||||
Assert.test((rightType == null && !(right instanceof String) && !rightClass.equals(String.class)) ^ (rightType != null && (right instanceof String) && rightClass.equals(String.class)));
|
||||
this.leftClass = leftClass;
|
||||
this.rightClass = rightClass;
|
||||
this.leftType = leftType;
|
||||
this.rightType = rightType;
|
||||
this.cache = new CachedEncode(this::encodeImpl);
|
||||
setLeft(left);
|
||||
setRight(right);
|
||||
}
|
||||
|
||||
public T getLeft() {
|
||||
return left;
|
||||
}
|
||||
|
||||
public S getRight() {
|
||||
return right;
|
||||
}
|
||||
|
||||
public void setLeft(T val1) {
|
||||
this.left = val1;
|
||||
cache.clearCached();
|
||||
}
|
||||
|
||||
public void setRight(S val2) {
|
||||
this.right = val2;
|
||||
cache.clearCached();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(NetBuffer data) {
|
||||
setLeft(smartDecode(data, leftClass, leftType));
|
||||
setRight(smartDecode(data, rightClass, rightType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encode() {
|
||||
Objects.requireNonNull(left, "left is null in encode()");
|
||||
Objects.requireNonNull(right, "right is null in encode()");
|
||||
return cache.encode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
return cache.encode().length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(NetBufferStream stream) {
|
||||
smartSave(stream, left, leftType);
|
||||
smartSave(stream, right, rightType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(NetBufferStream stream) {
|
||||
smartRead(stream, left, leftClass, leftType);
|
||||
smartRead(stream, right, rightClass, rightType);
|
||||
}
|
||||
|
||||
private byte [] encodeImpl() {
|
||||
if (left == null || right == null)
|
||||
return new byte[0];
|
||||
|
||||
byte [] left = smartEncode(this.left, leftType);
|
||||
byte [] right = smartEncode(this.right, rightType);
|
||||
byte [] combined = new byte[left.length + right.length];
|
||||
System.arraycopy(left, 0, combined, 0, left.length);
|
||||
System.arraycopy(right, 0, combined, left.length, right.length);
|
||||
return combined;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked") // should succeed based on checks in constructor
|
||||
private static <U> U smartDecode(NetBuffer data, Class<U> klass, StringType strType) {
|
||||
if (strType != null)
|
||||
return (U) data.getString(strType);
|
||||
return (U) data.getGeneric(klass);
|
||||
}
|
||||
|
||||
private static byte [] smartEncode(Object obj, StringType strType) {
|
||||
if (strType != null)
|
||||
return Encoder.encode(obj, strType);
|
||||
return Encoder.encode(obj);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked") // should succeed based on checks in constructor
|
||||
private static <U> U smartRead(NetBufferStream data, U obj, Class<U> klass, StringType strType) {
|
||||
if (strType != null)
|
||||
return (U) data.getString(strType);
|
||||
|
||||
// Try making our own persistable
|
||||
if (obj == null && Persistable.class.isAssignableFrom(klass)) {
|
||||
try {
|
||||
obj = klass.getConstructor().newInstance();
|
||||
} catch (Exception e) {
|
||||
Log.e(e);
|
||||
}
|
||||
}
|
||||
|
||||
// If we succeeded, read it - if not, try grabbing some generic
|
||||
if (obj instanceof Persistable) {
|
||||
((Persistable) obj).read(data);
|
||||
} else {
|
||||
obj = (U) data.getGeneric(klass);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
private static void smartSave(NetBufferStream data, Object obj, StringType strType) {
|
||||
if (strType != null) {
|
||||
data.addString((String) obj, strType);
|
||||
} else if (obj instanceof Persistable) {
|
||||
((Persistable) obj).save(data);
|
||||
} else {
|
||||
data.write(Encoder.encode(obj));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked") // it's pretty obvious T.getClass() should be Class<T>
|
||||
public static <T, S> Pair<T, S> createPair(T left, S right) {
|
||||
Objects.requireNonNull(left, "Left cannot be null in this method! Instead call createPair(left, right, leftClass, rightClass)");
|
||||
Objects.requireNonNull(right, "Right cannot be null in this method! Instead call createPair(left, right, leftClass, rightClass)");
|
||||
return createPair(left, right, (Class<T>) left.getClass(), (Class<S>) right.getClass());
|
||||
}
|
||||
|
||||
public static <T, S> Pair<T, S> createPair(T left, S right, Class<T> leftClass, Class<S> rightClass) {
|
||||
Assert.test(!(left instanceof String));
|
||||
Assert.test(!(right instanceof String));
|
||||
return new Pair<>(left, right, leftClass, rightClass, null, null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked") // it's pretty obvious T.getClass() should be Class<T>
|
||||
public static <T> Pair<String, T> createPair(String left, T right, StringType type) {
|
||||
Objects.requireNonNull(right, "Right cannot be null in this method! Instead call createPair(left, right, type, rightClass)");
|
||||
return createPair(left, right, type, (Class<T>) right.getClass());
|
||||
}
|
||||
|
||||
public static <T> Pair<String, T> createPair(String left, T right, StringType type, Class<T> rightClass) {
|
||||
Assert.test(!(right instanceof String));
|
||||
return new Pair<>(left, right, String.class, rightClass, type, null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked") // it's pretty obvious T.getClass() should be Class<T>
|
||||
public static <T> Pair<T, String> createPair(T left, String right, StringType type) {
|
||||
Objects.requireNonNull(left, "Left cannot be null in this method! Instead call createPair(left, right, leftClass, type)");
|
||||
return createPair(left, right, (Class<T>) left.getClass(), type);
|
||||
}
|
||||
|
||||
public static <T> Pair<T, String> createPair(T left, String right, Class<T> leftClass, StringType type) {
|
||||
Assert.test(!(left instanceof String));
|
||||
return new Pair<>(left, right, leftClass, String.class, null, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a pair with two strings
|
||||
* @param left the left string, can be null
|
||||
* @param right the right string, can be null
|
||||
* @param leftType the type of the left string: ASCII/UNICODE
|
||||
* @param rightType the type of the right string: ASCII/UNICODE
|
||||
* @return the corresponding pair
|
||||
*/
|
||||
public static Pair<String, String> createPair(String left, String right, StringType leftType, StringType rightType) {
|
||||
return new Pair<>(left, right, String.class, String.class, leftType, rightType);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -349,7 +349,8 @@ public enum RadialItem {
|
||||
BIO_LINK (""),
|
||||
LANDMINE_DISARM (""),
|
||||
LANDMINE_REVERSE_TRIGGER (""),
|
||||
REWARD_TRADE_IN ("");
|
||||
REWARD_TRADE_IN (""),
|
||||
TRANSFER_CREDITS_TO_BANK_ACCOUNT ("@space/space_loot:use_credit_chip");
|
||||
|
||||
private static final EnumLookup<Integer, RadialItem> LOOKUP = new EnumLookup<>(RadialItem.class, RadialItem::getId);
|
||||
|
||||
|
||||
61
src/com/projectswg/common/debug/ThreadPrinter.java
Normal file
61
src/com/projectswg/common/debug/ThreadPrinter.java
Normal file
@@ -0,0 +1,61 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (c) 2018 /// 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 <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
package com.projectswg.common.debug;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ThreadPrinter {
|
||||
|
||||
public static void printActiveThreads() {
|
||||
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
|
||||
int threadCount = threadGroup.activeCount();
|
||||
Thread[] threadsRaw = new Thread[threadCount];
|
||||
threadCount = threadGroup.enumerate(threadsRaw);
|
||||
List<Thread> threads = Arrays.stream(threadsRaw, 0, threadCount).filter(t -> t.getState() != Thread.State.TERMINATED).collect(Collectors.toList());
|
||||
int maxLength = threads.stream().mapToInt(t -> t.getName().length()).max().orElse(4);
|
||||
if (maxLength < 4)
|
||||
maxLength = 4;
|
||||
|
||||
Log.w("Active Threads: %d", threads.size());
|
||||
Log.w("+-%s---%s-+", createRepeatingDash(maxLength), createRepeatingDash(13));
|
||||
Log.w("| %-" + maxLength + "s | %-13s |", "Name", "State");
|
||||
Log.w("+-%s-+-%s-+", createRepeatingDash(maxLength), createRepeatingDash(13));
|
||||
for (Thread t : threads) {
|
||||
Log.w("| %-" + maxLength + "s | %-13s |", t.getName(), t.getState());
|
||||
}
|
||||
Log.w("+-%s---%s-+", createRepeatingDash(maxLength), createRepeatingDash(13));
|
||||
}
|
||||
|
||||
private static String createRepeatingDash(int count) {
|
||||
return String.join("", Collections.nCopies(count, "-"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,11 +18,6 @@ public class FileLogWrapper implements LogWrapper {
|
||||
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 {
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
***********************************************************************************/
|
||||
package com.projectswg.common.network;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.Charset;
|
||||
@@ -208,7 +209,7 @@ public class NetBuffer {
|
||||
}
|
||||
|
||||
public boolean getBoolean() {
|
||||
return getByte() == 1 ? true : false;
|
||||
return getByte() == 1;
|
||||
}
|
||||
|
||||
public String getAscii() {
|
||||
@@ -298,16 +299,16 @@ public class NetBuffer {
|
||||
return booleans;
|
||||
}
|
||||
|
||||
public <T> Object getGeneric(Class<T> type) {
|
||||
public Object getGeneric(Class<?> type) {
|
||||
if (Encodable.class.isAssignableFrom(type)) {
|
||||
T instance = null;
|
||||
Object instance = null;
|
||||
try {
|
||||
instance = type.newInstance();
|
||||
instance = type.getConstructor().newInstance();
|
||||
((Encodable) instance).decode(this);
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
|
||||
Log.e(e);
|
||||
}
|
||||
|
||||
|
||||
return instance;
|
||||
} else if (Integer.class.isAssignableFrom(type) || Integer.TYPE.isAssignableFrom(type))
|
||||
return getInt();
|
||||
@@ -336,11 +337,11 @@ public class NetBuffer {
|
||||
|
||||
try {
|
||||
for (int i = 0; i < size; i++) {
|
||||
T instance = type.newInstance();
|
||||
T instance = type.getConstructor().newInstance();
|
||||
instance.decode(this);
|
||||
list.add(instance);
|
||||
}
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
|
||||
Log.e(e);
|
||||
}
|
||||
|
||||
@@ -383,9 +384,9 @@ public class NetBuffer {
|
||||
public <T extends Encodable> T getEncodable(Class<T> type) {
|
||||
T instance = null;
|
||||
try {
|
||||
instance = type.newInstance();
|
||||
instance = type.getConstructor().newInstance();
|
||||
instance.decode(this);
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
|
||||
Log.e(e);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import com.projectswg.common.encoding.StringType;
|
||||
|
||||
public class NetBufferStream extends OutputStream {
|
||||
|
||||
private final Object expansionMutex;
|
||||
@@ -189,6 +191,23 @@ public class NetBufferStream extends OutputStream {
|
||||
}
|
||||
}
|
||||
|
||||
public Object getGeneric(Class<?> type) {
|
||||
synchronized (bufferMutex) {
|
||||
return buffer.getGeneric(type);
|
||||
}
|
||||
}
|
||||
|
||||
public String getString(StringType type) {
|
||||
switch (type) {
|
||||
case ASCII:
|
||||
return getAscii();
|
||||
case UNICODE:
|
||||
return getUnicode();
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown string type: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
public String getAscii() {
|
||||
synchronized (bufferMutex) {
|
||||
return buffer.getAscii();
|
||||
@@ -261,6 +280,12 @@ public class NetBufferStream extends OutputStream {
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getArrayLarge() {
|
||||
synchronized (bufferMutex) {
|
||||
return buffer.getArrayLarge();
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getArray(int size) {
|
||||
synchronized (bufferMutex) {
|
||||
return buffer.getArray(size);
|
||||
@@ -281,6 +306,19 @@ public class NetBufferStream extends OutputStream {
|
||||
}
|
||||
}
|
||||
|
||||
public void addString(String s, StringType type) {
|
||||
switch (type) {
|
||||
case ASCII:
|
||||
addAscii(s);
|
||||
break;
|
||||
case UNICODE:
|
||||
addUnicode(s);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown string type: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
public void addAscii(String s) {
|
||||
ensureCapacity(size+2+s.length());
|
||||
synchronized (bufferMutex) {
|
||||
|
||||
140
src/com/projectswg/common/network/SecureSocketFactory.java
Normal file
140
src/com/projectswg/common/network/SecureSocketFactory.java
Normal file
@@ -0,0 +1,140 @@
|
||||
package com.projectswg.common.network;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketImpl;
|
||||
import java.net.SocketImplFactory;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
|
||||
public class SecureSocketFactory extends SSLSocketFactory implements SocketImplFactory {
|
||||
|
||||
private final AtomicBoolean loaded;
|
||||
private SSLSocketFactory sslSocketFactory;
|
||||
|
||||
public SecureSocketFactory() {
|
||||
this.loaded = new AtomicBoolean(false);
|
||||
this.sslSocketFactory = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(Socket arg0, InputStream arg1, boolean arg2) throws IOException {
|
||||
return sslSocketFactory.createSocket(arg0, arg1, arg2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(Socket arg0, String arg1, int arg2, boolean arg3) throws IOException {
|
||||
return sslSocketFactory.createSocket(arg0, arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String arg0, int arg1) throws IOException, UnknownHostException {
|
||||
return sslSocketFactory.createSocket(arg0, arg1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress arg0, int arg1) throws IOException {
|
||||
return sslSocketFactory.createSocket(arg0, arg1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String arg0, int arg1, InetAddress arg2, int arg3) throws IOException, UnknownHostException {
|
||||
return sslSocketFactory.createSocket(arg0, arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress arg0, int arg1, InetAddress arg2, int arg3) throws IOException {
|
||||
return sslSocketFactory.createSocket(arg0, arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return sslSocketFactory.equals(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getDefaultCipherSuites() {
|
||||
return sslSocketFactory.getDefaultCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedCipherSuites() {
|
||||
return sslSocketFactory.getSupportedCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return sslSocketFactory.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return sslSocketFactory.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketImpl createSocketImpl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the encryption mechanism
|
||||
* @param keystoreFile the keystore file
|
||||
* @param password the password for the keystore
|
||||
* @throws KeyStoreException if KeyManagerFactory.init or TrustManagerFactory.init fails
|
||||
* @throws NoSuchAlgorithmException if the algorithm for the keystore or key manager could not be found
|
||||
* @throws CertificateException if any of the certificates in the keystore could not be loaded
|
||||
* @throws FileNotFoundException if the keystore file does not exist
|
||||
* @throws IOException if there is an I/O or format problem with the keystore data, if a password is required but not given, or if the given password was incorrect. If the error is due to a wrong password, the cause of the IOException should be an UnrecoverableKeyException
|
||||
* @throws KeyManagementException if SSLContext.init fails
|
||||
* @throws UnrecoverableKeyException if the key cannot be recovered (e.g. the given password is wrong).
|
||||
*/
|
||||
public void load(File keystoreFile, char [] password) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, KeyManagementException, UnrecoverableKeyException {
|
||||
// First initialize the key and trust material
|
||||
KeyStore ksKeys = KeyStore.getInstance("JKS");
|
||||
ksKeys.load(new FileInputStream(keystoreFile), password);
|
||||
|
||||
// KeyManagers decide which key material to use
|
||||
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
|
||||
kmf.init(ksKeys, password);
|
||||
|
||||
// TrustManagers decide whether to allow connections
|
||||
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
|
||||
tmf.init(ksKeys);
|
||||
|
||||
// Used to create the
|
||||
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
|
||||
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
|
||||
|
||||
this.sslSocketFactory = sslContext.getSocketFactory();
|
||||
loaded.set(true);
|
||||
}
|
||||
|
||||
public boolean isLoaded() {
|
||||
return loaded.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket() throws IOException {
|
||||
if (!loaded.get())
|
||||
throw new IllegalStateException("SecureSocketFactory hasn't been loaded yet!");
|
||||
return sslSocketFactory.createSocket();
|
||||
}
|
||||
|
||||
}
|
||||
44
src/com/projectswg/common/network/TCPSecureSocket.java
Normal file
44
src/com/projectswg/common/network/TCPSecureSocket.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package com.projectswg.common.network;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.CertificateException;
|
||||
|
||||
public class TCPSecureSocket extends TCPSocket {
|
||||
|
||||
private final SecureSocketFactory socketFactory;
|
||||
|
||||
public TCPSecureSocket(InetSocketAddress address, int bufferSize) {
|
||||
super(address, bufferSize);
|
||||
this.socketFactory = new SecureSocketFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the encryption mechanism
|
||||
* @param keystoreFile the keystore file
|
||||
* @param password the password for the keystore
|
||||
* @throws KeyStoreException if KeyManagerFactory.init or TrustManagerFactory.init fails
|
||||
* @throws NoSuchAlgorithmException if the algorithm for the keystore or key manager could not be found
|
||||
* @throws CertificateException if any of the certificates in the keystore could not be loaded
|
||||
* @throws FileNotFoundException if the keystore file does not exist
|
||||
* @throws IOException if there is an I/O or format problem with the keystore data, if a password is required but not given, or if the given password was incorrect. If the error is due to a wrong password, the cause of the IOException should be an UnrecoverableKeyException
|
||||
* @throws KeyManagementException if SSLContext.init fails
|
||||
* @throws UnrecoverableKeyException if the key cannot be recovered (e.g. the given password is wrong).
|
||||
*/
|
||||
public void setupEncryption(File keystoreFile, char [] password) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, KeyManagementException, UnrecoverableKeyException {
|
||||
socketFactory.load(keystoreFile, password);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket() throws IOException {
|
||||
return socketFactory.createSocket();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,77 +1,82 @@
|
||||
/***********************************************************************************
|
||||
* 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 <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
* Copyright (c) 2017 /// 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 <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
package com.projectswg.common.network;
|
||||
|
||||
import com.projectswg.common.concurrency.PswgBasicThread;
|
||||
import com.projectswg.common.concurrency.PswgThreadPool;
|
||||
import com.projectswg.common.debug.Log;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
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.nio.channels.*;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.projectswg.common.callback.CallbackManager;
|
||||
import com.projectswg.common.debug.Assert;
|
||||
import com.projectswg.common.debug.Log;
|
||||
|
||||
public class TCPServer {
|
||||
public class TCPServer<T extends TCPServer.TCPSession> {
|
||||
|
||||
private final CallbackManager<TCPCallback> callbackManager;
|
||||
private final Map<SocketAddress, SocketChannel> sockets;
|
||||
private final PswgThreadPool callbackThread;
|
||||
private final Map<SocketChannel, T> channels;
|
||||
private final Map<Long, T> sessionIdToChannel;
|
||||
|
||||
private final PswgBasicThread listener;
|
||||
private final AtomicBoolean running;
|
||||
private final InetAddress addr;
|
||||
private final int port;
|
||||
private final int bufferSize;
|
||||
private final TCPServerListener listener;
|
||||
private final InetSocketAddress addr;
|
||||
private final Function<SocketChannel, T> sessionCreator;
|
||||
private ServerSocketChannel channel;
|
||||
|
||||
public TCPServer(int port, int bufferSize) {
|
||||
this(null, port, bufferSize);
|
||||
private final ByteBuffer buffer;
|
||||
private final ByteArrayOutputStream bufferStream;
|
||||
private final WritableByteChannel byteBufferChannel;
|
||||
|
||||
public TCPServer(int port, int bufferSize, Function<SocketChannel, T> sessionCreator) {
|
||||
this(new InetSocketAddress((InetAddress) null, port), bufferSize, sessionCreator);
|
||||
}
|
||||
|
||||
public TCPServer(InetAddress addr, int port, int bufferSize) {
|
||||
this.callbackManager = new CallbackManager<>("tcpserver-"+port, 1);
|
||||
this.sockets = new HashMap<>();
|
||||
public TCPServer(InetSocketAddress addr, int bufferSize, Function<SocketChannel, T> sessionCreator) {
|
||||
this.callbackThread = new PswgThreadPool(false, 1, "tcpserver-" + addr.getPort());
|
||||
this.channels = new ConcurrentHashMap<>();
|
||||
this.sessionIdToChannel = new ConcurrentHashMap<>();
|
||||
this.listener = new PswgBasicThread("tcpserver-listener-" + addr.getPort(), this::runListener);
|
||||
this.running = new AtomicBoolean(false);
|
||||
this.addr = addr;
|
||||
this.port = port;
|
||||
this.bufferSize = bufferSize;
|
||||
this.channel = null;
|
||||
listener = new TCPServerListener();
|
||||
this.sessionCreator = sessionCreator;
|
||||
this.buffer = ByteBuffer.allocateDirect(bufferSize);
|
||||
this.bufferStream = new ByteArrayOutputStream(bufferSize);
|
||||
this.byteBufferChannel = Channels.newChannel(bufferStream);
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
@@ -79,190 +84,222 @@ public class TCPServer {
|
||||
}
|
||||
|
||||
public void bind() throws IOException {
|
||||
if (running.getAndSet(true)) {
|
||||
Assert.fail();
|
||||
assert !running.get() : "TCPServer is already running";
|
||||
if (running.getAndSet(true))
|
||||
return;
|
||||
}
|
||||
callbackManager.start();
|
||||
callbackThread.start();
|
||||
channel = ServerSocketChannel.open();
|
||||
channel.socket().bind(new InetSocketAddress(addr, port));
|
||||
channel.bind(addr, 50);
|
||||
channel.configureBlocking(false);
|
||||
listener.start();
|
||||
}
|
||||
|
||||
public boolean disconnect(SocketAddress sock) {
|
||||
Assert.notNull(sock);
|
||||
synchronized (sockets) {
|
||||
SocketChannel sc = sockets.get(sock);
|
||||
if (sc == null)
|
||||
return true;
|
||||
sockets.remove(sock);
|
||||
try {
|
||||
sc.close();
|
||||
callbackManager.callOnEach((callback) -> callback.onConnectionDisconnect(sc, sock));
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
Log.e(e);
|
||||
return false;
|
||||
public void disconnect(long sessionId) {
|
||||
T session = sessionIdToChannel.remove(sessionId);
|
||||
if (session == null) {
|
||||
Log.w("TCPServer - unknown session id in disconnect: %d", sessionId);
|
||||
return;
|
||||
}
|
||||
|
||||
disconnect(session.getChannel());
|
||||
}
|
||||
|
||||
public void disconnect(T session) {
|
||||
disconnect(Objects.requireNonNull(session, "session").getChannel());
|
||||
}
|
||||
|
||||
public void disconnect(SocketChannel sc) {
|
||||
T session = channels.remove(sc);
|
||||
if (session == null) {
|
||||
Log.w("TCPServer - unknown channel in disconnect: %d", sc);
|
||||
return;
|
||||
}
|
||||
sessionIdToChannel.remove(session.getSessionId());
|
||||
|
||||
session.close();
|
||||
callbackThread.execute(session::onDisconnected);
|
||||
}
|
||||
|
||||
public T getSession(long sessionId) {
|
||||
return sessionIdToChannel.get(sessionId);
|
||||
}
|
||||
|
||||
public T getSession(SocketChannel sc) {
|
||||
return channels.get(sc);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
assert running.get() : "TCPServer isn't running";
|
||||
if (!running.getAndSet(false))
|
||||
return;
|
||||
callbackThread.stop(false);
|
||||
listener.stop(true);
|
||||
safeClose(channel);
|
||||
}
|
||||
|
||||
private void runListener() {
|
||||
try (Selector selector = Selector.open()) {
|
||||
channel.register(selector, SelectionKey.OP_ACCEPT);
|
||||
while (running.get()) {
|
||||
selector.select();
|
||||
accept(selector);
|
||||
selector.selectedKeys().forEach(this::read);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 SocketChannel getChannel(SocketAddress sock) {
|
||||
synchronized (sockets) {
|
||||
return sockets.get(sock);
|
||||
}
|
||||
}
|
||||
|
||||
public void setCallback(TCPCallback callback) {
|
||||
callbackManager.setCallback(callback);
|
||||
}
|
||||
|
||||
public interface TCPCallback {
|
||||
void onIncomingConnection(SocketChannel s, SocketAddress addr);
|
||||
void onConnectionDisconnect(SocketChannel s, SocketAddress addr);
|
||||
void onIncomingData(SocketChannel s, SocketAddress addr, 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
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 void accept(Selector selector) {
|
||||
try {
|
||||
while (channel.isOpen()) {
|
||||
SocketChannel sc = channel.accept();
|
||||
if (sc == null)
|
||||
return;
|
||||
sc.configureBlocking(false);
|
||||
sc.register(selector, SelectionKey.OP_READ);
|
||||
acceptConnection(sc);
|
||||
}
|
||||
} catch (ClosedChannelException e) {
|
||||
// Ignored
|
||||
} catch (Throwable t) {
|
||||
Log.w("TCPServer - IOException in accept(): %s", t.getMessage());
|
||||
}
|
||||
|
||||
private Selector setupSelector() throws IOException {
|
||||
Selector selector = Selector.open();
|
||||
channel.register(selector, SelectionKey.OP_ACCEPT);
|
||||
return selector;
|
||||
}
|
||||
|
||||
private void acceptConnection(SocketChannel sc) {
|
||||
T session = sessionCreator.apply(sc);
|
||||
if (session == null) {
|
||||
Log.w("Session creator for TCPServer-%d created a null session!", addr.getPort());
|
||||
safeClose(sc);
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (session.getChannel() != sc) {
|
||||
Log.w("Session creator for TCPServer-%d created a session with an invalid channel!", addr.getPort());
|
||||
safeClose(sc);
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
SocketAddress addr = sc.getRemoteAddress();
|
||||
callbackManager.callOnEach((callback) -> callback.onIncomingConnection(sc, addr));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(e);
|
||||
}
|
||||
channels.put(sc, session);
|
||||
sessionIdToChannel.put(session.getSessionId(), session);
|
||||
callbackThread.execute(session::onConnected);
|
||||
}
|
||||
|
||||
private void read(SelectionKey key) {
|
||||
SelectableChannel selectableChannel = key.channel();
|
||||
if (selectableChannel == channel)
|
||||
return;
|
||||
SocketChannel sc = (SocketChannel) selectableChannel;
|
||||
T session = getSession(sc);
|
||||
if (session == null || !sc.isConnected()) {
|
||||
invalidate(sc, key);
|
||||
return;
|
||||
}
|
||||
|
||||
private boolean read(SelectionKey key, SocketChannel s) {
|
||||
try {
|
||||
buffer.position(0);
|
||||
buffer.limit(bufferSize);
|
||||
int n = s.read(buffer);
|
||||
try {
|
||||
bufferStream.reset();
|
||||
int n = 1;
|
||||
while (n > 0) {
|
||||
buffer.clear();
|
||||
n = sc.read(buffer);
|
||||
buffer.flip();
|
||||
if (n < 0) {
|
||||
key.cancel();
|
||||
disconnect(s);
|
||||
} else if (n > 0) {
|
||||
ByteBuffer smaller = ByteBuffer.allocate(n);
|
||||
smaller.put(buffer);
|
||||
SocketAddress addr = s.getRemoteAddress();
|
||||
callbackManager.callOnEach((callback) -> callback.onIncomingData(s, addr, smaller.array()));
|
||||
return true;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (e.getMessage() != null && e.getMessage().toLowerCase(Locale.US).contains("connection reset"))
|
||||
Log.e("Connection Reset with %s", s.socket().getRemoteSocketAddress());
|
||||
else if (!(e instanceof ClosedByInterruptException))
|
||||
Log.e(e);
|
||||
key.cancel();
|
||||
disconnect(s);
|
||||
byteBufferChannel.write(buffer);
|
||||
}
|
||||
return false;
|
||||
if (bufferStream.size() > 0) {
|
||||
byte[] data = bufferStream.toByteArray();
|
||||
callbackThread.execute(() -> session.onIncomingData(data));
|
||||
}
|
||||
if (n < 0) {
|
||||
invalidate(sc, key);
|
||||
}
|
||||
} catch (ClosedChannelException e) {
|
||||
// Ignored
|
||||
} catch (Throwable t) {
|
||||
Log.w("TCPServer - IOException in read(): %s", t.getMessage());
|
||||
invalidate(sc, key);
|
||||
}
|
||||
}
|
||||
|
||||
private void invalidate(SocketChannel sc, SelectionKey key) {
|
||||
key.cancel();
|
||||
disconnect(sc);
|
||||
}
|
||||
|
||||
private static void safeClose(Channel c) {
|
||||
try {
|
||||
c.close();
|
||||
} catch (IOException e) {
|
||||
// Ignored - as long as it's closed
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class TCPSession {
|
||||
|
||||
private static final AtomicLong GLOBAL_SESSION_ID = new AtomicLong(0);
|
||||
|
||||
private final SocketChannel sc;
|
||||
private final SocketAddress addr;
|
||||
private final long sessionId;
|
||||
|
||||
protected TCPSession(SocketChannel sc) {
|
||||
this.sc = Objects.requireNonNull(sc, "socket");
|
||||
this.sessionId = GLOBAL_SESSION_ID.incrementAndGet();
|
||||
|
||||
SocketAddress addr;
|
||||
try {
|
||||
addr = sc.getRemoteAddress();
|
||||
} catch (IOException e) {
|
||||
addr = null;
|
||||
}
|
||||
this.addr = addr;
|
||||
}
|
||||
|
||||
protected void onConnected() {
|
||||
|
||||
}
|
||||
|
||||
protected void onDisconnected() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a globally unique session id for this particular connection
|
||||
* @return the unique session id
|
||||
*/
|
||||
protected final long getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the socket channel associated with this session
|
||||
* @return the socket channel
|
||||
*/
|
||||
protected final SocketChannel getChannel() {
|
||||
return sc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the remote address that this socket is/was connected to
|
||||
* @return the remote socket address
|
||||
*/
|
||||
@SuppressWarnings("unused") // open for subclass to use
|
||||
protected final SocketAddress getRemoteAddress() {
|
||||
return addr;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused") // open for subclass to use
|
||||
protected void writeToChannel(ByteBuffer data) throws IOException {
|
||||
sc.write(data);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused") // open for subclass to use
|
||||
protected void writeToChannel(byte [] data) throws IOException {
|
||||
sc.write(ByteBuffer.wrap(data));
|
||||
}
|
||||
|
||||
protected void close() {
|
||||
safeClose(sc);
|
||||
}
|
||||
|
||||
protected abstract void onIncomingData(byte[] data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,10 +29,13 @@ package com.projectswg.common.network;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import com.projectswg.common.callback.CallbackManager;
|
||||
import com.projectswg.common.concurrency.Delay;
|
||||
@@ -43,16 +46,33 @@ public class TCPSocket {
|
||||
|
||||
private final CallbackManager<TCPSocketCallback> callbackManager;
|
||||
private final TCPSocketListener listener;
|
||||
private final InetSocketAddress address;
|
||||
private final AtomicReference<InetSocketAddress> address;
|
||||
private final AtomicReference<SocketState> state;
|
||||
private final Object stateMutex;
|
||||
private final int bufferSize;
|
||||
private SocketChannel socket;
|
||||
private Socket socket;
|
||||
private InputStream socketInputStream;
|
||||
private OutputStream socketOutputStream;
|
||||
|
||||
public TCPSocket(InetSocketAddress address, int bufferSize) {
|
||||
this.callbackManager = new CallbackManager<>("tcpsocket-"+address, 1);
|
||||
this.listener = new TCPSocketListener();
|
||||
this.address = address;
|
||||
this.address = new AtomicReference<>(address);
|
||||
this.state = new AtomicReference<>(SocketState.CLOSED);
|
||||
this.stateMutex = new Object();
|
||||
this.bufferSize = bufferSize;
|
||||
|
||||
this.socket = null;
|
||||
this.socketInputStream = null;
|
||||
this.socketOutputStream = null;
|
||||
}
|
||||
|
||||
public TCPSocket(int bufferSize) {
|
||||
this(null, bufferSize);
|
||||
}
|
||||
|
||||
public TCPSocket() {
|
||||
this(1024);
|
||||
}
|
||||
|
||||
public int getBufferSize() {
|
||||
@@ -60,19 +80,27 @@ public class TCPSocket {
|
||||
}
|
||||
|
||||
public InetSocketAddress getRemoteAddress() {
|
||||
return address;
|
||||
return address.get();
|
||||
}
|
||||
|
||||
public SocketChannel getSocket() {
|
||||
public void setRemoteAddress(InetSocketAddress address) {
|
||||
this.address.set(address);
|
||||
}
|
||||
|
||||
public Socket getSocket() {
|
||||
return socket;
|
||||
}
|
||||
|
||||
public boolean isAlive() {
|
||||
return socket != null && listener.isAlive();
|
||||
synchronized (stateLock()) {
|
||||
return socket != null && listener.isAlive();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return socket != null && socket.isConnected();
|
||||
synchronized (stateLock()) {
|
||||
return socket != null && socket.isConnected();
|
||||
}
|
||||
}
|
||||
|
||||
public void setCallback(TCPSocketCallback callback) {
|
||||
@@ -83,57 +111,114 @@ public class TCPSocket {
|
||||
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);
|
||||
public void createConnection() throws IOException {
|
||||
synchronized (stateLock()) {
|
||||
checkAndSetState(SocketState.CLOSED, SocketState.CREATED);
|
||||
socket = createSocket();
|
||||
}
|
||||
socket = null;
|
||||
callbackManager.stop();
|
||||
return false;
|
||||
}
|
||||
|
||||
public void startConnection() throws IOException {
|
||||
synchronized (stateLock()) {
|
||||
try {
|
||||
checkAndSetState(SocketState.CREATED, SocketState.CONNECTING);
|
||||
socket.connect(getRemoteAddress());
|
||||
socketInputStream = socket.getInputStream();
|
||||
socketOutputStream = socket.getOutputStream();
|
||||
} catch (IOException e) {
|
||||
checkAndSetState(SocketState.CONNECTING, SocketState.CLOSED);
|
||||
socket = null;
|
||||
socketInputStream = null;
|
||||
socketOutputStream = null;
|
||||
throw e;
|
||||
}
|
||||
|
||||
callbackManager.start();
|
||||
listener.start();
|
||||
checkAndSetState(SocketState.CONNECTING, SocketState.CONNECTED);
|
||||
callbackManager.callOnEach((callback) -> callback.onConnected(this));
|
||||
}
|
||||
}
|
||||
|
||||
public void connect() throws IOException {
|
||||
createConnection();
|
||||
startConnection();
|
||||
}
|
||||
|
||||
public boolean disconnect() {
|
||||
if (socket == null)
|
||||
return true;
|
||||
try {
|
||||
socket.close();
|
||||
if (listener.isAlive()) {
|
||||
listener.stop();
|
||||
listener.awaitTermination();
|
||||
synchronized (stateLock()) {
|
||||
if (socket == null)
|
||||
return true;
|
||||
try {
|
||||
checkAndSetState(SocketState.CONNECTED, SocketState.CLOSED);
|
||||
socket.close();
|
||||
socket = null;
|
||||
socketInputStream = null;
|
||||
socketOutputStream = null;
|
||||
|
||||
if (listener.isAlive()) {
|
||||
listener.stop();
|
||||
listener.awaitTermination();
|
||||
}
|
||||
|
||||
if (callbackManager.isRunning()) {
|
||||
callbackManager.callOnEach((callback) -> callback.onDisconnected(this));
|
||||
callbackManager.stop();
|
||||
}
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
Log.e(e);
|
||||
}
|
||||
if (callbackManager.isRunning()) {
|
||||
callbackManager.callOnEach((callback) -> callback.onDisconnected(this));
|
||||
callbackManager.stop();
|
||||
}
|
||||
socket = null;
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
Log.e(e);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean send(NetBuffer data) {
|
||||
return send(data.array(), data.position(), data.remaining());
|
||||
}
|
||||
|
||||
public boolean send(ByteBuffer data) {
|
||||
try {
|
||||
while (data.hasRemaining()) {
|
||||
if (socket == null || socket.write(data) <= 0)
|
||||
return send(data.array(), data.position(), data.remaining());
|
||||
}
|
||||
|
||||
public boolean send(byte [] data) {
|
||||
return send(data, 0, data.length);
|
||||
}
|
||||
|
||||
public boolean send(byte [] data, int offset, int length) {
|
||||
synchronized (stateLock()) {
|
||||
try {
|
||||
if (socket == null)
|
||||
return false;
|
||||
|
||||
if (length > 0)
|
||||
socketOutputStream.write(data, offset, length);
|
||||
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
Log.e(e);
|
||||
}
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
Log.e(e);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected Socket createSocket() throws IOException {
|
||||
return new Socket();
|
||||
}
|
||||
|
||||
protected final Object stateLock() {
|
||||
return stateMutex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the current state to see if it matches the expected, and if so, changes it to the new state. If not, it fails the assertion
|
||||
* @param expected the expected state
|
||||
* @param state the new state
|
||||
*/
|
||||
private void checkAndSetState(SocketState expected, SocketState state) {
|
||||
Assert.notNull(expected, "Expected state cannot be null!");
|
||||
Assert.notNull(state, "New state cannot be null!");
|
||||
Assert.test(this.state.compareAndSet(expected, state), "Failed to set state! Was: " + this.state.get() + " Expected: " + expected + " Update: " + state);
|
||||
}
|
||||
|
||||
public interface TCPSocketCallback {
|
||||
@@ -142,6 +227,13 @@ public class TCPSocket {
|
||||
void onIncomingData(TCPSocket socket, byte [] data);
|
||||
}
|
||||
|
||||
private enum SocketState {
|
||||
CLOSED,
|
||||
CREATED,
|
||||
CONNECTING,
|
||||
CONNECTED
|
||||
}
|
||||
|
||||
private class TCPSocketListener implements Runnable {
|
||||
|
||||
private final AtomicBoolean running;
|
||||
@@ -158,7 +250,7 @@ public class TCPSocket {
|
||||
public void start() {
|
||||
Assert.test(!running.get(), "Cannot start listener! Already started!");
|
||||
Assert.isNull(thread, "Cannot start listener! Already started!");
|
||||
thread = new Thread(this);
|
||||
thread = new Thread(this, "TCPServer Port#" + getRemoteAddress().getPort());
|
||||
running.set(true);
|
||||
thread.start();
|
||||
}
|
||||
@@ -187,13 +279,10 @@ public class TCPSocket {
|
||||
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);
|
||||
InputStream input = TCPSocket.this.socketInputStream;
|
||||
byte [] buffer = new byte[TCPSocket.this.bufferSize];
|
||||
while (running.get()) {
|
||||
waitIncoming(sc, buf, bufferSize);
|
||||
waitIncoming(input, buffer);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
|
||||
@@ -205,18 +294,14 @@ public class TCPSocket {
|
||||
}
|
||||
}
|
||||
|
||||
private void waitIncoming(SocketChannel sc, ByteBuffer buf, int bufferSize) throws IOException {
|
||||
buf.position(0);
|
||||
buf.limit(bufferSize);
|
||||
int n = sc.read(buf);
|
||||
buf.flip();
|
||||
private void waitIncoming(InputStream input, byte [] buffer) throws IOException {
|
||||
int n = input.read(buffer);
|
||||
if (n == 0)
|
||||
return;
|
||||
if (n < 0)
|
||||
throw new EOFException();
|
||||
byte [] data = new byte[n];
|
||||
buf.position(0);
|
||||
buf.get(data, 0, n);
|
||||
System.arraycopy(buffer, 0, data, 0, n);
|
||||
callbackManager.callOnEach((callback) -> callback.onIncomingData(TCPSocket.this, data));
|
||||
}
|
||||
|
||||
|
||||
@@ -370,7 +370,7 @@ public enum PacketType {
|
||||
return null;
|
||||
Class <? extends SWGPacket> c = type.c;
|
||||
try {
|
||||
return c.newInstance();
|
||||
return c.getConstructor().newInstance();
|
||||
} catch (Exception e) {
|
||||
Log.e("Packet: [%08X] %s", crc, c.getName());
|
||||
Log.e(e);
|
||||
|
||||
94
src/com/projectswg/common/process/ArgumentParser.java
Normal file
94
src/com/projectswg/common/process/ArgumentParser.java
Normal file
@@ -0,0 +1,94 @@
|
||||
package com.projectswg.common.process;
|
||||
|
||||
import com.projectswg.common.debug.Log;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class ArgumentParser {
|
||||
|
||||
public static Map<String, String> parseArguments(String [] args, Function<String, String> renameArgument, Predicate<String>expectsArgument) {
|
||||
Map<String, String> arguments = new HashMap<>();
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
String arg = args[i];
|
||||
int equalIndex = arg.indexOf('=');
|
||||
|
||||
String key = arg;
|
||||
String value = null;
|
||||
if (equalIndex != -1)
|
||||
key = arg.substring(0, equalIndex);
|
||||
key = renameArgument.apply(key);
|
||||
|
||||
if (expectsArgument.test(key)) {
|
||||
if (equalIndex == -1) { // seek ahead for argument
|
||||
if (i + 1 < args.length)
|
||||
value = args[i+1];
|
||||
} else { // parse out argument
|
||||
value = arg.substring(equalIndex+1);
|
||||
}
|
||||
}
|
||||
arguments.put(key, value);
|
||||
}
|
||||
return arguments;
|
||||
}
|
||||
|
||||
public static void requireValue(Map<String, String> arguments, String key) {
|
||||
if (!arguments.containsKey(key) || arguments.get(key) == null) {
|
||||
throw new IllegalArgumentException(key + " - no argument");
|
||||
}
|
||||
}
|
||||
|
||||
public static void requireShort(Map<String, String> arguments, String key) {
|
||||
requireValue(arguments, key);
|
||||
try {
|
||||
Short.parseShort(arguments.get(key));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException(key + " - invalid argument");
|
||||
}
|
||||
}
|
||||
|
||||
public static void requireInt(Map<String, String> arguments, String key) {
|
||||
requireValue(arguments, key);
|
||||
try {
|
||||
Integer.parseInt(arguments.get(key));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException(key + " - invalid argument");
|
||||
}
|
||||
}
|
||||
|
||||
public static void requireLong(Map<String, String> arguments, String key) {
|
||||
requireValue(arguments, key);
|
||||
try {
|
||||
Long.parseLong(arguments.get(key));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException(key + " - invalid argument");
|
||||
}
|
||||
}
|
||||
|
||||
public static int parseShort(String str, int defaultValue) {
|
||||
try {
|
||||
return Short.parseShort(str);
|
||||
} catch (NumberFormatException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static int parseInt(String str, int defaultValue) {
|
||||
try {
|
||||
return Integer.parseInt(str);
|
||||
} catch (NumberFormatException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static long parseLong(String str, long defaultValue) {
|
||||
try {
|
||||
return Long.parseLong(str);
|
||||
} catch (NumberFormatException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
261
src/com/projectswg/common/process/JarProcessBuilder.java
Normal file
261
src/com/projectswg/common/process/JarProcessBuilder.java
Normal file
@@ -0,0 +1,261 @@
|
||||
package com.projectswg.common.process;
|
||||
|
||||
import com.projectswg.common.debug.Assert;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.ProcessBuilder.Redirect;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
public class JarProcessBuilder {
|
||||
|
||||
private final ProcessBuilder builder;
|
||||
private final String javaPath;
|
||||
private final String jarPath;
|
||||
private final List<String> vmArguments;
|
||||
private final List<String> arguments;
|
||||
private long minMemory;
|
||||
private long maxMemory;
|
||||
|
||||
public JarProcessBuilder(File java, File jar) {
|
||||
this.builder = new ProcessBuilder();
|
||||
String javaPath, jarPath;
|
||||
try {
|
||||
javaPath = java.getCanonicalPath();
|
||||
jarPath = jar.getCanonicalPath();
|
||||
} catch (IOException e) {
|
||||
javaPath = java.getAbsolutePath();
|
||||
jarPath = jar.getAbsolutePath();
|
||||
}
|
||||
this.javaPath = javaPath;
|
||||
this.jarPath = jarPath;
|
||||
this.vmArguments = new CopyOnWriteArrayList<>();
|
||||
this.arguments = new CopyOnWriteArrayList<>();
|
||||
this.minMemory = Long.MIN_VALUE;
|
||||
this.maxMemory = Long.MIN_VALUE;
|
||||
}
|
||||
|
||||
public JarProcessBuilder setMinMemory(long minMemory, MemoryUnit unit) {
|
||||
Assert.test(unit == MemoryUnit.KILOBYTES || unit == MemoryUnit.MEGABYTES || unit == MemoryUnit.GIGABYTES, "Unsupported memory unit!");
|
||||
this.minMemory = unit.getBytes(minMemory);
|
||||
return this;
|
||||
}
|
||||
|
||||
public JarProcessBuilder setMaxMemory(long maxMemory, MemoryUnit unit) {
|
||||
Assert.test(unit == MemoryUnit.KILOBYTES || unit == MemoryUnit.MEGABYTES || unit == MemoryUnit.GIGABYTES, "Unsupported memory unit!");
|
||||
this.maxMemory = unit.getBytes(maxMemory);
|
||||
return this;
|
||||
}
|
||||
|
||||
public JarProcessBuilder setMemory(long minMemory, long maxMemory, MemoryUnit unit) {
|
||||
Assert.test(unit == MemoryUnit.KILOBYTES || unit == MemoryUnit.MEGABYTES || unit == MemoryUnit.GIGABYTES, "Unsupported memory unit!");
|
||||
this.minMemory = unit.getBytes(minMemory);
|
||||
this.maxMemory = unit.getBytes(maxMemory);
|
||||
return this;
|
||||
}
|
||||
|
||||
public JarProcessBuilder setVMArguments(String ... args) {
|
||||
return setVMArguments(Arrays.asList(args));
|
||||
}
|
||||
|
||||
public JarProcessBuilder setVMArguments(List<String> args) {
|
||||
this.vmArguments.clear();
|
||||
this.vmArguments.addAll(args);
|
||||
return this;
|
||||
}
|
||||
|
||||
public JarProcessBuilder setArguments(String ... args) {
|
||||
return setArguments(Arrays.asList(args));
|
||||
}
|
||||
|
||||
public JarProcessBuilder setArguments(List<String> args) {
|
||||
this.arguments.clear();
|
||||
this.arguments.addAll(args);
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<String> command() {
|
||||
return builder.command();
|
||||
}
|
||||
|
||||
public Map<String, String> environment() {
|
||||
return builder.environment();
|
||||
}
|
||||
|
||||
public File directory() {
|
||||
return builder.directory();
|
||||
}
|
||||
|
||||
public JarProcessBuilder directory(File directory) {
|
||||
builder.directory(directory);
|
||||
return this;
|
||||
}
|
||||
|
||||
public JarProcessBuilder redirectInput(Redirect source) {
|
||||
builder.redirectInput(source);
|
||||
return this;
|
||||
}
|
||||
|
||||
public JarProcessBuilder redirectOutput(Redirect destination) {
|
||||
builder.redirectOutput(destination);
|
||||
return this;
|
||||
}
|
||||
|
||||
public JarProcessBuilder redirectError(Redirect destination) {
|
||||
builder.redirectError(destination);
|
||||
return this;
|
||||
}
|
||||
|
||||
public JarProcessBuilder redirectInput(File file) {
|
||||
builder.redirectInput(file);
|
||||
return this;
|
||||
}
|
||||
|
||||
public JarProcessBuilder redirectOutput(File file) {
|
||||
builder.redirectOutput(file);
|
||||
return this;
|
||||
}
|
||||
|
||||
public JarProcessBuilder redirectError(File file) {
|
||||
builder.redirectError(file);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Redirect redirectInput() {
|
||||
return builder.redirectInput();
|
||||
}
|
||||
|
||||
public Redirect redirectOutput() {
|
||||
return builder.redirectOutput();
|
||||
}
|
||||
|
||||
public Redirect redirectError() {
|
||||
return builder.redirectError();
|
||||
}
|
||||
|
||||
public JarProcessBuilder inheritIO() {
|
||||
builder.inheritIO();
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean redirectErrorStream() {
|
||||
return builder.redirectErrorStream();
|
||||
}
|
||||
|
||||
public JarProcessBuilder redirectErrorStream(boolean redirectErrorStream) {
|
||||
builder.redirectErrorStream(redirectErrorStream);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Process start() throws IOException {
|
||||
return builder.command(createCommand()).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return builder.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return builder.equals(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private List<String> createCommand() {
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add(javaPath);
|
||||
if (minMemory > 0) {
|
||||
MemoryUnit unit = MemoryUnit.getAppropriateUnit(minMemory);
|
||||
command.add("-Xms" + minMemory/unit.getFactor() + getJavaChar(unit));
|
||||
}
|
||||
if (maxMemory > 0) {
|
||||
MemoryUnit unit = MemoryUnit.getAppropriateUnit(maxMemory);
|
||||
command.add("-Xmx" + maxMemory/unit.getFactor() + getJavaChar(unit));
|
||||
}
|
||||
command.addAll(vmArguments);
|
||||
command.add("-jar");
|
||||
command.add(jarPath);
|
||||
command.addAll(arguments);
|
||||
return command;
|
||||
}
|
||||
|
||||
private static char getJavaChar(MemoryUnit unit) {
|
||||
switch (unit) {
|
||||
case BYTES:
|
||||
return 'b';
|
||||
case KILOBYTES:
|
||||
default:
|
||||
return 'k';
|
||||
case MEGABYTES:
|
||||
return 'm';
|
||||
case GIGABYTES:
|
||||
return 'g';
|
||||
case TERABYTES:
|
||||
return 't';
|
||||
}
|
||||
}
|
||||
|
||||
public static File getJavaPath() {
|
||||
return new File(System.getProperty("java.home"), "bin/java");
|
||||
}
|
||||
|
||||
public enum MemoryUnit {
|
||||
BYTES ("B", 1),
|
||||
KILOBYTES ("kB", 1024),
|
||||
MEGABYTES ("MB", 1048576),
|
||||
GIGABYTES ("GB", 1073741824),
|
||||
TERABYTES ("TB", 1099511627776L);
|
||||
|
||||
private final String suffix;
|
||||
private final long factor;
|
||||
|
||||
MemoryUnit(String suffix, long factor) {
|
||||
this.suffix = suffix;
|
||||
this.factor = factor;
|
||||
}
|
||||
|
||||
public String getSuffix() {
|
||||
return suffix;
|
||||
}
|
||||
|
||||
public long getFactor() {
|
||||
return factor;
|
||||
}
|
||||
|
||||
public long getBytes(double val) {
|
||||
return (long) (val * factor);
|
||||
}
|
||||
|
||||
public long getBytes(long val) {
|
||||
return val * factor;
|
||||
}
|
||||
|
||||
public double convert(double val, MemoryUnit unit) {
|
||||
long bytes = getBytes(val);
|
||||
return bytes / (double) unit.getFactor();
|
||||
}
|
||||
|
||||
public static MemoryUnit getAppropriateUnit(long val) {
|
||||
if (val < KILOBYTES.getFactor())
|
||||
return MemoryUnit.BYTES;
|
||||
if (val < MEGABYTES.getFactor())
|
||||
return MemoryUnit.KILOBYTES;
|
||||
if (val < GIGABYTES.getFactor())
|
||||
return MemoryUnit.MEGABYTES;
|
||||
if (val < TERABYTES.getFactor())
|
||||
return MemoryUnit.GIGABYTES;
|
||||
return MemoryUnit.TERABYTES;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
50
src/com/projectswg/common/utilities/LocalUtilities.java
Normal file
50
src/com/projectswg/common/utilities/LocalUtilities.java
Normal file
@@ -0,0 +1,50 @@
|
||||
package com.projectswg.common.utilities;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public class LocalUtilities {
|
||||
|
||||
private static final AtomicReference<String> APP_NAME = new AtomicReference<>(".projectswg");
|
||||
|
||||
public static void setApplicationName(String name) {
|
||||
APP_NAME.set(name);
|
||||
}
|
||||
|
||||
public static File getSubApplicationDirectory(String ... names) {
|
||||
File dir = getApplicationDirectory();
|
||||
for (String name : names) {
|
||||
dir = new File(dir, name);
|
||||
if (!dir.exists())
|
||||
dir.mkdir();
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
public static File getApplicationDirectory() {
|
||||
String home = getHomeDirectory();
|
||||
if (home == null)
|
||||
throw new IllegalStateException("Unknown home directory for OS '"+System.getProperty("os.name")+"'");
|
||||
|
||||
File dir = new File(home, APP_NAME.get());
|
||||
if (!dir.exists())
|
||||
dir.mkdirs();
|
||||
return dir;
|
||||
}
|
||||
|
||||
private static String getHomeDirectory() {
|
||||
String os = System.getProperty("os.name").toUpperCase();
|
||||
|
||||
if (os.contains("WIN"))
|
||||
return System.getenv("APPDATA");
|
||||
|
||||
if (os.contains("MAC"))
|
||||
return System.getProperty("user.home") + "/Library/Application Support";
|
||||
|
||||
if (os.contains("NUX"))
|
||||
return System.getProperty("user.home");
|
||||
|
||||
return System.getProperty("user.dir");
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user