commit 37fe627477286d8440b51e6579d7b23e8ef4de32 Author: 459985746472861716 (TudbuT#2624) Date: Sat Aug 22 14:26:35 2020 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f047d31 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +/build/classes/ +/build/generated/ +/build/libs/ +/build/tmp/ +/build/reports +/.gradle +/build/test-results +/.idea +*.png +deployToGitlab \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..aee97f1 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,37 @@ +# This file is a template, and might need editing before it works on your project. +# To contribute improvements to CI/CD templates, please follow the Development guide at: +# https://docs.gitlab.com/ee/development/cicd/templates.html +# This specific template is located at: +# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Getting-Started.gitlab-ci.yml + +# This is a sample GitLab CI/CD configuration file that should run without any modifications. +# It demonstrates a basic 3 stage CI/CD pipeline. Instead of real tests or scripts, +# it uses echo commands to simulate the pipeline execution. +# +# A pipeline is composed of independent jobs that run scripts, grouped into stages. +# Stages run in sequential order, but jobs within stages run in parallel. +# +# For more information, see: https://docs.gitlab.com/ee/ci/yaml/index.html#stages +# + +default: + image: openjdk:8 + +stages: # List of stages for jobs, and their order of execution + - build + - deploy + +build-job: # This job runs in the build stage, which runs first. + stage: build + script: + - echo "Compiling the code..." + - ./gradlew jar + - echo "Compile complete." + + +deploy-job: # This job runs in the deploy stage. + stage: deploy # It only runs when *both* jobs in the test stage complete successfully. + script: + - echo "Deploying application..." + - ./gradlew publish + - echo "Application successfully deployed." diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..73f69e0 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/discord.xml b/.idea/discord.xml new file mode 100644 index 0000000..cd711a0 --- /dev/null +++ b/.idea/discord.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..8fe4d04 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/serialmonitor_settings.xml b/.idea/serialmonitor_settings.xml new file mode 100644 index 0000000..751f26b --- /dev/null +++ b/.idea/serialmonitor_settings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..e96534f --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e289f75 --- /dev/null +++ b/LICENSE @@ -0,0 +1,41 @@ +Lesser TudbuT License + +Copyright (c) 2023 Daniel H. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, and/or distribute copies of the +Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +1. The above copyright notice, this permission notice, and these conditions + shall be included in all copies or substantial portions of the Software. + +2. The Software must not be (re-)sold or sublicensed without significant + modifications. + +3. If this Software is an application, that is a piece of Software which the + end-user directly interacts with in order to do some task, or one which + is run in a stand-alone fashion in order to provide a service, then the + addition or improvement of usable and/or meaningful functionality is a + significant modification. The user should also quickly be able to tell that + they are using the modified Software, upon beginning to use it, on the + basis of major improvements. + +4. If this Software is a library, that is a collection of utilities that is + NOT an application by itself, or is marketed as such, then the addition + of any aspects that make it an application usable by an end-user are + deemed significant modifications. An application may also be an extension + of an already-existing application adding functionality which is usable + and meaningful to the end-user. The developer should also quickly be able + to tell that they are using the modified Software on the basis of major + improvements. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0782cde --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +[![Codacy Badge](https://app.codacy.com/project/badge/Grade/b57be0d44d5c4a10a22124f5815cc2bb)](https://www.codacy.com/gh/TudbuT/tuddylib/dashboard?utm_source=github.com&utm_medium=referral&utm_content=TudbuT/tuddylib&utm_campaign=Badge_Grade) + +# tuddylib + + +TuddyLIB's package hell has been unified. + +To convert your own code, run `bash -c "find src -type f -print0 | while IFS= read -d \$'\0' s ; do (cat \"\$s\" | sed -E 's/([^.])tudbut\./\1de.tudbut./g' > \"\$s.tmp\") && mv \"\$s.tmp\" \"\$s\" && echo processed \"\$s\" ; done"` \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..4a9a08a --- /dev/null +++ b/build.gradle @@ -0,0 +1,84 @@ +import java.nio.file.Paths +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream + +plugins { + id 'java' + id 'maven-publish' +} + +repositories { + mavenCentral() +} + +sourceCompatibility = '1.8' +targetCompatibility = '1.8' + +compileJava { + options.encoding = "UTF-8" +} + +dependencies { + implementation 'org.jetbrains:annotations:20.1.0' + implementation 'junit:junit:4.13.1' + implementation 'org.junit.jupiter:junit-jupiter:5.7.0' +} + +test { +} + +javadoc { + options.encoding = "UTF-8" + source = sourceSets.main.allJava + classpath = configurations.compile +} + +javadoc { + doLast { + def f; (f = new FileOutputStream("build/docs/javadoc/stylesheet.css")).write( + new URL("https://raw.githubusercontent.com/TudbuT/tools/master/dark_javadoc.css").newInputStream(). + readLines().join("\n").getBytes() + ); f.close() + } +} + +jar { + doLast { + File jar = new File("build/libs/tuddylib.jar") + File loc = new File("TuddyLIB.jar") + jar.renameTo(loc) + + ZipOutputStream out = new ZipOutputStream(new FileOutputStream("TuddyLIB-javadoc.zip")) + new File("build/docs/javadoc").eachFileRecurse(groovy.io.FileType.FILES) { + out.putNextEntry(new ZipEntry(Paths.get("build/docs/javadoc").relativize(it.toPath()).toString())) + byte[] bytes = new byte[it.length() as int] + new FileInputStream(it).read(bytes) + out.write(bytes) + out.closeEntry() + } + out.close() + } +} + +//jar.dependsOn("javadoc") + +publishing { + publications { + maven(MavenPublication) { + artifact('TuddyLIB.jar') + } + } + repositories { + maven { + url = "${System.getenv('CI_API_V4_URL')}/groups//-/packages/maven" + name = "GitLab" + credentials(HttpHeaderCredentials) { + name = 'Job-Token' + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..be52383 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..cba06f8 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'tuddylib' + diff --git a/src/main/java/de/tudbut/algorithm/Algorithm.java b/src/main/java/de/tudbut/algorithm/Algorithm.java new file mode 100644 index 0000000..16e6465 --- /dev/null +++ b/src/main/java/de/tudbut/algorithm/Algorithm.java @@ -0,0 +1,17 @@ +package de.tudbut.algorithm; + +public abstract class Algorithm { + + public abstract T solve(Input[] inputs); + + + public static class Input { + protected T t; + protected String[] data; + + public Input(T t, String[] data) { + this.t = t; + this.data = data; + } + } +} diff --git a/src/main/java/de/tudbut/algorithm/AlgorithmAI.java b/src/main/java/de/tudbut/algorithm/AlgorithmAI.java new file mode 100644 index 0000000..7457fa1 --- /dev/null +++ b/src/main/java/de/tudbut/algorithm/AlgorithmAI.java @@ -0,0 +1,124 @@ +package de.tudbut.algorithm; + +import de.tudbut.parsing.TudSort; + +import java.util.ArrayList; +import java.util.Arrays; + +public class AlgorithmAI extends Algorithm { + + private ArrayList associations = new ArrayList<>(); + private int nextChoiceID = 1; + + public AlgorithmAI(Association[] associations) { + this.associations = new ArrayList<>(Arrays.asList(associations)); + for (int i = 0; i < associations.length; i++) { + if(associations[i].choiceID > nextChoiceID + 1) { + nextChoiceID = associations[i].choiceID + 1; + } + } + } + + public AlgorithmAI() { + } + + public Association[] getAssociations() { + return associations.toArray(new Association[0]); + } + + public void reward(float amount) { + int lastChoiceID = nextChoiceID - 1; + associations = new ArrayList<>(Arrays.asList(TudSort.sort(associations.toArray(new Association[0]), association -> association.choiceID))); + for (int j = 1, i = associations.size() - 1; i >= 0; i--) { + Association association = associations.get(i); + if(association.choiceID < lastChoiceID) { + j *= 0.5; + lastChoiceID = association.choiceID; + } + association.score += amount * j; + } + } + + public void punish(float amount) { + int lastChoiceID = nextChoiceID - 1; + associations = new ArrayList<>(Arrays.asList(TudSort.sort(associations.toArray(new Association[0]), association -> association.choiceID))); + for (int j = 1, i = associations.size() - 1; i >= 0; i--) { + Association association = associations.get(i); + if(association.choiceID < lastChoiceID) { + j *= 0.5; + lastChoiceID = association.choiceID; + } + association.score -= amount * j; + } + } + + @Override + public T solve(Input[] inputs) { + float highest = -Float.MAX_VALUE; + Input best = null; + for (int i = 0; i < inputs.length; i++) { + float score = score(inputs[i]); + if(score > highest) { + highest = score; + best = inputs[i]; + } + } + if(best != null) { + for (int i = 0; i < best.data.length; i++) { + Association association = associationFromString(best.data[i]); + if(association == null) + continue; + association.choiceID = nextChoiceID; + } + nextChoiceID++; + return best.t; + } else { + nextChoiceID++; + return null; + } + } + + private float score(Input input) { + float r = 0; + for (int i = 0; i < input.data.length; i++) { + try { + int finalI = i; + if (associations.stream().noneMatch(association -> association.string != null && input.data[finalI] != null && association.string.equals(input.data[finalI]))) { + Association association = new Association(); + association.string = input.data[i]; + associations.add(association); + } + Association association = associationFromString(input.data[i]); + if (association == null) + continue; + r += association.score; + } catch (NullPointerException ignore) { } + } + return r; + } + + private Association associationFromString(String s) { + for (int i = 0; i < associations.size(); i++) { + if(associations.get(i).string.equals(s)) + return associations.get(i); + } + return null; + } + + public static class Association { + private String string; + private float score = 0.0f; + private int choiceID = 0; + + public Association() { } + + public Association(String s, float score) { + this.string = s; + this.score = score; + } + + public String toString() { + return string + ":" + score + " " + choiceID + ";"; + } + } +} diff --git a/src/main/java/de/tudbut/async/Async.java b/src/main/java/de/tudbut/async/Async.java new file mode 100644 index 0000000..5a37dfe --- /dev/null +++ b/src/main/java/de/tudbut/async/Async.java @@ -0,0 +1,53 @@ +package de.tudbut.async; + +/** + * @author TudbuT + * @since 03 Jun 2022 + * Include this with import static. + */ +public class Async { + + static final ThreadLocal context = ThreadLocal.withInitial(() -> TaskQueue.main); + + public static T await(Task task) { + return task.await(); + } + + public static void context(TaskQueue queue) { + context.set(queue); + } + + public static Task t(TaskCallable callable) { + return new Task<>(callable); + } + + /** + * This can be problematic if errors might happen in the callable. use t() instead. + */ + public static Task s(TaskCallable callable) { + return new Task<>(callable).ok(); + } + + public static Task loop(TaskCallable condition, TaskCallable body) { + return new Task<>((res, rej) -> { + while(new Task<>(condition).err(rej).ok().await()) { + new Task<>(body).err(rej).ok().await(); + ((TaskQueue)Thread.currentThread()).processNextHere(); + } + res.call(null); + }); + } + + public static boolean unblockQueue() { + if(Thread.currentThread() instanceof TaskQueue) { + TaskQueue q = (TaskQueue)Thread.currentThread(); + if(q.queue.hasNext()) { + while(q.queue.hasNext()) { + q.processNextHere(); + } + return true; + } + } + return false; + } +} diff --git a/src/main/java/de/tudbut/async/Callback.java b/src/main/java/de/tudbut/async/Callback.java new file mode 100644 index 0000000..149098f --- /dev/null +++ b/src/main/java/de/tudbut/async/Callback.java @@ -0,0 +1,11 @@ +package de.tudbut.async; + +/** + * @author TudbuT + * @since 03 Jun 2022 + */ + +public interface Callback { + + void call(T t); +} diff --git a/src/main/java/de/tudbut/async/CallbackList.java b/src/main/java/de/tudbut/async/CallbackList.java new file mode 100644 index 0000000..63835e9 --- /dev/null +++ b/src/main/java/de/tudbut/async/CallbackList.java @@ -0,0 +1,38 @@ +package de.tudbut.async; + +import java.util.ArrayList; + +/** + * @author TudbuT + * @since 03 Jun 2022 + */ + +public class CallbackList implements Callback { + + private final ArrayList> callbacks = new ArrayList<>(); + private boolean done = false; + + public void add(Callback callback) { + callbacks.add(callback); + } + + @Override + public void call(T t) { + done = true; + for (int i = 0 ; i < callbacks.size() ; i++) { + callbacks.get(i).call(t); + } + } + + public boolean done() { + return done; + } + + public void setDone() { + done = true; + } + + public boolean exists() { + return callbacks.size() != 0; + } +} diff --git a/src/main/java/de/tudbut/async/ComposeCallback.java b/src/main/java/de/tudbut/async/ComposeCallback.java new file mode 100644 index 0000000..5d5e5bf --- /dev/null +++ b/src/main/java/de/tudbut/async/ComposeCallback.java @@ -0,0 +1,11 @@ +package de.tudbut.async; + +/** + * @author TudbuT + * @since 03 Jun 2022 + */ + +public interface ComposeCallback { + + void call(T t, Callback resolve, Callback reject); +} diff --git a/src/main/java/de/tudbut/async/InternalReject.java b/src/main/java/de/tudbut/async/InternalReject.java new file mode 100644 index 0000000..2b720bc --- /dev/null +++ b/src/main/java/de/tudbut/async/InternalReject.java @@ -0,0 +1,11 @@ +package de.tudbut.async; + +class InternalReject extends Error { + Task task; + Reject real; + + InternalReject(Task task, Reject real) { + this.task = task; + this.real = real; + } +} diff --git a/src/main/java/de/tudbut/async/Reject.java b/src/main/java/de/tudbut/async/Reject.java new file mode 100644 index 0000000..7564bee --- /dev/null +++ b/src/main/java/de/tudbut/async/Reject.java @@ -0,0 +1,23 @@ +package de.tudbut.async; + +/** + * @author TudbuT + * @since 03 Jun 2022 + */ + +public class Reject extends Error { + final Object real; + + public Reject(Throwable throwable) { + super(throwable); + real = throwable; + } + public Reject(Object object) { + super(object.toString()); + real = object; + } + + public T getReal() { + return (T) real; + } +} diff --git a/src/main/java/de/tudbut/async/Resolve.java b/src/main/java/de/tudbut/async/Resolve.java new file mode 100644 index 0000000..516d803 --- /dev/null +++ b/src/main/java/de/tudbut/async/Resolve.java @@ -0,0 +1,23 @@ +package de.tudbut.async; + +/** + * @author TudbuT + * @since 03 Jun 2022 + */ + +public class Resolve extends RuntimeException { + final Object real; + + public Resolve(Throwable throwable) { + super(throwable); + real = throwable; + } + public Resolve(Object object) { + super(object.toString()); + real = object; + } + + public T getReal() { + return (T) real; + } +} diff --git a/src/main/java/de/tudbut/async/Task.java b/src/main/java/de/tudbut/async/Task.java new file mode 100644 index 0000000..b33c315 --- /dev/null +++ b/src/main/java/de/tudbut/async/Task.java @@ -0,0 +1,115 @@ +package de.tudbut.async; + +/** + * @author TudbuT + * @since 03 Jun 2022 + */ + +public class Task { + TaskQueue queue; + final TaskCallable callable; + final CallbackList resolve = new CallbackList<>(); + final CallbackList reject = new CallbackList<>(); + private boolean done = false; + private T result = null; + private Throwable rejection = null; + boolean isAwaiting = false; + Task parent = null; + + public Task(TaskCallable callable) { + this.callable = callable; + resolve.add((t) -> this.result = t); + } + public Task(TaskCallable callable, Class clazz) { + this.callable = callable; + resolve.add((t) -> this.result = t); + } + + public Task then(Callback resolve) { + this.resolve.add(resolve); + if(this.resolve.done()) { + resolve.call(result); + } + return this; + } + + public Task compose(ComposeCallback callback) { + return new Task((res, rej) -> { + callback.call(result, res, rej); + }).appendTo(this); + } + + private Task appendTo(Task task) { + this.parent = task; + task.then(done -> { + task.queue.register(this); + }); + return this; + } + + public Task err(Callback reject) { + if(parent != null) + parent.err(reject); + this.reject.add(reject); + return this; + } + + public T await() { + try { + if(!done) { + isAwaiting = true; + // If it is in the queue already + if(Thread.currentThread() == queue) { + while(!done) { + // Work while awaiting + queue.processNextHere(); + } + } + else { + synchronized (this) { + if(!done) { + wait(); + } + } + } + } + } + catch (InterruptedException e) { + throw new RuntimeException(e); + } + if(rejection != null && !reject.exists()) + throw new Reject(rejection); + return result; + } + + void setDone(Throwable rejection) { + if(this.rejection == null) { + this.rejection = rejection; + } + if (!done) { + synchronized (this) { + done = true; + notifyAll(); + } + } + } + + public boolean done() { + return done; + } + + public Task ok() { + return ok(Async.context.get()); + } + public Task ok(TaskQueue queue) { + if(this.queue != null) + return this; + this.queue = queue; + if (parent != null) { + parent.ok(queue); + return this; + } + queue.register(this); + return this; + } +} diff --git a/src/main/java/de/tudbut/async/TaskCallable.java b/src/main/java/de/tudbut/async/TaskCallable.java new file mode 100644 index 0000000..4efb89f --- /dev/null +++ b/src/main/java/de/tudbut/async/TaskCallable.java @@ -0,0 +1,11 @@ +package de.tudbut.async; + +/** + * @author TudbuT + * @since 03 Jun 2022 + */ + +public interface TaskCallable { + + void execute(Callback resolve, Callback reject) throws Resolve, Reject; +} diff --git a/src/main/java/de/tudbut/async/TaskQueue.java b/src/main/java/de/tudbut/async/TaskQueue.java new file mode 100644 index 0000000..ace9a9a --- /dev/null +++ b/src/main/java/de/tudbut/async/TaskQueue.java @@ -0,0 +1,193 @@ +package de.tudbut.async; + +import java.util.concurrent.atomic.AtomicBoolean; + +import de.tudbut.global.DebugStateManager; +import de.tudbut.tools.Queue; + +/** + * @author TudbuT + * @since 03 Jun 2022 + */ + +public class TaskQueue extends Thread { + + public static final TaskQueue main = new TaskQueue(); + + private boolean stop = false; + final Queue> queue = new Queue<>(); + public final CallbackList rejectionHandlers = new CallbackList<>(); + private AtomicBoolean waiting = new AtomicBoolean(); + private boolean running = false; + private boolean queueEmpty = true; + + public TaskQueue() { + this.start(); + while (!waiting.get()); + try { + Thread.sleep(5); + } catch (InterruptedException e) {} + } + + public Queue> stopProcessing() throws IllegalAccessException { + if(this == main) + throw new IllegalAccessException("Can't stop main queue!"); + if(stop) + throw new IllegalStateException("Already stopped!"); + stop = true; + synchronized (this) { + this.notifyAll(); + } + return queue; + } + + public void finish() { + if(stop) + throw new IllegalStateException("Already stopped!"); + synchronized (this) { + this.notifyAll(); + } + while (queue.hasNext() || running || !queueEmpty) { + synchronized (queue) { + try { + queue.wait(); + } + catch (InterruptedException ignored) { + } + } + } + stop = true; + } + + Task register(Task task) { + task.queue = this; + queue.add(task); + synchronized (this) { + this.notifyAll(); + } + return task; + } + + /** + * + * @param task The task to make + * @param clazz The clazz of the return value. This is only used to determine T. + * @return The task that was created + */ + Task register(TaskCallable task, Class clazz) { + return register(new Task<>(task)); + } + + @Override + public void run() { + if(DebugStateManager.isDebugEnabled()) + System.out.println("[TaskQueue] Thread started."); + while (!stop) { + try { + synchronized (this) { + waiting.set(true); + wait(); + } + } + catch (InterruptedException e) { + return; + } + finally { + waiting.set(false); + } + if(stop) + return; + + process(); + } + } + + public void process() { + while(queue.hasNext()) { + queueEmpty = false; + try { + processNextHere(); + } + catch (Throwable e) { + if(!rejectionHandlers.exists()) { + System.err.println("!! Unhandled Task rejection:"); + e.printStackTrace(); + continue; + } + rejectionHandlers.call(e); + } + } + queueEmpty = true; + synchronized (queue) { + queue.notifyAll(); + } + } + + public void processNextHere() { + try { + if(!queue.hasNext()) + return; + + running = true; + Task task = (Task) queue.next(); + + // Execute the task. If it rejects using reject(), throw a reject to be handled by the exception block. + // If it resolves using throwResolve, redirect that to task.resolve. + // If it throws something, redirect that to task.reject if possible, otherwise throw a Reject of it to be handled. + try { + try { + task.callable.execute((t) -> { + if (!task.resolve.done()) + task.resolve.call(t); + task.setDone(null); + }, (t) -> { + throw new InternalReject(task, new Reject(t)); + }); + } + catch (Resolve resolve) { + if (!task.resolve.done()) + task.resolve.call(resolve.getReal()); + task.setDone(null); + } + catch (Reject reject) { + throw new InternalReject(task, reject); + } + catch (InternalReject r) { throw r; } + catch (Throwable throwable) { + throw new InternalReject(task, new Reject(throwable)); + } + } + catch(InternalReject rej) { + reject(rej.task, rej.real.getReal()); + } finally { + running = false; + } + } + catch (Reject e) { + if(!rejectionHandlers.exists()) { + System.err.println("!! Unhandled Task rejection:"); + e.printStackTrace(); + e.getCause().printStackTrace(); + return; + } + rejectionHandlers.call(e); + } + } + + private void reject(Task task, Throwable real) { + if (!task.reject.done()) { + task.setDone(real); + if (task.reject.exists() || task.isAwaiting) { + try { + task.reject.call(real); + } catch(InternalReject r) { + reject(r.task, r.real.getReal()); + } + } + else { + throw new Reject(real); + } + } + } + +} diff --git a/src/main/java/de/tudbut/debug/Debug.java b/src/main/java/de/tudbut/debug/Debug.java new file mode 100644 index 0000000..57abb48 --- /dev/null +++ b/src/main/java/de/tudbut/debug/Debug.java @@ -0,0 +1,26 @@ +package de.tudbut.debug; + +import java.util.HashMap; +import java.util.Map; + +public class Debug { + + private static final Map> debugProfilerMap = new HashMap<>(); + + public static DebugProfiler getDebugProfiler(java.lang.reflect.GenericDeclaration o, boolean allowFinished) { + if(!debugProfilerMap.containsKey(Thread.currentThread())) + debugProfilerMap.put(Thread.currentThread(), new HashMap<>()); + + if(debugProfilerMap.get(Thread.currentThread()).containsKey(o)) { + DebugProfiler profiler = debugProfilerMap.get(Thread.currentThread()).get(o); + if(allowFinished && profiler.isLocked()) { + return profiler; + } + else if(!profiler.isLocked()) { + return profiler; + } + } + debugProfilerMap.get(Thread.currentThread()).put(o, new DebugProfiler(o.toString(), "init")); + return debugProfilerMap.get(Thread.currentThread()).get(o); + } +} diff --git a/src/main/java/de/tudbut/debug/DebugProfiler.java b/src/main/java/de/tudbut/debug/DebugProfiler.java new file mode 100644 index 0000000..a315045 --- /dev/null +++ b/src/main/java/de/tudbut/debug/DebugProfiler.java @@ -0,0 +1,194 @@ +package de.tudbut.debug; + +import de.tudbut.parsing.TudSort; + +import java.util.ArrayList; +import java.util.Date; + +import static java.lang.System.currentTimeMillis; + +public class DebugProfiler { + + private final String name; + private final long start = currentTimeMillis(); + private long end; + private final ArrayList
sections = new ArrayList<>(); + private Section currentSection; + private boolean locked = false; + private Results results = null; + private boolean dirty = true; + + public static final class Section { + public final String name; + private long start; + private long end = 0; + private Results results; + + private Section(String s) { + name = s; + start = new Date().getTime(); + } + + private void end() { + end = new Date().getTime(); + } + + public long getTime() { + return end - start; + } + + public float getTimeRelative() { + return (float) getTime() / (float) results.getTotalTime(); + } + + @Override + public String toString() { + return name + ": " + getTime() + " (/" + results.getTotalTime() + ") (" + (getTimeRelative() * 100) + "%)"; + } + } + + public static class Results { + private long time; + private Section[] sections; + + public long getTotalTime() { + return time; + } + + public Section[] getSections() { + return sections; + } + + public Section getSectionByName(String name) { + for (int i = 0 ; i < sections.length ; i++) { + if(sections[i].name.equals(name)) + return sections[i]; + } + return null; + } + + + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + for (int i = 0 ; i < sections.length ; i++) { + s.append(sections[i].toString()); + if(i < sections.length - 1) + s.append("\n"); + } + return s.toString(); + } + } + + public DebugProfiler(String name, String startingSection) { + this.name = name; + currentSection = new Section(startingSection); + } + + public synchronized DebugProfiler next(String next) { + checkLocked(); + dirty = true; + currentSection.end(); + synchronized (sections) { + sections.add(currentSection); + } + currentSection = new Section(next); + return this; + } + + public synchronized DebugProfiler endAll() { + checkLocked(); + dirty = true; + currentSection.end(); + synchronized (sections) { + sections.add(currentSection); + } + currentSection = null; + end = currentTimeMillis(); + + locked = true; + return this; + } + + public synchronized Results getResults() { + if(!locked) + throw new RuntimeException("DebugProfiler results requested before call to endAll()!"); + if(results == null) { + return createResults(); + } + return results; + } + + public synchronized Results getTempResults() { + checkLocked(); + end = currentTimeMillis(); + return createResults(); + } + + private Results createResults() { + results = new Results(); + results.time = end - start; + optimize(); + results.sections = TudSort.sort(sections.toArray(new Section[0]), section -> -section.getTime()); + return results; + } + + public void optimize() { + synchronized (sections) { + if(dirty) { + ArrayList
realSections = new ArrayList<>(); + for (int i = 0 ; i < sections.size() ; i++) { + Section sec = sections.get(i); + sec.results = results; + if (realSections.stream().noneMatch(section -> section.name.equals(sec.name))) { + Section section = new Section(sec.name); + section.start = 0; + section.end = sec.getTime(); + section.results = results; + realSections.add(sec); + } + else { + for (int j = 0 ; j < realSections.size() ; j++) { + if (realSections.get(j).name.equals(sections.get(i).name)) { + realSections.get(j).end += sec.getTime(); + } + } + } + } + sections.clear(); + sections.addAll(realSections); + dirty = false; + } + } + } + + private synchronized void checkLocked() throws RuntimeException { + if(locked) + throw new RuntimeException("DebugProfiler modify requested after call to endAll()!"); + } + + public boolean isLocked() { + return locked; + } + + public String getName() { + return name; + } + + public synchronized void delete() { + locked = true; + sections.clear(); + currentSection = null; + results = null; + dirty = true; + } + + public void finalize() { + delete(); + } + + @Override + public String toString() { + return name + ":\n" + getTempResults().toString(); + } +} diff --git a/src/main/java/de/tudbut/global/DebugStateManager.java b/src/main/java/de/tudbut/global/DebugStateManager.java new file mode 100644 index 0000000..b1227c1 --- /dev/null +++ b/src/main/java/de/tudbut/global/DebugStateManager.java @@ -0,0 +1,111 @@ +package de.tudbut.global; + +import de.tudbut.logger.DetailedLogger; +import de.tudbut.logger.EmptyLogger; +import de.tudbut.logger.Logger; +import de.tudbut.logger.LoggerSink; + +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +public class DebugStateManager { + private static final ArrayList> toBeUpdated = new ArrayList<>(); + private static final ArrayList> dToBeUpdated = new ArrayList<>(); + private static final Logger debugLogger = new Logger("DEBUG"); + private static final DetailedLogger debugDLogger = new DetailedLogger("DEBUG"); + private static boolean debugEnabled = false; + + static { + updateState(); + } + + public static void enableDebug() { + debugEnabled = true; + updateState(); + } + + public static void disableDebug() { + debugEnabled = false; + updateState(); + } + + public static boolean isDebugEnabled() { + return debugEnabled; + } + + public static AtomicBoolean debugEnabled() { + return new AtomicBoolean(debugEnabled); + } + + public static void updateState() { + for (AtomicReference reference : toBeUpdated) { + reference.set(getDebugLogger(reference.get().getName())); + } + for (AtomicReference reference : dToBeUpdated) { + reference.set(getDebugDLogger(reference.get().getName())); + } + } + + public static LoggerSink getDebugLogger() { + if (isDebugEnabled()) + return debugLogger; + else + return new EmptyLogger("DEBUG"); + } + + public static LoggerSink getDebugLogger(String name) { + if (isDebugEnabled()) { + if (name == null || name.equals("DEBUG")) { + return debugLogger; + } + else + return new Logger(name); + } + else + return new EmptyLogger(name); + } + + public static AtomicReference debugLoggerReference() { + AtomicReference reference = new AtomicReference<>(getDebugLogger()); + toBeUpdated.add(reference); + return reference; + } + + public static AtomicReference debugLoggerReference(String name) { + AtomicReference reference = new AtomicReference<>(getDebugLogger(name)); + toBeUpdated.add(reference); + return reference; + } + + public static LoggerSink getDebugDLogger() { + if (isDebugEnabled()) + return debugDLogger; + else + return new EmptyLogger("DEBUG"); + } + + public static LoggerSink getDebugDLogger(String name) { + if (isDebugEnabled()) { + if (name == null || name.equals("DEBUG")) { + return debugDLogger; + } + else + return new DetailedLogger(name); + } + else + return new EmptyLogger(name); + } + + public static AtomicReference debugDLoggerReference() { + AtomicReference reference = new AtomicReference<>(getDebugDLogger()); + dToBeUpdated.add(reference); + return reference; + } + + public static AtomicReference debugDLoggerReference(String name) { + AtomicReference reference = new AtomicReference<>(getDebugDLogger(name)); + dToBeUpdated.add(reference); + return reference; + } +} diff --git a/src/main/java/de/tudbut/global/GlobalSyncQueue.java b/src/main/java/de/tudbut/global/GlobalSyncQueue.java new file mode 100644 index 0000000..c0e328c --- /dev/null +++ b/src/main/java/de/tudbut/global/GlobalSyncQueue.java @@ -0,0 +1,116 @@ +package de.tudbut.global; + +import de.tudbut.timer.Ticker; +import de.tudbut.tools.ThrowingRunnable; +import de.tudbut.tools.Queue; + +import java.util.ArrayList; +import java.util.Date; +import java.util.concurrent.atomic.AtomicBoolean; + +public class GlobalSyncQueue { + private static final Queue queue = new Queue<>(); + private static final Queue immediateQueue = new Queue<>(); + private static final ArrayList timers = new ArrayList<>(); + + static { + initAndRunAsynchronously(); + } + + public static void add(ThrowingRunnable runnable) { + if(runnable == null) + throw new IllegalArgumentException(); + queue.add(runnable); + } + + public static void addImmediate(ThrowingRunnable runnable) { + if(runnable == null) + throw new IllegalArgumentException(); + immediateQueue.add(runnable); + } + + public static void addTimer(Ticker ticker, AtomicBoolean stop, int delay) { + addTimer(new Ticker() { + public int getDelay() { + return delay; + } + + public void run() { + ticker.run(); + } + + public boolean doRun() { + return !stop.get(); + } + }); + } + + public static void addTimer(Ticker ticker, int delay) { + addTimer(new Ticker() { + public int getDelay() { + return delay; + } + + public void run() { + ticker.run(); + } + }); + } + + public static void addTimer(Ticker ticker) { + timers.add(new Object[]{ticker, 0L}); + } + + private static void initAndRun() { + while (true) { + if (immediateQueue.hasNext()) { + try { + immediateQueue.next().run(); + } + catch (Exception exception) { + exception.printStackTrace(); + } + continue; + } + if (queue.hasNext()) { + try { + queue.next().run(); + } + catch (Exception exception) { + exception.printStackTrace(); + } + } + if (immediateQueue.hasNext()) { + try { + immediateQueue.next().run(); + } + catch (Exception exception) { + exception.printStackTrace(); + } + continue; + } + // An enhanced loop will throw an exception, fuck you intellij for this shit suggestion + for (int i = 0; i < timers.size(); i++) { + Object[] timer = timers.get(i); + if (new Date().getTime() - ((Long) timer[1]) >= ((Ticker) timer[0]).getDelay()) { + if (((Ticker) timer[0]).doRun()) { + ((Ticker) timer[0]).run(); + timer[1] = new Date().getTime(); + } + } + } + if (immediateQueue.hasNext()) { + try { + immediateQueue.next().run(); + } + catch (Exception exception) { + exception.printStackTrace(); + } + } + } + } + + private static void initAndRunAsynchronously() { + new Thread(GlobalSyncQueue::initAndRun, "GlobalSyncQueue").start(); + } +} diff --git a/src/main/java/de/tudbut/gui/SettingsGUI.java b/src/main/java/de/tudbut/gui/SettingsGUI.java new file mode 100644 index 0000000..6474833 --- /dev/null +++ b/src/main/java/de/tudbut/gui/SettingsGUI.java @@ -0,0 +1,166 @@ +package de.tudbut.gui; + +import de.tudbut.global.DebugStateManager; +import de.tudbut.tools.FileRW; +import de.tudbut.tools.Tools; +import de.tudbut.window.Window; +import de.tudbut.gui.settingsgui.Setting; + +import javax.swing.*; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +public class SettingsGUI { + public final ArrayList settings = new ArrayList<>(); + public final ArrayList changeListeners = new ArrayList<>(); + private final boolean save; + private final Window window; + private final String name; + + public SettingsGUI(boolean saveToFile, String name) { + this.save = saveToFile; + window = new Window(name, name, false); + this.name = name; + + if (saveToFile) + loadFromFile(); + } + + public void open() { + window.setSize(500, 500); + window.open(); + + for (int i = 0; i < settings.size(); i++) { + Setting setting = settings.get(i); + for (int j = 0; j < i; j++) { + Setting s = settings.get(j); + if (s.id.equals(setting.id)) { + setting = s; + settings.remove(i); + i = j; + } + } + JButton button = new JButton( + setting.name + ( + setting.type != Setting.Type.BUTTON ? ( + ": " + ( + setting.value ? + setting.type == Setting.Type.ON_OFF ? "ON" : "YES" : + setting.type == Setting.Type.ON_OFF ? "OFF" : "NO" + ) + ) : "" + ) + ); + + Setting finalSetting = setting; + button.addActionListener(actionEvent -> { + finalSetting.value = !finalSetting.value; + button.setText( + finalSetting.name + ( + finalSetting.type != Setting.Type.BUTTON ? ( + ": " + ( + finalSetting.value ? + finalSetting.type == Setting.Type.ON_OFF ? "ON" : "YES" : + finalSetting.type == Setting.Type.ON_OFF ? "OFF" : "NO" + ) + ) : "" + ) + ); + for (Runnable changeListener : finalSetting.onChange) { + changeListener.run(); + } + for (Runnable changeListener : changeListeners) { + changeListener.run(); + } + if (save) { + try { + saveSettings(); + } + catch (IOException e) { + e.printStackTrace(); + } + } + }); + + button.setLocation(0, i * 25); + button.setSize(500, 25); + window.frame.addComponentListener(new ComponentListener() { + @Override + public void componentResized(ComponentEvent componentEvent) { + button.setSize(window.frame.getSize().width, 25); + } + + @Override + public void componentMoved(ComponentEvent componentEvent) { + } + + @Override + public void componentShown(ComponentEvent componentEvent) { + } + + @Override + public void componentHidden(ComponentEvent componentEvent) { + } + }); + button.setVisible(true); + window.frame.add(button); + } + } + + public void saveSettings() throws IOException { + FileRW file = new FileRW("settings_" + name + ".map"); + file.setContent(Tools.mapToString(toMap()).replaceAll(";", ";\n")); + } + + public void loadFromFile() { + if (Files.exists(Paths.get("settings_" + name + ".map"))) { + try { + fromMap(Tools.stringToMap(new FileRW("settings_" + name + ".map").getContent().join(""))); + } + catch (Exception e) { + DebugStateManager.getDebugDLogger("Setting parser").subChannel(name).error("Couldn't read settings! Resetting them."); + try { + new FileRW("settings_" + name + ".map").setContent(":ON_OFF@%I false;"); + } + catch (IOException ioException) { + ioException.printStackTrace(); + } + } + } + } + + public Map toMap() { + Map map = new HashMap<>(); + for (Setting setting : settings) { + map.put(setting.id, setting.type + "@" + setting.name + ": " + setting.value); + } + return map; + } + + public void fromMap(Map map) { + for (String key : map.keySet()) { + try { + Setting setting = new Setting(); + setting.id = key; + String value = map.get(key); + + setting.type = Setting.Type.valueOf(value.split("@")[0]); + value = value.split("@")[1]; + setting.name = value.split(": ")[0]; + value = value.split(": ")[1]; + setting.value = Boolean.parseBoolean(value); + + settings.add(setting); + } + catch (Exception e) { + DebugStateManager.getDebugDLogger("Setting parser").subChannel(name).warn("Skipping bad setting: " + key); + } + } + } +} diff --git a/src/main/java/de/tudbut/gui/settingsgui/Setting.java b/src/main/java/de/tudbut/gui/settingsgui/Setting.java new file mode 100644 index 0000000..3f31305 --- /dev/null +++ b/src/main/java/de/tudbut/gui/settingsgui/Setting.java @@ -0,0 +1,17 @@ +package de.tudbut.gui.settingsgui; + +import java.util.ArrayList; + +public class Setting { + public final ArrayList onChange = new ArrayList<>(); + public Type type = Type.ON_OFF; + public String name = ""; + public String id = ""; + public boolean value = false; + + public enum Type { + ON_OFF, + YES_NO, + BUTTON + } +} diff --git a/src/main/java/de/tudbut/io/AdaptiveSocketInputStream.java b/src/main/java/de/tudbut/io/AdaptiveSocketInputStream.java new file mode 100644 index 0000000..5a3a560 --- /dev/null +++ b/src/main/java/de/tudbut/io/AdaptiveSocketInputStream.java @@ -0,0 +1,50 @@ +package de.tudbut.io; + + +import de.tudbut.tools.Queue; + +import java.io.IOException; +import java.io.InputStream; +import java.net.Socket; +import java.net.SocketTimeoutException; + +public class AdaptiveSocketInputStream extends InputStream { + + public final InputStream parent; + public final Socket socket; + private final Queue cache = new Queue<>(); + + public AdaptiveSocketInputStream(Socket socket) throws IOException { + this.socket = socket; + this.parent = socket.getInputStream(); + } + + @Override + public int read() throws IOException { + if(cache.hasNext()) + return cache.next(); + else + return parent.read(); + } + + @Override + public int available() throws IOException { + int timeout = socket.getSoTimeout(); + socket.setSoTimeout(1); + try { + while(true) cache.add(parent.read()); + } catch (IOException e) { + Throwable throwable = e; + // Expected! + while (!(throwable instanceof SocketTimeoutException) && throwable != null) { + throwable = throwable.getCause(); + } + if (throwable == null) + throw e; + } + socket.setSoTimeout(timeout); + if(parent.available() != 0) + return parent.available(); + return cache.size() + parent.available(); + } +} diff --git a/src/main/java/de/tudbut/io/Buffer.java b/src/main/java/de/tudbut/io/Buffer.java new file mode 100644 index 0000000..354f643 --- /dev/null +++ b/src/main/java/de/tudbut/io/Buffer.java @@ -0,0 +1,18 @@ +package de.tudbut.io; + +public class Buffer { + private byte[] content; + + protected Buffer() { + } + + public static Buffer create(int length) { + Buffer buffer = new Buffer(); + buffer.content = new byte[length]; + return buffer; + } + + public Object get() { + return content; + } +} diff --git a/src/main/java/de/tudbut/io/CLSPrintWriter.java b/src/main/java/de/tudbut/io/CLSPrintWriter.java new file mode 100644 index 0000000..e8eca93 --- /dev/null +++ b/src/main/java/de/tudbut/io/CLSPrintWriter.java @@ -0,0 +1,61 @@ +package de.tudbut.io; + +import de.tudbut.tools.ReflectUtil; + +import java.io.*; + +public class CLSPrintWriter extends PrintWriter { + + public String customLineSeparator = "\n"; + + public CLSPrintWriter(Writer writer) { + super(writer); + } + + public CLSPrintWriter(Writer writer, boolean b) { + super(writer, b); + } + + public CLSPrintWriter(OutputStream outputStream) { + super(outputStream); + } + + public CLSPrintWriter(OutputStream outputStream, boolean b) { + super(outputStream, b); + } + + public CLSPrintWriter(String s) throws FileNotFoundException { + super(s); + } + + public CLSPrintWriter(String s, String s1) throws FileNotFoundException, UnsupportedEncodingException { + super(s, s1); + } + + public CLSPrintWriter(File file) throws FileNotFoundException { + super(file); + } + + public CLSPrintWriter(File file, String s) throws FileNotFoundException, UnsupportedEncodingException { + super(file, s); + } + + @Override + public void println() { + try { + synchronized(this.lock) { + if (this.out == null) { + throw new IOException("Stream closed"); + } + this.out.write(customLineSeparator); + if (ReflectUtil.getPrivateFieldByTypeIndex(PrintWriter.class, this, boolean.class, 0)) { + this.out.flush(); + } + } + } catch (InterruptedIOException var4) { + Thread.currentThread().interrupt(); + } catch (IOException var5) { + ReflectUtil.setPrivateFieldByTypeIndex(PrintWriter.class, this, boolean.class, 1, true); + } + } +} diff --git a/src/main/java/de/tudbut/io/CharBuffer.java b/src/main/java/de/tudbut/io/CharBuffer.java new file mode 100644 index 0000000..bb5e8c0 --- /dev/null +++ b/src/main/java/de/tudbut/io/CharBuffer.java @@ -0,0 +1,28 @@ +package de.tudbut.io; + +public class CharBuffer extends Buffer { + private char[] content; + + private CharBuffer() { + } + + public static CharBuffer create(int length) { + CharBuffer buffer = new CharBuffer(); + buffer.content = new char[length / Character.BYTES]; + return buffer; + } + + public static CharBuffer createN(int length) { + CharBuffer buffer = new CharBuffer(); + buffer.content = new char[length]; + return buffer; + } + + public Object get() { + return content; + } + + public String getAsString() { + return new String(content); + } +} diff --git a/src/main/java/de/tudbut/io/DirectBuffers.java b/src/main/java/de/tudbut/io/DirectBuffers.java new file mode 100644 index 0000000..7048b13 --- /dev/null +++ b/src/main/java/de/tudbut/io/DirectBuffers.java @@ -0,0 +1,15 @@ +package de.tudbut.io; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class DirectBuffers { + + public static ByteBuffer createDirectByteBuffer(int size) { + return prepareBuffer(ByteBuffer.allocateDirect(size)); + } + + private static ByteBuffer prepareBuffer(ByteBuffer buffer) { + return buffer.order(ByteOrder.nativeOrder()); + } +} \ No newline at end of file diff --git a/src/main/java/de/tudbut/io/DoubleBuffer.java b/src/main/java/de/tudbut/io/DoubleBuffer.java new file mode 100644 index 0000000..233342f --- /dev/null +++ b/src/main/java/de/tudbut/io/DoubleBuffer.java @@ -0,0 +1,24 @@ +package de.tudbut.io; + +public class DoubleBuffer extends Buffer { + private double[] content; + + private DoubleBuffer() { + } + + public static DoubleBuffer create(int length) { + DoubleBuffer buffer = new DoubleBuffer(); + buffer.content = new double[length / Double.BYTES]; + return buffer; + } + + public static DoubleBuffer createN(int length) { + DoubleBuffer buffer = new DoubleBuffer(); + buffer.content = new double[length]; + return buffer; + } + + public Object get() { + return content; + } +} diff --git a/src/main/java/de/tudbut/io/FileBus.java b/src/main/java/de/tudbut/io/FileBus.java new file mode 100644 index 0000000..daa5a00 --- /dev/null +++ b/src/main/java/de/tudbut/io/FileBus.java @@ -0,0 +1,111 @@ +package de.tudbut.io; + +import de.tudbut.tools.Lock; + +import java.io.*; +import java.net.URI; +import java.nio.channels.FileLock; +import java.nio.channels.OverlappingFileLockException; + +/** + * Use files as Sockets/SocketServers, unlimited clients/servers supported on every file + */ +public class FileBus extends File { + { + try { + if(createNewFile()); + } + catch (IOException e) { + e.printStackTrace(); + } + } + + final RandomAccessFile file = new RandomAccessFile(this, "rw"); + final InputStream i; + final OutputStream o; + + FileLock lock; + final Lock localLock = new Lock(); + + { + try { + file.seek((int) file.length()); + } + catch (Exception e) { + e.printStackTrace(); + } + i = new InputStream() { + @Override + public int read() throws IOException { + return file.read(); + } + }; + o = new OutputStream() { + @Override + public void write(int i) throws IOException { + file.write(i); + } + }; + } + final TypedInputStream ir = new TypedInputStream(i); + final TypedOutputStream ow = new TypedOutputStream(o); + + public FileBus(String s) throws FileNotFoundException { + super(s); + } + + public FileBus(String s, String s1) throws FileNotFoundException { + super(s, s1); + } + + public FileBus(File file, String s) throws FileNotFoundException { + super(file, s); + } + + public FileBus(URI uri) throws FileNotFoundException { + super(uri); + } + + public FileBus(File file) throws FileNotFoundException { + super(file.getAbsolutePath()); + } + + public TypedInputStream getTypedReader() { + return ir; + } + + public TypedOutputStream getTypedWriter() { + return ow; + } + + public OutputStream getWriter() { + return o; + } + + public InputStream getReader() { + return i; + } + + public void waitForInput() throws IOException { + ir.waitForInput(); + } + + public void startWrite() throws IOException { + localLock.waitHere(); + localLock.lock(); + while (lock == null) { + try { + lock = file.getChannel().lock(); + } catch (OverlappingFileLockException ignore) {} + } + } + + public void stopWrite() throws IOException { + if(lock != null) { + lock.release(); + localLock.unlock(); + lock = null; + } + o.flush(); + } +} diff --git a/src/main/java/de/tudbut/io/FileBusTransmitter.java b/src/main/java/de/tudbut/io/FileBusTransmitter.java new file mode 100644 index 0000000..4cace77 --- /dev/null +++ b/src/main/java/de/tudbut/io/FileBusTransmitter.java @@ -0,0 +1,102 @@ +package de.tudbut.io; + +import de.tudbut.obj.NotSupportedException; +import de.tudbut.timer.AsyncCatcher; +import de.tudbut.type.Stoppable; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.concurrent.Executor; + +/** + * Transmits a FileBus over a different InputStream. always use a separate instance of FileBus + * to avoid losing data! + */ +public class FileBusTransmitter implements Stoppable { + + @Override + public void start() throws NotSupportedException { + throw new NotSupportedException(); + } + + public FileBusTransmitter(FileBus bus, InputStream i, OutputStream o, Executor e0, Executor e1, AsyncCatcher onError) { + e0.execute(() -> runI(bus,i,onError)); + e1.execute(() -> runO(bus,o,onError)); + } + + private void runI(FileBus bus, InputStream i, AsyncCatcher onError) { + boolean locked = false; + boolean wait = false; + while (!isStopped()) { + try { + if (i.available() == 0) { + if (!wait) { + wait = true; + Thread.sleep(1); + continue; + } + else if(locked) { + wait = false; + locked = false; + bus.stopWrite(); + } + } + int input = i.read(); + if(input != -1) { + if(!locked) { + locked = true; + bus.startWrite(); + } + bus.o.write(input); + } + else if(!wait) { + wait = true; + Thread.sleep(1); + continue; + } + else if(locked) { + wait = false; + locked = false; + bus.stopWrite(); + } + wait = false; + } + catch (IOException | InterruptedException e) { + try { + onError.run(e); + } + catch (Exception exception) { + exception.printStackTrace(); + } + } + } + } + + private void runO(FileBus bus, OutputStream o, AsyncCatcher onError) { + boolean locked = false; + boolean wait = false; + while (!isStopped()) { + try { + int input = bus.i.read(); + if(input != -1) { + o.write(input); + } + else if(!wait) { + wait = true; + Thread.sleep(1); + continue; + } + wait = false; + } + catch (IOException | InterruptedException e) { + try { + onError.run(e); + } + catch (Exception exception) { + exception.printStackTrace(); + } + } + } + } +} diff --git a/src/main/java/de/tudbut/io/ImageIOUtils.java b/src/main/java/de/tudbut/io/ImageIOUtils.java new file mode 100644 index 0000000..8dd6259 --- /dev/null +++ b/src/main/java/de/tudbut/io/ImageIOUtils.java @@ -0,0 +1,30 @@ +package de.tudbut.io; + +import de.tudbut.tools.Tools; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.DataBufferInt; +import java.io.ByteArrayInputStream; +import java.io.IOException; + +public class ImageIOUtils { + + public static int[] imageToBytes(BufferedImage image) { + DataBuffer buffer = image.getData().getDataBuffer(); + + if(buffer.getClass() == DataBufferInt.class) { + return ((DataBufferInt) buffer).getData(); + } + if(buffer.getClass() == DataBufferByte.class) { + return Tools.byteArrayToIntArray(((DataBufferByte) buffer).getData()); + } + throw new IllegalStateException(); + } + + public static BufferedImage imageFromBytes(int[] ints) throws IOException { + return ImageIO.read(new ByteArrayInputStream(Tools.charArrayToByteArray(Tools.intArrayToCharArray(ints)))); + } +} diff --git a/src/main/java/de/tudbut/io/IntBuffer.java b/src/main/java/de/tudbut/io/IntBuffer.java new file mode 100644 index 0000000..f9862b2 --- /dev/null +++ b/src/main/java/de/tudbut/io/IntBuffer.java @@ -0,0 +1,24 @@ +package de.tudbut.io; + +public class IntBuffer extends Buffer { + private int[] content; + + private IntBuffer() { + } + + public static IntBuffer create(int length) { + IntBuffer buffer = new IntBuffer(); + buffer.content = new int[length / Integer.BYTES]; + return buffer; + } + + public static IntBuffer createN(int length) { + IntBuffer buffer = new IntBuffer(); + buffer.content = new int[length]; + return buffer; + } + + public Object get() { + return content; + } +} diff --git a/src/main/java/de/tudbut/io/LineReader.java b/src/main/java/de/tudbut/io/LineReader.java new file mode 100644 index 0000000..231d831 --- /dev/null +++ b/src/main/java/de/tudbut/io/LineReader.java @@ -0,0 +1,47 @@ +package de.tudbut.io; + +import java.io.IOException; +import java.io.Reader; + +public class LineReader { + + public static String readLineLF(Reader stream) throws IOException { + StringBuilder buffer = new StringBuilder(); + char[] chars = new char[1]; + stream.read(chars); + while (chars[0] != 0x0A) { + stream.read(chars); + buffer.append(chars[0]); + } + buffer.setLength(buffer.length()-1); + buffer.trimToSize(); + return buffer.toString(); + } + + public static String readLineCRLForLF(Reader stream) throws IOException { + StringBuilder buffer = new StringBuilder(); + char[] chars = new char[1]; + stream.read(chars); + while (chars[0] != 0x0A) { + stream.read(chars); + buffer.append(chars[0]); + } + if(buffer.toString().endsWith("\r\n")) + buffer.setLength(buffer.length()-1); + buffer.trimToSize(); + return buffer.toString(); + } + + public static String readLineCRLF(Reader stream) throws IOException { + StringBuilder buffer = new StringBuilder(); + char[] chars = new char[1]; + stream.read(chars); + while (chars[0] != 0x0A) { + buffer.append(chars[0]); + stream.read(chars); + } + buffer.setLength(buffer.length()-1); + buffer.trimToSize(); + return buffer.toString(); + } +} diff --git a/src/main/java/de/tudbut/io/RawLineReader.java b/src/main/java/de/tudbut/io/RawLineReader.java new file mode 100644 index 0000000..4aa445a --- /dev/null +++ b/src/main/java/de/tudbut/io/RawLineReader.java @@ -0,0 +1,60 @@ +package de.tudbut.io; + +import java.io.IOException; +import java.io.InputStream; + +/** + * @author TudbuT + * @since 03 Oct 2021 + */ + +// Fuck BufferedReader, im making my own. +public class RawLineReader extends InputStream { + + private boolean isAtCR = false; + private boolean readLineInvoked = false; + + private final InputStream stream; + + public RawLineReader(InputStream stream) { + this.stream = stream; + } + + @Override + public synchronized int read() throws IOException { + int i = stream.read(); + if(readLineInvoked && i == '\n' && isAtCR) { + readLineInvoked = false; + return read(); + } + if(isAtCR) { + isAtCR = false; + } + if(i == '\r') { + isAtCR = true; + } + return i; + } + + @Override + public int available() throws IOException { + return stream.available(); + } + + @Override + public void close() throws IOException { + stream.close(); + } + + public synchronized String readLine() throws IOException { + String line = ""; + int i; + boolean _isAtCR = isAtCR; + while ((i = read()) != '\r' && (_isAtCR || i != '\n')) { + if(i != '\n') + line += (char) i; + } + readLineInvoked = true; + return line; + } +} diff --git a/src/main/java/de/tudbut/io/StreamReader.java b/src/main/java/de/tudbut/io/StreamReader.java new file mode 100644 index 0000000..5431556 --- /dev/null +++ b/src/main/java/de/tudbut/io/StreamReader.java @@ -0,0 +1,156 @@ +package de.tudbut.io; + +import de.tudbut.type.IntArrayList; +import de.tudbut.obj.CarrierException; + +import java.io.*; + + +/** + * Helper for reading {@link java.io.InputStream} + */ +public class StreamReader { + + /** + * Buffer size for reading. Directly affects speed. + */ + public static int BUFFER_SIZE = 4096; + + /** + * The stream to read from + */ + public final InputStream inputStream; + + private boolean endReached = false; + + /** + * Constructs a StreamReader for an InputStream + * @param stream The stream to read + */ + public StreamReader(InputStream stream) { + this.inputStream = stream; + } + + /** + * Reads a byte from the input stream. Unaffected by {@link StreamReader#BUFFER_SIZE} + * @return read byte + * @throws IOException Inherited from {@link InputStream#read} + * @throws ArrayIndexOutOfBoundsException When the stream end was reached + */ + public byte readNextByte() throws IOException, ArrayIndexOutOfBoundsException { + int got = inputStream.read(); + if (got < 0) { + endReached = true; + throw new ArrayIndexOutOfBoundsException("Stream end reached"); + } + return (byte) got; + } + + /** + * Reads a byte from the input stream. Unaffected by {@link StreamReader#BUFFER_SIZE}. + * Byte is converted to int, being unsigned in the process. + * @return read unsigned bytes + * @throws IOException Inherited from {@link StreamReader#readNextByte()} + * @throws ArrayIndexOutOfBoundsException Inherited from {@link StreamReader#readNextByte()} + */ + public int readNextUnsignedByte() throws IOException, ArrayIndexOutOfBoundsException { + return Byte.toUnsignedInt(readNextByte()); + } + + /** + * Reads bytes from the input stream until end is reached. Unaffected by {@link StreamReader#BUFFER_SIZE}. + * Byte is converted to int, being unsigned in the process. + * @return read unsigned bytes + * @throws IOException Inherited from {@link StreamReader#readNextUnsignedByte()} + */ + public int[] readAllAsUnsignedBytes() throws IOException { + IntArrayList bytes = new IntArrayList(); + int currentByte; + while (!endReached) { + try { + currentByte = readNextUnsignedByte(); + bytes.add(currentByte); + } + catch (ArrayIndexOutOfBoundsException ignore) { + } + } + return bytes.toIntArray(); + } + + /** + * Reads bytes from the input stream until end is reached. Affected by {@link StreamReader#BUFFER_SIZE}. + * @return read bytes + * @throws IOException Inherited from {@link StreamReader#readNextByte()} + */ + public byte[] readAllAsBytes() throws IOException { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + byte[] currentBytes = new byte[BUFFER_SIZE]; + int len; + try { + while ((len = inputStream.read(currentBytes)) > 0) { + bytes.write(currentBytes, 0, len); + } + } catch (IOException e) { + throw new CarrierException(e, bytes.toByteArray()); + } + return bytes.toByteArray(); + } + + /** + * Reads all bytes in the stream and converts them to a char[] + * @return {@link Character} array (native) + * @throws IOException Inherited from {@link StreamReader#readAllAsBytes()} + */ + public char[] readAllAsChars() throws IOException { + return new String(readAllAsBytes()).toCharArray(); + } + + /** + * Reads all bytes in the stream and converts them to a char[] + * @param encoding The encoding to use for conversion + * @return {@link Character} array (native) + * @throws IOException Inherited from {@link StreamReader#readAllAsBytes()} + */ + public char[] readAllAsChars(String encoding) throws IOException { + return new String(readAllAsBytes(), encoding).toCharArray(); + } + + /** + * Same as {@link StreamReader#readAllAsChars()}, but returns a string instead. + * @return The string + * @throws IOException Inherited from {@link StreamReader#readAllAsBytes()} + */ + public String readAllAsString() throws IOException { + return new String(readAllAsBytes()); + } + + /** + * Returns all lines in the file. All line ending are supported (\r\n, \n, \r). + * @return The lines + * @throws IOException Inherited from {@link StreamReader#readAllAsBytes()} + */ + public String[] readAllAsLines() throws IOException { + return new String(readAllAsBytes()).replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n"); + } + + /** + * Same as {@link StreamReader#readAllAsChars()}, but returns a string instead. + * @param encoding The encoding to use + * @return The string + * @throws IOException Inherited from {@link StreamReader#readAllAsBytes()} + */ + public String readAllAsString(String encoding) throws IOException { + return new String(readAllAsBytes(), encoding); + } + + /** + * Returns all lines in the file. All line ending are supported (\r\n, \n, \r). + * @param encoding The encoding to use + * @return The lines + * @throws IOException Inherited from {@link StreamReader#readAllAsBytes()} + */ + public String[] readAllAsLines(String encoding) throws IOException { + return new String(readAllAsBytes(), encoding).replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n"); + } + +} diff --git a/src/main/java/de/tudbut/io/StreamRedirect.java b/src/main/java/de/tudbut/io/StreamRedirect.java new file mode 100644 index 0000000..f728a91 --- /dev/null +++ b/src/main/java/de/tudbut/io/StreamRedirect.java @@ -0,0 +1,33 @@ +package de.tudbut.io; + +import de.tudbut.timer.AsyncTask; +import de.tudbut.type.Nothing; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Map; + +public class StreamRedirect { + + public static AsyncTask redirect(InputStream from, OutputStream to, Map replacers) { + return new AsyncTask<>(() -> { + int i; + while ((i = from.read()) != -1) { + block0: + { + for (Integer key : replacers.keySet()) { + if (i == key) { + Integer[] r = replacers.get(key); + for (Integer j : r) { + to.write(j); + } + break block0; + } + } + to.write(i); + } + } + return null; + }); + } +} diff --git a/src/main/java/de/tudbut/io/StreamWriter.java b/src/main/java/de/tudbut/io/StreamWriter.java new file mode 100644 index 0000000..b7a1821 --- /dev/null +++ b/src/main/java/de/tudbut/io/StreamWriter.java @@ -0,0 +1,68 @@ +package de.tudbut.io; + +import java.io.IOException; +import java.io.OutputStream; + + +/** + * Write data to an {@link OutputStream} + */ +public class StreamWriter { + /** + * The output stream to write to + */ + public final OutputStream stream; + + /** + * Constructs a new StreamWriter + * @param stream + */ + public StreamWriter(OutputStream stream) { + this.stream = stream; + } + + public void writeChars(char[] c) throws IOException { + byte[] bytes = new String(c).getBytes(); + stream.write(bytes); + stream.flush(); + } + + public void writeChars(char[] c, String encoding) throws IOException { + byte[] bytes = new String(c).getBytes(encoding); + for (int i = 0; i < bytes.length; i++) { + writeByte(bytes[i]); + } + stream.flush(); + } + + public void writeByte(byte b) throws IOException { + stream.write(Byte.toUnsignedInt(b)); + } + + public void writeUnsignedByte(short b) throws IOException { + stream.write(b); + } + + public void writeUnsignedByte(int b) throws IOException { + stream.write(b); + } + + public void writeBytes(byte[] bytes) throws IOException { + stream.write(bytes); + stream.flush(); + } + + public void writeUnsignedBytes(int[] bytes) throws IOException { + for (int theByte : bytes) { + stream.write(theByte); + } + stream.flush(); + } + + public void writeUnsignedBytes(short[] bytes) throws IOException { + for (short theByte : bytes) { + stream.write(theByte); + } + stream.flush(); + } +} diff --git a/src/main/java/de/tudbut/io/TypedInputStream.java b/src/main/java/de/tudbut/io/TypedInputStream.java new file mode 100644 index 0000000..920dd72 --- /dev/null +++ b/src/main/java/de/tudbut/io/TypedInputStream.java @@ -0,0 +1,161 @@ +package de.tudbut.io; + +import java.io.IOException; +import java.io.InputStream; + +/** + * InputStream for more data types + */ +public class TypedInputStream { + + protected InputStream stream; + public int last = -1; + private final Object waitForInputLock = new Object(); + private final Object readLock = new Object(); + + public InputStream getStream() { + return stream; + } + + private WriteDirection direction = WriteDirection.HIGH_FIRST; + + public TypedInputStream(InputStream stream) { + this.stream = stream; + } + + public byte readByte() throws IOException { + return (byte) read(); + } + + public short readShort() throws IOException { + short s = 0; + if(direction == WriteDirection.HIGH_FIRST) { + s += (read() << 8 * 1); + s += (read() << 8 * 0); + } + else { + s += (read() << 8 * 0); + s += (read() << 8 * 1); + } + return s; + } + + public char readChar() throws IOException { + char c = 0; + if(direction == WriteDirection.HIGH_FIRST) { + c += (read() << 8 * 1); + c += (read() << 8 * 0); + } + else { + c += (read() << 8 * 0); + c += (read() << 8 * 1); + } + return c; + } + + public int readInt() throws IOException { + int i = 0; + if(direction == WriteDirection.HIGH_FIRST) { + i += (read() << 8 * 3); + i += (read() << 8 * 2); + i += (read() << 8 * 1); + i += (read() << 8 * 0); + } + else { + i += (read() << 8 * 0); + i += (read() << 8 * 1); + i += (read() << 8 * 2); + i += (read() << 8 * 3); + } + return i; + } + + public float readFloat() throws IOException { + return Float.intBitsToFloat(readInt()); + } + + public long readLong() throws IOException { + long l = 0; + if(direction == WriteDirection.HIGH_FIRST) { + l += ((long) read() << 8 * 7); + l += ((long) read() << 8 * 6); + l += ((long) read() << 8 * 5); + l += ((long) read() << 8 * 4); + l += ((long) read() << 8 * 3); + l += ((long) read() << 8 * 2); + l += ((long) read() << 8 * 1); + l += ((long) read() << 8 * 0); + } + else { + l += ((long) read() << 8 * 0); + l += ((long) read() << 8 * 1); + l += ((long) read() << 8 * 2); + l += ((long) read() << 8 * 3); + l += ((long) read() << 8 * 4); + l += ((long) read() << 8 * 5); + l += ((long) read() << 8 * 6); + l += ((long) read() << 8 * 7); + } + return l; + } + + public double readDouble() throws IOException { + return Double.longBitsToDouble(readLong()); + } + + public boolean readBoolean() throws IOException { + return (read() & 1) != 0; + } + + public boolean[] readBooleans() throws IOException { + boolean[] booleans = new boolean[8]; + int i = read() & 0xff; + for (int j = 0 ; j < 8 ; ) { + booleans[j] = (i & 1 << 8 >> ++j ) != 0; + } + return booleans; + } + + public String readString() throws IOException { + int i = readInt(); + StringBuilder builder = new StringBuilder(); + for (int j = 0 ; j < i ; j++) { + builder.append(readChar()); + } + return builder.toString(); + } + + public void waitForInput() throws IOException { + synchronized (waitForInputLock) { + if(last != -1) + return; + while ((last = stream.read()) == -1) ; + } + } + + public int read() throws IOException { + synchronized (readLock) { + synchronized (waitForInputLock) { + int i; + if (last == -1) { + while ((i = stream.read()) == -1) ; + } + else { + i = last; + last = -1; + } + return i; + } + } + } + + public WriteDirection getDirection() { + return direction; + } + + public void setDirection(WriteDirection direction) { + if(direction == null) + throw new IllegalArgumentException(); + this.direction = direction; + } +} diff --git a/src/main/java/de/tudbut/io/TypedOutputStream.java b/src/main/java/de/tudbut/io/TypedOutputStream.java new file mode 100644 index 0000000..f3017a8 --- /dev/null +++ b/src/main/java/de/tudbut/io/TypedOutputStream.java @@ -0,0 +1,141 @@ +package de.tudbut.io; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * OutputStream for more data types + */ +public class TypedOutputStream { + + public OutputStream getStream() { + return stream; + } + + OutputStream stream; + private WriteDirection direction = WriteDirection.HIGH_FIRST; + + public TypedOutputStream(OutputStream stream) { + this.stream = stream; + } + + public byte writeByte(byte b) throws IOException { + stream.write(Byte.toUnsignedInt(b)); + stream.flush(); + return b; + } + + public short writeShort(short s) throws IOException { + if(direction == WriteDirection.HIGH_FIRST) { + stream.write(s >> 8 * 1 & 0xff); + stream.write(s >> 8 * 0 & 0xff); + } + else { + stream.write(s >> 8 * 0 & 0xff); + stream.write(s >> 8 * 1 & 0xff); + } + stream.flush(); + return s; + } + + public char writeChar(char c) throws IOException { + if(direction == WriteDirection.HIGH_FIRST) { + stream.write(c >> 8 * 1 & 0xff); + stream.write(c >> 8 * 0 & 0xff); + } + else { + stream.write(c >> 8 * 0 & 0xff); + stream.write(c >> 8 * 1 & 0xff); + } + stream.flush(); + return c; + } + + public int writeInt(int i) throws IOException { + if(direction == WriteDirection.HIGH_FIRST) { + stream.write(i >> 8 * 3 & 0xff); + stream.write(i >> 8 * 2 & 0xff); + stream.write(i >> 8 * 1 & 0xff); + stream.write(i >> 8 * 0 & 0xff); + } + else { + stream.write(i >> 8 * 0 & 0xff); + stream.write(i >> 8 * 1 & 0xff); + stream.write(i >> 8 * 2 & 0xff); + stream.write(i >> 8 * 3 & 0xff); + } + stream.flush(); + return i; + } + + public float writeFloat(float f) throws IOException { + writeInt(Float.floatToIntBits(f)); + return f; + } + + public long writeLong(long l) throws IOException { + if(direction == WriteDirection.HIGH_FIRST) { + stream.write((int) (l >> 8 * 7 & 0xff)); + stream.write((int) (l >> 8 * 6 & 0xff)); + stream.write((int) (l >> 8 * 5 & 0xff)); + stream.write((int) (l >> 8 * 4 & 0xff)); + stream.write((int) (l >> 8 * 3 & 0xff)); + stream.write((int) (l >> 8 * 2 & 0xff)); + stream.write((int) (l >> 8 * 1 & 0xff)); + stream.write((int) (l >> 8 * 0 & 0xff)); + } + else { + stream.write((int) (l >> 8 * 0 & 0xff)); + stream.write((int) (l >> 8 * 1 & 0xff)); + stream.write((int) (l >> 8 * 2 & 0xff)); + stream.write((int) (l >> 8 * 3 & 0xff)); + stream.write((int) (l >> 8 * 4 & 0xff)); + stream.write((int) (l >> 8 * 5 & 0xff)); + stream.write((int) (l >> 8 * 6 & 0xff)); + stream.write((int) (l >> 8 * 7 & 0xff)); + } + stream.flush(); + return l; + } + + public double writeDouble(double d) throws IOException { + writeLong(Double.doubleToLongBits(d)); + return d; + } + + public boolean writeBoolean(boolean b) throws IOException { + stream.write(b ? 1 : 0); + stream.flush(); + return b; + } + + public boolean[] writeBooleans(boolean[] booleans) throws IOException { + int i = 0; + for (int j = 0 ; j < 8 ; ) { + i += (booleans[j] ? 1 : 0) << 8 >> ++j; + } + stream.write(i); + stream.flush(); + return booleans; + } + + public String writeString(String string) throws IOException { + int i = string.length(); + writeInt(i); + char[] chars = string.toCharArray(); + for (int j = 0 ; j < i ; j++) { + writeChar(chars[j]); + } + return string; + } + + public WriteDirection getDirection() { + return direction; + } + + public void setDirection(WriteDirection direction) { + if(direction == null) + throw new IllegalArgumentException(); + this.direction = direction; + } +} diff --git a/src/main/java/de/tudbut/io/WriteDirection.java b/src/main/java/de/tudbut/io/WriteDirection.java new file mode 100644 index 0000000..5098b6b --- /dev/null +++ b/src/main/java/de/tudbut/io/WriteDirection.java @@ -0,0 +1,29 @@ +package de.tudbut.io; + +/** + * Indicator for how to write/read in TypedOutputStream and TypedInputStream + */ +public enum WriteDirection { + + /** + * Integer 1 writes as + * 0x00 0x00 0x00 0x01
+ *
+ * Integer 256 writes as + * 0x00 0x00 0x01 0x00
+ *
+ * This is the standard for most java applications + */ + HIGH_FIRST, + /** + * Integer 1 writes as + * 0x01 0x00 0x00 0x00
+ *
+ * Integer 256 writes as + * 0x00 0x01 0x00 0x00
+ *
+ * This is most commonly used in C/C++ + */ + LOW_FIRST, + ; +} diff --git a/src/main/java/de/tudbut/logger/DetailedLogger.java b/src/main/java/de/tudbut/logger/DetailedLogger.java new file mode 100644 index 0000000..40524af --- /dev/null +++ b/src/main/java/de/tudbut/logger/DetailedLogger.java @@ -0,0 +1,53 @@ +package de.tudbut.logger; + +import java.io.PrintStream; +import java.sql.Time; +import java.util.Date; + +public class DetailedLogger implements LoggerSink { + String name; + public PrintStream out; + + { + out = System.out; + } + + + public DetailedLogger(String name) { + this.name = name; + } + + public DetailedLogger subChannel(String subChannel) { + PrintStream stream = out; + return new DetailedLogger(name + "] [" + subChannel) { + { + out = stream; + } + }; + } + + @Override + public String getName() { + return name; + } + + public void info(String string) { + String time = String.valueOf(new Date().getTime()); + out.println("[ " + new Time(new Date().getTime()) + "." + time.substring(time.length() - 3) + " " + Thread.currentThread().getName() + " INFO ] [" + this.name + "] " + string); + } + + public void debug(String string) { + String time = String.valueOf(new Date().getTime()); + out.println("[ " + new Time(new Date().getTime()) + "." + time.substring(time.length() - 3) + " " + Thread.currentThread().getName() + " DEBUG ] [" + this.name + "] " + string); + } + + public void warn(String string) { + String time = String.valueOf(new Date().getTime()); + out.println("[ " + new Time(new Date().getTime()) + "." + time.substring(time.length() - 3) + " " + Thread.currentThread().getName() + " WARN ] [" + this.name + "] " + string); + } + + public void error(String string) { + String time = String.valueOf(new Date().getTime()); + out.println("[ " + new Time(new Date().getTime()) + "." + time.substring(time.length() - 3) + " " + Thread.currentThread().getName() + " ERROR ] [" + this.name + "] " + string); + } +} diff --git a/src/main/java/de/tudbut/logger/EmptyLogger.java b/src/main/java/de/tudbut/logger/EmptyLogger.java new file mode 100644 index 0000000..4567dca --- /dev/null +++ b/src/main/java/de/tudbut/logger/EmptyLogger.java @@ -0,0 +1,43 @@ +package de.tudbut.logger; + +public class EmptyLogger implements LoggerSink { + private final String name; + + public EmptyLogger(String name) { + this.name = name; + } + + public EmptyLogger() { + this.name = null; + } + + public LoggerSink subChannel(String subChannel) { + return new EmptyLogger(name + "] [" + subChannel); + } + + @Override + public void info(String string) { + + } + + @Override + public void debug(String string) { + + } + + @Override + public void warn(String string) { + + } + + @Override + public void error(String string) { + + } + + @Override + public String getName() { + return name; + } + +} diff --git a/src/main/java/de/tudbut/logger/Logger.java b/src/main/java/de/tudbut/logger/Logger.java new file mode 100644 index 0000000..7b2faf3 --- /dev/null +++ b/src/main/java/de/tudbut/logger/Logger.java @@ -0,0 +1,184 @@ +package de.tudbut.logger; + +import java.io.*; +import java.sql.Time; +import java.util.Date; + +public class Logger implements LoggerSink { + String name; + public PrintStream out; + + { + out = System.out; + } + + public Logger(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + public Logger subChannel(String subChannel) { + PrintStream stream = out; + return new Logger(name + "] [" + subChannel) { + { + out = stream; + } + }; + } + + public PrintWriter infoAsWriter() { + return new PrintWriter(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + info(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + public PrintStream infoAsStream() { + return new PrintStream(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + info(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + + public PrintWriter debugAsWriter() { + return new PrintWriter(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + debug(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + public PrintStream debugAsStream() { + return new PrintStream(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + debug(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + + public PrintWriter warnAsWriter() { + return new PrintWriter(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + warn(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + public PrintStream warnAsStream() { + return new PrintStream(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + warn(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + + public PrintWriter errorAsWriter() { + return new PrintWriter(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + error(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + public PrintStream errorAsStream() { + return new PrintStream(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + error(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + + public void info(String string) { + out.println("[" + new Time(new Date().getTime()) + " INFO] [" + this.name + "] " + string); + } + + public void debug(String string) { + out.println("[" + new Time(new Date().getTime()) + " DEBUG] [" + this.name + "] " + string); + } + + public void debug(Throwable throwable) { + throwable.printStackTrace(debugAsWriter()); + } + + public void warn(String string) { + out.println("[" + new Time(new Date().getTime()) + " WARN] [" + this.name + "] " + string); + } + + public void warn(Exception exception) { + exception.printStackTrace(warnAsWriter()); + } + + public void error(String string) { + out.println("[" + new Time(new Date().getTime()) + " ERROR] [" + this.name + "] " + string); + } + + public void error(Throwable throwable) { + throwable.printStackTrace(errorAsWriter()); + } +} diff --git a/src/main/java/de/tudbut/logger/LoggerSink.java b/src/main/java/de/tudbut/logger/LoggerSink.java new file mode 100644 index 0000000..8f1cf49 --- /dev/null +++ b/src/main/java/de/tudbut/logger/LoggerSink.java @@ -0,0 +1,155 @@ +package de.tudbut.logger; + +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.PrintStream; + +public interface LoggerSink { + String getName(); + + LoggerSink subChannel(String subChannel); + + default PrintWriter infoAsWriter() { + return new PrintWriter(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + info(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + default PrintStream infoAsStream() { + return new PrintStream(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + info(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + + default PrintWriter debugAsWriter() { + return new PrintWriter(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + debug(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + default PrintStream debugAsStream() { + return new PrintStream(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + debug(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + + default PrintWriter warnAsWriter() { + return new PrintWriter(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + warn(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + default PrintStream warnAsStream() { + return new PrintStream(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + warn(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + + default PrintWriter errorAsWriter() { + return new PrintWriter(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + error(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + default PrintStream errorAsStream() { + return new PrintStream(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + error(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + + void info(String string); + + void debug(String string); + + default void debug(Throwable throwable) { + throwable.printStackTrace(debugAsWriter()); + } + + void warn(String string); + + default void warn(Exception exception) { + exception.printStackTrace(warnAsWriter()); + } + + void error(String string); + + default void error(Throwable throwable) { + throwable.printStackTrace(errorAsWriter()); + } +} diff --git a/src/main/java/de/tudbut/math/ComplexFunction.java b/src/main/java/de/tudbut/math/ComplexFunction.java new file mode 100644 index 0000000..f4b75e9 --- /dev/null +++ b/src/main/java/de/tudbut/math/ComplexFunction.java @@ -0,0 +1,10 @@ +package de.tudbut.math; + +public interface ComplexFunction { + + ComplexNumber solve(ComplexNumber n); + + default ComplexNumber solve(double n) { + return solve(ComplexNumber.create(n, 0)); + } +} diff --git a/src/main/java/de/tudbut/math/ComplexNumber.java b/src/main/java/de/tudbut/math/ComplexNumber.java new file mode 100644 index 0000000..824762e --- /dev/null +++ b/src/main/java/de/tudbut/math/ComplexNumber.java @@ -0,0 +1,55 @@ +package de.tudbut.math; + +import de.tudbut.type.Vector2d; + +public class ComplexNumber { + + private double real = 0; + private double imaginary = 0; + private Vector2d vec; + public boolean autoRecalculate = true; + + public double getReal() { + return real; + } + + public double getImaginary() { + return imaginary; + } + + public Vector2d getVec() { + return vec; + } + + public void setReal(double real) { + this.real = real; + vec = null; + if(autoRecalculate) + recalculateVector(); + } + + public void setImaginary(double imaginary) { + this.imaginary = imaginary; + vec = null; + if(autoRecalculate) + recalculateVector(); + } + + private ComplexNumber() {} + + public static ComplexNumber create(double real, double imaginary) { + ComplexNumber n = new ComplexNumber(); + n.real = real; + n.imaginary = imaginary; + return n; + } + + public Vector2d recalculateVector() { + vec = new Vector2d(real, imaginary); + return vec; + } + + public Vector2d getVector() { + return vec == null ? recalculateVector() : vec; + } +} diff --git a/src/main/java/de/tudbut/math/Function.java b/src/main/java/de/tudbut/math/Function.java new file mode 100644 index 0000000..451ead2 --- /dev/null +++ b/src/main/java/de/tudbut/math/Function.java @@ -0,0 +1,10 @@ +package de.tudbut.math; + +public interface Function { + + double solve(double n); + + default double solve(ComplexNumber n) { + return solve(n.getReal()); + } +} diff --git a/src/main/java/de/tudbut/net/http/HTTPContentType.java b/src/main/java/de/tudbut/net/http/HTTPContentType.java new file mode 100644 index 0000000..a45d7dc --- /dev/null +++ b/src/main/java/de/tudbut/net/http/HTTPContentType.java @@ -0,0 +1,89 @@ +package de.tudbut.net.http; + +/** + * Content types + */ +public enum HTTPContentType { + AAC( "audio/aac"), + ABW( "application/x-abiword"), + ARC( "application/x-freearc"), + AVI( "video/x-msvideo"), + AZW( "application/vnd.amazon.ebook"), + BIN( "application/octet-stream"), + BMP( "image/bmp"), + BZ( "application/x-bzip"), + BZ2( "application/x-bzip2"), + CSH( "application/x-csh"), + CSS( "text/css"), + CSV( "text/csv"), + DOC( "application/msword"), + DOCX( "application/vnd.openxmlformats-officedocument.wordprocessingml.document"), + EOT( "application/vnd.ms-fontobject"), + EPUB( "application/epub+zip"), + GZ( "application/gzip"), + GIF( "image/gif"), + HTML( "text/html"), + HTTP( "message/http"), + ICO( "image/vnd.microsoft.icon"), + ICS( "text/calendar"), + JAR( "application/java-archive"), + JPG( "image/jpeg"), + JS( "text/javascript"), + JSON( "application/json"), + JSONLD( "application/ld+json"), + MID( "audio/midi"), + MJS( "text/javascript"), + MP3( "audio/mpeg"), + MPEG( "video/mpeg"), + MPKG( "application/vnd.apple.installer+xml"), + ODP( "application/vnd.oasis.opendocument.presentation"), + ODS( "application/vnd.oasis.opendocument.spreadsheet"), + ODT( "application/vnd.oasis.opendocument.text"), + OGG( "audio/ogg"), + OGV( "video/ogg"), + OGX( "application/ogg"), + OPUS( "audio/opus"), + OTF( "font/otf"), + PNG( "image/png"), + PDF( "application/pdf"), + PHP( "application/x-httpd-php"), + PPT( "application/vnd.ms-powerpoint"), + PPTX( "application/vnd.openxmlformats-officedocument.presentationml.presentation"), + RAR( "application/vnd.rar"), + RTF( "application/rtf"), + SH( "application/x-sh"), + SVG( "image/svg+xml"), + SWF( "application/x-shockwave-flash"), + TAR( "application/x-tar"), + TCN( "text/tcn"), + TCNMAP( "application/tcnmap"), + TS( "video/mp2t"), + TTF( "font/ttf"), + TXT( "text/plain"), + VSD( "application/vnd.visio"), + WAV( "audio/wav"), + WEBA( "audio/webm"), + WEBM( "video/webm"), + WEBP( "image/webp"), + WOFF( "font/woff"), + WOFF2( "font/woff2"), + XHTML( "application/xhtml+xml"), + XLS( "application/vnd.ms-excel"), + XLSX( "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"), + XML( "application/xml"), + XML_READABLE("text/xml"), + XUL( "application/vnd.mozilla.xul+xml"), + ZIP( "application/zip"), + _3GP_AUDIO( "audio/3gpp"), + _3GP_VIDEO( "video/3gpp"), + _3G2_AUDIO( "audio/3gpp2"), + _3G2_VIDEO( "video/3gpp2"), + _7Z( "application/x-7z-compressed"), + ANY( "*/*"), + ; + public final String asHeaderString; + + HTTPContentType(String asHeaderStringIn) { + asHeaderString = asHeaderStringIn; + } +} diff --git a/src/main/java/de/tudbut/net/http/HTTPHeader.java b/src/main/java/de/tudbut/net/http/HTTPHeader.java new file mode 100644 index 0000000..b9a51f9 --- /dev/null +++ b/src/main/java/de/tudbut/net/http/HTTPHeader.java @@ -0,0 +1,91 @@ +package de.tudbut.net.http; + +import de.tudbut.obj.TLMap; + +/** + * Header for HTTP exchanges + */ +public class HTTPHeader { + private final String name; + private final String value; + private final TLMap param; + + public HTTPHeader(String s) { + String[] spl = s.split(": ", 2); + name = spl[0]; + spl = spl[1].split("; "); + value = spl[0]; + param = new TLMap<>(); + for (int i = 1 ; i < spl.length ; i++) { + try { + String[] kv = spl[i].split("=", 2); + if(kv[1].startsWith("\"")) { + kv[1] = kv[1] + .split("\"", 2)[1] + .replaceAll("%", "%P") + .replaceAll("\\\\", "%B") + .replaceAll("%B%B", "\\") + .replaceAll("%Bn", "\n") + .replaceAll("%B\"", "\"") + .replaceAll("%B", "\\") + .replaceAll("%P", "%"); + kv[1] = kv[1].substring(0, kv.length - 2); + } + else { + kv[1] = kv[1] + .replaceAll("%", "%P") + .replaceAll("\\\\", "%B") + .replaceAll("%B%B", "\\") + .replaceAll("%Bn", "\n") + .replaceAll("%B", "\\") + .replaceAll("%P", "%"); + } + param.set(kv[0], kv[1]); + } catch (Exception ignored) {} + } + } + + /** + * Constructs a HTTPHeader from a key and a value + * @param nameIn Key + * @param valueIn Value + */ + public HTTPHeader(String nameIn, String valueIn) { + this(nameIn, valueIn, new TLMap<>()); + } + + /** + * Constructs a HTTPHeader from a key, a value, and a parameter + * @param nameIn Key + * @param valueIn Value + * @param paramIn Parameter + */ + public HTTPHeader(String nameIn, String valueIn, TLMap paramIn) { + name = nameIn; + value = valueIn; + param = paramIn; + } + + /** + * + * @return The form used in the request + */ + public String toString() { + String m = TLMap.mapToString(param).replaceAll(":", "=\"").replaceAll(";", "\"; "); + if(param.size() > 0) + m = m.substring(0, m.length()-2); + return name + ": " + value + (param.size() == 0 ? "" : "; " + m); + } + + public String key() { + return name; + } + + public String value() { + return value; + } + + public TLMap parameter() { + return param.clone(); + } +} diff --git a/src/main/java/de/tudbut/net/http/HTTPRequest.java b/src/main/java/de/tudbut/net/http/HTTPRequest.java new file mode 100644 index 0000000..593a294 --- /dev/null +++ b/src/main/java/de/tudbut/net/http/HTTPRequest.java @@ -0,0 +1,224 @@ +package de.tudbut.net.http; + +import de.tudbut.global.DebugStateManager; +import de.tudbut.io.StreamReader; +import de.tudbut.io.StreamWriter; +import de.tudbut.obj.DoubleTypedObject; +import de.tudbut.obj.Partial; +import de.tudbut.timer.AsyncTask; + +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.InetAddress; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; + +/** + * Fully compatible, fast version of java HTTP requests + */ +public class HTTPRequest { + public final ArrayList headers = new ArrayList<>(); + public final String content; + private final HTTPRequestType requestType; + private final String path; + private final String host; + private final int port; + private final boolean ssl; + + /** + * Constructs a HTTPRequest without sending it + * @param requestType The type of the request, see {@link HTTPRequestType}. + * @param host Host, add "https://" in front to attempt an HTTPS connection, otherwise, dont specify protocol + * @param port Port, 80 is standard for HTTP, 443 is standard for HTTPS + * @param path Path to request, use "/" for main path + * @param headers HTTPHeaders to use, can be empty + */ + public HTTPRequest(HTTPRequestType requestType, String host, int port, String path, HTTPHeader... headers) { + this(requestType, host, port, path, (String) null, "", headers); + } + + /** + * Constructs a HTTPRequest without sending it + * @param requestTypeIn The type of the request, see {@link HTTPRequestType}. + * @param hostIn Host, add "https://" in front to attempt a HTTPS connection, otherwise, dont specify protocol + * @param portIn Port, 80 is standard for HTTP, 443 is standard for HTTPS + * @param pathIn Path to request, use "/" for main path + * @param type The type of the body + * @param contentIn The body + * @param headersIn HTTPHeaders to use, can be empty + */ + public HTTPRequest(HTTPRequestType requestTypeIn, String hostIn, int portIn, String pathIn, HTTPContentType type, String contentIn, HTTPHeader... headersIn) { + this(requestTypeIn, hostIn, portIn, pathIn, type.asHeaderString, contentIn, headersIn); + } + /** + * Constructs a HTTPRequest without sending it + * @param requestTypeIn The type of the request, see {@link HTTPRequestType}. + * @param hostIn Host, add "https://" in front to attempt a HTTPS connection, otherwise, dont specify protocol + * @param portIn Port, 80 is standard for HTTP, 443 is standard for HTTPS + * @param pathIn Path to request, use "/" for main path + * @param type The type of the body + * @param contentIn The body + * @param headersIn HTTPHeaders to use, can be empty + */ + public HTTPRequest(HTTPRequestType requestTypeIn, String hostIn, int portIn, String pathIn, String type, String contentIn, HTTPHeader... headersIn) { + this(requestTypeIn, hostIn, portIn, pathIn, new HTTPHeader("Content-Type", type), contentIn, headersIn); + } + + /** + * Constructs a HTTPRequest without sending it + * @param requestTypeIn The type of the request, see {@link HTTPRequestType}. + * @param hostIn Host, add "https://" in front to attempt a HTTPS connection, otherwise, dont specify protocol + * @param portIn Port, 80 is standard for HTTP, 443 is standard for HTTPS + * @param pathIn Path to request, use "/" for main path + * @param type The type of the body + * @param contentIn The body + * @param headersIn HTTPHeaders to use, can be empty + */ + public HTTPRequest(HTTPRequestType requestTypeIn, String hostIn, int portIn, String pathIn, HTTPHeader type, String contentIn, HTTPHeader... headersIn) { + ssl = hostIn.startsWith("https://"); + + requestType = requestTypeIn; + path = pathIn; + host = hostIn; + port = portIn; + headers.add(new HTTPHeader("Host", ssl ? host.split("https://")[1] : host)); + if(!contentIn.equals("")) { + headers.add(new HTTPHeader("Content-Type", type.value(), type.parameter())); + headers.add(new HTTPHeader("Content-Length", String.valueOf(contentIn.getBytes(StandardCharsets.ISO_8859_1).length))); + } + if(Arrays.stream(headersIn).noneMatch(httpHeader -> httpHeader.toString().startsWith("Connection: "))) + headers.add(new HTTPHeader("Connection", "Close")); + headers.addAll(Arrays.asList(headersIn)); + content = contentIn; + } + + /** + * @return The string to send to the server + */ + public String toString() { + String s = ""; + + s += requestType.name() + " " + path + " HTTP/1.1\r\n"; + for (HTTPHeader header : headers) { + s += header.toString() + "\r\n"; + } + s += "\r\n"; + s += content; + + return s; + } + + /** + * Sends the request synchronously + * @return The response + * @throws IOException Inherited + */ + public HTTPResponse send() throws IOException { + Socket socket = connect(); + return new HTTPResponse(new String(new StreamReader(socket.getInputStream()).readAllAsBytes(), StandardCharsets.ISO_8859_1)); + } + + /** + * Sends the request synchronously without returning a response + * @return The socket + * @throws IOException Inherited + */ + public Socket sendNoRead() throws IOException { + return connect(); + } + + /** + * Sends the request asynchronously + * @return The response enclosed in a {@link Partial} + */ + public Partial sendKeepAlive() { + return sendKeepAlive(-1); + } + + /** + * Sends the request asynchronously with optional timeout + * @param timeout The timeout at which to stop receiving, use -1 for infinity + * @return The response enclosed in a {@link Partial} + */ + public Partial sendKeepAlive(int timeout) { + Partial partialResponse = new Partial<>(null); + AsyncTask task = new AsyncTask<>(() -> { + try { + Socket socket = connect(); + BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.ISO_8859_1)); + int c; + StringBuilder builder = new StringBuilder(); + while ((c = reader.read()) != -1) { + builder.append((char)c); + if(c == '\n') + partialResponse.change(new HTTPResponse(builder.toString())); + } + socket.close(); + partialResponse.change(new HTTPResponse(builder.toString())); + partialResponse.complete(partialResponse.get()); + } catch (Exception ignored) { } + return partialResponse.get(); + }); + task.setTimeout(timeout); + return partialResponse; + } + + /** + * Sends the request asynchronously with optional timeout + * @param timeout The timeout at which to stop receiving, use -1 for infinity + * @return The task and enclosed response (in a {@link Partial}), enclosed in a {@link DoubleTypedObject} + */ + public DoubleTypedObject, AsyncTask> sendKeepAliveWithTask(int timeout) { + Partial partialResponse = new Partial<>(null); + AsyncTask task = new AsyncTask<>(() -> { + try { + Socket socket = connect(); + BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.ISO_8859_1)); + int c; + StringBuilder builder = new StringBuilder(); + while ((c = reader.read()) != -1) { + builder.append((char)c); + if(c == '\n') + partialResponse.change(new HTTPResponse(builder.toString())); + } + socket.close(); + partialResponse.change(new HTTPResponse(builder.toString())); + partialResponse.complete(partialResponse.get()); + return partialResponse.get(); + } catch (Exception ignored) { } + return partialResponse.get(); + }); + task.setTimeout(timeout); + return new DoubleTypedObject<>(partialResponse, task); + } + /** + * Sends the request and return the socket from which {@link #send} and {@link #sendKeepAlive} will read + * @return The created socket + * @throws IOException Inherited + */ + private Socket connect() throws IOException { + Socket socket; + if (ssl) { + socket = new Socket(host.substring("https://".length()), port); + SSLSocket sslSocket = (SSLSocket) ((SSLSocketFactory) SSLSocketFactory.getDefault()).createSocket(socket, host.substring("https://".length()), port, true); + sslSocket.startHandshake(); + if(DebugStateManager.isDebugEnabled()) { + DebugStateManager.getDebugLogger().debug("HTTPRequest using SSL/TLS version " + sslSocket.getSession().getProtocol()); + } + socket = sslSocket; + } + else + socket = new Socket(InetAddress.getByName(host), port); + socket.setSoTimeout(10000); + socket.setSoLinger(true, 1000); + StreamWriter writer = new StreamWriter(socket.getOutputStream()); + writer.writeChars(toString().toCharArray(), "ISO_8859_1"); + return socket; + } + +} diff --git a/src/main/java/de/tudbut/net/http/HTTPRequestType.java b/src/main/java/de/tudbut/net/http/HTTPRequestType.java new file mode 100644 index 0000000..d9770a4 --- /dev/null +++ b/src/main/java/de/tudbut/net/http/HTTPRequestType.java @@ -0,0 +1,58 @@ +package de.tudbut.net.http; + +/** + * Request types + */ +public enum HTTPRequestType { + /** + * Get request, default for browsers + */ + GET, + + /** + * Post request, common for APIs + */ + POST, + + /** + * Head request, asks the server to respond with the same headers + * as a {@link #GET}, but without content. Commonly used in browsers + * that use caching. More + */ + HEAD, + + /** + * Put request, used for writing files to a server + */ + PUT, + + /** + * Patch request, used for changing files on a server. + * More + */ + PATCH, + + /** + * Delete request, used for deleting files on a server. + * More + */ + DELETE, + + /** + * Trace request, used for debugging + */ + TRACE, + + /** + * Options request, used for asking the server for allowed communication methods. + * More + */ + OPTIONS, + + /** + * Connect request, used to indicate start of a two-way transfer. + * More + */ + CONNECT, + ; +} diff --git a/src/main/java/de/tudbut/net/http/HTTPResponse.java b/src/main/java/de/tudbut/net/http/HTTPResponse.java new file mode 100644 index 0000000..432f989 --- /dev/null +++ b/src/main/java/de/tudbut/net/http/HTTPResponse.java @@ -0,0 +1,148 @@ +package de.tudbut.net.http; + +import de.tudbut.tools.Value; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Map; + +/** + * Response to {@link HTTPRequest} + */ +public class HTTPResponse extends Value { + + /** + * Constructs a HTTPResponse from non-exact content + * @param value The content + */ + public HTTPResponse(String value) { + super(spl(value)); + } + + /** + * Constructs a HTTPResponse from content + * @param value The content + */ + public HTTPResponse(String value, boolean exact) { + super(exact ? value : spl(value)); + } + + private static String spl(String s) { + String[] splitString = s.split("\r\n\r\n", 2); + if(splitString.length == 1) { + return s.replaceAll("\r", ""); + } + else { + return splitString[0].replaceAll("\r", "") + "\n\n" + splitString[1]; + } + } + + /** + * Parses the response + * @return The {@link ParsedHTTPValue} of the response + */ + public ParsedHTTPValue parse() { + String[] splitValue = value.split("\n", 2)[0].split(" "); + + String httpVersion = splitValue[0]; + int statusCode = Integer.parseInt(splitValue[1]); + String statusCodeString = splitValue[2]; + HTTPResponseCode code = null; + for (int i = 0 ; i < HTTPResponseCode.values().length; i++) { + HTTPResponseCode responseCode = HTTPResponseCode.values()[i]; + if(responseCode.asInt == statusCode) { + code = responseCode; + } + } + ArrayList headersList = new ArrayList<>(); + String s = value; + s = s.substring(s.split("\n")[0].length() + 1); + for (String line : s.split("\n")) { + if (line.equals("")) + break; + headersList.add(new HTTPHeader(line)); + } + HTTPHeader[] headers = headersList.toArray(new HTTPHeader[0]); + StringBuilder body = new StringBuilder(); + try { + int start = value.indexOf("\n\n") + 2; + HTTPHeader header = null; + for (int i = 0; i < headers.length; i++) { + if(headers[i].key().equalsIgnoreCase("Content-Length")) + header = headers[i]; + } + if(header != null) { + int end = value.length(); + body = new StringBuilder(value.substring(start, end)); + } + else { + + /* + * INCREDIBLY hacky way to make chunk transfer work, will make better later + */ + ByteArrayInputStream b = new ByteArrayInputStream(value.substring(start).getBytes(StandardCharsets.ISO_8859_1)); + + for (int i = -1 ; i != 0 ; ) { + StringBuilder sbuf = new StringBuilder(); + int c; + while (!sbuf.toString().endsWith("\n") && (c = b.read()) != -1) { + sbuf.append((char) c); + } + i = Integer.parseInt(sbuf.toString().replaceAll("\r", "").split("\n")[0], 16); + byte[] buf = new byte[i]; + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + b.read(buf); + stream.write(buf); + body.append(stream.toString()); + } + } + } catch (Exception ignored) { + } + + HTTPResponseCode finalCode = code; + String finalBody = body.toString(); + return new ParsedHTTPValue() { + @Override + public String getHTTPVersion() { + return httpVersion; + } + + @Override + public int getStatusCode() { + return statusCode; + } + + @Override + public String getStatusCodeAsString() { + return statusCodeString; + } + + @Override + public String getPath() { + return null; + } + + @Override + public Map getQuery() { + return null; + } + + @Override + public HTTPResponseCode getStatusCodeAsEnum() { + return finalCode; + } + + @Override + public String getBodyRaw() { + return finalBody; + } + + @Override + public HTTPHeader[] getHeaders() { + return headers; + } + }; + } +} diff --git a/src/main/java/de/tudbut/net/http/HTTPResponseCode.java b/src/main/java/de/tudbut/net/http/HTTPResponseCode.java new file mode 100644 index 0000000..c5bff46 --- /dev/null +++ b/src/main/java/de/tudbut/net/http/HTTPResponseCode.java @@ -0,0 +1,84 @@ +package de.tudbut.net.http; + +/** + * Response codes to HTTPRequests + */ +public enum HTTPResponseCode { + Continue(100), + SwitchingProtocols(101), + Processing(102), + EarlyHints(103), + + OK(200), + Created(201), + Accepted(202), + NonAuthoritativeInformation(203), + NoContent(204), + ResetContent(205), + PartialContent(206), + MultiStatus(207), + AlreadyReported(208), + IM_Used(226), + + MultipleChoices(300), + MovedPermanently(301), + MovedTemporarily(302), + SeeOther(303), + NotModified(304), + UseProxy(305), + RESERVED__SwitchProxy(306), + TemporaryRedirect(307), + PermanentRedirect(308), + + BadRequest(400), + Unauthorized(401), + PaymentRequired(402), + Forbidden(403), + NotFound(404), + MethodNotAllowed(405), + NotAcceptable(406), + ProxyAuthenticationRequired(407), + RequestTimeout(408), + Conflict(409), + Gone(410), + LengthRequired(411), + PreconditionFailed(412), + PayloadTooLarge(413), + URI_TooLong(414), + UnsupportedMediaType(415), + RangeNotSatisfiable(416), + ExpectationFailed(417), + MisdirectedRequest(421), + UnprocessableEntity(422), + Locked(423), + FailedDependency(424), + TooEarly(425), + UpgradeRequired(426), + PreconditionRequired(428), + TooManyRequests(429), + RequestHeaderFieldsTooLarge(431), + UnavailableForLegalReasons(451), + + InternalServerError(500), + NotImplemented(501), + BadGateway(502), + ServiceUnavailable(503), + GatewayTimeout(504), + HTTPVersionNotSupported(505), + VariantAlsoNegotiates(506), + InsufficientStorage(507), + LoopDetected(508), + BandwidthLimitExceeded(509), + NotExtended(510), + NetworkAuthenticationRequired(511), + ; + + /** + * The id + */ + public final int asInt; + + HTTPResponseCode(int asIntIn) { + asInt = asIntIn; + } +} \ No newline at end of file diff --git a/src/main/java/de/tudbut/net/http/HTTPResponseFactory.java b/src/main/java/de/tudbut/net/http/HTTPResponseFactory.java new file mode 100644 index 0000000..f6ed1fe --- /dev/null +++ b/src/main/java/de/tudbut/net/http/HTTPResponseFactory.java @@ -0,0 +1,55 @@ +package de.tudbut.net.http; + +import java.nio.charset.StandardCharsets; + +/** + * Class to build HTTP responses + */ +public class HTTPResponseFactory { + + /** + * Creates a HTTPResponse + * @param responseCode {@link HTTPResponseCode} + * @param body The body of the request + * @param contentType {@link HTTPContentType} + * @param headers {@link HTTPHeader} Headers for the response + * @return The constructed {@link HTTPResponse} + */ + public static HTTPResponse create(HTTPResponseCode responseCode, String body, HTTPContentType contentType, HTTPHeader... headers) { + return create(responseCode, body, contentType.asHeaderString, headers); + } + /** + * Creates a HTTPResponse + * @param responseCode {@link HTTPResponseCode} + * @param body The body of the request + * @param contentType {@link HTTPContentType} + * @param headers {@link HTTPHeader} Headers for the response + * @return The constructed {@link HTTPResponse} + */ + public static HTTPResponse create(HTTPResponseCode responseCode, String body, String contentType, HTTPHeader... headers) { + return create(responseCode, body, new HTTPHeader("Content-Type", contentType), headers); + } + + /** + * Creates a HTTPResponse + * @param responseCode {@link HTTPResponseCode} + * @param body The body of the request + * @param contentType content type + * @param headers {@link HTTPHeader} Headers for the response + * @return The constructed {@link HTTPResponse} + */ + public static HTTPResponse create(HTTPResponseCode responseCode, String body, HTTPHeader contentType, HTTPHeader... headers) { + StringBuilder builder = new StringBuilder(); + + builder.append("HTTP/1.1 ").append(responseCode.asInt).append(" ").append(responseCode.name()).append("\r\n"); + builder.append(new HTTPHeader("Content-Type", contentType.value(), contentType.parameter())).append("\r\n"); + builder.append(new HTTPHeader("Content-Length", body.getBytes(StandardCharsets.ISO_8859_1).length + "")).append("\r\n"); + for (HTTPHeader header : headers) { + builder.append(header.toString()).append("\r\n"); + } + builder.append("\r\n"); + builder.append(body); + + return new HTTPResponse(builder.toString(), true); + } +} diff --git a/src/main/java/de/tudbut/net/http/HTTPServer.java b/src/main/java/de/tudbut/net/http/HTTPServer.java new file mode 100644 index 0000000..befeb4d --- /dev/null +++ b/src/main/java/de/tudbut/net/http/HTTPServer.java @@ -0,0 +1,213 @@ +package de.tudbut.net.http; + +import de.tudbut.io.RawLineReader; +import de.tudbut.io.StreamWriter; +import de.tudbut.type.Stoppable; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; +import java.security.*; +import java.util.ArrayList; +import java.util.concurrent.Executor; + +// TODO: this has a broken thing, please dont use for HTTPS until later. + +/** + * A HTTP server + */ +public class HTTPServer implements Stoppable { + private final int port; + private final ServerSocket serverSocket; + private final HTTPResponse serverError; + private final ArrayList handlers = new ArrayList<>(); + private final Executor executor; + + /** + * Constructs a HTTPServer without HTTPS + * @param portIn Port to listen on + * @throws IOException Inherited + */ + public HTTPServer(int portIn) throws IOException { + port = portIn; + serverError = HTTPResponseFactory.create(HTTPResponseCode.InternalServerError, "500 InternalServerError", HTTPContentType.TXT); + serverSocket = new ServerSocket(port); + executor = Runnable::run; + } + + /** + * Constructs a HTTPServer without HTTPS + * @param portIn Port to listen on + * @param serverErrorIn Response to send on error + * @param executorIn Executor + * @throws IOException Inherited + */ + public HTTPServer(int portIn, HTTPResponse serverErrorIn, Executor executorIn) throws IOException { + port = portIn; + serverError = serverErrorIn; + serverSocket = new ServerSocket(port); + executor = executorIn; + } + + /** + * Constructs a HTTPServer with HTTPS + * @param portIn Port to listen on + * @param serverErrorIn Response to send on error + * @param executorIn Executor + * @param keyStore {@link KeyStore} The PRIVATE KEY KeyStore + * @param keyStorePass Password for the KeyStore + * @throws IOException Inherited + */ + public HTTPServer(int portIn, HTTPResponse serverErrorIn, Executor executorIn, KeyStore keyStore, String keyStorePass) throws IOException { + port = portIn; + serverError = serverErrorIn; + try { + SSLContext context = SSLContext.getInstance("TLS"); + KeyManagerFactory managerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + managerFactory.init(keyStore, keyStorePass.toCharArray()); + context.init(managerFactory.getKeyManagers(), null, null); + serverSocket = context.getServerSocketFactory().createServerSocket(portIn); + } + catch (NoSuchAlgorithmException | KeyStoreException | UnrecoverableKeyException | KeyManagementException e) { + throw new IllegalArgumentException(e); + } + + executor = executorIn; + } + + /** + * Start listening for requests + */ + public void listen() { + new Thread(() -> { + Socket socket; + while (!isStopped()) { + try { + socket = serverSocket.accept(); + socket.setSoTimeout(10000); + Socket finalSocket = socket; + boolean b = true; + for (int i = 0 ; i < handlers.size() ; i++) { + if(!handlers.get(i).accept(socket.getRemoteSocketAddress()) || !handlers.get(i).accept(socket, socket.getRemoteSocketAddress())) + b = false; + } + if(b) { + executor.execute(() -> { + HTTPServerRequest serverRequest = new HTTPServerRequest("", finalSocket); + try { + HTTPHandler[] handlers = this.handlers.toArray(new HTTPHandler[0]); + + String s; + ArrayList headers = new ArrayList<>(); + String fullRequest = ""; + RawLineReader reader = new RawLineReader(finalSocket.getInputStream()); + int line = 0; + while ((s = reader.readLine()) != null) { + fullRequest += s + "\r\n"; + if (s.equals("")) { + break; + } + if (line != 0) { + headers.add(new HTTPHeader(s)); + } + line++; + } + int contentLength = 0; + for (HTTPHeader header : headers) { + if (header.key().equalsIgnoreCase("Content-Length")) { + contentLength = Integer.parseInt(header.value()); + } + } + if(contentLength != 0) { + for (int i = 0 ; i < contentLength ; i++) { + fullRequest += (char) reader.read(); + } + } + HTTPServerRequest request = new HTTPServerRequest(fullRequest, finalSocket); + serverRequest = request; + for (HTTPHandler handler : handlers) { + handler.handle(request); + } + } + catch (Throwable e) { + try { + new StreamWriter(finalSocket.getOutputStream()).writeChars(handlers.get(0).onError(serverRequest, serverError, e).value.toCharArray(), "ISO-8859-1"); + } + catch (Throwable ignore) { + } + } + }); + } + else { + executor.execute(() -> { + for (int i = 0 ; i < handlers.size() ; i++) { + try { + handlers.get(i).handleDeny(new HTTPServerRequest("", finalSocket)); + } + catch (Exception ignored) { } + } + }); + } + } catch (IOException ignore) { } + } + }).start(); + } + + /** + * Add a handler to handle requests + * @param handler The handler to add + */ + public void addHandler(HTTPHandler handler) { + handlers.add(handler); + } + + /** + * Handler for incoming HTTP request + */ + public interface HTTPHandler { + /** + * Handle a request to the server + * @param request The request to handle + * @throws Exception To make handling easier (less catching required) + */ + void handle(HTTPServerRequest request) throws Exception; + + /** + * Should the connection be accepted? (Useful for rate-limiting) + * @param address The address the connection is coming from + * @return If the connection should be accepted + */ + default boolean accept(SocketAddress address) { + return true; + } + /** + * Should the connection be accepted? (Useful for rate-limiting) + * @param socket The socket + * @param address The address the connection is coming from + * @return If the connection should be accepted + */ + default boolean accept(Socket socket, SocketAddress address) { + return true; + } + + /** + * + * @param request An empty request to hold the response methods + * @throws Exception To make handling easier (less catching required) + */ + default void handleDeny(HTTPServerRequest request) throws Exception { } + + /** + * @param request The request as constructed so far + * @param defaultResponse The server's default response + * @param theError The error + * @throws Exception To make handling easier (less catching required) + */ + default HTTPResponse onError(HTTPServerRequest request, HTTPResponse defaultResponse, Throwable theError) throws Exception { + return defaultResponse; + } + } +} diff --git a/src/main/java/de/tudbut/net/http/HTTPServerRequest.java b/src/main/java/de/tudbut/net/http/HTTPServerRequest.java new file mode 100644 index 0000000..aaaff6c --- /dev/null +++ b/src/main/java/de/tudbut/net/http/HTTPServerRequest.java @@ -0,0 +1,221 @@ +package de.tudbut.net.http; + +import de.tudbut.io.StreamWriter; +import de.tudbut.obj.Closable; +import de.tudbut.obj.ClosedClosableException; +import de.tudbut.obj.NotSupportedException; +import de.tudbut.tools.Value; + +import javax.net.ssl.SSLSocket; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +/** + * Used by the server to handle incoming HTTP requests + */ +public class HTTPServerRequest extends Value implements Closable { + /** + * The socket of the request + */ + public final Socket socket; + + public HTTPServerRequest(String value, Socket s) { + super(spl(value)); + socket = s; + } + + private static String spl(String s) { + String[] splitString = s.split("\r\n\r\n", 2); + if(splitString.length == 1) { + return s.replaceAll("\r", ""); + } + else { + return splitString[0].replaceAll("\r", "") + "\n\n" + splitString[1]; + } + } + + /** + * Close the streams + */ + @Override + public void close() throws IOException { + Closable.super.close(); + socket.shutdownOutput(); + if(socket instanceof SSLSocket) { + socket.setSoLinger(true, 100); + try { + Thread.sleep(100); + } catch (InterruptedException ignored) { + } + try { + SSLSocket.class.getMethod("close").invoke(socket); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + System.err.println("!!! ERROR: OBFUSCATED OR INVALID JRE"); + socket.close(); + } + } + else + socket.close(); + } + + /** + * Not supported. + * @throws NotSupportedException [Always thrown] + */ + @Override + public void open() throws NotSupportedException { + throw new NotSupportedException(); + } + + /** + * Responds to the request. Returns if the request was already responded to. + * @param response The response to send + * @throws IOException Inherited + */ + public void respond(HTTPResponse response) throws IOException { + if(isClosed()) + return; + StreamWriter writer = new StreamWriter(socket.getOutputStream()); + writer.writeChars(response.value.toCharArray(), "ISO_8859_1"); + close(); + } + + /** + * Responds to the request. Throws if the request was already responded to. + * @param response The response to send + * @throws IOException Inherited + * @throws ClosedClosableException If the request has already been responded to. + */ + public void forceRespond(HTTPResponse response) throws IOException, ClosedClosableException { + if(isClosed()) + throw new ClosedClosableException(); + StreamWriter writer = new StreamWriter(socket.getOutputStream()); + writer.writeChars(response.value.toCharArray(), "ISO_8859_1"); + close(); + } + + /** + * Parses the request + * @return The {@link ParsedHTTPValue} of the request + */ + public ParsedHTTPValue parse() { + String[] splitValue = value.split("\n")[0].split(" "); + + String httpVersion = splitValue[2]; + String statusCode = splitValue[0]; + String actualPath = splitValue[1]; + String path = actualPath.split("\\?")[0]; + Map map = new HashMap<>(); + try { + String query = actualPath.split("\\?")[1]; + + String[] splitByAnd = query.split("&"); + for (int i = 0; i < splitByAnd.length; i++) { + try { + String[] v = splitByAnd[i].split("="); + map.put(HTTPUtils.decodeUTF8(v[0]), HTTPUtils.decodeUTF8(v[1])); + } catch (Exception ignore) { } + } + } catch (Exception ignore) { } + HTTPRequestType code = null; + for (int i = 0; i < HTTPRequestType.values().length; i++) { + HTTPRequestType requestType = HTTPRequestType.values()[i]; + if(requestType.name().equals(statusCode)) { + code = requestType; + } + } + HTTPRequestType finalCode = code; + ArrayList headersList = new ArrayList<>(); + String s = value; + s = s.substring(s.split("\n")[0].length() + 1); + for (String line : s.split("\n")) { + if (line.equals("")) + break; + headersList.add(new HTTPHeader(line.split(": ")[0], line.split(": ")[1])); + } + HTTPHeader[] headers = headersList.toArray(new HTTPHeader[0]); + String body = ""; + try { + int start = value.indexOf("\n\n") + 2; + HTTPHeader header = null; + for (int i = 0; i < headers.length; i++) { + if(headers[i].key().equalsIgnoreCase("Content-Length")) + header = headers[i]; + } + if(header != null) { + int end = value.length(); + body = value.substring(start, end); + } + else { + /* + * INCREDIBLY hacky way to make chunk transfer work, will make better later + */ + ByteArrayInputStream b = new ByteArrayInputStream(value.substring(start).getBytes(StandardCharsets.ISO_8859_1)); + + for (int chunk = 0, i = -1 ; i != 0 ; chunk++) { + StringBuilder sbuf = new StringBuilder(); + int c; + while (!sbuf.toString().endsWith("\n") && (c = b.read()) != -1) { + sbuf.append((char) c); + } + i = Integer.parseInt(sbuf.toString().replaceAll("\r", "").split("\n")[0], 16); + byte[] buf = new byte[i]; + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + b.read(buf); + stream.write(buf); + body += stream.toString(); + } + } + } catch (Exception ignored) { + } + String finalBody = body; + return new ParsedHTTPValue() { + @Override + public String getHTTPVersion() { + return httpVersion; + } + + @Override + public int getStatusCode() { + return 0; + } + + @Override + public String getStatusCodeAsString() { + return statusCode; + } + + @Override + public String getPath() { + return path; + } + + @Override + public Map getQuery() { + return map; + } + + @Override + public HTTPRequestType getStatusCodeAsEnum() { + return finalCode; + } + + @Override + public String getBodyRaw() { + return finalBody; + } + + @Override + public HTTPHeader[] getHeaders() { + return headers; + } + }; + } +} diff --git a/src/main/java/de/tudbut/net/http/HTTPUtils.java b/src/main/java/de/tudbut/net/http/HTTPUtils.java new file mode 100644 index 0000000..fb52261 --- /dev/null +++ b/src/main/java/de/tudbut/net/http/HTTPUtils.java @@ -0,0 +1,80 @@ +package de.tudbut.net.http; + +import de.tudbut.obj.DoubleTypedObject; +import de.tudbut.obj.TLMap; +import de.tudbut.tools.Lock; + +import java.io.UnsupportedEncodingException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +/** + * Utils for sending/receiving with HTTP + */ +public class HTTPUtils { + public static String encodeUTF8(String s) { + try { + return URLEncoder.encode(s, String.valueOf(StandardCharsets.UTF_8)).replace("+", "%20"); + } + catch (UnsupportedEncodingException e) { + throw new RuntimeException("Too old OS"); + } + } + public static String decodeUTF8(String s) { + try { + return URLDecoder.decode(s.replace("+", "%20"), String.valueOf(StandardCharsets.UTF_8)); + } + catch (UnsupportedEncodingException e) { + throw new RuntimeException("Too old OS"); + } + } + + public static String rawToUtf8(String raw) { + return new String(raw.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8); + } + + public static String utf8ToRaw(String utf8) { + return new String(utf8.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1); + } + + public static HTTPServer.HTTPHandler createRateLimitingHandler(int rps, HTTPServer.HTTPHandler requestHandler) { + TLMap> map = new TLMap<>(); + return new HTTPServer.HTTPHandler() { + @Override + public void handle(HTTPServerRequest request) throws Exception { + requestHandler.handle(request); + } + + @Override + public boolean accept(SocketAddress address) { + String ip = ((InetSocketAddress) address).getHostString(); + DoubleTypedObject user = map.get(ip); + if(user == null) + map.set(ip, user = new DoubleTypedObject<>(0, new Lock())); + user.o++; + if (user.t.isLocked()) { + if (user.o > rps) + return false; + } + else { + user.t.lock(1000); + user.o = 1; + } + return requestHandler.accept(address); + } + + @Override + public void handleDeny(HTTPServerRequest request) throws Exception { + requestHandler.handleDeny(request); + } + + @Override + public HTTPResponse onError(HTTPServerRequest request, HTTPResponse defaultResponse, Throwable theError) throws Exception { + return requestHandler.onError(request, defaultResponse, theError); + } + }; + } +} diff --git a/src/main/java/de/tudbut/net/http/ParsedHTTPValue.java b/src/main/java/de/tudbut/net/http/ParsedHTTPValue.java new file mode 100644 index 0000000..abf868f --- /dev/null +++ b/src/main/java/de/tudbut/net/http/ParsedHTTPValue.java @@ -0,0 +1,65 @@ +package de.tudbut.net.http; + +import java.nio.charset.StandardCharsets; +import java.util.Map; + +/** + * {@link HTTPResponse#parse} + * {@link HTTPServerRequest#parse} + */ +public interface ParsedHTTPValue { + + /** + * @return The version used to send the request/response + */ + String getHTTPVersion(); + + /** + * @return The status code + */ + int getStatusCode(); + + /** + * @return The status code + */ + String getStatusCodeAsString(); + + /** + * @return The path. Empty if a response + */ + String getPath(); + + /** + * @return The query. Empty if a response + */ + Map getQuery(); + + /** + * @return The status code + */ + Object getStatusCodeAsEnum(); + + /** + * @return The body of the request + */ + String getBodyRaw(); + + /** + * @return The body of the request as bytes (raw) + */ + default byte[] getBodyBytes() { + return getBodyRaw().getBytes(StandardCharsets.ISO_8859_1); + } + + /** + * @return The body of the request as UTF-8 + */ + default String getBody() { + return HTTPUtils.rawToUtf8(getBodyRaw()); + } + + /** + * @return The headers of the request + */ + HTTPHeader[] getHeaders(); +} \ No newline at end of file diff --git a/src/main/java/de/tudbut/net/http/serverimpl/ContentType.java b/src/main/java/de/tudbut/net/http/serverimpl/ContentType.java new file mode 100644 index 0000000..77b5bb5 --- /dev/null +++ b/src/main/java/de/tudbut/net/http/serverimpl/ContentType.java @@ -0,0 +1,14 @@ +package de.tudbut.net.http.serverimpl; + +import de.tudbut.net.http.HTTPContentType; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface ContentType { + HTTPContentType value(); +} diff --git a/src/main/java/de/tudbut/net/http/serverimpl/HTTPServerImpl.java b/src/main/java/de/tudbut/net/http/serverimpl/HTTPServerImpl.java new file mode 100644 index 0000000..e64421e --- /dev/null +++ b/src/main/java/de/tudbut/net/http/serverimpl/HTTPServerImpl.java @@ -0,0 +1,292 @@ +package de.tudbut.net.http.serverimpl; + +import de.tudbut.net.http.*; +import de.tudbut.obj.TLMap; +import de.tudbut.tools.ReflectUtil; +import de.tudbut.tools.Tools; +import de.tudbut.net.http.*; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Objects; + +import static de.tudbut.net.http.serverimpl.Method.from; + +public class HTTPServerImpl implements HTTPServer.HTTPHandler { + + ArrayList listeners = new ArrayList<>(); + TLMap> methods = new TLMap<>(); + TLMap methodObjects = new TLMap<>(); + + public void addListener(Object listener) { + if(!checkValidity(listener)) + throw new IllegalArgumentException("The listener contains a method that is annotated with @Serve " + + "but doesn't have matching arguments! Required: " + + "de.tudbut.net.http.HTTPServerRequest, de.tudbut.net.http.ParsedHTTPValue, [etc]" + ); + listeners.add(listener); + reindex(); + } + public void removeListener(Object listener) { + listeners.remove(listener); + reindex(); + } + + private void reindex() { + methods = new TLMap<>(); + for (int i = 0; i < listeners.size(); i++) { + Object listener = listeners.get(i); + Class clazz = listener.getClass(); + Method[] clazzMethods = clazz.getDeclaredMethods(); + for (int j = 0; j < clazzMethods.length; j++) { + Method method = clazzMethods[j]; + if(method.isAnnotationPresent(Serve.class)) { + Serve annotation = method.getAnnotation(Serve.class); + Class[] params = method.getParameterTypes(); + if( + params[0] != HTTPServerRequest.class || + params[1] != ParsedHTTPValue.class || + checkParams(method, params) + ) + continue; + ReflectUtil.forceAccessible(method); + ArrayList list = methods.get(annotation.type().toString().charAt(0) + annotation.value(), ArrayList::new); + list.add(method); + if(list.size() == 1) + methods.set(annotation.type().toString().charAt(0) + annotation.value(), list); + methodObjects.set(method, listener); + } + } + } + } + + private boolean checkParams(Method method, Class[] params) { + Annotation[][] paramAnnotations = method.getParameterAnnotations(); + boolean gvalid = true; + for (int k = 2; k < params.length; k++) { + boolean valid = false; + for (int l = 0; l < paramAnnotations[k].length; l++) { + if( + paramAnnotations[k][l].annotationType() == Parameter.class || + paramAnnotations[k][l].annotationType() == Path.class || + paramAnnotations[k][l].annotationType() == Header.class + ) { + valid = true; + break; + } + } + gvalid = gvalid && valid; + } + return !gvalid; + } + + private boolean checkValidity(Object listener) { + Class clazz = listener.getClass(); + Method[] clazzMethods = clazz.getDeclaredMethods(); + for (int j = 0; j < clazzMethods.length; j++) { + Method method = clazzMethods[j]; + if(method.isAnnotationPresent(Serve.class)) { + Class[] params = method.getParameterTypes(); + if(params[0] != HTTPServerRequest.class) + return false; + if(params[1] != ParsedHTTPValue.class) + return false; + if(checkParams(method, params)) + return false; + } + } + return true; + } + + private static boolean checkRequestMethods(Method method, HTTPRequestType requestType) { + HTTPRequestType[] values = HTTPRequestType.values(); + ArrayList allowed = new ArrayList<>(); + for (int i = 0, valuesLength = values.length ; i < valuesLength ; i++) { + HTTPRequestType value = values[i]; + if(method.getDeclaredAnnotation(from(value)) != null) { + allowed.add(value); + } + } + return allowed.size() == 0 || allowed.contains(requestType); + } + + private Method find(ArrayList methods, ParsedHTTPValue httpValue) { + for (int j = 0 ; j < methods.size() ; j++) { + Method val = methods.get(j); + if(!checkRequestMethods(val, (HTTPRequestType) httpValue.getStatusCodeAsEnum())) { + continue; + } + return val; + } + return null; + } + + @Override + public void handle(HTTPServerRequest request) throws Exception { + ParsedHTTPValue httpValue = request.parse(); + Method method = null; + String[] keys = methods.keys().toArray(new String[0]); + ArrayList[] vals = ((ArrayList[]) methods.values().toArray(new ArrayList[0])); + String path = httpValue.getPath(); + while (!path.equals("/") && path.endsWith("/")) + path = path.substring(0, path.length() - 1); + for (int i = 0; i < keys.length; i++) { + String key = keys[i].substring(1); + String[] splitKey = key.split("/"); + String[] splitPath = path.split("/"); + if(!key.endsWith("/*") && splitKey.length != splitPath.length) { + continue; + } + boolean valid = true; + for (int k = 0 ; k < splitKey.length && k < splitPath.length ; k++) { + String s = splitKey[k]; + switch (keys[i].charAt(0)) { + case 'W': + if (!splitPath[k].matches(Tools.wildcardToRegex(splitKey[k]))) + valid = false; + break; + case 'P': + if (!splitPath[k].equals(splitKey[k])) + valid = false; + break; + case 'R': + if (!splitPath[k].matches(splitKey[k])) + valid = false; + break; + } + } + if(valid) { + Method m = find(vals[i], httpValue); + if(m != null) + method = m; + } + } + if(method == null) { + method = find(methods.get("404"), httpValue); + } + Object[] params = createParams(method, httpValue, request); + if(params == null) { + method = find(methods.get("400"), httpValue); + params = new Object[] {request, httpValue}; + } + try { + if (Objects.requireNonNull(method).getReturnType() == HTTPResponse.class) { + request.respond((HTTPResponse) method.invoke(methodObjects.get(method), params)); + return; + } + String s = method.invoke(methodObjects.get(method), params).toString(); + ContentType contentType = method.getDeclaredAnnotation(ContentType.class); + if(contentType == null) { + contentType = new ContentType() { + @Override + public Class annotationType() { + return ContentType.class; + } + + @Override + public HTTPContentType value() { + return HTTPContentType.HTML; + } + }; + } + request.respond(HTTPResponseFactory.create(HTTPResponseCode.OK, s, contentType.value())); + } catch (InvocationTargetException exception) { + if(exception.getCause() instanceof Response) { + request.respond(((Response) exception.getCause()).response); + } + else + throw exception; + } + } + + private Parameter getParameterAnnotation(Annotation[] annotations) { + for (int i = 0; i < annotations.length; i++) { + if(annotations[i].annotationType() == Parameter.class) + return (Parameter) annotations[i]; + } + return null; + } + + private Path getPathAnnotation(Annotation[] annotations) { + for (int i = 0; i < annotations.length; i++) { + if(annotations[i].annotationType() == Path.class) + return (Path) annotations[i]; + } + return null; + } + + private Header getHeaderAnnotation(Annotation[] annotations) { + for (int i = 0; i < annotations.length; i++) { + if(annotations[i].annotationType() == Header.class) + return (Header) annotations[i]; + } + return null; + } + + private Object[] createParams(Method method, ParsedHTTPValue v, HTTPServerRequest r) { + try { + ArrayList list = new ArrayList<>(); + list.add(r); + list.add(v); + Class[] parameterTypes = method.getParameterTypes(); + Annotation[][] parameters = method.getParameterAnnotations(); + String[] splitPath = v.getPath().split("/"); + for (int i = 2; i < parameterTypes.length; i++) { + Parameter param = getParameterAnnotation(parameters[i]); + Path path = getPathAnnotation(parameters[i]); + Header header = getHeaderAnnotation(parameters[i]); + String s = null; + if (header != null) { + HTTPHeader[] headers = v.getHeaders(); + for (int j = 0, headersLength = headers.length ; j < headersLength ; j++) { + HTTPHeader httpHeader = headers[j]; + if (httpHeader.key().equalsIgnoreCase(header.value())) { + s = httpHeader.value(); + } + } + } + if (s == null && param != null) { + s = v.getQuery().get(param.value()); + } + if (s == null && path != null) { + if (splitPath.length > path.value()) { + s = splitPath[path.value()]; + } + } + if(s == null) { + list.add(null); + continue; + } + if(parameterTypes[i] == String.class) { + list.add(s); + } + else if(parameterTypes[i] == Integer.class) { + list.add(Integer.parseInt(s)); + } + else if(parameterTypes[i] == Boolean.class) { + list.add(!s.equals("0") && !s.equals("false") && !s.equals("null")); + } + else if(parameterTypes[i] == Long.class) { + list.add(Long.parseLong(s)); + } + else if(parameterTypes[i] == Float.class) { + list.add(Float.parseFloat(s)); + } + else if(parameterTypes[i] == Double.class) { + list.add(Double.parseDouble(s)); + } + else if(parameterTypes[i] == Short.class) { + list.add(Short.parseShort(s)); + } + else { + return null; + } + } + return list.toArray(new Object[0]); + } catch (Exception e) { + return null; + } + } +} diff --git a/src/main/java/de/tudbut/net/http/serverimpl/Header.java b/src/main/java/de/tudbut/net/http/serverimpl/Header.java new file mode 100644 index 0000000..a4f58ce --- /dev/null +++ b/src/main/java/de/tudbut/net/http/serverimpl/Header.java @@ -0,0 +1,12 @@ +package de.tudbut.net.http.serverimpl; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +public @interface Header { + String value(); +} diff --git a/src/main/java/de/tudbut/net/http/serverimpl/Method.java b/src/main/java/de/tudbut/net/http/serverimpl/Method.java new file mode 100644 index 0000000..efb2e20 --- /dev/null +++ b/src/main/java/de/tudbut/net/http/serverimpl/Method.java @@ -0,0 +1,77 @@ +package de.tudbut.net.http.serverimpl; + +import de.tudbut.net.http.HTTPRequestType; + +import java.lang.annotation.*; + +public class Method { + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Get { + + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Post { + + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Head { + + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Put { + + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Patch { + + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Delete { + + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Trace { + + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Options { + + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Connect { + + } + + public static Class from(HTTPRequestType type) { + switch (type) { + case GET: return Get.class; + case PUT: return Put.class; + case HEAD: return Head.class; + case POST: return Post.class; + case PATCH: return Patch.class; + case TRACE: return Trace.class; + case DELETE: return Delete.class; + case CONNECT: return Connect.class; + case OPTIONS: return Options.class; + default: return null; + } + } +} diff --git a/src/main/java/de/tudbut/net/http/serverimpl/Parameter.java b/src/main/java/de/tudbut/net/http/serverimpl/Parameter.java new file mode 100644 index 0000000..17d80ea --- /dev/null +++ b/src/main/java/de/tudbut/net/http/serverimpl/Parameter.java @@ -0,0 +1,12 @@ +package de.tudbut.net.http.serverimpl; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +public @interface Parameter { + String value(); +} diff --git a/src/main/java/de/tudbut/net/http/serverimpl/Path.java b/src/main/java/de/tudbut/net/http/serverimpl/Path.java new file mode 100644 index 0000000..cc3e258 --- /dev/null +++ b/src/main/java/de/tudbut/net/http/serverimpl/Path.java @@ -0,0 +1,13 @@ +package de.tudbut.net.http.serverimpl; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +public @interface Path { + + int value(); +} diff --git a/src/main/java/de/tudbut/net/http/serverimpl/Response.java b/src/main/java/de/tudbut/net/http/serverimpl/Response.java new file mode 100644 index 0000000..a08a1e1 --- /dev/null +++ b/src/main/java/de/tudbut/net/http/serverimpl/Response.java @@ -0,0 +1,15 @@ +package de.tudbut.net.http.serverimpl; + +import de.tudbut.net.http.HTTPContentType; +import de.tudbut.net.http.HTTPResponse; +import de.tudbut.net.http.HTTPResponseCode; +import de.tudbut.net.http.HTTPResponseFactory; + +public class Response extends RuntimeException { + + HTTPResponse response; + + public Response(HTTPResponseCode code, String body, HTTPContentType contentType) { + response = HTTPResponseFactory.create(code, body, contentType); + } +} diff --git a/src/main/java/de/tudbut/net/http/serverimpl/Serve.java b/src/main/java/de/tudbut/net/http/serverimpl/Serve.java new file mode 100644 index 0000000..c693460 --- /dev/null +++ b/src/main/java/de/tudbut/net/http/serverimpl/Serve.java @@ -0,0 +1,21 @@ +package de.tudbut.net.http.serverimpl; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Serve { + + String value(); + Type type() default Type.WILDCARD; + + enum Type { + PLAIN, + REGEX, + WILDCARD, + ; + } +} diff --git a/src/main/java/de/tudbut/net/ic/Bus.java b/src/main/java/de/tudbut/net/ic/Bus.java new file mode 100644 index 0000000..b538f09 --- /dev/null +++ b/src/main/java/de/tudbut/net/ic/Bus.java @@ -0,0 +1,82 @@ +package de.tudbut.net.ic; + +import de.tudbut.tools.Lock; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.nio.ByteBuffer; + +public class Bus implements Closeable { // Work in progress, will comment later! + + final InputStream i; + final OutputStream o; + final Lock syncI = new Lock(), syncO = new Lock(); + boolean isClosed; + + public Bus(InputStream i, OutputStream o) { + this.i = i; + this.o = o; + } + + public Bus(Socket socket) throws IOException { + this(socket.getInputStream(), socket.getOutputStream()); + } + + public void write(ByteBuffer buffer) throws IOException { + StringBuilder r = new StringBuilder("Written: "); + syncO.waitHere(); + syncO.lock(); + try { + byte[] bytes = buffer.array(); + for (int j = 0; j < bytes.length; j++) { + o.write(bytes[j]); + r.append((char) Byte.toUnsignedInt(bytes[j])); + } + o.flush(); + syncO.unlock(); + } catch (IOException e) { + syncO.unlock(); + throw new IOException(r.toString(), e); + } + } + + public void read(ByteBuffer buffer) throws IOException { + StringBuilder r = new StringBuilder("Read: "); + syncI.waitHere(); + syncI.lock(); + try { + byte[] bytes = buffer.array(); + for (int j = 0; j < bytes.length; j++) { + bytes[j] = (byte) i.read(); + r.append((char) Byte.toUnsignedInt(bytes[j])); + } + syncI.unlock(); + } catch (IOException e) { + syncI.unlock(); + throw new IOException(r.toString(), e); + } + } + + public void close() throws IOException { + i.close(); + o.close(); + syncI.unlock(); + syncO.unlock(); + isClosed = true; + } + + public boolean isClosed() { + if(isClosed) + return true; + try { + i.available(); + } + catch (IOException e) { + isClosed = true; + } + return isClosed; + } +} diff --git a/src/main/java/de/tudbut/net/ic/PBIC.java b/src/main/java/de/tudbut/net/ic/PBIC.java new file mode 100644 index 0000000..fa6baff --- /dev/null +++ b/src/main/java/de/tudbut/net/ic/PBIC.java @@ -0,0 +1,272 @@ +package de.tudbut.net.ic; + +import de.tudbut.tools.BetterJ; +import de.tudbut.tools.Tools; + +import java.io.Closeable; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +public class PBIC { + + public static int getInt(byte[] bytes) { + if(bytes.length == 4) { + int i = 0; + int[] ints = Tools.byteArrayToIntArray(bytes); + i += ints[0] << 3 * 8; + i += ints[1] << 2 * 8; + i += ints[2] << 1 * 8; + i += ints[3] << 0 * 8; + return i; + } + else + throw new NumberFormatException(); + } + + public static byte[] putInt(int i) { + byte[] bytes = new byte[4]; + bytes[0] = (byte) ((i >> 3 * 8) & 0xff); + bytes[1] = (byte) ((i >> 2 * 8) & 0xff); + bytes[2] = (byte) ((i >> 1 * 8) & 0xff); + bytes[3] = (byte) ((i >> 0 * 8) & 0xff); + return bytes; + } + + public static class Server implements Closeable { + private final int port; + protected final ServerSocket server; + public final ArrayList sockets = new ArrayList<>(); + public final ArrayList busses = new ArrayList<>(); + public final ArrayList connections = new ArrayList<>(); + public final ArrayList onJoin = new ArrayList<>(); + public Connection lastConnection; + + public Server(int port) throws IOException { + this.port = port; + server = new ServerSocket(); + } + + protected void listen() { + while (server.isBound() && !server.isClosed()) { + try { + Socket socket = server.accept(); + + sockets.add(socket); + Bus bus = new Bus(socket); + busses.add(bus); + lastConnection = onJoin(socket, bus); + Runnable[] array = onJoin.toArray(new Runnable[0]); + for (int i = 0; i < array.length; i++) { + BetterJ.t(array[i]); + } + Thread.sleep(10); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private Connection onJoin(Socket socket, Bus bus) { + Connection connection = new Connection() { + public Bus getBus() { + return bus; + } + + public Socket getSocket() { + return socket; + } + }; + this.connections.add(connection); + return connection; + } + + public void start() throws IOException { + server.bind(new InetSocketAddress(port)); + BetterJ.t(this::listen); + } + + public int getPort() { + return port; + } + + @Override + public void close() throws IOException { + int i; + for(i = 0; i < this.busses.size(); ++i) { + this.busses.get(i).close(); + } + + for(i = 0; i < this.sockets.size(); ++i) { + this.sockets.get(i).close(); + } + + this.server.close(); + } + + } + + public static class PBICException extends Exception { + public PBICException(String message) { + super(message); + } + public PBICException(String message, Exception cause) { + super(message, cause); + } + + public PBICException(Exception cause) { + super(cause); + } + + public static class PBICReadException extends PBICException { + + public PBICReadException(Exception cause) { + super(cause); + } + } + public static class PBICWriteException extends PBICException { + + public PBICWriteException(Exception cause) { + super(cause); + } + } + } + + public interface Connection { + Map oSync = new HashMap<>(); + Map iSync = new HashMap<>(); + + Bus getBus(); + + Socket getSocket(); + + + default boolean isClosed() { + return getBus().isClosed() || getSocket().isClosed(); + } + + default void writePacket(Packet packet) throws PBICException.PBICWriteException { + + while (oSync.getOrDefault(this, false)); + + oSync.put(this, true); + + ByteBuffer buffer; + + int length = packet.getLength(); + byte[] content = packet.getContentBytes(); + + try { + // Send length + buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.put(putInt(length)); + getBus().write(buffer); + + // Send content + buffer = ByteBuffer.allocate(length); + for (int i = 0; i < content.length; i++) { + buffer.put(content[i]); + } + getBus().write(buffer); + } catch (Exception e) { + oSync.put(this, false); + throw new PBICException.PBICWriteException(e); + } + + oSync.put(this, false); + } + + default Packet readPacket() throws PBICException.PBICReadException { + + while (iSync.getOrDefault(this, false)); + + iSync.put(this, true); + + ByteBuffer buffer; + + int length; + byte[] content; + + String strContent; + try { + // Read length + buffer = ByteBuffer.allocate(Integer.BYTES); + getBus().read(buffer); + length = getInt(buffer.array()); + + // Read content + buffer = ByteBuffer.allocate(length); + getBus().read(buffer); + content = buffer.array(); + + // Parse content + strContent = new String(content); + } catch (Exception e) { + iSync.put(this, false); + throw new PBICException.PBICReadException(e); + } + + iSync.put(this, false); + + return new Packet() { + @Override + public int getLength() { + return length; + } + + @Override + public String getContent() { + return strContent; + } + }; + } + } + + public interface Packet { + default int getLength() { + return getContentBytes().length; + } + default byte[] getContentBytes() { + return getContent().getBytes(); + } + + String getContent(); + } + + public static class Client implements Closeable { + private final Socket client; + public Connection connection; + + public Client(String ip, int port) throws IOException { + client = new Socket(ip, port); + + start(new Bus(client)); + } + + private void start(Bus bus) { + connection = new Connection() { + @Override + public Bus getBus() { + return bus; + } + + @Override + public Socket getSocket() { + return client; + } + }; + } + + @Override + public void close() throws IOException { + connection.getBus().close(); + client.close(); + } + } + +} diff --git a/src/main/java/de/tudbut/net/nethandler/Packet.java b/src/main/java/de/tudbut/net/nethandler/Packet.java new file mode 100644 index 0000000..9c2d56a --- /dev/null +++ b/src/main/java/de/tudbut/net/nethandler/Packet.java @@ -0,0 +1,249 @@ +package de.tudbut.net.nethandler; + +import de.tudbut.net.ws.Connection; +import de.tudbut.tools.Tools; +import de.tudbut.type.Vector3d; +import de.tudbut.global.DebugStateManager; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +@Deprecated +public class Packet { + private final AtomicInteger id = new AtomicInteger(-1); + private final AtomicReference data = new AtomicReference<>(); + + public Packet(PacketData data) { + setData(data); + } + + public Packet() { + } + + public static PacketData fromString(String packet) { + try { + return fromString(packet, null); + } + catch (Exception ignore) { + DebugStateManager.getDebugLogger().debug("Packet couldn't be received. Returning null!"); + return null; + } + } + + public static PacketData fromString(String packet, Connection c) throws IOException { + if (packet.startsWith("[BEGIN")) { + StringBuilder d = new StringBuilder(packet); + + if (!d.toString().startsWith("[BEGIN(") && !d.toString().endsWith(")[")) + throw new IllegalStateException("Next data in line is not a splitPacket!"); + + d = new StringBuilder(); + while (!d.toString().endsWith("]END]")) { + d.append(c.receive()); + } + + d = new StringBuilder(d.substring(0, d.length() - "]END]".length())); + + return fromString(d.toString()); + } + + + Map map = Tools.stringToMap(packet); + + return new PacketData() { + public int getID() { + return Integer.parseInt(map.get("id")); + } + + public String getString0() { + return map.get("s0"); + } + + public String getString1() { + return map.get("s1"); + } + + public int getInt0() { + return Integer.parseInt(map.getOrDefault("i0", "0")); + } + + public int getInt1() { + return Integer.parseInt(map.getOrDefault("i1", "0")); + } + + public long getLong0() { + return Long.parseLong(map.getOrDefault("l0", "0")); + } + + public long getLong1() { + return Long.parseLong(map.getOrDefault("l1", "0")); + } + + public Map getMap0() { + return Tools.stringToMap(map.get("m0")); + } + + public Map getMap1() { + return Tools.stringToMap(map.get("m1")); + } + + public double getDouble0() { + return Double.parseDouble(map.getOrDefault("d0", "0")); + } + + public double getDouble1() { + return Double.parseDouble(map.getOrDefault("d1", "0")); + } + + public float getFloat0() { + return Float.parseFloat(map.getOrDefault("f0", "0")); + } + + public float getFloat1() { + return Float.parseFloat(map.getOrDefault("f1", "0")); + } + + public Vector3d getVector0() { + return new Vector3d(Double.parseDouble(Tools.stringToMap(map.get("v0")).get("x")), Double.parseDouble(Tools.stringToMap(map.get("v0")).get("y")), Double.parseDouble(Tools.stringToMap(map.get("v0")).get("z"))); + } + + public Vector3d getVector1() { + return new Vector3d(Double.parseDouble(Tools.stringToMap(map.get("v1")).get("x")), Double.parseDouble(Tools.stringToMap(map.get("v1")).get("y")), Double.parseDouble(Tools.stringToMap(map.get("v1")).get("z"))); + } + }; + } + + public static PacketData receivePieces(Connection c) throws IOException { + try { + StringBuilder d = new StringBuilder(c.receive()); + + if (!d.toString().startsWith("[BEGIN(") && !d.toString().endsWith(")[")) + throw new IllegalStateException("Next data in line is not a splitPacket!"); + + int bytesPerPiece = Integer.parseInt(d.toString().split("\\(")[1].split("\\)")[0]); + + d = new StringBuilder(); + while (!d.toString().endsWith("]END]")) { + d.append(c.receive()); + } + + d = new StringBuilder(d.substring(0, d.length() - "]END]".length())); + + return fromString(d.toString()); + } + catch (IOException e) { + throw e; + } + catch (Exception e) { + throw new IllegalStateException("Next data in line is not a splitPacket!"); + } + } + + public void send(Connection connection) throws IOException { + connection.send(this.toString()); + } + + public int getID() { + return id.get(); + } + + public void setID(int i) { + id.set(i); + } + + public T getDataValue(PacketDataID id) { + switch (id) { + case ID: + return (T) new Integer(getData().getID()); + case INT0: + return (T) new Integer(getData().getInt0()); + case INT1: + return (T) new Integer(getData().getInt1()); + case STRING0: + return (T) getData().getString0(); + case STRING1: + return (T) getData().getString1(); + case LONG0: + return (T) new Long(getData().getLong0()); + case LONG1: + return (T) new Long(getData().getLong1()); + case DOUBLE0: + return (T) new Double(getData().getDouble0()); + case DOUBLE1: + return (T) new Double(getData().getDouble1()); + case FLOAT0: + return (T) new Float(getData().getFloat0()); + case FLOAT1: + return (T) new Float(getData().getFloat1()); + case MAP0: + return (T) getData().getMap0(); + case MAP1: + return (T) getData().getMap1(); + case VECTOR0: + return (T) getData().getVector0(); + case VECTOR1: + return (T) getData().getVector1(); + } + return null; + } + + public PacketData getData() { + return data.get(); + } + + public void setData(PacketData d) { + data.set(d); + setID(data.get().getID()); + } + + @Override + public String toString() { + Map map = new HashMap<>(); + map.put("id", String.valueOf(getID())); + map.put("i0", String.valueOf(((Integer) getDataValue(PacketDataID.INT0)).intValue())); + map.put("i1", String.valueOf(((Integer) getDataValue(PacketDataID.INT1)).intValue())); + map.put("s0", getDataValue(PacketDataID.STRING0)); + map.put("s1", getDataValue(PacketDataID.STRING1)); + map.put("l0", String.valueOf(((Long) getDataValue(PacketDataID.LONG0)).longValue())); + map.put("l1", String.valueOf(((Long) getDataValue(PacketDataID.LONG1)).longValue())); + map.put("d0", String.valueOf(((Double) getDataValue(PacketDataID.DOUBLE0)).doubleValue())); + map.put("d1", String.valueOf(((Double) getDataValue(PacketDataID.DOUBLE1)).doubleValue())); + map.put("f0", String.valueOf(((Float) getDataValue(PacketDataID.FLOAT0)).floatValue())); + map.put("f1", String.valueOf(((Float) getDataValue(PacketDataID.FLOAT1)).floatValue())); + map.put("m0", Tools.mapToString(getDataValue(PacketDataID.MAP0))); + map.put("m1", Tools.mapToString(getDataValue(PacketDataID.MAP1))); + Map v0map = new HashMap<>(); + v0map.put("x", String.valueOf(((Vector3d) getDataValue(PacketDataID.VECTOR0)).getX())); + v0map.put("y", String.valueOf(((Vector3d) getDataValue(PacketDataID.VECTOR0)).getY())); + v0map.put("z", String.valueOf(((Vector3d) getDataValue(PacketDataID.VECTOR0)).getZ())); + map.put("v0", Tools.mapToString(v0map)); + Map v1map = new HashMap<>(); + v1map.put("x", String.valueOf(((Vector3d) getDataValue(PacketDataID.VECTOR1)).getX())); + v1map.put("y", String.valueOf(((Vector3d) getDataValue(PacketDataID.VECTOR1)).getY())); + v1map.put("z", String.valueOf(((Vector3d) getDataValue(PacketDataID.VECTOR1)).getZ())); + map.put("v1", Tools.mapToString(v1map)); + return Tools.mapToString(map); + } + + public void sendAsPieces(int bytesPerPiece, Connection c) throws IOException { + String d = toString(); + c.send("[BEGIN(" + d.length() + ")["); + + for (int i = 0; i < d.length(); i += bytesPerPiece) { + StringBuilder piece = new StringBuilder(); + for (int j = 0; j < bytesPerPiece; j++) { + try { + piece.append(d.toCharArray()[i + j]); + } + catch (ArrayIndexOutOfBoundsException ignore) { + } + } + c.send(piece.toString()); + } + + c.send("]END]"); + } +} diff --git a/src/main/java/de/tudbut/net/nethandler/PacketData.java b/src/main/java/de/tudbut/net/nethandler/PacketData.java new file mode 100644 index 0000000..b9d061c --- /dev/null +++ b/src/main/java/de/tudbut/net/nethandler/PacketData.java @@ -0,0 +1,82 @@ +package de.tudbut.net.nethandler; + +import de.tudbut.net.ws.Connection; +import de.tudbut.type.Vector3d; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +@Deprecated +public interface PacketData { + int getID(); + + default String getString0() { + return null; + } + + default String getString1() { + return null; + } + + default int getInt0() { + return 0; + } + + default int getInt1() { + return 0; + } + + default long getLong0() { + return 0; + } + + default long getLong1() { + return 0; + } + + default Map getMap0() { + return new HashMap<>(); + } + + default Map getMap1() { + return new HashMap<>(); + } + + default double getDouble0() { + return 0; + } + + default double getDouble1() { + return 0; + } + + default float getFloat0() { + return 0f; + } + + default float getFloat1() { + return 0f; + } + + default Vector3d getVector0() { + return new Vector3d(0, 0, 0); + } + + default Vector3d getVector1() { + return new Vector3d(0, 0, 0); + } + + + default Packet toPacket() { + return new Packet(this); + } + + default String asString() { + return new Packet(this).toString(); + } + + default void sendInPieces(int bytesPerPiece, Connection connection) throws IOException { + new Packet(this).sendAsPieces(bytesPerPiece, connection); + } +} diff --git a/src/main/java/de/tudbut/net/nethandler/PacketDataID.java b/src/main/java/de/tudbut/net/nethandler/PacketDataID.java new file mode 100644 index 0000000..2802793 --- /dev/null +++ b/src/main/java/de/tudbut/net/nethandler/PacketDataID.java @@ -0,0 +1,6 @@ +package de.tudbut.net.nethandler; + +@Deprecated +public enum PacketDataID { + ID, INT0, INT1, STRING0, STRING1, LONG0, LONG1, DOUBLE0, DOUBLE1, FLOAT0, FLOAT1, MAP0, MAP1, VECTOR0, VECTOR1 +} diff --git a/src/main/java/de/tudbut/net/pbic2/PBIC2.java b/src/main/java/de/tudbut/net/pbic2/PBIC2.java new file mode 100644 index 0000000..0ae2525 --- /dev/null +++ b/src/main/java/de/tudbut/net/pbic2/PBIC2.java @@ -0,0 +1,40 @@ +package de.tudbut.net.pbic2; + +import de.tudbut.io.TypedInputStream; +import de.tudbut.io.TypedOutputStream; +import de.tudbut.tools.ReflectUtil; + +import javax.net.ssl.SSLSocket; +import java.io.IOException; +import java.lang.reflect.Field; +import java.net.Socket; + +public interface PBIC2 { + + String readMessage() throws IOException; + + String writeMessage(String s) throws IOException; + + String writeMessageWithResponse(String s) throws IOException; + + Socket getSocket(); + TypedInputStream getInput(); + TypedOutputStream getOutput(); + + default boolean isSSL() { + return getSocket() instanceof SSLSocket; + } + default Socket getRealSocket() { + if(!isSSL()) + throw new IllegalStateException("getRealSocket() called despite socket not being SSL"); + try { + Class BaseSSLSocketImpl = Class.forName("sun.security.ssl.BaseSSLSocketImpl"); + Field self = BaseSSLSocketImpl.getDeclaredField("self"); + ReflectUtil.forceAccessible(self); + return (Socket) self.get(getSocket()); + } + catch (Exception e) { + throw new IllegalStateException("cannot call getRealSocket() on this JVM"); + } + } +} diff --git a/src/main/java/de/tudbut/net/pbic2/PBIC2ADisconnect.java b/src/main/java/de/tudbut/net/pbic2/PBIC2ADisconnect.java new file mode 100644 index 0000000..fd87854 --- /dev/null +++ b/src/main/java/de/tudbut/net/pbic2/PBIC2ADisconnect.java @@ -0,0 +1,4 @@ +package de.tudbut.net.pbic2; + +public class PBIC2ADisconnect extends Exception { +} diff --git a/src/main/java/de/tudbut/net/pbic2/PBIC2AEventHandler.java b/src/main/java/de/tudbut/net/pbic2/PBIC2AEventHandler.java new file mode 100644 index 0000000..c75f58e --- /dev/null +++ b/src/main/java/de/tudbut/net/pbic2/PBIC2AEventHandler.java @@ -0,0 +1,98 @@ +package de.tudbut.net.pbic2; + +import de.tudbut.obj.TLMap; +import de.tudbut.type.Stoppable; +import de.tudbut.io.TypedInputStream; + +import java.io.IOException; +import java.io.InputStream; +import java.net.Socket; +import java.net.SocketTimeoutException; +import java.util.ArrayList; + +public final class PBIC2AEventHandler implements Stoppable { + + private final ArrayList array = new ArrayList<>(); + private final TLMap map = new TLMap<>(); + private final TLMap listeners = new TLMap<>(); + private final TLMap inputStreams = new TLMap<>(); + + public void start(PBIC2 pbic2, PBIC2AListener listener) throws IOException { + if(array.contains(pbic2)) + return; + array.add(pbic2); + listeners.set(pbic2, listener); + inputStreams.set(pbic2, pbic2.getInput().getStream()); + } + public void remove(PBIC2 pbic2) { + if(!array.contains(pbic2)) + return; + array.remove(pbic2); + map.set(pbic2, null); + listeners.set(pbic2, null); + inputStreams.set(pbic2, null); + } + + public PBIC2AEventHandler() { + new Thread(this::run).start(); + } + + private void runOn(PBIC2 pbic2) throws IOException { + Socket socket = pbic2.getSocket(); + InputStream stream = inputStreams.get(pbic2); + TypedInputStream tis = pbic2.getInput(); + if (!socket.isConnected() || socket.isClosed()) { + listeners.get(pbic2).onError(new PBIC2ADisconnect()); + remove(pbic2); + } + if(pbic2.isSSL()) { + if (pbic2.getRealSocket().getInputStream().available() > 0) { + listeners.get(pbic2).onMessage(tis.readString()); + } + } + else { + if (map.get(pbic2) == null) { + if (stream.available() >= 4) + map.set(pbic2, tis.readInt()); + } + else if (stream.available() >= map.get(pbic2) * 2) { + int i = map.get(pbic2); + StringBuilder builder = new StringBuilder(); + for (int j = 0 ; j < i ; j++) { + builder.append(tis.readChar()); + } + listeners.get(pbic2).onMessage(builder.toString()); + map.set(pbic2, null); + } + } + } + + private void run() { + while (!isStopped()) { + ArrayList array = this.array; + try { + for (int i = 0 ; i < array.size() ; i++) { + try { + runOn(array.get(i)); + } + catch (Throwable e) { + listeners.get(array.get(i)).onError(e); + } + } + } catch (Exception ignored) { } + try { + Thread.sleep(50); + } + catch (InterruptedException ignored) { } + } + } + + private int readWithTimeout(TypedInputStream stream) throws IOException { + try { + return stream.read(); + } catch (SocketTimeoutException e) { + // There is no data available. + return -1; + } + } +} diff --git a/src/main/java/de/tudbut/net/pbic2/PBIC2AListener.java b/src/main/java/de/tudbut/net/pbic2/PBIC2AListener.java new file mode 100644 index 0000000..14a6244 --- /dev/null +++ b/src/main/java/de/tudbut/net/pbic2/PBIC2AListener.java @@ -0,0 +1,10 @@ +package de.tudbut.net.pbic2; + +import java.io.IOException; + +public interface PBIC2AListener { + + void onMessage(String message) throws IOException; + + void onError(Throwable throwable); +} diff --git a/src/main/java/de/tudbut/net/pbic2/PBIC2Client.java b/src/main/java/de/tudbut/net/pbic2/PBIC2Client.java new file mode 100644 index 0000000..eb1f871 --- /dev/null +++ b/src/main/java/de/tudbut/net/pbic2/PBIC2Client.java @@ -0,0 +1,96 @@ +package de.tudbut.net.pbic2; + +import de.tudbut.io.TypedInputStream; +import de.tudbut.io.TypedOutputStream; +import de.tudbut.net.http.HTTPHeader; +import de.tudbut.net.http.HTTPRequest; +import de.tudbut.net.http.HTTPResponse; +import de.tudbut.net.http.HTTPResponseCode; +import de.tudbut.net.http.*; + +import java.io.*; +import java.net.Socket; + +public final class PBIC2Client implements PBIC2 { + + Socket socket; + public final TypedInputStream in; + public final TypedOutputStream out; + + public PBIC2Client(HTTPRequest request) throws IOException { + this(request, i -> i); + } + + public PBIC2Client(HTTPRequest request, PBIC2Passthrough passthrough) throws IOException { + this(request, passthrough, passthrough); + } + + public PBIC2Client(HTTPRequest request, PBIC2Passthrough passthroughIn, PBIC2Passthrough passthroughOut) throws IOException { + request.headers.removeIf(h -> h.key().equals("Connection")); + request.headers.add(new HTTPHeader("Connection", "Upgrade")); + request.headers.add(new HTTPHeader("Upgrade", "TudbuT/PBIC2")); + socket = request.sendNoRead(); + socket.setSoLinger(false, 0); + StringBuilder s = new StringBuilder(); + int res; + while ((res = socket.getInputStream().read()) != 0) { + s.append((char) res); + if(res == 0x0a) { + try { + if (new HTTPResponse(s.toString()).parse().getStatusCodeAsEnum() != HTTPResponseCode.SwitchingProtocols) { + throw new IOException("Invalid response."); + } + } + catch (Exception ignored) { } + } + } + socket.getOutputStream().write(0); + InputStream adapter = socket.getInputStream(); + + in = new TypedInputStream(new InputStream() { + @Override + public int read() throws IOException { + return passthroughIn.pass(adapter.read()); + } + + @Override + public int available() throws IOException { + return adapter.available(); + } + }); + out = new TypedOutputStream(new OutputStream() { + @Override + public void write(int i) throws IOException { + socket.getOutputStream().write(passthroughOut.pass(i)); + } + }); + } + + public String readMessage() throws IOException { + return in.readString(); + } + + public synchronized String writeMessage(String s) throws IOException { + return out.writeString(s); + } + + public synchronized String writeMessageWithResponse(String s) throws IOException { + out.writeString(s); + return in.readString(); + } + + @Override + public Socket getSocket() { + return socket; + } + + @Override + public TypedInputStream getInput() { + return in; + } + + @Override + public TypedOutputStream getOutput() { + return out; + } +} diff --git a/src/main/java/de/tudbut/net/pbic2/PBIC2Passthrough.java b/src/main/java/de/tudbut/net/pbic2/PBIC2Passthrough.java new file mode 100644 index 0000000..9c4946e --- /dev/null +++ b/src/main/java/de/tudbut/net/pbic2/PBIC2Passthrough.java @@ -0,0 +1,6 @@ +package de.tudbut.net.pbic2; + +public interface PBIC2Passthrough { + + int pass(int b); +} diff --git a/src/main/java/de/tudbut/net/pbic2/PBIC2Server.java b/src/main/java/de/tudbut/net/pbic2/PBIC2Server.java new file mode 100644 index 0000000..aa67c1a --- /dev/null +++ b/src/main/java/de/tudbut/net/pbic2/PBIC2Server.java @@ -0,0 +1,89 @@ +package de.tudbut.net.pbic2; + +import de.tudbut.io.TypedInputStream; +import de.tudbut.io.TypedOutputStream; +import de.tudbut.net.http.*; +import de.tudbut.net.http.*; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +public final class PBIC2Server implements PBIC2 { + + Socket socket; + public final TypedInputStream in; + public final TypedOutputStream out; + + public PBIC2Server(HTTPServerRequest request) throws IOException { + this(request, i -> i); + } + + public PBIC2Server(HTTPServerRequest request, PBIC2Passthrough passthrough) throws IOException { + this(request, passthrough, passthrough); + } + + public PBIC2Server(HTTPServerRequest request, PBIC2Passthrough passthroughIn, PBIC2Passthrough passthroughOut) throws IOException { + ParsedHTTPValue p = request.parse(); + if(Arrays.stream(p.getHeaders()).noneMatch(h -> h.toString().equals("Connection: Upgrade"))) { + throw new IOException("Invalid request."); + } + if(Arrays.stream(p.getHeaders()).noneMatch(h -> h.toString().equals("Upgrade: TudbuT/PBIC2"))) { + throw new IOException("Invalid request."); + } + socket = request.socket; + socket.getOutputStream().write(HTTPResponseFactory.create(HTTPResponseCode.SwitchingProtocols, "\u0000", HTTPContentType.ANY).value.getBytes(StandardCharsets.ISO_8859_1)); + while (socket.getInputStream().read() != 0); + + InputStream adapter = socket.getInputStream(); + + in = new TypedInputStream(new InputStream() { + @Override + public int read() throws IOException { + return passthroughIn.pass(adapter.read()); + } + + @Override + public int available() throws IOException { + return adapter.available(); + } + }); + out = new TypedOutputStream(new OutputStream() { + @Override + public void write(int i) throws IOException { + socket.getOutputStream().write(passthroughOut.pass(i)); + } + }); + } + + public String readMessage() throws IOException { + return in.readString(); + } + + public synchronized String writeMessage(String s) throws IOException { + return out.writeString(s); + } + + public synchronized String writeMessageWithResponse(String s) throws IOException { + out.writeString(s); + return in.readString(); + } + + @Override + public Socket getSocket() { + return socket; + } + + @Override + public TypedInputStream getInput() { + return in; + } + + @Override + public TypedOutputStream getOutput() { + return out; + } +} diff --git a/src/main/java/de/tudbut/net/smtp/SMTPDataOutput.java b/src/main/java/de/tudbut/net/smtp/SMTPDataOutput.java new file mode 100644 index 0000000..5510ef2 --- /dev/null +++ b/src/main/java/de/tudbut/net/smtp/SMTPDataOutput.java @@ -0,0 +1,99 @@ +package de.tudbut.net.smtp; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.time.format.DateTimeFormatter; +import java.util.Date; + +public class SMTPDataOutput { + private final SMTPSender parent; + final PrintWriter write; + + private boolean headersOpen = true; + private boolean dataOpen = false; + + SMTPDataOutput(SMTPSender sender, PrintWriter writer) { + parent = sender; + this.write = writer; + } + + private void ensureHeadersOpen() { + if(!headersOpen) + throw new HeadersClosedException(); + } + private void ensureDataOpen() { + if(!dataOpen) + throw new DataClosedException(); + } + + public void from(String s) { + ensureHeadersOpen(); + write.println("From: " + s); + } + + public void to(String s) { + ensureHeadersOpen(); + write.println("To: " + s); + } + + public void subject(String s) { + ensureHeadersOpen(); + write.println("Subject: " + s); + } + + public void date(Date date) { + ensureHeadersOpen(); + write.println("Date: " + DateTimeFormatter.ISO_DATE_TIME.format(date.toInstant())); + } + + public void header(String k, String v) { + ensureHeadersOpen(); + write.println(k + ": " + v); + } + + public void closeHeaders() { + ensureHeadersOpen(); + write.println(); + headersOpen = false; + } + + public PrintStream getDataOutputStream() { + if(headersOpen) + closeHeaders(); + + return new PrintStream(new OutputStream() { + int last = 0x0A; + + @Override + public void write(int i) throws IOException { + if(i != '.' && last == 0x0A) + write.write(i); + else + write.write(" ."); + last = i; + } + + @Override + public void close() throws IOException { + super.close(); + closeData(); + } + }); + } + + public void closeData() throws IOException { + ensureDataOpen(); + write.println("."); + dataOpen = false; + parent.expect250(); + } + + public static class HeadersClosedException extends RuntimeException { + + } + public static class DataClosedException extends RuntimeException { + + } +} diff --git a/src/main/java/de/tudbut/net/smtp/SMTPSender.java b/src/main/java/de/tudbut/net/smtp/SMTPSender.java new file mode 100644 index 0000000..75a45e9 --- /dev/null +++ b/src/main/java/de/tudbut/net/smtp/SMTPSender.java @@ -0,0 +1,103 @@ +package de.tudbut.net.smtp; + +import de.tudbut.io.CLSPrintWriter; + +import javax.net.ssl.SSLSocketFactory; +import java.io.*; +import java.net.Socket; +import java.nio.charset.StandardCharsets; + +public class SMTPSender { + + InputStreamReader in; + OutputStreamWriter out; + PrintWriter write; + BufferedReader read; + + public BufferedReader getReader() { + return read; + } + + + public SMTPSender(String host, int port, boolean ssl) throws IOException { + this(socket(host, port, ssl)); + } + + private static Socket socket(String host, int port, boolean ssl) throws IOException { + Socket socket; + if(ssl) { + socket = SSLSocketFactory.getDefault().createSocket(host, port); + } + else { + socket = new Socket(host, port); + } + return socket; + } + + public SMTPSender(InputStream inputStream, OutputStream outputStream) { + in = new InputStreamReader(inputStream, StandardCharsets.ISO_8859_1); + out = new OutputStreamWriter(outputStream, StandardCharsets.ISO_8859_1); + write = new CLSPrintWriter(out); + ((CLSPrintWriter)write).customLineSeparator = "\r\n"; + read = new BufferedReader(in); + } + + public SMTPSender(Socket socket) throws IOException { + this(socket.getInputStream(), socket.getOutputStream()); + } + + public void helo(String name) throws IOException { + write.println("HELO " + name); + expect220(); + } + + public void from(String sender) throws IOException { + write.println("MAIL FROM:" + sender); + expect250(); + } + + public void to(String recipient) throws IOException { + write.println("RCPT TO:" + recipient); + expect250(); + } + + public SMTPDataOutput data() throws IOException { + write.println("DATA"); + expect354(); + return new SMTPDataOutput(this, write); + } + + public void quit() throws IOException { + write.println("QUIT"); + expect221(); + } + + void expect220() throws IOException { + String s = read.readLine(); + if(!s.replaceAll("-", " ").split(" ", 2)[0].equals("220")) { + thrUnexpected(s); + } + } + void expect250() throws IOException { + String s = read.readLine(); + if(!s.replaceAll("-", " ").split(" ", 2)[0].equals("250")) { + thrUnexpected(s); + } + } + void expect354() throws IOException { + String s = read.readLine(); + if(!s.replaceAll("-", " ").split(" ", 2)[0].equals("354")) { + thrUnexpected(s); + } + } + void expect221() throws IOException { + String s = read.readLine(); + if(!s.replaceAll("-", " ").split(" ", 2)[0].equals("221")) { + thrUnexpected(s); + } + } + + private void thrUnexpected(String code) { + throw new SMTPServerErrorException(code); + } +} diff --git a/src/main/java/de/tudbut/net/smtp/SMTPServerErrorException.java b/src/main/java/de/tudbut/net/smtp/SMTPServerErrorException.java new file mode 100644 index 0000000..58550f3 --- /dev/null +++ b/src/main/java/de/tudbut/net/smtp/SMTPServerErrorException.java @@ -0,0 +1,7 @@ +package de.tudbut.net.smtp; + +public class SMTPServerErrorException extends RuntimeException { + public SMTPServerErrorException(String code) { + super("Code: " + code); + } +} diff --git a/src/main/java/de/tudbut/net/websocket/Operation.java b/src/main/java/de/tudbut/net/websocket/Operation.java new file mode 100644 index 0000000..f15ebdd --- /dev/null +++ b/src/main/java/de/tudbut/net/websocket/Operation.java @@ -0,0 +1,11 @@ +package de.tudbut.net.websocket; + +class Operation { + final int opcode; + final byte[] data; + + Operation(int opcode, byte[] data) { + this.opcode = opcode; + this.data = data; + } +} diff --git a/src/main/java/de/tudbut/net/websocket/WebSocket.java b/src/main/java/de/tudbut/net/websocket/WebSocket.java new file mode 100644 index 0000000..6158e28 --- /dev/null +++ b/src/main/java/de/tudbut/net/websocket/WebSocket.java @@ -0,0 +1,59 @@ +package de.tudbut.net.websocket; + +import de.tudbut.io.TypedInputStream; + +import java.io.IOException; +import java.io.InputStream; + +public class WebSocket { + + InputStream in; + TypedInputStream tin; + + public Operation readFrame() throws IOException { + int i; + i = in.read(); + boolean fin = (i & 0b10000000) != 0; + if((i & 0b01110000) != 0) { + fail(); + } + int opcode = i & 0xF; + i = in.read(); + boolean mask = (i & 0b10000000) != 0; + long payloadLen = i & 0b01111111; + long tmpLen = payloadLen; + if(payloadLen == 126) { + tmpLen = tin.readChar(); + } + if(payloadLen == 127) { + tmpLen = tin.readLong(); + } + payloadLen = tmpLen; + int maskKey = 0; + if(mask) { + maskKey = tin.readInt(); + } + int[] appData = new int[(int) Math.ceil(payloadLen / 4d)]; + for (int j = 0 ; j < appData.length ; j++) { + i = tin.readInt(); + appData[j] = mask ? i ^ maskKey : i; + } + byte[] appDataBytes = new byte[(int) (appData.length - (4 - (payloadLen % 4)))]; + for (int j = 0 ; j < appData.length ;) { + // TODO check the way this is executed by JVM! (should work tho) + if(j < appDataBytes.length) + appDataBytes[j] = (byte) (appData[j++] >> 8 * 3 & 0xff); + if(j < appDataBytes.length) + appDataBytes[j] = (byte) (appData[j++] >> 8 * 2 & 0xff); + if(j < appDataBytes.length) + appDataBytes[j] = (byte) (appData[j++] >> 8 * 1 & 0xff); + if(j < appDataBytes.length) + appDataBytes[j] = (byte) (appData[j++] >> 8 * 0 & 0xff); + } + return new Operation(opcode, appDataBytes); + } + + private void fail() { + + } +} diff --git a/src/main/java/de/tudbut/net/websocket/WebSocketServer.java b/src/main/java/de/tudbut/net/websocket/WebSocketServer.java new file mode 100644 index 0000000..c16ffda --- /dev/null +++ b/src/main/java/de/tudbut/net/websocket/WebSocketServer.java @@ -0,0 +1,4 @@ +package de.tudbut.net.websocket; + +public class WebSocketServer { +} diff --git a/src/main/java/de/tudbut/net/ws/Client.java b/src/main/java/de/tudbut/net/ws/Client.java new file mode 100644 index 0000000..2c2bf9a --- /dev/null +++ b/src/main/java/de/tudbut/net/ws/Client.java @@ -0,0 +1,9 @@ +package de.tudbut.net.ws; + +import java.io.IOException; + +public class Client extends Connection { + public Client(String ip, int port) throws IOException { + super(ip, port); + } +} diff --git a/src/main/java/de/tudbut/net/ws/Connection.java b/src/main/java/de/tudbut/net/ws/Connection.java new file mode 100644 index 0000000..92f35d7 --- /dev/null +++ b/src/main/java/de/tudbut/net/ws/Connection.java @@ -0,0 +1,113 @@ +package de.tudbut.net.ws; + +import de.tudbut.io.StreamReader; +import de.tudbut.io.StreamWriter; +import de.tudbut.timer.AsyncTask; +import de.tudbut.type.IntArrayList; +import de.tudbut.type.Nothing; +import de.tudbut.type.Stoppable; + +import java.io.*; +import java.net.Socket; + +public class Connection implements Stoppable, Runnable { + private final Socket theSocket; + private final Runnable[] handlers = new Runnable[256]; + private int latestHandler = -1; + + Connection(String ip, int port) throws IOException { + theSocket = new Socket(ip, port); + } + + Connection(Socket socket) { + theSocket = socket; + } + + public Socket getSocket() { + return theSocket; + } + + public void send(String s) throws IOException { + PrintWriter writer = new PrintWriter(new OutputStreamWriter(theSocket.getOutputStream())); + writer.println(s); + writer.flush(); + } + + public void send(int[] bin) throws IOException { + StreamWriter writer = new StreamWriter(theSocket.getOutputStream()); + for (int i = 0; i < bin.length; i++) { + if (bin[i] == 0x00) { + writer.writeUnsignedByte(0x01); + writer.writeUnsignedByte(0x01); + continue; + } + if (bin[i] == 0x01) { + writer.writeUnsignedByte(0x01); + writer.writeUnsignedByte(0x02); + } + writer.writeUnsignedByte(bin[i]); + } + writer.writeUnsignedByte(0x00); + } + + public AsyncTask sendAsync(String s) { + return new AsyncTask<>(() -> { + send(s); + return null; + }); + } + + public AsyncTask sendAsync(int[] s) { + return new AsyncTask<>(() -> { + send(s); + return null; + }); + } + + public int[] receiveBin() throws IOException { + while (!new InputStreamReader(getSocket().getInputStream()).ready()); + StreamReader reader = new StreamReader(theSocket.getInputStream()); + IntArrayList list = new IntArrayList(); + int i; + while ((i = reader.readNextUnsignedByte()) != 0x00) { + if(i == 0x01) { + if(reader.readNextUnsignedByte() == 0x01) + i = 0x00; + if(reader.readNextUnsignedByte() == 0x02) + i = 0x01; + } + + list.add(i); + } + return list.toIntArray(); + } + + public String receive() throws IOException { + while (!new InputStreamReader(getSocket().getInputStream()).ready()); + return new BufferedReader(new InputStreamReader(theSocket.getInputStream())).readLine(); + } + + public void addReceiveHook(Runnable runnable) { + latestHandler++; + handlers[latestHandler] = runnable; + } + + @Override + public void run() { + new Thread(() -> { + while (true) { + try { + if ((!isStopped() && !theSocket.isClosed() && theSocket.getInputStream().available() > 0)) { + for (Runnable handler : handlers) { + if (handler != null) + new Thread(handler).start(); + } + } + } + catch (IOException e) { + e.printStackTrace(); + } + } + }).start(); + } +} diff --git a/src/main/java/de/tudbut/net/ws/ConnectionHandler.java b/src/main/java/de/tudbut/net/ws/ConnectionHandler.java new file mode 100644 index 0000000..770e9a2 --- /dev/null +++ b/src/main/java/de/tudbut/net/ws/ConnectionHandler.java @@ -0,0 +1,8 @@ +package de.tudbut.net.ws; + + +import java.io.IOException; + +public interface ConnectionHandler { + void run(Connection connection) throws IOException; +} diff --git a/src/main/java/de/tudbut/net/ws/Server.java b/src/main/java/de/tudbut/net/ws/Server.java new file mode 100644 index 0000000..f4c4f5e --- /dev/null +++ b/src/main/java/de/tudbut/net/ws/Server.java @@ -0,0 +1,48 @@ +package de.tudbut.net.ws; + +import de.tudbut.tools.Tools; +import de.tudbut.type.Stoppable; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; + +public class Server extends Tools.ObjectMapping implements Runnable, Stoppable { + private final ServerSocket serverSocket; + private final ConnectionHandler[] handlers = new ConnectionHandler[256]; + private int latestHandler = -1; + + public Server(int port) throws IOException { + serverSocket = new ServerSocket(port); + } + + public void addHandler(ConnectionHandler connectionHandler) { + latestHandler++; + handlers[latestHandler] = connectionHandler; + } + + public void run() { + new Thread(() -> { + while (!isStopped()) { + try { + Socket s = serverSocket.accept(); + s.setKeepAlive(true); + for (ConnectionHandler handler : handlers) { + if (handler != null) + new Thread(() -> { + try { + handler.run(new Connection(s)); + } + catch (IOException e) { + e.printStackTrace(); + } + }).start(); + } + } + catch (IOException e) { + e.printStackTrace(); + } + } + }).start(); + } +} diff --git a/src/main/java/de/tudbut/obj/Atomic.java b/src/main/java/de/tudbut/obj/Atomic.java new file mode 100644 index 0000000..19cce90 --- /dev/null +++ b/src/main/java/de/tudbut/obj/Atomic.java @@ -0,0 +1,15 @@ +package de.tudbut.obj; + +public class Atomic implements AtomicSink { + private T object; + + public Atomic() {} + public Atomic(T object) { this.object = object; } + + public void set(T object) { this.object = object; } + + public T get() { return object; } + + @Override + public String toString() { return object.toString(); } +} diff --git a/src/main/java/de/tudbut/obj/AtomicSink.java b/src/main/java/de/tudbut/obj/AtomicSink.java new file mode 100644 index 0000000..468cd56 --- /dev/null +++ b/src/main/java/de/tudbut/obj/AtomicSink.java @@ -0,0 +1,7 @@ +package de.tudbut.obj; + +public interface AtomicSink { + + void set(T object); + T get(); +} \ No newline at end of file diff --git a/src/main/java/de/tudbut/obj/AtomicUtils.java b/src/main/java/de/tudbut/obj/AtomicUtils.java new file mode 100644 index 0000000..33290ed --- /dev/null +++ b/src/main/java/de/tudbut/obj/AtomicUtils.java @@ -0,0 +1,4 @@ +package de.tudbut.obj; + +public class AtomicUtils { +} diff --git a/src/main/java/de/tudbut/obj/CarrierException.java b/src/main/java/de/tudbut/obj/CarrierException.java new file mode 100644 index 0000000..c1ec136 --- /dev/null +++ b/src/main/java/de/tudbut/obj/CarrierException.java @@ -0,0 +1,11 @@ +package de.tudbut.obj; + +public class CarrierException extends RuntimeException { + + public final Object carried; + + public CarrierException(Exception e, Object o) { + super(e); + carried = o; + } +} diff --git a/src/main/java/de/tudbut/obj/Closable.java b/src/main/java/de/tudbut/obj/Closable.java new file mode 100644 index 0000000..74102a2 --- /dev/null +++ b/src/main/java/de/tudbut/obj/Closable.java @@ -0,0 +1,29 @@ +package de.tudbut.obj; + +import java.io.IOException; + +public interface Closable { + InstanceBoundMap vars = new InstanceBoundMap<>(); + + default void close() throws IOException { + vars.set(this, C.CLOSED, true); + } + + default void open() throws NotSupportedException { + vars.set(this, C.CLOSED, false); + } + + default boolean isClosed() { + vars.setIfNull(this, C.CLOSED, false); + return vars.get(this, C.CLOSED); + } + + default boolean isOpen() { + vars.setIfNull(this, C.CLOSED, false); + return !vars.get(this, C.CLOSED); + } + + class C { + public static final String CLOSED = "closed"; + } +} diff --git a/src/main/java/de/tudbut/obj/ClosedClosableException.java b/src/main/java/de/tudbut/obj/ClosedClosableException.java new file mode 100644 index 0000000..21a5d9f --- /dev/null +++ b/src/main/java/de/tudbut/obj/ClosedClosableException.java @@ -0,0 +1,7 @@ +package de.tudbut.obj; + +public class ClosedClosableException extends Exception { + + public ClosedClosableException() { } + public ClosedClosableException(String message) { super(message); } +} diff --git a/src/main/java/de/tudbut/obj/DoubleObject.java b/src/main/java/de/tudbut/obj/DoubleObject.java new file mode 100644 index 0000000..8cdd53b --- /dev/null +++ b/src/main/java/de/tudbut/obj/DoubleObject.java @@ -0,0 +1,33 @@ +package de.tudbut.obj; + +import de.tudbut.tools.ArrayGetter; + +public class DoubleObject { + private final T t1; + private final T t2; + + public DoubleObject(T in1, T in2) { + t1 = in1; + t2 = in2; + } + + @SafeVarargs + public final T[] get(T... ignore) { + T[] ts = ArrayGetter.newGenericArray(2, ignore); + ts[0] = t1; + ts[1] = t2; + return ts; + } + + public T get1() { + return t1; + } + + public T get2() { + return t2; + } + + public DoubleObject clone() { + return new DoubleObject<>(t1, t2); + } +} diff --git a/src/main/java/de/tudbut/obj/DoubleTypedObject.java b/src/main/java/de/tudbut/obj/DoubleTypedObject.java new file mode 100644 index 0000000..a6d76b1 --- /dev/null +++ b/src/main/java/de/tudbut/obj/DoubleTypedObject.java @@ -0,0 +1,32 @@ +package de.tudbut.obj; + +import de.tudbut.tools.ReflectUtil; + +import java.util.Objects; + +public class DoubleTypedObject implements Cloneable { + + public O o; + public T t; + + public DoubleTypedObject(O o, T t) { + this.o = o; + this.t = t; + } + public DoubleTypedObject() { + } + + @Override + public boolean equals(Object o1) { + if (this == o1) return true; + if (!(o1 instanceof DoubleTypedObject)) return false; + DoubleTypedObject that = (DoubleTypedObject) o1; + return Objects.equals(o, that.o) && Objects.equals(t, that.t); + } + + @SuppressWarnings("MethodDoesntCallSuperMethod") + @Override + public DoubleTypedObject clone() { + return new DoubleTypedObject<>(ReflectUtil.forceClone(o), ReflectUtil.forceClone(t)); + } +} diff --git a/src/main/java/de/tudbut/obj/IgnoreThrowRunnable.java b/src/main/java/de/tudbut/obj/IgnoreThrowRunnable.java new file mode 100644 index 0000000..5714393 --- /dev/null +++ b/src/main/java/de/tudbut/obj/IgnoreThrowRunnable.java @@ -0,0 +1,12 @@ +package de.tudbut.obj; + +public interface IgnoreThrowRunnable extends Runnable { + + default void run() { + try { + this.doRun(); + } catch (Throwable ignore) { } + } + + void doRun() throws Throwable; +} diff --git a/src/main/java/de/tudbut/obj/InstanceBoundMap.java b/src/main/java/de/tudbut/obj/InstanceBoundMap.java new file mode 100644 index 0000000..acc1c5c --- /dev/null +++ b/src/main/java/de/tudbut/obj/InstanceBoundMap.java @@ -0,0 +1,99 @@ +package de.tudbut.obj; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; + +public class InstanceBoundMap { + protected final HashMap> bindings = new HashMap<>(); + + public synchronized void createBindingIfNonExistent(Object bindingObject) { + if(!bindings.containsKey(bindingObject)) + bindings.put(bindingObject, new Binding<>()); + } + + public synchronized void set(Object bindingObject, K key, V value) { + createBindingIfNonExistent(bindingObject); + bindings.get(bindingObject).set(key, value); + } + + public synchronized void setIfNull(Object bindingObject, K key, V value) { + createBindingIfNonExistent(bindingObject); + if(bindings.get(bindingObject).get(key) == null) { + bindings.get(bindingObject).set(key, value); + } + } + + public V get(Object bindingObject, K key) { + return bindings.get(bindingObject).get(key); + } + + public K[] keys(Object bindingObject) { + return bindings.get(bindingObject).keys(); + } + + public V[] values(Object bindingObject) { + return bindings.get(bindingObject).values(); + } + + + protected static class Binding { + private final ArrayList entries = new ArrayList<>(); + + private void set(K key, V value) { + boolean exists = false; + for (int i = 0; i < entries.size(); i++) { + Entry entry = entries.get(i); + if (key == entry.key || entry.key.equals(key)) { + entry.val = value; + exists = true; + } + } + if(!exists) { + this.entries.add(new Entry(key, value)); + } + } + + private V get(K key) { + ArrayList entries = (ArrayList) this.entries.clone(); + for (Entry entry : entries) { + if (key == entry.key || entry.key.equals(key)) + return entry.val; + } + return null; + } + + private K[] keys() { + HashSet keys = new HashSet<>(); + for (int i = 0; i < entries.size(); i++) { + keys.add(entries.get(i).key); + } + if(keys.size() == 0) { + return (K[]) new Object[0]; + } + return keys.toArray((K[]) Array.newInstance(entries.get(0).key.getClass(), keys.size())); + } + + private V[] values() { + HashSet vals = new HashSet<>(); + for (int i = 0; i < entries.size(); i++) { + vals.add(entries.get(i).val); + } + if(vals.size() == 0) { + return (V[]) new Object[0]; + } + return vals.toArray((V[]) Array.newInstance(entries.get(0).val.getClass(), vals.size())); + } + + private class Entry { + private final K key; + private V val; + + private Entry(K key, V val) { + this.key = key; + this.val = val; + } + } + } +} diff --git a/src/main/java/de/tudbut/obj/Mappable.java b/src/main/java/de/tudbut/obj/Mappable.java new file mode 100644 index 0000000..4cb8e3d --- /dev/null +++ b/src/main/java/de/tudbut/obj/Mappable.java @@ -0,0 +1,8 @@ +package de.tudbut.obj; + +import java.util.Map; + +public interface Mappable { + + Map map(); +} diff --git a/src/main/java/de/tudbut/obj/NotSupportedException.java b/src/main/java/de/tudbut/obj/NotSupportedException.java new file mode 100644 index 0000000..577038e --- /dev/null +++ b/src/main/java/de/tudbut/obj/NotSupportedException.java @@ -0,0 +1,11 @@ +package de.tudbut.obj; + +public class NotSupportedException extends RuntimeException { + public NotSupportedException() { + super(); + } + + public NotSupportedException(String s) { + super(s); + } +} diff --git a/src/main/java/de/tudbut/obj/PAtomic.java b/src/main/java/de/tudbut/obj/PAtomic.java new file mode 100644 index 0000000..bcaed67 --- /dev/null +++ b/src/main/java/de/tudbut/obj/PAtomic.java @@ -0,0 +1,15 @@ +package de.tudbut.obj; + +public class PAtomic implements AtomicSink { + public T object; + + public PAtomic() {} + public PAtomic(T object) { this.object = object; } + + public void set(T object) { this.object = object; } + + public T get() { return object; } + + @Override + public String toString() { return object.toString(); } +} diff --git a/src/main/java/de/tudbut/obj/Partial.java b/src/main/java/de/tudbut/obj/Partial.java new file mode 100644 index 0000000..fba30a2 --- /dev/null +++ b/src/main/java/de/tudbut/obj/Partial.java @@ -0,0 +1,60 @@ +package de.tudbut.obj; + +import de.tudbut.tools.ArrayGetter; + +import java.util.ArrayList; + +public class Partial { + private T current; + private final ArrayList> listeners = new ArrayList<>(); + private boolean completed = false; + + public Partial(T original) { + current = original; + } + + @SafeVarargs + public final T change(T changed, Listener... ignore) { + if(completed) + return null; + + Listener[] listeners = ArrayGetter.newGenericArray(this.listeners.size(), ignore); + for (int i = 0; i < listeners.length; i++) { + listeners[i] = this.listeners.get(i); + } + for (Listener listener : listeners) { + listener.onChange(current, changed); + } + current = changed; + return changed; + } + + @SafeVarargs + public final void complete(T completedValue, Listener... ignore) { + completed = true; + current = completedValue; + Listener[] listeners = ArrayGetter.newGenericArray(this.listeners.size(), ignore); + for (int i = 0; i < listeners.length; i++) { + listeners[i] = this.listeners.get(i); + } + for (Listener listener : listeners) { + listener.onComplete(current); + } + } + + public T get() { + return current; + } + + public void addChangeListener(Listener listener) { + listeners.add(listener); + } + + public interface Listener { + default void onChange(T original, T changed) { + + } + + void onComplete(T completed); + } +} diff --git a/src/main/java/de/tudbut/obj/PrintThrowRunnable.java b/src/main/java/de/tudbut/obj/PrintThrowRunnable.java new file mode 100644 index 0000000..357063c --- /dev/null +++ b/src/main/java/de/tudbut/obj/PrintThrowRunnable.java @@ -0,0 +1,14 @@ +package de.tudbut.obj; + +public interface PrintThrowRunnable extends Runnable { + + default void run() { + try { + this.doRun(); + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + } + + void doRun() throws Throwable; +} diff --git a/src/main/java/de/tudbut/obj/RayCast.java b/src/main/java/de/tudbut/obj/RayCast.java new file mode 100644 index 0000000..f9dc79b --- /dev/null +++ b/src/main/java/de/tudbut/obj/RayCast.java @@ -0,0 +1,39 @@ +package de.tudbut.obj; + +import de.tudbut.rendering.Maths3D; +import de.tudbut.rendering.Rectangle3D; +import de.tudbut.type.Vector3d; + +public class RayCast { + + private final Vector3d step = new Vector3d(0, 0, -1); + private final Vector3d origin; + + public RayCast(Vector3d origin, Vector3d step) { + this.origin = origin; + this.step.set(step); + } + + public boolean hits(Rectangle3D rectangle, int maxSteps) { + Vector3d pos = origin.clone(); + for (int i = 0; i < maxSteps; i++) { + if(intersects(rectangle, new Rectangle3D(origin, pos.clone().negate().add(origin).negate()))) + return true; + pos.add(step); + } + return intersects(rectangle, new Rectangle3D(origin, pos.clone().negate().add(origin).negate())); + } + + private boolean intersects(Rectangle3D rectangle, Rectangle3D castRectangle) { + rectangle.sort(); + castRectangle.sort(); + boolean[] relation = Maths3D.getRelation(rectangle, castRectangle); + return + !relation[0] && + !relation[1] && + !relation[2] && + !relation[3] && + !relation[4] && + !relation[5] ; + } +} diff --git a/src/main/java/de/tudbut/obj/RelativeVector2d.java b/src/main/java/de/tudbut/obj/RelativeVector2d.java new file mode 100644 index 0000000..22c30a3 --- /dev/null +++ b/src/main/java/de/tudbut/obj/RelativeVector2d.java @@ -0,0 +1,48 @@ +package de.tudbut.obj; + +import de.tudbut.type.Vector2d; + +public class RelativeVector2d extends Vector2d { + private final Vector2d relativeTo = new Vector2d(0,0); + + public RelativeVector2d(Vector2d relativeTo, double x, double y) { + super(relativeTo.getX() + x, relativeTo.getY() + y); + this.relativeTo.set(relativeTo); + } + + public RelativeVector2d(Vector2d relativeTo, Vector2d relative) { + super(relativeTo.getX() + relative.getX(), relativeTo.getY() + relative.getY()); + this.relativeTo.set(relativeTo); + } + + @Override + public Vector2d set(double x, double y) { + Vector2d rpos = getRelativePos(); + relativeTo.set(x - rpos.getX(), y - rpos.getY()); + return super.set(x, y); + } + + public Vector2d setSize(double x, double y) { + return super.set(relativeTo.getX() + x, relativeTo.getY() + y); + } + + public Vector2d getRelativeTo() { + return relativeTo.clone(); + } + + public Vector2d getRelativePos() { + return new Vector2d(getX() - relativeTo.getX(), getY() - relativeTo.getY()); + } + + public double getRX() { + return getRelativePos().getX(); + } + + public double getRY() { + return getRelativePos().getY(); + } + + public RelativeVector2d clone() { + return new RelativeVector2d(relativeTo, getRelativePos()); + } +} diff --git a/src/main/java/de/tudbut/obj/RelativeVector3d.java b/src/main/java/de/tudbut/obj/RelativeVector3d.java new file mode 100644 index 0000000..8b3d624 --- /dev/null +++ b/src/main/java/de/tudbut/obj/RelativeVector3d.java @@ -0,0 +1,53 @@ +package de.tudbut.obj; + +import de.tudbut.type.Vector3d; + +public class RelativeVector3d extends Vector3d { + private final Vector3d relativeTo = new Vector3d(0,0, 0); + + public RelativeVector3d(Vector3d relativeTo, double x, double y, double z) { + super(relativeTo.getX() + x, relativeTo.getY() + y, relativeTo.getZ() + z); + this.relativeTo.set(relativeTo); + } + + public RelativeVector3d(Vector3d relativeTo, Vector3d relative) { + super(relativeTo.getX() + relative.getX(), relativeTo.getY() + relative.getY(), relativeTo.getZ() + relative.getZ()); + this.relativeTo.set(relativeTo); + } + + @Override + public Vector3d set(double x, double y, double z) { + Vector3d rpos = getRelativePos(); + relativeTo.set(x - rpos.getX(), y - rpos.getY(), z - rpos.getZ()); + return super.set(x, y, z); + } + + public Vector3d setSize(double x, double y, double z) { + super.set(relativeTo.getX() + x, relativeTo.getY() + y, relativeTo.getZ()); + return this; + } + + public Vector3d getRelativeTo() { + return relativeTo.clone(); + } + + public Vector3d getRelativePos() { + return new Vector3d(getX() - relativeTo.getX(), getY() - relativeTo.getY(), getZ() - relativeTo.getZ()); + } + + public double getRX() { + return getRelativePos().getX(); + } + + public double getRY() { + return getRelativePos().getY(); + } + + public double getRZ() { + return getRelativePos().getZ(); + } + + public RelativeVector3d clone() { + return new RelativeVector3d(relativeTo, getRelativePos()); + } +} diff --git a/src/main/java/de/tudbut/obj/Save.java b/src/main/java/de/tudbut/obj/Save.java new file mode 100644 index 0000000..b6f571c --- /dev/null +++ b/src/main/java/de/tudbut/obj/Save.java @@ -0,0 +1,11 @@ +package de.tudbut.obj; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Save { +} diff --git a/src/main/java/de/tudbut/obj/TLClassLoader.java b/src/main/java/de/tudbut/obj/TLClassLoader.java new file mode 100644 index 0000000..0683826 --- /dev/null +++ b/src/main/java/de/tudbut/obj/TLClassLoader.java @@ -0,0 +1,9 @@ +package de.tudbut.obj; + +public interface TLClassLoader { + Class lc(String s) throws ClassNotFoundException; + + Class get(String s) throws ClassNotFoundException; + + Class friendlyGet(String s); +} diff --git a/src/main/java/de/tudbut/obj/TLMap.java b/src/main/java/de/tudbut/obj/TLMap.java new file mode 100644 index 0000000..5b80fb7 --- /dev/null +++ b/src/main/java/de/tudbut/obj/TLMap.java @@ -0,0 +1,199 @@ +package de.tudbut.obj; + +import de.tudbut.tools.Retriever; + +import java.util.*; + +public class TLMap { + protected Binding binding = new Binding<>(); + + public static TLMap stringToMap(String mapStringParsable) { + TLMap map = new TLMap<>(); + + String[] splitTiles = mapStringParsable.split(";"); + for (int i = 0; i < splitTiles.length; i++) { + String tile = splitTiles[i]; + String[] splitTile = tile.split(":"); + if (tile.contains(":")) { + if (splitTile.length == 2) + map.set( + splitTile[0].replaceAll("%I", ":").replaceAll("%B", ";").replaceAll("%P", "%"), + splitTile[1].equals("%N") ? null : splitTile[1].replaceAll("%I", ":").replaceAll("%B", ";").replaceAll("%P", "%") + ); + else + map.set(splitTile[0].replaceAll("%I", ":").replaceAll("%B", ";").replaceAll("%P", "%"), ""); + } + } + + return map; + } + + public static String mapToString(TLMap map) { + StringBuilder r = new StringBuilder(); + + for (String key : map.keys().toArray(new String[0])) { + + r + .append(key.replaceAll("%", "%P").replaceAll(";", "%B").replaceAll(":", "%I")) + .append(":") + .append(map.get(key) == null ? "%N" : map.get(key).replaceAll("%", "%P").replaceAll(";", "%B").replaceAll(":", "%I")) + .append(";") + ; + } + + return r.toString(); + } + + public void set(K key, V value) { + binding.set(key, value); + } + + public void setIfNull(K key, V value) { + if(binding.get(key) == null) { + binding.set(key, value); + } + } + + public V get(K key) { + return binding.get(key); + } + + public V get(K key, V def) { + V v = binding.get(key); + return v == null ? def : v; + } + public V get(K key, Retriever def) { + V v = binding.get(key); + return v == null ? def.retrieve() : v; + } + + public Set keys() { + return binding.keys(); + } + + public int size() { + return binding.size(); + } + + public Set values() { + return binding.values(); + } + + public ArrayList> entries() { + return binding.entries(); + } + + public TLMap flip() { + TLMap map = new TLMap<>(); + map.binding = binding.flip(); + return map; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TLMap tlMap = (TLMap) o; + return tlMap.binding.entries.equals(binding.entries); + } + + protected static class Binding { + protected ArrayList> entries = new ArrayList<>(); + + protected void set(K key, V value) { + boolean exists = false; + for (int i = 0; i < entries.size(); i++) { + Entry entry = entries.get(i); + if (key == entry.key || entry.key.equals(key)) { + exists = true; + if(value == null) { + entries.remove(i); + break; + } + entry.val = value; + } + } + if(!exists && value != null && key != null) { + this.entries.add(new Entry<>(key, value)); + } + } + + protected V get(K key) { + ArrayList> entries = (ArrayList>) this.entries.clone(); + for (Entry entry : entries) { + if (key == entry.key || entry.key.equals(key)) + return entry.val; + } + return null; + } + + protected Set keys() { + Set keys = new LinkedHashSet<>(); + for (int i = 0; i < entries.size(); i++) { + keys.add(entries.get(i).key); + } + return keys; + } + + protected Set values() { + Set vals = new LinkedHashSet<>(); + for (int i = 0; i < entries.size(); i++) { + vals.add(entries.get(i).val); + } + return vals; + } + + protected ArrayList> entries() { + ArrayList> vals = new ArrayList<>(); + vals.addAll(entries); + return vals; + } + + protected Binding flip() { + Binding binding = new Binding<>(); + for (int i = 0 ; i < entries.size() ; i++) { + Entry entry = entries.get(i); + binding.entries.add(new Entry<>(entry.val, entry.key)); + } + return binding; + } + + public int size() { + return entries.size(); + } + } + + public static class Entry { + public K key; + public V val; + + protected Entry() { + } + + protected Entry(K key, V val) { + this.key = key; + this.val = val; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Entry entry = (Entry) o; + if (!Objects.equals(key, entry.key)) return false; + return Objects.equals(val, entry.val); + } + } + + @Override + public TLMap clone() { + TLMap n = new TLMap<>(); + Object[] keys = keys().toArray(); + Object[] vals = values().toArray(); + for (int i = 0, arrayLength = keys.length ; i < arrayLength ; i++) { + Object key = keys[i]; + n.set((K)key, (V)vals[i]); + } + return n; + } +} diff --git a/src/main/java/de/tudbut/obj/Transient.java b/src/main/java/de/tudbut/obj/Transient.java new file mode 100644 index 0000000..bdacfbf --- /dev/null +++ b/src/main/java/de/tudbut/obj/Transient.java @@ -0,0 +1,11 @@ +package de.tudbut.obj; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Transient { +} diff --git a/src/main/java/de/tudbut/obj/TypedArray.java b/src/main/java/de/tudbut/obj/TypedArray.java new file mode 100644 index 0000000..353f189 --- /dev/null +++ b/src/main/java/de/tudbut/obj/TypedArray.java @@ -0,0 +1,118 @@ +package de.tudbut.obj; + +import de.tudbut.tools.Tools; + +import java.lang.reflect.Array; +import java.util.Arrays; + +public class TypedArray { + + private Class tClass = null; + private boolean locked = false; + protected final Object synchronizer = new Object(); + + protected T[] ts; + + public TypedArray(int length) { + ts = (T[]) new Object[length]; + } + + public TypedArray(T[] ts) { + tClass = ts.getClass().getComponentType(); + if(ts.length != 0) + tClass = ts[0].getClass(); + this.ts = ts; + } + + public TypedArray(int length, T[] ts) { + T[] oTs = ts; + ts = (T[]) new Object[length]; + Tools.copyArray(oTs, ts, Math.min(ts.length, oTs.length)); + tClass = ts.getClass().getComponentType(); + if(ts.length != 0 && ts[0] != null) + tClass = ts[0].getClass(); + this.ts = ts; + + } + + public T get(int i) throws ArrayIndexOutOfBoundsException { + synchronized (synchronizer) { + return ((T[]) ts)[i]; + } + } + + public T set(int i, T t) throws ArrayIndexOutOfBoundsException { + synchronized (synchronizer) { + checkLocked(); + return ts[i] = t; + } + } + + public String toString() { + synchronized (synchronizer) { + return Arrays.toString(ts); + } + } + + public T[] toArray(T... type) { + synchronized (synchronizer) { + tClass = type.getClass().getComponentType(); + T[] nts = (T[]) Array.newInstance(tClass, ts.length); + Tools.copyArray(ts, nts, nts.length); + ts = nts; + return ts; + } + } + + public int length() { + synchronized (synchronizer) { + return ts.length; + } + } + + public TypedArray lock() { + locked = true; + return this; + } + + public boolean isLocked() { + return locked; + } + + protected void checkLocked() throws IllegalStateException { + if(isLocked()) + throw new IllegalStateException("TypedArray is locked"); + } + + @Override + public TypedArray clone() { + return new TypedArray<>(ts); + } + + public boolean equals(Object o) { + return o instanceof TypedArray && (Arrays.equals(((TypedArray) o).ts, ts)); + } + + + @SafeVarargs + public static T[] convertToArray(T... ts) { + return new TypedArray<>(ts).toArray(); + } + + public static Object[] convertToObjectArray(Object... objects) { + return new TypedArray<>(objects).toArray(); + } + + @SafeVarargs + public static TypedArray convert(T... ts) { + return new TypedArray<>(ts); + } + + public static TypedArray convertObjects(Object... objects) { + return new TypedArray<>(objects); + } + + public static String string(Object... objects) { + return Arrays.toString(objects); + } +} diff --git a/src/main/java/de/tudbut/obj/TypedList.java b/src/main/java/de/tudbut/obj/TypedList.java new file mode 100644 index 0000000..269d5bd --- /dev/null +++ b/src/main/java/de/tudbut/obj/TypedList.java @@ -0,0 +1,138 @@ +package de.tudbut.obj; + +import de.tudbut.tools.Tools; + +import java.util.List; + +public class TypedList extends TypedArray { + public TypedList() { + super(0); + } + + public TypedList(T[] ts) { + super(ts); + } + + public static TypedList get(TypedArray typedArray) { + TypedList list = new TypedList<>(typedArray.toArray()); + if(typedArray.isLocked()) + list.lock(); + return list; + } + + public synchronized T[] add(T[] t) { + synchronized (synchronizer) { + checkLocked(); + T[] n = new TypedArray<>(length() + t.length, ts).toArray(); + for (int i = ts.length, j = 0; j < t.length; i++, j++) { + n[i] = t[j]; + } + ts = n; + return t; + } + } + + public synchronized TypedArray add(TypedArray t) { + synchronized (synchronizer) { + add(t.ts); + return t; + } + } + + public synchronized List add(List t) { + synchronized (synchronizer) { + add(t.toArray(ts)); + return t; + } + } + + public synchronized T add(T t) { + synchronized (synchronizer) { + checkLocked(); + T[] n = new TypedArray<>(length() + 1, ts).toArray(); + n[n.length - 1] = t; + ts = n; + return t; + } + } + + public synchronized T add(int i, T t) { + synchronized (synchronizer) { + checkLocked(); + if (i == ts.length - 1) + return add(t); + + T[] n = new TypedArray<>(length() + 1, ts).toArray(); + int tPos = 0; + for (int j = 0; j < n.length; j++) { + if (j == i) { + n[j] = t; + j++; + continue; + } + n[j] = ts[tPos]; + tPos++; + } + ts = n; + return t; + } + } + + public synchronized T pop(int i) { + synchronized (synchronizer) { + checkLocked(); + int aP = 0; + boolean removed = false; + T t = null; + for (int j = 0; j < ts.length; j++) { + if (j == i && !removed) { + t = ts[j]; + removed = true; + continue; + } + ts[aP] = ts[j]; + aP++; + } + decrementSize(); + return t; + } + } + + public synchronized T pop() { + synchronized (synchronizer) { + checkLocked(); + T t = ts[ts.length - 1]; + decrementSize(); + return t; + } + } + + public synchronized void remove(int i) { + synchronized (synchronizer) { + checkLocked(); + pop(i); + } + } + + public synchronized void removeLast() { + synchronized (synchronizer) { + checkLocked(); + pop(); + } + } + + private synchronized void decrementSize() { + checkLocked(); + ts = new TypedArray<>(length() - 1, ts).toArray(); + } + + public T random() { + synchronized (synchronizer) { + return Tools.randomOutOfArray(this); + } + } + + public TypedList clone() { + return new TypedList<>(ts); + } +} diff --git a/src/main/java/de/tudbut/obj/Vector2i.java b/src/main/java/de/tudbut/obj/Vector2i.java new file mode 100644 index 0000000..c28b049 --- /dev/null +++ b/src/main/java/de/tudbut/obj/Vector2i.java @@ -0,0 +1,104 @@ +package de.tudbut.obj; + +import de.tudbut.tools.Tools; +import de.tudbut.type.Vector2d; + +import java.util.Map; +import java.util.Objects; + +public class Vector2i implements Mappable { + private int x, y; + + public Vector2i(int x, int y) { + this.x = x; + this.y = y; + } + + public void set(int x, int y) { + this.x = x; + this.y = y; + } + + public void set(Vector2i vec) { + this.x = vec.x; + this.y = vec.y; + } + + public Vector2i add(Vector2i vec) { + set(x+vec.x, y+vec.y); + return this; + } + + public int getX() { + return x; + } + + public void setX(int x) { + this.x = x; + } + + public int getY() { + return y; + } + + public void setY(int y) { + this.y = y; + } + + @Override + public Vector2i clone() { + return new Vector2i(x, y); + } + + public Vector2i multiply(double m) { + set((int) (x * m), (int) (y * m)); + return this; + } + + public Vector2i multiply(double mx, double my) { + set((int) (x * mx), (int) (y * my)); + return this; + } + + public Vector2i multiply(Vector2d vec) { + set((int) (x * vec.getX()), (int) (y * vec.getY())); + return this; + } + + + public Vector2i negate() { + set(-x, -y); + return this; + } + + public String toString() { + return "x:" + x + ";y:" + y; + } + + @Override + public Map map() { + return Tools.stringToMap("x:" + x + ";y:" + y); + } + + public static Vector2i fromMap(Map map) { + return + new Vector2i( + Integer.parseInt(map.get("x")), + Integer.parseInt(map.get("y")) + ) + ; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Vector2i)) return false; + Vector2i vector2i = (Vector2i) o; + return x == vector2i.x && y == vector2i.y; + } + + @Override + public int hashCode() { + return Objects.hash(x, y); + } +} diff --git a/src/main/java/de/tudbut/parsing/AddressedTCN.java b/src/main/java/de/tudbut/parsing/AddressedTCN.java new file mode 100644 index 0000000..6be94c2 --- /dev/null +++ b/src/main/java/de/tudbut/parsing/AddressedTCN.java @@ -0,0 +1,88 @@ +package de.tudbut.parsing; + +import de.tudbut.obj.TLMap; + +import java.util.ArrayList; + +public class AddressedTCN { + + public static TCN addressedToNormal(TCN db) { + TCN tcn = new TCN(); + ArrayList keys = new ArrayList<>(db.map.keys()); + TLMap objects = new TLMap<>(); + for (int i = 0 ; i < keys.size() ; i++) { + if(keys.get(i).startsWith("_")) { + Object o = db.get(keys.get(i)); + objects.set(keys.get(i).substring(1), o); + } + } + reverse(objects); + tcn.map = db.getSub("_" + db.getInteger("main")).map; + return tcn; + } + + private static void reverse(TLMap objects) { + for (Object v : objects.values()) { + if(v instanceof TCN) { + TCN tcn = (TCN) v; + for (String key : tcn.map.keys()) { + int i = tcn.getInteger(key); + tcn.set(key, objects.get(String.valueOf(i))); + } + } + if(v instanceof TCNArray) { + TCNArray tcn = (TCNArray) v; + for (int l = 0 ; l < tcn.size() ; l++) { + int i = tcn.getInteger(l); + tcn.set(l, objects.get(String.valueOf(i))); + } + } + } + } + + public static TCN normalToAddressed(TCN tcn) { + TCN db = new TCN(); + ArrayList allObjects = new ArrayList<>(); + TCN theTCN = new TCN(); + theTCN.set("main", tcn); + recursiveScan(allObjects, db, theTCN); + db.set("main", 0); + for (String key : db.map.keys()) { + TCN.deepConvert(key, db.get(key), db); + } + return db; + } + + private static void recursiveScan(ArrayList objects, TCN main, TCN tcn) { + for (String key : tcn.map.keys()) { + if(tcn.get(key) instanceof TCNArray) + add(objects, main, key, ((TCNArray) tcn.get(key)).toTCN()); + else + add(objects, main, key, tcn.get(key)); + } + } + + private static Integer add(ArrayList objects, TCN main, String key, Object o) { + if(o instanceof TCN) { + if(!objects.contains(o)) { + int i = objects.size(); + objects.add(o); + TCN tcn = ((TCN) o).isArray ? new TCNArray().toTCN() : new TCN(); + recursiveScan(objects, main, (TCN) o); + for (String theKey : ((TCN) o).map.keys()) { + tcn.set(theKey, add(objects, main, theKey, ((TCN) o).get(theKey))); + } + main.set("_" + i, tcn); + return i; + } + return objects.indexOf(o); + } + if(!objects.contains(o)) { + int i = objects.size(); + objects.add(o); + main.set("_" + i, o); + return i; + } + return objects.indexOf(o); + } +} diff --git a/src/main/java/de/tudbut/parsing/ArgumentParser.java b/src/main/java/de/tudbut/parsing/ArgumentParser.java new file mode 100644 index 0000000..e5ad7d9 --- /dev/null +++ b/src/main/java/de/tudbut/parsing/ArgumentParser.java @@ -0,0 +1,74 @@ +package de.tudbut.parsing; + +import java.util.HashMap; +import java.util.Map; + +public class ArgumentParser { + public static Map parseDefault(String[] args) { + Map map = new HashMap<>(); + for (int i = 0; i < args.length; i++) { + try { + // -a | -abc &! -a bc + if (args[i].startsWith("-") && args[i].charAt(1) != '-' && (args.length == i + 1 || args[i + 1].startsWith("-"))) { + for (int j = 1; j < args[i].length(); j++) { + map.put(String.valueOf(args[i].toCharArray()[j]), "true"); + } + } + + // -a bc + if (args[i].startsWith("-") && args[i].charAt(1) != '-' && args.length != i + 1 && !args[i + 1].startsWith("-")) { + map.put(String.valueOf(args[i].charAt(1)), args[i + 1]); + i++; + } + + // --abc &! -abc de + if (args[i].startsWith("-") && args[i].charAt(1) == '-' && (args.length == i + 1 || args[i + 1].startsWith("-"))) { + map.put(args[i].substring(2), "true"); + } + + // --abc de + if (args[i].startsWith("-") && args[i].charAt(1) == '-' && args.length != i + 1 && !args[i + 1].startsWith("-")) { + map.put(args[i].substring(2), args[i + 1]); + i++; + } + } + catch (Exception ignore) { + } + } + return map; + } + + public static Map parseSlash(String[] args) { + Map map = new HashMap<>(); + for (int i = 0; i < args.length; i++) { + try { + // -a | -abc &! -a bc + if (args[i].startsWith("/") && args[i].charAt(1) != '/' && (args.length == i + 1 || args[i + 1].startsWith("/"))) { + for (int j = 1; j < args[i].length(); j++) { + map.put(String.valueOf(args[i].toCharArray()[j]), "true"); + } + } + + // -a bc + if (args[i].startsWith("/") && args[i].charAt(1) != '/' && args.length != i + 1 && !args[i + 1].startsWith("/")) { + map.put(String.valueOf(args[i].charAt(1)), args[i + 1]); + i++; + } + + // --abc &! --abc de + if (args[i].startsWith("/") && args[i].charAt(1) == '/' && (args.length == i + 1 || args[i + 1].startsWith("/"))) { + map.put(args[i].substring(2), "true"); + } + + // --abc de + if (args[i].startsWith("/") && args[i].charAt(1) == '/' && args.length != i + 1 && !args[i + 1].startsWith("/")) { + map.put(args[i].substring(2), args[i + 1]); + i++; + } + } + catch (Exception ignore) { + } + } + return map; + } +} diff --git a/src/main/java/de/tudbut/parsing/AsyncJSON.java b/src/main/java/de/tudbut/parsing/AsyncJSON.java new file mode 100644 index 0000000..a36fd65 --- /dev/null +++ b/src/main/java/de/tudbut/parsing/AsyncJSON.java @@ -0,0 +1,437 @@ +package de.tudbut.parsing; + +import de.tudbut.async.Task; +import de.tudbut.tools.Stack; +import de.tudbut.tools.StringTools; + +import java.util.ArrayList; + +/** + * Interconverting JSON and TCN + */ +public class AsyncJSON { + + /** + * Converts a JSON string to a TCN object + * @param str The JSON string, supports most compact and readable formats + * @return The parsed TCN object + */ + public static Task read(String str) { + return new Task<>((gres, grej) -> { + String string = str; + + while (string.startsWith(" ")) { + string = string.substring(1); + } + if (!string.startsWith("{") && !string.startsWith("[")) { + grej.call(new JSON.JSONFormatException("Expected: { or [ at 0 (String is '" + string + "')")); + return; + } + boolean array = string.startsWith("["); + TCN tcn = new TCN("AJSON", array); + final boolean[] escape = { false }; + final int[] pos = { 1 }; + char[] a = string.toCharArray(); + final char[] c = { a[pos[0]] }; + final int[] arrayPos = { 0 }; + final boolean[] inString = { false }; + final boolean[] startString = { false }; + final StringBuilder[] theString = { new StringBuilder() }; + final boolean[] kv = { false }; + final String[] key = { "" }; + final boolean[] inStringKV = { false }; + final boolean[] inObjectKV = { false }; + final TCN[] sub = { null }; + + try { + while (inString[0] || (c[0] != '}' && c[0] != ']')) { + new Task((res, rej) -> { + if (array) { + kv[0] = true; + } + + if (startString[0]) { + inString[0] = true; + startString[0] = false; + } + if (c[0] == '\\') { + escape[0] = !escape[0]; + } + if (!escape[0] && c[0] == '"') { + startString[0] = !inString[0]; + if (startString[0]) { + if (kv[0]) + inStringKV[0] = true; + theString[0] = new StringBuilder(); + } + else { + inString[0] = false; + if (!kv[0]) { // Key + key[0] = theString[0].toString(); + } + } + } + if (inString[0]) { + if (!escape[0]) + theString[0].append(c[0]); + else { + a: + { + // Make escapes work + if (c[0] == 'n') + theString[0].append('\n'); + if (c[0] == 'r') + theString[0].append('\r'); + if (c[0] == 'u') { + String e = ""; + e += c[0] = a[++pos[0]]; + e += c[0] = a[++pos[0]]; + e += c[0] = a[++pos[0]]; + e += c[0] = a[++pos[0]]; + theString[0].append((char) Integer.parseInt(e, 16)); + break a; + } + if (c[0] == '0') { + String e = ""; + e += c[0] = a[pos[0]]; + e += c[0] = a[++pos[0]]; + e += c[0] = a[++pos[0]]; + theString[0].append((char) Integer.parseInt(e, 8)); + break a; + } + if (c[0] == '1') { + String e = ""; + e += c[0] = a[pos[0]]; + e += c[0] = a[++pos[0]]; + e += c[0] = a[++pos[0]]; + theString[0].append((char) Integer.parseInt(e, 8)); + break a; + } + if (c[0] == '2') { + String e = ""; + e += c[0] = a[pos[0]]; + e += c[0] = a[++pos[0]]; + e += c[0] = a[++pos[0]]; + theString[0].append((char) Integer.parseInt(e, 8)); + break a; + } + if (c[0] == '3') { + String e = ""; + e += c[0] = a[pos[0]]; + e += c[0] = a[++pos[0]]; + e += c[0] = a[++pos[0]]; + theString[0].append((char) Integer.parseInt(e, 8)); + break a; + } + if (c[0] == 'x') { + String e = ""; + e += c[0] = a[++pos[0]]; + e += c[0] = a[++pos[0]]; + theString[0].append((char) Integer.parseInt(e, 16)); + break a; + } + if (c[0] == '"') { + theString[0].append("\""); + } + } + } + } + + // Booleans, ints, etc + else if (kv[0] && !startString[0] && !inStringKV[0] && c[0] != ',' && (Character.isLetterOrDigit(c[0]) || c[0] == '.' || c[0] == '-')) { + theString[0].append(c[0]); + } + + // SubObjects + if (!inString[0] && c[0] == '{') { + inObjectKV[0] = true; + escape[0] = false; + theString[0] = new StringBuilder("{"); + int layer = 1; + while (layer > 0) { + c[0] = a[++pos[0]]; + theString[0].append(c[0]); + if (c[0] == '{' && !inString[0]) { + layer++; + } + if (c[0] == '}' && !inString[0]) { + layer--; + } + + if (c[0] == '\\') { + escape[0] = !escape[0]; + } + if (c[0] == '\"' && !escape[0]) { + inString[0] = !inString[0]; + } + if (c[0] != '\\') { + escape[0] = false; + } + } + theString[0].append("}"); + sub[0] = read(theString[0].toString()).err(rej).ok().await(); + theString[0] = new StringBuilder(); + } + // Arrays + if (!inString[0] && c[0] == '[') { + inObjectKV[0] = true; + escape[0] = false; + theString[0] = new StringBuilder("["); + int layer = 1; + while (layer != 0) { + c[0] = a[++pos[0]]; + theString[0].append(c[0]); + if (c[0] == '[' && !inString[0]) { + layer++; + } + if (c[0] == ']' && !inString[0]) { + layer--; + } + + if (c[0] == '\\') { + escape[0] = !escape[0]; + } + if (c[0] == '\"' && !escape[0]) { + inString[0] = !inString[0]; + } + if (c[0] != '\\') { + escape[0] = false; + } + } + theString[0].append(']'); + sub[0] = read(theString[0].toString()).err(rej).ok().await(); + theString[0] = new StringBuilder(); + } + + + if (array) { + kv[0] = true; + } + // Key vs Value parsing + if (!inString[0] && c[0] == ':') { + theString[0] = new StringBuilder(); + if (!kv[0]) + kv[0] = true; + else { + rej.call(new JSON.JSONFormatException("Unexpected: '" + c[0] + "' at " + pos[0] + " - Should be ','")); + return; + } + } + if (!inString[0] && c[0] == ',') { + if (array) + key[0] = String.valueOf(arrayPos[0]++); + + if (inObjectKV[0]) { + tcn.set(key[0], sub[0]); + } + else if (inStringKV[0] || !theString[0].toString().equals("null")) { + tcn.set(key[0], theString[0].toString()); + } + inObjectKV[0] = false; + inStringKV[0] = false; + theString[0] = new StringBuilder(); + if (kv[0]) + kv[0] = false; + else { + rej.call(new JSON.JSONFormatException("Unexpected: '" + c[0] + "' at " + pos[0] + " - Should be ':'")); + return; + } + + } + + if (c[0] != '\\') { + escape[0] = false; + } + + c[0] = a[++pos[0]]; + res.call(null); + }).err(grej).ok().await(); + } + if (kv[0]) { + if (array) + key[0] = String.valueOf(arrayPos[0]); + if (inObjectKV[0]) + tcn.set(key[0], sub[0]); + else if (inStringKV[0] || !theString[0].toString().equals("null")) + tcn.set(key[0], theString[0].toString()); + } + + + for (String theKey : tcn.map.keys()) { + TCN.deepConvert(theKey, tcn.get(theKey), tcn); + } + } + catch (Throwable e) { + grej.call(new JSON.JSONFormatException("At " + pos[0] + " in " + string + " (Debug: " + inString[0] + " " + kv[0] + " " + theString[0] + " " + key[0] + " " + array + ")", e)); + } + gres.call(tcn); + }); + } + + private static String indent(boolean b, int i, int il) { + if(b) + return StringTools.multiply(StringTools.multiply(" ", il), i); + else + return ""; + } + + /** + * Converts a TCN object to a JSON string, uses the most compact form + * @param tcn The TCN to write to JSON + * @return The JSON string + */ + public static Task write(TCN tcn) { + return write(tcn, false, false, 0); + } + + /** + * Converts a TCN object to a JSON string + * @param tcn The TCN to write to JSON + * @param spaces If the JSON string should have spaces after : and , + * @return The JSON string + */ + public static Task write(TCN tcn, boolean spaces) { + return write(tcn, false, spaces, 0); + } + + /** + * Converts a TCN object to a JSON string, uses newlines with selected indent and without spaces + * @param tcn The TCN to write to JSON + * @param indent The indent to use + * @return The JSON string + */ + public static Task write(TCN tcn, int indent) { + return write(tcn, true, false, indent); + } + + /** + * Converts a TCN object to a JSON string, uses newlines with an indent of 2 and spaces + * @param tcn The TCN to write to JSON + * @return The JSON string + */ + public static Task writeReadable(TCN tcn) { + return write(tcn, true, true, 2); + } + + /** + * Converts a TCN object to a JSON string, uses newlines with selected indent and spaces + * @param tcn The TCN to write to JSON + * @param indent The indent to use + * @return The JSON string + */ + public static Task writeReadable(TCN tcn, int indent) { + return write(tcn, true, true, indent); + } + + /** + * Converts a TCN object to a JSON string + * @param tcn The TCN to write to JSON + * @param newlines If the JSON string should have newlines and indents + * @param spaces If the JSON string should have spaces after : and , + * @param indentLength The indent to use + * @return The JSON string + */ + public static Task write(TCN tcn, boolean newlines, boolean spaces, int indentLength) { + return new Task<>((gres, grej) -> { + StringBuilder s = new StringBuilder(); + s.append(tcn.isArray ? "[" : "{").append(newlines ? "\n" : ""); + final int[] i = { 1 }; + + ArrayList> paths = new ArrayList<>(); + Stack tcnStack = new Stack<>(); + Stack path = new Stack<>(); + tcnStack.add(tcn); + path.add(""); + while (tcnStack.size() > 0) { + final boolean[] b = { false }; + for (String key : tcnStack.peek().map.keys()) { + new Task((res, rej) -> { + Object o = tcnStack.peek().map.get(key); + + if (o == null) { + res.call(null); + return; + } + + String k = key.replaceAll("\\\\", "\\\\\\\\").replaceAll("\n", "\\\\n").replaceAll("\"", "\\\\\""); + if (o.getClass() == TCN.class) { + path.add(key); + if (!paths.contains(path)) { + paths.add(path.clone()); + TCN theTCN = tcnStack.peek(); + tcnStack.add((TCN) o); + if (theTCN.isArray) { + s.append(indent(newlines, i[0], indentLength)).append("{").append(newlines ? "\n" : ""); + } + else + s.append(indent(newlines, i[0], indentLength)).append("\"").append(k).append("\":").append(spaces ? " " : "").append("{").append(newlines ? "\n" : ""); + b[0] = true; + i[0]++; + } + else + path.next(); + } + else if (o.getClass() == TCNArray.class) { + path.add(key); + if (!paths.contains(path)) { + paths.add(path.clone()); + TCN theTCN = tcnStack.peek(); + tcnStack.add(((TCNArray) o).toTCN()); + if (theTCN.isArray) { + s.append(indent(newlines, i[0], indentLength)).append("[").append(newlines ? "\n" : ""); + } + else + s.append(indent(newlines, i[0], indentLength)).append("\"").append(k).append("\":").append(spaces ? " " : "").append("[").append(newlines ? "\n" : ""); + i[0]++; + b[0] = true; + } + else + path.next(); + } + else if (o instanceof String) { + path.add(key); + if (!paths.contains(path)) { + paths.add(path.clone()); + String val = o.toString().replaceAll("\\\\", "\\\\\\\\").replaceAll("\n", "\\\\n").replaceAll("\r", "\\\\r").replaceAll("\"", "\\\\\""); + if (tcnStack.peek().isArray) { + s.append(indent(newlines, i[0], indentLength)).append("\"").append(val).append("\",").append(spaces ? " " : "").append(newlines ? "\n" : ""); + } + else + s.append(indent(newlines, i[0], indentLength)).append("\"").append(k).append("\":").append(spaces ? " \"" : "\"").append(val).append("\",").append(spaces ? " " : "").append(newlines ? "\n" : ""); + b[0] = true; + } + path.next(); + } + else { + path.add(key); + if (!paths.contains(path)) { + paths.add(path.clone()); + String val = o.toString(); + if (tcnStack.peek().isArray) { + s.append(indent(newlines, i[0], indentLength)).append(val).append(",").append(spaces ? " " : "").append(newlines ? "\n" : ""); + } + else + s.append(indent(newlines, i[0], indentLength)).append("\"").append(k).append("\":").append(spaces ? " " : "").append(val).append(",").append(spaces ? " " : "").append(newlines ? "\n" : ""); + b[0] = true; + } + path.next(); + } + res.call(null); + }).err(grej).ok().await(); + } + if (!b[0]) { + TCN theTCN = tcnStack.next(); + path.next(); + i[0]--; + if (theTCN.map.keys().isEmpty()) + s.append(",").append(spaces ? " " : ""); + s.delete(s.length() - ((newlines ? 2 : 1) + (spaces ? 1 : 0)), s.length()); + s.append(newlines ? "\n" : "").append(indent(newlines, i[0], indentLength)).append(theTCN.isArray ? "]" : "}").append(",").append(spaces ? " " : "").append(newlines ? "\n" : ""); + } + } + s.delete(s.length() - ((newlines ? 2 : 1) + (spaces ? 1 : 0)), s.length()); + gres.call(s.toString()); + }); + } + +} diff --git a/src/main/java/de/tudbut/parsing/JSON.java b/src/main/java/de/tudbut/parsing/JSON.java new file mode 100644 index 0000000..e0f5e48 --- /dev/null +++ b/src/main/java/de/tudbut/parsing/JSON.java @@ -0,0 +1,424 @@ +package de.tudbut.parsing; + +import de.tudbut.tools.Stack; +import de.tudbut.tools.StringTools; + +import java.util.ArrayList; + +/** + * Interconverting JSON and TCN + */ +public class JSON { + + /** + * Converts a JSON string to a TCN object + * @param string The JSON string, supports most compact and readable formats + * @return The parsed TCN object + * @throws JSONFormatException If a format error is found + */ + public static TCN read(String string) throws JSONFormatException { + while (string.startsWith(" ")) { + string = string.substring(1); + } + if(!string.startsWith("{") && !string.startsWith("[")) { + throw new JSONFormatException("Expected: { or [ at 0 (String is '" + string + "')"); + } + boolean array = string.startsWith("["); + TCN tcn = new TCN("JSON", array); + boolean escape = false; + int pos = 1; + char[] a = string.toCharArray(); + char c = a[pos]; + int arrayPos = 0; + boolean inString = false; + boolean startString = false; + StringBuilder theString = new StringBuilder(); + boolean kv = false; + String key = ""; + boolean inStringKV = false; + boolean inObjectKV = false; + TCN sub = null; + + try { + while (inString || (c != '}' && c != ']')) { + if(array) { + kv = true; + } + + if (startString) { + inString = true; + startString = false; + } + if (c == '\\') { + escape = !escape; + } + if (!escape && c == '"') { + startString = !inString; + if (startString) { + if (kv) + inStringKV = true; + theString = new StringBuilder(); + } + else { + inString = false; + if (!kv) { // Key + key = theString.toString(); + } + } + } + if (inString) { + if (!escape) + theString.append(c); + else { + a: + { + // Make escapes work + if (c == 'n') + theString.append('\n'); + if (c == 'r') + theString.append('\r'); + if (c == 'u') { + String e = ""; + e += c = a[++pos]; + e += c = a[++pos]; + e += c = a[++pos]; + e += c = a[++pos]; + theString.append((char) Integer.parseInt(e, 16)); + break a; + } + if (c == '0') { + String e = ""; + e += c = a[pos]; + e += c = a[++pos]; + e += c = a[++pos]; + theString.append((char) Integer.parseInt(e, 8)); + break a; + } + if (c == '1') { + String e = ""; + e += c = a[pos]; + e += c = a[++pos]; + e += c = a[++pos]; + theString.append((char) Integer.parseInt(e, 8)); + break a; + } + if (c == '2') { + String e = ""; + e += c = a[pos]; + e += c = a[++pos]; + e += c = a[++pos]; + theString.append((char) Integer.parseInt(e, 8)); + break a; + } + if (c == '3') { + String e = ""; + e += c = a[pos]; + e += c = a[++pos]; + e += c = a[++pos]; + theString.append((char) Integer.parseInt(e, 8)); + break a; + } + if (c == 'x') { + String e = ""; + e += c = a[++pos]; + e += c = a[++pos]; + theString.append((char) Integer.parseInt(e, 16)); + break a; + } + if (c == '"') { + theString.append("\""); + } + } + } + } + + // Booleans, ints, etc + else if (kv && !startString && !inStringKV && c != ',' && (Character.isLetterOrDigit(c) || c == '.' || c == '-')) { + theString.append(c); + } + + // SubObjects + if (!inString && c == '{') { + inObjectKV = true; + escape = false; + theString = new StringBuilder("{"); + int layer = 1; + while (layer > 0) { + c = a[++pos]; + theString.append(c); + if(c == '{' && !inString) { + layer++; + } + if(c == '}' && !inString) { + layer--; + } + + if (c == '\\') { + escape = !escape; + } + if (c == '\"' && !escape) { + inString = !inString; + } + if (c != '\\') { + escape = false; + } + } + theString.append("}"); + sub = read(theString.toString()); + theString = new StringBuilder(); + } + // Arrays + if (!inString && c == '[') { + inObjectKV = true; + escape = false; + theString = new StringBuilder("["); + int layer = 1; + while (layer != 0) { + c = a[++pos]; + theString.append(c); + if(c == '[' && !inString) { + layer++; + } + if(c == ']' && !inString) { + layer--; + } + + if (c == '\\') { + escape = !escape; + } + if (c == '\"' && !escape) { + inString = !inString; + } + if (c != '\\') { + escape = false; + } + } + theString.append(']'); + sub = read(theString.toString()); + theString = new StringBuilder(); + } + + + if(array) { + kv = true; + } + // Key vs Value parsing + if (!inString && c == ':') { + theString = new StringBuilder(); + if (!kv) + kv = true; + else + throw new JSONFormatException("Unexpected: '" + c + "' at " + pos + " - Should be ','"); + } + if (!inString && c == ',') { + if(array) + key = String.valueOf(arrayPos++); + + if (inObjectKV) { + tcn.set(key, sub); + } else if(inStringKV || !theString.toString().equals("null")) { + tcn.set(key, theString.toString()); + } + inObjectKV = false; + inStringKV = false; + theString = new StringBuilder(); + if (kv) + kv = false; + else + throw new JSONFormatException("Unexpected: '" + c + "' at " + pos + " - Should be ':'"); + + } + + if (c != '\\') { + escape = false; + } + + c = a[++pos]; + } + if(kv) { + if (array) + key = String.valueOf(arrayPos); + if (inObjectKV) + tcn.set(key, sub); + else if(inStringKV || !theString.toString().equals("null")) + tcn.set(key, theString.toString()); + } + + + for (String theKey : tcn.map.keys()) { + TCN.deepConvert(theKey, tcn.get(theKey), tcn); + } + return tcn; + } catch (JSONFormatException e) { + throw e; + } catch (Throwable e) { + throw new JSONFormatException("At " + pos + " in " + string + " (Debug: " + inString + " " + kv + " " + theString + " " + key + " " + array + ")", e); + } + } + + private static String indent(boolean b, int i, int il) { + if(b) + return StringTools.multiply(StringTools.multiply(" ", il), i); + else + return ""; + } + + /** + * Converts a TCN object to a JSON string, uses the most compact form + * @param tcn The TCN to write to JSON + * @return The JSON string + */ + public static String write(TCN tcn) { + return write(tcn, false, false, 0); + } + + /** + * Converts a TCN object to a JSON string + * @param tcn The TCN to write to JSON + * @param spaces If the JSON string should have spaces after : and , + * @return The JSON string + */ + public static String write(TCN tcn, boolean spaces) { + return write(tcn, false, spaces, 0); + } + + /** + * Converts a TCN object to a JSON string, uses newlines with selected indent and without spaces + * @param tcn The TCN to write to JSON + * @param indent The indent to use + * @return The JSON string + */ + public static String write(TCN tcn, int indent) { + return write(tcn, true, false, indent); + } + + /** + * Converts a TCN object to a JSON string, uses newlines with an indent of 2 and spaces + * @param tcn The TCN to write to JSON + * @return The JSON string + */ + public static String writeReadable(TCN tcn) { + return write(tcn, true, true, 2); + } + + /** + * Converts a TCN object to a JSON string, uses newlines with selected indent and spaces + * @param tcn The TCN to write to JSON + * @param indent The indent to use + * @return The JSON string + */ + public static String writeReadable(TCN tcn, int indent) { + return write(tcn, true, true, indent); + } + + /** + * Converts a TCN object to a JSON string + * @param tcn The TCN to write to JSON + * @param newlines If the JSON string should have newlines and indents + * @param spaces If the JSON string should have spaces after : and , + * @param indentLength The indent to use + * @return The JSON string + */ + public static String write(TCN tcn, boolean newlines, boolean spaces, int indentLength) { + + StringBuilder s = new StringBuilder(); + s.append(tcn.isArray ? "[" : "{").append(newlines ? "\n" : ""); + int i = 1; + + ArrayList> paths = new ArrayList<>(); + Stack tcnStack = new Stack<>(); + Stack path = new Stack<>(); + tcnStack.add(tcn); + path.add(""); + while (tcnStack.size() > 0) { + boolean b = false; + for(String key : tcnStack.peek().map.keys()) { + Object o = tcnStack.peek().map.get(key); + + if(o == null) + continue; + + String k = key.replaceAll("\\\\", "\\\\\\\\").replaceAll("\n", "\\\\n").replaceAll("\"", "\\\\\""); + if(o.getClass() == TCN.class) { + path.add(key); + if(!paths.contains(path)) { + paths.add(path.clone()); + TCN theTCN = tcnStack.peek(); + tcnStack.add((TCN) o); + if(theTCN.isArray) { + s.append(indent(newlines, i, indentLength)).append("{").append(newlines ? "\n" : ""); + } + else + s.append(indent(newlines, i, indentLength)).append("\"").append(k).append("\":").append(spaces ? " " : "").append("{").append(newlines ? "\n" : ""); + b = true; + i++; + } else + path.next(); + } else if(o.getClass() == TCNArray.class) { + path.add(key); + if(!paths.contains(path)) { + paths.add(path.clone()); + TCN theTCN = tcnStack.peek(); + tcnStack.add(((TCNArray) o).toTCN()); + if(theTCN.isArray) { + s.append(indent(newlines, i, indentLength)).append("[").append(newlines ? "\n" : ""); + } + else + s.append(indent(newlines, i, indentLength)).append("\"").append(k).append("\":").append(spaces ? " " : "").append("[").append(newlines ? "\n" : ""); + i++; + b = true; + } else + path.next(); + } else if (o instanceof String) { + path.add(key); + if(!paths.contains(path)) { + paths.add(path.clone()); + String val = o.toString().replaceAll("\\\\", "\\\\\\\\").replaceAll("\n", "\\\\n").replaceAll("\r", "\\\\r").replaceAll("\"", "\\\\\""); + if(tcnStack.peek().isArray) { + s.append(indent(newlines, i, indentLength)).append("\"").append(val).append("\",").append(spaces ? " " : "").append(newlines ? "\n" : ""); + } + else + s.append(indent(newlines, i, indentLength)).append("\"").append(k).append("\":").append(spaces ? " \"" : "\"").append(val).append("\",").append(spaces ? " " : "").append(newlines ? "\n" : ""); + b = true; + } + path.next(); + } + else { + path.add(key); + if(!paths.contains(path)) { + paths.add(path.clone()); + String val = o.toString(); + if(tcnStack.peek().isArray) { + s.append(indent(newlines, i, indentLength)).append(val).append(",").append(spaces ? " " : "").append(newlines ? "\n" : ""); + } + else + s.append(indent(newlines, i, indentLength)).append("\"").append(k).append("\":").append(spaces ? " " : "").append(val).append(",").append(spaces ? " " : "").append(newlines ? "\n" : ""); + b = true; + } + path.next(); + } + } + if(!b) { + TCN theTCN = tcnStack.next(); + path.next(); + i--; + if(theTCN.map.keys().isEmpty()) { + s.append(",").append(spaces ? " " : ""); + } + s.delete(s.length() - ((newlines ? 2 : 1) + (spaces ? 1 : 0)), s.length()); + s.append(newlines ? "\n" : "").append(indent(newlines, i, indentLength)).append(theTCN.isArray ? "]" : "}").append(",").append(spaces ? " " : "").append(newlines ? "\n" : ""); + } + } + s.delete(s.length() - ((newlines ? 2 : 1) + (spaces ? 1 : 0)), s.length()); + return s.toString(); + } + + public static class JSONFormatException extends Exception { + public JSONFormatException(String s, Throwable e) { + super(s, e); + } + public JSONFormatException(String s) { + super(s); + } + } +} diff --git a/src/main/java/de/tudbut/parsing/Lang.java b/src/main/java/de/tudbut/parsing/Lang.java new file mode 100644 index 0000000..7d7e2b2 --- /dev/null +++ b/src/main/java/de/tudbut/parsing/Lang.java @@ -0,0 +1,68 @@ +package de.tudbut.parsing; + +import de.tudbut.tools.Tools; +import de.tudbut.obj.InstanceBoundMap; + +import java.util.ArrayList; +import java.util.Map; + +public class Lang extends InstanceBoundMap { + + private String language = "default"; + private static final ArrayList factories = new ArrayList<>(); + + public String getLanguage() { + return language; + } + + public void setLanguage(String languageIn) { + language = languageIn; + } + + public String get(String key) { + return get(language, key); + } + + private Lang() { + } + + public static Factory factory() { + for (int i = 0; i < factories.size(); i++) { + if(!factories.get(i).inUse) { + for (int j = i+1; j < factories.size(); j++) { + factories.remove(i); + i--; + } + factories.get(i).theLang = new Lang(); + return factories.get(i); + } + } + Factory f = new Factory(); + factories.add(f); + return f; + } + + public static class Factory { + private boolean inUse = true; + private Lang theLang = new Lang(); + + private Factory() {} + + public Factory addLanguage(String name, Map map) { + for (String key : map.keySet()) { + theLang.set(name, key, map.get(key)); + } + return this; + } + + public Factory addLanguage(String name, String langFile) { + return addLanguage(name, Tools.stringToMap(langFile.replaceAll("\n", ";"))); + } + + public Lang build(String defaultLanguage) { + theLang.setLanguage(defaultLanguage); + inUse = false; + return theLang; + } + } +} diff --git a/src/main/java/de/tudbut/parsing/StringMapParser.java b/src/main/java/de/tudbut/parsing/StringMapParser.java new file mode 100644 index 0000000..58597fc --- /dev/null +++ b/src/main/java/de/tudbut/parsing/StringMapParser.java @@ -0,0 +1,32 @@ +package de.tudbut.parsing; + +import java.util.Map; + +public class StringMapParser { + public static boolean getBoolean(Map map, String key) { + try { + return Boolean.parseBoolean(map.getOrDefault(key, "false")); + } + catch (Exception e) { + return false; + } + } + + public static String get(Map map, String key) { + try { + return map.getOrDefault(key, ""); + } + catch (Exception e) { + return ""; + } + } + + public static int getInt(Map map, String key) { + try { + return Integer.parseInt(map.getOrDefault(key, "0")); + } + catch (Exception e) { + return 0; + } + } +} diff --git a/src/main/java/de/tudbut/parsing/TCN.java b/src/main/java/de/tudbut/parsing/TCN.java new file mode 100644 index 0000000..67e8a23 --- /dev/null +++ b/src/main/java/de/tudbut/parsing/TCN.java @@ -0,0 +1,516 @@ +package de.tudbut.parsing; + + +import de.tudbut.tools.Tools; +import de.tudbut.obj.DoubleTypedObject; +import de.tudbut.obj.TLMap; +import de.tudbut.tools.Stack; +import de.tudbut.tools.StringTools; + +import java.util.*; + +/** + * T udbuT
+ * C onfig
+ * N otation
+ */ +public class TCN { + + /** + * The map + */ + public TLMap map = new TLMap<>(); + public final boolean isArray; + public String type; + + /** + * Creates a new, empty TCN + */ + public TCN() { + isArray = false; + this.type = "TCN"; + } + + TCN(boolean array) { + isArray = array; + this.type = "TCN"; + } + + public TCN(String type) { + isArray = false; + this.type = type; + } + + TCN(String type, boolean array) { + isArray = array; + this.type = type; + } + + + /** + * Sets something in the map + * @param key Key + * @param o Object, can be a native type, string, or another TCN (or TCNArray) + */ + public void set(String key, Object o) { + /*TLMap map = this.map; + ArrayList path = new ArrayList<>(Arrays.asList(key.split("#"))); + + while (path.size() > 1) { + map = ((TCN) map.get(path.remove(0))).map; + } + */ + map.set(key, o); + } + + public String getString(String key) { + Object o = map.get(key); + if(o != null) + return get(key).toString(); + else + return null; + } + + public Short getShort(String key) { + Object o = get(key); + if(o != null) + return Short.valueOf(String.valueOf(o)); + else + return null; + } + + public Integer getInteger(String key) { + Object o = get(key); + if(o != null) + return Integer.valueOf(String.valueOf(o)); + else + return null; + } + + public Boolean getBoolean(String key) { + Object o = get(key); + if(o != null) + return Boolean.valueOf(String.valueOf(o)); + else + return null; + } + + public Float getFloat(String key) { + Object o = get(key); + if(o != null) + return Float.valueOf(String.valueOf(o)); + else + return null; + } + + public Long getLong(String key) { + Object o = get(key); + if(o != null) + return Long.valueOf(String.valueOf(o)); + else + return null; + } + + public Double getDouble(String key) { + Object o = get(key); + if(o != null) + return Double.valueOf(String.valueOf(o)); + else + return null; + } + + public TCN getSub(String key) { + Object o = get(key); + if(o != null && o.getClass() == TCN.class) + return (TCN) map.get(key); + else + return null; + } + + public TCNArray getArray(String key) { + Object o = get(key); + if(o != null && o.getClass() == TCNArray.class) + return (TCNArray) map.get(key); + else + return null; + } + + public Object get(String key) { + TLMap map = this.map; + ArrayList path = new ArrayList<>(Collections.singletonList(key)); + + while (path.size() > 1) { + map = ((TCN) map.get(path.remove(0))).map; + } + + return map.get(path.get(0)); + } + + /** + * Converts a Map to a TCN + * @param map The map to convert + * @return The converted TCN + */ + public static TCN readMap(Map map) { + TCN tcn = new TCN(map.containsKey("TCN%isArray") && Boolean.parseBoolean(map.get("TCN%isArray"))); + + map.remove("TCN%isArray"); + + String[] array = map.keySet().toArray(new String[0]); + for (int i = 0, arrayLength = array.length; i < arrayLength; i++) { + String key = array[i]; + String s = map.get(key); + + if(s.contains(":")) { + tcn.map.set(key, TCN.readMap(Tools.stringToMap(s))); + } + else { + tcn.map.set(key, s.replaceAll("%C", ":").replaceAll("%P", "%")); + } + } + + for (String key : tcn.map.keys()) { + deepConvert(key, tcn.get(key), tcn); + } + + return tcn; + } + + /** + * Converts this TCN object to a Map + * {@link #readMap} + * @return The converted Map + */ + public Map toMap() { + Map r = new LinkedHashMap<>(); + + if(isArray) + r.put("TCN%isArray", "true"); + + String[] array = map.keys().toArray(new String[0]); + for (int i = 0, arrayLength = array.length; i < arrayLength; i++) { + String key = array[i]; + Object o = map.get(key); + + if(o == null) + continue; + + if(o.getClass() == TCN.class) { + r.put(key, Tools.mapToString(((TCN) o).toMap())); + } + else if(o.getClass() == TCNArray.class) { + r.put(key, Tools.mapToString(((TCNArray) o).toMap())); + } + else + r.put(key, o.toString().replaceAll("%", "%P").replaceAll(":", "%C")); + } + + return r; + } + + /** + * Converts this TCN to a reversible string + * @return The converted string + */ + public String toString() { + if(type.equalsIgnoreCase("TCN")) { + StringBuilder s = new StringBuilder(); + int i = 0; + + ArrayList> paths = new ArrayList<>(); + Stack tcnStack = new Stack<>(); + Stack path = new Stack<>(); + tcnStack.add(this); + path.add(""); + while (tcnStack.size() > 0) { + boolean b = false; + for (String key : tcnStack.peek().map.keys()) { + Object o = tcnStack.peek().map.get(key); + + if (o == null) + continue; + + String k = key.replaceAll("%", "%P").replaceAll(":", "%C").replaceAll("\n", "%N"); + if (k.startsWith(" ")) { + if (!k.equals(" ")) { + k = "%S" + k.substring(1); + } else + k = "%S"; + } + if (k.startsWith("#")) { + if (!k.equals("#")) { + k = "%H" + k.substring(1); + } else + k = "%H"; + } + if (o.getClass() == TCN.class) { + path.add(key); + if (!paths.contains(path)) { + paths.add(path.clone()); + TCN tcn = tcnStack.peek(); + tcnStack.add((TCN) o); + String indent = StringTools.multiply(" ", i); + if (tcn.isArray) { + s.append("\n").append(indent).append(";").append(((TCN) o).isArray ? " [\n" : " {\n"); + } else + s.append("\n").append(indent).append(k).append(((TCN) o).isArray ? " [\n" : " {\n"); + i++; + b = true; + break; + } else + path.next(); + } else if (o.getClass() == TCNArray.class) { + path.add(key); + if (!paths.contains(path)) { + paths.add(path.clone()); + TCN tcn = tcnStack.peek(); + tcnStack.add(((TCNArray) o).toTCN()); + String indent = StringTools.multiply(" ", i); + if (tcn.isArray) { + s.append("\n").append(indent).append(";").append(" [\n"); + } else + s.append("\n").append(indent).append(k).append(" [\n"); + i++; + b = true; + break; + } else + path.next(); + } else { + path.add(key); + if (!paths.contains(path)) { + paths.add(path.clone()); + String indent = StringTools.multiply(" ", i); + String val = o.toString().replaceAll("%", "%P").replaceAll("\n", "%N"); + if (tcnStack.peek().isArray) { + s.append(indent).append("; ").append(val).append("\n"); + } else + s.append(indent).append(k).append(": ").append(val).append("\n"); + b = true; + } + path.next(); + } + } + if (!b) { + TCN tcn = tcnStack.next(); + path.next(); + i--; + String indent = StringTools.multiply(" ", i); + s.append(indent).append(tcn.isArray ? "]\n\n" : "}\n\n"); + } + } + try { + s.setLength(s.length() - "\n#\n\n".length()); + s.trimToSize(); + } catch (Exception ignored) { + } + + return s.toString(); + } + else if(type.equalsIgnoreCase("JSON")) { + return JSON.write(this); + } + else if(type.equalsIgnoreCase("AJSON")) { + return AsyncJSON.write(this).ok().await(); + } + return ""; + } + + /** + * Converts a string {@link #toString()} to a TCN object + * @param s The string + * @return The converted TCN + * @throws TCNException If a format error occurs + */ + public static TCN read(String s) throws TCNException { + TCN tcn = new TCN(); + + Map, Stack>, String> scanned = deepScan(s); + Set, Stack>> keys = scanned.keySet(); + for (DoubleTypedObject, Stack> path : keys) { + deepPut(path.clone(), tcn, scanned.get(path)); + } + for (String key : tcn.map.keys()) { + deepConvert(key, tcn.get(key), tcn); + } + + return tcn; + } + + /** + * Converts stray arrays to TCNArray objects, recursive.
+ *
Example:{@code
+     *     for (String key : tcn.map.keys()) {
+     *         deepConvert(key, tcn.get(key), tcn);
+     *     }
+     * }
+ * @param key The key of o + * @param o Any value in a TCN + * @param top The TCN that o is embedded in + */ + public static void deepConvert(String key, Object o, TCN top) { + if(!(o instanceof TCN) && !(o instanceof TCNArray)) + return; + TCN tcn; + if(o instanceof TCN) { + tcn = (TCN) o; + } + else + tcn = ((TCNArray) o).toTCN(); + + for (String theKey : tcn.map.keys()) { + deepConvert(theKey, tcn.get(theKey), tcn); + } + + if (tcn.isArray) { + top.set(key, TCNArray.fromTCN(tcn)); + } + } + + private static void deepPut(DoubleTypedObject, Stack> path, TCN tcn, String value) throws TCNException { + try { + if (path.o.size() == 1) { + tcn.map.set(path.o.next(), value); + } + else { + int arrayPos = path.t.popBottom(); + TCN toPut = (TCN) tcn.map.get(path.o.getBottom(), () -> new TCN(arrayPos != -1)); + tcn.map.set(path.o.popBottom(), toPut); + deepPut(path, toPut, value); + } + } catch (Exception e) { + throw new TCNException(null, pathToString(path.o) + ": " + value, e); + } + } + + private static Map, Stack>, String> deepScan(String s) throws TCNException { + String[] lines = s.split("\n"); + Map, Stack>, String> map = new LinkedHashMap<>(); + + Stack array = new Stack<>(); + array.add(false); + Stack arrayPos = new Stack<>(); + Stack path = new Stack<>(); + for (int i = 0; i < lines.length; i++) { + try { + String line = removePrefixSpaces(lines[i]); + if (!line.isEmpty() && !line.startsWith("#")) { + if (line.equals("}") || line.equals("]")) { + path.next(); + array.next(); + arrayPos.next(); + } + else if (line.endsWith(" {") && !line.contains(": ")) { + String k = line.split(" \\{")[0].replaceAll("%C", ":").replaceAll("%N", "\n"); + if (k.startsWith("%S")) { + if (!k.equals("%S")) + k = " " + k.substring(2); + else + k = " "; + } + if (k.startsWith("%H")) { + if (!k.equals("%H")) + k = "#" + k.substring(2); + else + k = "#"; + } + if(!array.peek()) + path.add(k.replaceAll("%P", "%")); + else { + path.add(arrayPos.peek() + ""); + arrayPos.add(arrayPos.next() + 1); + } + array.add(false); + arrayPos.add(-1); + } + else if (line.endsWith(" [") && !line.contains(": ")) { + String k = line.split(" \\[")[0].replaceAll("%C", ":").replaceAll("%N", "\n"); + if (k.startsWith("%S")) { + if (!k.equals("%S")) + k = " " + k.substring(2); + else + k = " "; + } + if (k.startsWith("%H")) { + if (!k.equals("%H")) + k = "#" + k.substring(2); + else + k = "#"; + } + if(!array.peek()) + path.add(k.replaceAll("%P", "%")); + else { + path.add(arrayPos.peek() + ""); + arrayPos.add(arrayPos.next() + 1); + } + array.add(true); + arrayPos.add(0); + } + else { + Stack p = path.clone(); + if(array.peek()) { + p.add(String.valueOf(arrayPos.peek())); + if(line.equals(";")) + line = "; "; + map.put(new DoubleTypedObject<>(p, arrayPos.clone()), line.substring(2).replaceAll("%N", "\n").replaceAll("%P", "%")); + arrayPos.add(arrayPos.next() + 1); + } + else { + String rawk = line.split(": ")[0].replaceAll("%C", ":").replaceAll("%N", "\n"); + String k = rawk; + if (k.startsWith("%S")) { + if (!k.equals("%S")) + k = " " + k.substring(2); + else + k = " "; + } + if (k.startsWith("%H")) { + if (!k.equals("%H")) + k = "#" + k.substring(2); + else + k = "#"; + } + p.add(k.replaceAll("%P", "%")); + map.put(new DoubleTypedObject<>(p, arrayPos.clone()), line.substring(rawk.length() + 2).replaceAll("%N", "\n").replaceAll("%P", "%")); + } + } + } + } catch (Exception e) { + throw new TCNException(i, lines[i], e); + } + } + + return map; + } + + static String pathToString(Stack path) { + StringBuilder s = new StringBuilder("/"); + path = path.clone(); + while (path.hasNext()) { + s.append(path.popBottom()).append("/"); + } + return s.toString(); + } + + private static String removePrefixSpaces(String s) { + while (s.startsWith(" ")) + s = s.substring(1); + return s; + } + + /** + * Creates a new, empty TCN object + * @deprecated Use {@code new} {@link #TCN()} + * @return The new TCN + */ + @Deprecated + public static TCN getEmpty() { + return new TCN(); + } + + public static class TCNException extends Exception { + + public TCNException(Integer line, String lineString, Exception e) { + super("Error in line " + line + " (" + lineString + ")", e); + } + } +} diff --git a/src/main/java/de/tudbut/parsing/TCNArray.java b/src/main/java/de/tudbut/parsing/TCNArray.java new file mode 100644 index 0000000..555250e --- /dev/null +++ b/src/main/java/de/tudbut/parsing/TCNArray.java @@ -0,0 +1,127 @@ +package de.tudbut.parsing; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +/** + * TCN-Compatible Arrays + */ +public class TCNArray extends ArrayList { + + /** + * Creates a new, empty TCNArray + */ + public TCNArray() { } + /** + * Creates a new TCNArray from a collection + */ + public TCNArray(Collection collection) { + addAll(collection); + } + + public String getString(int key) { + Object o = get(key); + if(o != null) + return o.toString(); + else + return null; + } + + public Short getShort(int key) { + Object o = get(key); + if(o != null) + return Short.valueOf(String.valueOf(o)); + else + return null; + } + + public Integer getInteger(int key) { + Object o = get(key); + if(o != null) + return Integer.valueOf(String.valueOf(o)); + else + return null; + } + + public Boolean getBoolean(int key) { + Object o = get(key); + if(o != null) + return Boolean.valueOf(String.valueOf(o)); + else + return null; + } + + public Float getFloat(int key) { + Object o = get(key); + if(o != null) + return Float.valueOf(String.valueOf(o)); + else + return null; + } + + public Long getLong(int key) { + Object o = get(key); + if(o != null) + return Long.valueOf(String.valueOf(o)); + else + return null; + } + + public Double getDouble(int key) { + Object o = get(key); + if(o != null) + return Double.valueOf(String.valueOf(o)); + else + return null; + } + + public TCN getSub(int key) { + Object o = get(key); + if(o != null && o.getClass() == TCN.class) + return (TCN) o; + else + return null; + } + + public TCNArray getArray(int key) { + Object o = get(key); + if(o != null && o.getClass() == TCNArray.class) + return (TCNArray) o; + else + return null; + } + + /** + * + * @return a TCN object from this TCNArray, this is the only way to get a TCN with {@link TCN#isArray} true + */ + public TCN toTCN() { + TCN tcn = new TCN(true); + for (int i = 0 ; i < this.size() ; i++) { + tcn.set(String.valueOf(i), get(i)); + } + return tcn; + } + + /** + * Converts a TCN to a TCNArray + * @param tcn The TCN to convert from + * @return The created TCNArray + */ + public static TCNArray fromTCN(TCN tcn) { + TCNArray array = new TCNArray(); + for (String key : tcn.map.keys()) { + array.add(tcn.get(key)); + } + return array; + } + + /** + * Converts the TCNArray to a TCN, then maps it + * @return the created map + */ + public Map toMap() { + return toTCN().toMap(); + } +} diff --git a/src/main/java/de/tudbut/parsing/TudSort.java b/src/main/java/de/tudbut/parsing/TudSort.java new file mode 100644 index 0000000..80eb008 --- /dev/null +++ b/src/main/java/de/tudbut/parsing/TudSort.java @@ -0,0 +1,102 @@ +package de.tudbut.parsing; + +import de.tudbut.tools.Tools; +import de.tudbut.tools.ArrayGetter; +import de.tudbut.tools.ArrayTools; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class TudSort { + + public static T[] sort(T[] toSort, Sorter sorter) { + return sort(toSort, sorter, false); + } + + public static T[] sort(T[] toSort, Sorter sorter, boolean reverse) { + if(toSort.length == 0) + return toSort; + T[] nt = (T[]) ArrayGetter.newArray(toSort.length, toSort.getClass().getComponentType()); + + boolean[] indexes = new boolean[toSort.length]; + for (int i = 0; i < toSort.length; i++) { + long smallest = Long.MAX_VALUE; + int smallestIndex = 0; + for (int j = 0; j < toSort.length; j++) { + long id = sorter.get(toSort[j]); + if(id <= smallest && !indexes[j]) { + smallest = id; + smallestIndex = j; + } + } + nt[i] = toSort[smallestIndex]; + indexes[smallestIndex] = true; + } + + if(reverse) { + List list = Arrays.asList(nt); + Collections.reverse(list); + return list.toArray(toSort); + } + + return nt; + } + + public static T[] sortDouble(T[] toSort, SorterDouble sorter) { + if(toSort.length == 0) + return toSort; + T[] nt = (T[]) ArrayGetter.newArray(toSort.length, toSort.getClass().getComponentType()); + + boolean[] indexes = new boolean[toSort.length]; + for (int i = 0; i < toSort.length; i++) { + double smallest = Integer.MAX_VALUE; + int smallestIndex = 0; + for (int j = 0; j < toSort.length; j++) { + double id = sorter.get(toSort[j]); + if(id <= smallest && !indexes[j]) { + smallest = id; + smallestIndex = j; + } + } + nt[i] = toSort[smallestIndex]; + indexes[smallestIndex] = true; + } + + return nt; + } + + public static void sortSet(T[] toSort, Sorter sorter) { + T[] sorted = sort(toSort, sorter); + Tools.copyArray(sorted, toSort, toSort.length); + } + + public static T find(T[] toFindIn, ArrayTools.Getter getter, Object o) { + for (T t : toFindIn) { + try { + if (getter.get(t).equals(o)) + return t; + } + catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + } + return null; + } + + public interface Sorter { + long sort(T t); + + default long get(T t) { + return sort(t); + } + } + + public interface SorterDouble { + double sort(T t); + + default double get(T t) { + return sort(t); + } + } +} diff --git a/src/main/java/de/tudbut/pluginapi/Plugin.java b/src/main/java/de/tudbut/pluginapi/Plugin.java new file mode 100644 index 0000000..3be94bb --- /dev/null +++ b/src/main/java/de/tudbut/pluginapi/Plugin.java @@ -0,0 +1,114 @@ +package de.tudbut.pluginapi; + + +import de.tudbut.tools.BetterClassLoader; +import de.tudbut.logger.DetailedLogger; +import de.tudbut.logger.Logger; +import de.tudbut.logger.LoggerSink; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public abstract class Plugin { + static void loadClass() { + + } + + private LoggerSink logger; + private Logger nLogger; + private DetailedLogger dLogger; + private BetterClassLoader cl; + private String jar; + + public abstract void onLoad(); + + public abstract void onUnload(); + + public abstract void onEvent(PluginEvent event); + + public BetterClassLoader getCL() { + return cl; + } + + public LoggerSink getLogger() { + return logger; + } + + public LoggerSink getLogger(String sub) { + return logger.subChannel(sub); + } + + public void setLoggerType(boolean detailed) { + logger = detailed ? dLogger : nLogger; + } + + public void setLogger(String name) { + this.nLogger = new Logger(name); + this.dLogger = new DetailedLogger(name); + setLoggerType(false); + } + + public void setCL(BetterClassLoader cl, String jar) throws PluginException { + if (this.cl == null) { + this.cl = cl; + this.jar = jar; + } + else + throw new PluginException("Redefining classLoader"); + } + + public String getName() { + return jar; + } + + public Object useCustomMethod(String methodName, Object... args) { + for (Method method : getClass().getDeclaredMethods()) { + try { + if(method.getName().equals(methodName)) + return method.invoke(this, args); + } catch (IllegalAccessException | InvocationTargetException ignore) { + } + } + return null; + } + + public Object useStaticCustomMethod(String methodName, Object... args) { + for (Method method : getClass().getDeclaredMethods()) { + try { + if(method.getName().equals(methodName)) + return method.invoke(null, args); + } catch (IllegalAccessException | InvocationTargetException ignore) { + } + } + return null; + } + + public Object useStaticCustomMethodInCustomClass(String className, String methodName, Object... args) { + for (Method method : getCustomClass(className).getDeclaredMethods()) { + try { + if(method.getName().equals(methodName)) + return method.invoke(null, args); + } catch (IllegalAccessException | InvocationTargetException ignore) { + } + } + return null; + } + + public Object useCustomMethodInCustomClass(String methodName, Object instance, Object... args) { + for (Method method : instance.getClass().getDeclaredMethods()) { + try { + if(method.getName().equals(methodName)) + return method.invoke(instance, args); + } catch (IllegalAccessException | InvocationTargetException ignore) { + } + } + return null; + } + + public Class getCustomClass(String className) { + try { + return getCL().friendlyGet(className); + } catch (NullPointerException ignore) { } + return null; + } +} diff --git a/src/main/java/de/tudbut/pluginapi/PluginEvent.java b/src/main/java/de/tudbut/pluginapi/PluginEvent.java new file mode 100644 index 0000000..4a4fe8d --- /dev/null +++ b/src/main/java/de/tudbut/pluginapi/PluginEvent.java @@ -0,0 +1,4 @@ +package de.tudbut.pluginapi; + +public class PluginEvent { +} diff --git a/src/main/java/de/tudbut/pluginapi/PluginException.java b/src/main/java/de/tudbut/pluginapi/PluginException.java new file mode 100644 index 0000000..dc9ece4 --- /dev/null +++ b/src/main/java/de/tudbut/pluginapi/PluginException.java @@ -0,0 +1,7 @@ +package de.tudbut.pluginapi; + +public class PluginException extends Exception { + public PluginException(String msg) { + System.out.println(msg); + } +} diff --git a/src/main/java/de/tudbut/pluginapi/PluginGetEvent.java b/src/main/java/de/tudbut/pluginapi/PluginGetEvent.java new file mode 100644 index 0000000..3b2fbb3 --- /dev/null +++ b/src/main/java/de/tudbut/pluginapi/PluginGetEvent.java @@ -0,0 +1,10 @@ +package de.tudbut.pluginapi; + +public class PluginGetEvent extends PluginEvent { + public String id = ""; + + public String backS = ""; + public Object backO = new Object(); + public String[] backAS = new String[0]; + public Object[] backAO = new Object[0]; +} diff --git a/src/main/java/de/tudbut/pluginapi/PluginLoadEvent.java b/src/main/java/de/tudbut/pluginapi/PluginLoadEvent.java new file mode 100644 index 0000000..fe7a0bb --- /dev/null +++ b/src/main/java/de/tudbut/pluginapi/PluginLoadEvent.java @@ -0,0 +1,4 @@ +package de.tudbut.pluginapi; + +public class PluginLoadEvent extends PluginEvent { +} diff --git a/src/main/java/de/tudbut/pluginapi/PluginManager.java b/src/main/java/de/tudbut/pluginapi/PluginManager.java new file mode 100644 index 0000000..f91ad71 --- /dev/null +++ b/src/main/java/de/tudbut/pluginapi/PluginManager.java @@ -0,0 +1,149 @@ +package de.tudbut.pluginapi; + +import de.tudbut.tools.BetterClassLoader; +import de.tudbut.type.FileFormatException; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; + +public class PluginManager { + + public static Plugin loadPlugin(String jarPath) + throws IOException { + String main = ""; + + JarFile file = new JarFile(new File(jarPath)); + + if(file.getEntry("main.tplv") != null) + main = new BufferedReader(new InputStreamReader(file.getInputStream(file.getEntry("main.tplv")))).readLine(); + + return loadPlugin(jarPath, main); + } + + public static Plugin loadPlugin(String jarPath, boolean autoLoad) + throws IOException { + String main = ""; + + JarFile file = new JarFile(new File(jarPath)); + + if(file.getEntry("main.tplv") != null) + main = new BufferedReader(new InputStreamReader(file.getInputStream(file.getEntry("main.tplv")))).readLine(); + + + return loadPlugin(jarPath, main, autoLoad); + } + + public static Plugin loadPlugin(String jarPath, String main) { + return loadPlugin(jarPath, main, true); + } + + public static Plugin loadPlugin(String jarPath, String main, boolean autoLoad) { + + System.out.println("Loading plugin " + jarPath); + Plugin.loadClass(); + URL[] urls; + JarFile file; + try { + file = new JarFile(jarPath); + } + catch (IOException e) { + System.out.println("Couldn't load plugin " + jarPath); + e.printStackTrace(); + return null; + } + try { + urls = new URL[]{new File(jarPath).toURI().toURL()}; + } catch (MalformedURLException e) { + System.out.println("Couldn't load plugin " + jarPath); + e.printStackTrace(); + return null; + } + BetterClassLoader cl = new BetterClassLoader(urls); + + Plugin r = null; + try { + if(!main.equals("")) { + Class lc = cl.lc(main); + if(Plugin.class.isAssignableFrom(lc.getSuperclass())) + r = (Plugin) lc.newInstance(); + else { + System.out.println(lc.getSuperclass().hashCode()); + System.out.println(Plugin.class.hashCode()); + } + } else { + ArrayList> classes = new ArrayList<>(); + Enumeration enumerator = file.entries(); + while (enumerator.hasMoreElements()) { + ZipEntry entry = enumerator.nextElement(); + if(entry.getName().endsWith(".class")) { + String s = entry.getName().split("\\.")[0].replaceAll("/", "."); + System.out.println("Loading class " + s); + Class clazz = cl.lc(s); + if(clazz != null) + classes.add(clazz); + } + } + + for (int i = 0; i < classes.size(); i++) { + if(classes.get(i).getSuperclass().getName().equals(Plugin.class.getName())) { + try { + r = classes.get(i).asSubclass(Plugin.class).newInstance(); + } catch (InstantiationException ignore) { } + } + } + } + if (r != null) { + r.setLogger(jarPath.split(File.separator)[jarPath.split(File.separator).length - 1]); + r.setCL(cl, jarPath.split(File.separator)[jarPath.split(File.separator).length - 1]); + if (autoLoad) { + r.onLoad(); + r.onEvent(new PluginLoadEvent()); + } + } + else { + System.out.println("Couldn't load plugin " + jarPath); + return null; + } + } catch (InstantiationException | IllegalAccessException | ClassNotFoundException | PluginException e) { + System.out.println("Couldn't load plugin " + jarPath); + e.printStackTrace(); + return null; + } + + + return r; + } + + public static Plugin[] loadPlugins(File dir) throws FileFormatException { + if (!dir.isDirectory()) { + throw new FileFormatException(); + } + + Plugin[] plugins = new Plugin[dir.listFiles(file -> file.getAbsolutePath().endsWith(".jar")).length]; + for (int i = 0; i < dir.listFiles(file -> file.getAbsolutePath().endsWith(".jar")).length; i++) { + try { + plugins[i] = loadPlugin(dir.listFiles(file -> file.getAbsolutePath().endsWith(".jar"))[i].getAbsolutePath()); + } catch (IOException ignore) { } + } + return plugins; + } + + public static void unloadPlugin(Plugin plugin) throws IOException { + plugin.onUnload(); + plugin.onEvent(new PluginUnloadEvent()); + plugin.getCL().close(); + } + + public static void unloadPlugins(Plugin[] plugins) throws IOException { + for (Plugin plugin : plugins) + unloadPlugin(plugin); + } +} diff --git a/src/main/java/de/tudbut/pluginapi/PluginUnloadEvent.java b/src/main/java/de/tudbut/pluginapi/PluginUnloadEvent.java new file mode 100644 index 0000000..573e37e --- /dev/null +++ b/src/main/java/de/tudbut/pluginapi/PluginUnloadEvent.java @@ -0,0 +1,4 @@ +package de.tudbut.pluginapi; + +public class PluginUnloadEvent extends PluginEvent { +} diff --git a/src/main/java/de/tudbut/rendering/GIFEncoder.java b/src/main/java/de/tudbut/rendering/GIFEncoder.java new file mode 100644 index 0000000..3bfd7b7 --- /dev/null +++ b/src/main/java/de/tudbut/rendering/GIFEncoder.java @@ -0,0 +1,72 @@ +package de.tudbut.rendering; + +import javax.imageio.*; +import javax.imageio.metadata.IIOInvalidTreeException; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataNode; +import javax.imageio.stream.ImageOutputStream; +import java.awt.image.RenderedImage; +import java.io.IOException; + +public class GIFEncoder { + + protected final ImageWriter writer; + protected final ImageWriteParam params; + protected final IIOMetadata metadata; + protected ImageOutputStream stream; + + public GIFEncoder(ImageOutputStream out, int imageType, int delay, boolean loop) throws IOException { + writer = ImageIO.getImageWritersBySuffix("gif").next(); + params = writer.getDefaultWriteParam(); + + ImageTypeSpecifier imageTypeSpecifier = ImageTypeSpecifier.createFromBufferedImageType(imageType); + metadata = writer.getDefaultImageMetadata(imageTypeSpecifier, params); + + configureRootMetadata(delay, loop); + + writer.setOutput(out); + writer.prepareWriteSequence(null); + } + + private void configureRootMetadata(int delay, boolean loop) throws IIOInvalidTreeException { + String metaFormatName = metadata.getNativeMetadataFormatName(); + IIOMetadataNode root = (IIOMetadataNode) metadata.getAsTree(metaFormatName); + + IIOMetadataNode meta = getNode(root, "GraphicControlExtension"); + meta.setAttribute("disposalMethod", "none"); + meta.setAttribute("userInputFlag", "FALSE"); + meta.setAttribute("transparentColorFlag", "FALSE"); + meta.setAttribute("delayTime", Integer.toString(delay / 10)); + meta.setAttribute("transparentColorIndex", "0"); + + meta = getNode(root, "ApplicationExtensions"); + + int loopContinuously = loop ? 0 : 1; + IIOMetadataNode child = new IIOMetadataNode("ApplicationExtension"); + child.setAttribute("applicationID", "TuddyLIB"); + child.setAttribute("authenticationCode", "1.0"); + child.setUserObject(new byte[]{ 0x1, (byte) (loopContinuously & 0xFF), 0}); + meta.appendChild(child); + metadata.setFromTree(metaFormatName, root); + } + + private static IIOMetadataNode getNode(IIOMetadataNode rootNode, String nodeName){ + int nNodes = rootNode.getLength(); + for (int i = 0; i < nNodes; i++){ + if (rootNode.item(i).getNodeName().equalsIgnoreCase(nodeName)){ + return (IIOMetadataNode) rootNode.item(i); + } + } + IIOMetadataNode node = new IIOMetadataNode(nodeName); + rootNode.appendChild(node); + return(node); + } + + public void addFrame(RenderedImage img) throws IOException { + writer.writeToSequence(new IIOImage(img, null, metadata), params); + } + + public void close() throws IOException { + writer.endWriteSequence(); + } +} diff --git a/src/main/java/de/tudbut/rendering/Graph.java b/src/main/java/de/tudbut/rendering/Graph.java new file mode 100644 index 0000000..42ca1ef --- /dev/null +++ b/src/main/java/de/tudbut/rendering/Graph.java @@ -0,0 +1,6 @@ +package de.tudbut.rendering; + +public interface Graph { + + double getAsFunction(double x); +} diff --git a/src/main/java/de/tudbut/rendering/GraphRenderer.java b/src/main/java/de/tudbut/rendering/GraphRenderer.java new file mode 100644 index 0000000..f77d10f --- /dev/null +++ b/src/main/java/de/tudbut/rendering/GraphRenderer.java @@ -0,0 +1,62 @@ +package de.tudbut.rendering; + +import java.awt.*; +import java.awt.image.BufferedImage; + +public class GraphRenderer { + + double scale = 1; + int offsetX = 0; + int offsetY = 0; + double scaleY; + + public GraphRenderer() { + + } + + public void setScaleX(double scale) { + if(scale <= 0) + throw new IllegalArgumentException(); + + this.scale = 1 / scale; + } + + public void setScaleY(double scale) { + if(scale <= 0) + throw new IllegalArgumentException(); + + this.scaleY = scale; + } + + public void setOffsetX(int offset) { + this.offsetX = offset; + } + + public void setOffsetY(int offset) { + this.offsetY = offset; + } + + public BufferedImage render(Graph graph, int pxX, int pxY, boolean markZero) { + BufferedImage image = new BufferedImage(pxX, pxY, BufferedImage.TYPE_INT_RGB); + + for (int y = 0; y < pxY; y++) { + for (int x = 0; x < pxX; x++) { + image.setRGB(x,y, 0xffffff); + } + } + + Graphics graphics = image.getGraphics().create(); + graphics.setColor(new Color(0x000000)); + + int lastPixelY = Maths2D.center(Maths2D.camera((int) Math.round(-graph.getAsFunction(-Maths2D.center(Maths2D.camera(0, offsetX), pxX) * scale) * scaleY), offsetY), pxY); + if(markZero) + graphics.drawRect(Maths2D.center(Maths2D.camera(0, offsetX), pxX) - 1, Maths2D.center(Maths2D.camera(0, offsetY), pxY) - 1, 2, 2); + for (int i = -Maths2D.center(Maths2D.camera(0, offsetX), pxX) ; i < Maths2D.center(Maths2D.camera(0, offsetX), pxX) * 2; i++) { + int y = Maths2D.center(Maths2D.camera((int) Math.round(-graph.getAsFunction(i * scale) * scaleY), offsetY), pxY); + graphics.drawLine(Maths2D.center(Maths2D.camera(i, offsetX), pxX) - 1, lastPixelY, Maths2D.center(Maths2D.camera(i, offsetX), pxX), y); + lastPixelY = y; + } + + return image; + } +} diff --git a/src/main/java/de/tudbut/rendering/Maths2D.java b/src/main/java/de/tudbut/rendering/Maths2D.java new file mode 100644 index 0000000..d484fd9 --- /dev/null +++ b/src/main/java/de/tudbut/rendering/Maths2D.java @@ -0,0 +1,218 @@ +package de.tudbut.rendering; + +import de.tudbut.type.Vector2d; +import de.tudbut.tools.NoiseGenerator; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.Random; + +public class Maths2D { + public static int camera(int i, int camera) { + return i - camera; + } + + public static int center(int i, int size) { + return i + size / 2; + } + + public static boolean[] getRelation(Rectangle2D measuringObject, Rectangle2D toMeasure) { + boolean[] b = new boolean[4]; + Vector2d a0 = toMeasure.getPos(); + Vector2d a1 = toMeasure.getEndPoint(); + Vector2d b0 = measuringObject.getPos(); + Vector2d b1 = measuringObject.getEndPoint(); + + b[0] = a0.getX() < b1.getX(); + b[1] = a0.getY() < b1.getY(); + b[2] = a1.getX() > b0.getX(); + b[3] = a1.getY() > b0.getY(); + + for (int i = 0; i < b.length; i++) { + b[i] = !b[i]; + } + + return b; + } + + public static BufferedImage createNoiseImage(BufferedImage base, int smoothness, Random random, int color) { + int x = base.getWidth(), y = base.getHeight(); + BufferedImage image = Maths2D.distortImage(base, x, y, 1); + //noinspection SuspiciousNameCombination + float[][] floats = NoiseGenerator.generateRandom(1, x, y, smoothness, 1, random)[0]; + + Graphics graphics = image.getGraphics(); + for (int i = 0 ; i < y ; i++) { + for (int j = 0 ; j < x ; j++) { + Color c = new Color(color, true); + c = new Color(c.getRed() * floats[j][i] / 255, c.getGreen() * floats[j][i] / 255, c.getBlue() * floats[j][i] / 255, 1); + graphics.setColor(c); + graphics.drawRect(j, i, 1, 1); + } + } + + return image; + } + + public static BufferedImage createMultiNoiseImage(BufferedImage base, int smoothness, Random random, int color) { + int x = base.getWidth(), y = base.getHeight(); + BufferedImage image = Maths2D.distortImage(base, x, y, 1); + //noinspection SuspiciousNameCombination + float[][][] floats = new float[][][] { + NoiseGenerator.generateRandom(1, x, y, smoothness, 1, random)[0], + NoiseGenerator.generateRandom(1, x, y, smoothness, 1, random)[0], + NoiseGenerator.generateRandom(1, x, y, smoothness, 1, random)[0], + NoiseGenerator.generateRandom(1, x, y, smoothness, 1, random)[0] + }; + + Graphics graphics = image.getGraphics(); + for (int i = 0 ; i < y ; i++) { + for (int j = 0 ; j < x ; j++) { + Color c = new Color(color, true); + c = new Color((int) (c.getRed() * floats[1][j][i]), (int) (c.getGreen() * floats[2][j][i]), (int) (c.getBlue() * floats[3][j][i]), (int) (c.getAlpha() * floats[0][j][i])); + graphics.setColor(c); + graphics.drawRect(j, i, 1, 1); + } + } + + return image; + } + + public static boolean collides(Rectangle2D rectangle0, Rectangle2D rectangle1) { + boolean b = true; + boolean[] rel = getRelation(rectangle0, rectangle1); + for (int i = 0; i < rel.length; i++) { + if (rel[i]) { + b = false; + break; + } + } + return b; + } + + public static int[][] getFillingCoordinatesForRectangle(RenderObject2D o) { + int[][] r = new int[2][4]; + + r[0][0] = (int) o.vectors[0].getX(); + r[0][1] = (int) o.vectors[1].getX(); + r[0][2] = (int) o.vectors[2].getX(); + r[0][3] = (int) o.vectors[3].getX(); + + r[1][0] = (int) o.vectors[0].getY(); + r[1][1] = (int) o.vectors[1].getY(); + r[1][2] = (int) o.vectors[2].getY(); + r[1][3] = (int) o.vectors[3].getY(); + + return r; + } + + public static int[][] getFillingCoordinatesForTriangle(RenderObject2D o) { + int[][] r = new int[2][3]; + + r[0][0] = (int) o.vectors[0].getX(); + r[0][1] = (int) o.vectors[1].getX(); + r[0][2] = (int) o.vectors[2].getX(); + + r[1][0] = (int) o.vectors[0].getY(); + r[1][1] = (int) o.vectors[1].getY(); + r[1][2] = (int) o.vectors[2].getY(); + + return r; + } + + public static BufferedImage distortImage(Image image, int newResolutionX, int newResolutionY, double multiplier) { + newResolutionX = (int) (newResolutionX * multiplier); + newResolutionY = (int) (newResolutionY * multiplier); + + BufferedImage r = new BufferedImage(newResolutionX, newResolutionY, BufferedImage.TYPE_INT_ARGB); + BufferedImage img = (BufferedImage) image; + + for (int x = 0; x < newResolutionX; x++) { + for (int y = 0; y < newResolutionY; y++) { + r.setRGB(x, y, img.getRGB((int) ((double) x / ((double) newResolutionX / (double) img.getWidth())), (int) ((double) y / ((double) newResolutionY / (double) img.getHeight())))); + } + } + + return r; + } + + public static BufferedImage prepareImage(BufferedImage image, Vector2d pos, Vector2d endPos) { + endPos = endPos.clone().add(pos.clone().negate()); + return distortImage(image, (int) endPos.getX(), (int) endPos.getY(), 1); + } + + public static BufferedImage rotateImage(Image image, double rot) { + rot = Math.PI * (rot / 360 * 2); + BufferedImage img = (BufferedImage) image; + BufferedImage r = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB); + Vector2d p = new Vector2d(img.getWidth() / 2d, img.getHeight() / 2d); + + for (int x = 0; x < img.getWidth(); x++) { + for (int y = 0; y < img.getHeight(); y++) { + Vector2d rotated = rotate(new Vector2d(x,y), p, rot); + try { + r.setRGB(x, y, img.getRGB((int) rotated.getX(), (int) rotated.getY())); + } catch (Exception ignore) { } + } + } + + return r; + } + + public static BufferedImage rotateImage(Image image, double rot, boolean clockwise) { + return clockwise ? rotateImage(image, -rot) : rotateImage(image, rot); + } + + public static BufferedImage cropImage(Image image, Vector2d start, Vector2d end) { + BufferedImage img = (BufferedImage) image; + BufferedImage r = new BufferedImage((int) (end.getY() - start.getY()), (int) (end.getY() - start.getY()), BufferedImage.TYPE_INT_ARGB); + + for (int x = 0; x < r.getWidth(); x++) { + for (int y = 0; y < r.getHeight(); y++) { + r.setRGB(x, y, img.getRGB((int) (x + start.getX()), (int) (y + start.getY()))); + } + } + + return r; + } + + public static BufferedImage addImageBorder(Image image, Vector2d posOnResult, Vector2d resultSize, int color) { + BufferedImage img = (BufferedImage) image; + BufferedImage r = new BufferedImage((int) resultSize.getX(), (int) resultSize.getY(), BufferedImage.TYPE_INT_ARGB); + Graphics g = r.getGraphics().create(); + + if(color >= 0 && color <= 0xffffff) + g.setColor(new Color(color, false)); + else + g.setColor(new Color(color, true)); + + g.fillRect(0,(int) posOnResult.getY(), (int) posOnResult.getX(), (int) resultSize.getY()); + + g.fillRect(0,0, (int) resultSize.getX(), (int) posOnResult.getY()); + + g.fillRect((int) posOnResult.getX() + img.getWidth(), (int) (posOnResult.getY()), (int) (resultSize.getX() - (posOnResult.getX() + img.getWidth())), (int) (resultSize.getY() - (resultSize.getY() - img.getHeight()))); + + g.fillRect((int) posOnResult.getX(), (int) posOnResult.getY() + img.getHeight(), (int) resultSize.getX(), (int) (resultSize.getY() - (posOnResult.getY() + img.getHeight()))); + + + g.drawImage(img, (int) posOnResult.getX(), (int) posOnResult.getY(), null); + + g.dispose(); + return r; + } + + public static Vector2d rotate(Vector2d vec, Vector2d point, double rotation) { + point.negate(); + vec.add(point); + double sinRot = Math.sin(rotation); + double cosRot = Math.cos(rotation); + vec.set( + vec.getX() * cosRot + vec.getY() * -sinRot, + vec.getX() * sinRot + vec.getY() * cosRot + ); + point.negate(); + vec.add(point); + return vec; + } +} + diff --git a/src/main/java/de/tudbut/rendering/Maths3D.java b/src/main/java/de/tudbut/rendering/Maths3D.java new file mode 100644 index 0000000..80f415a --- /dev/null +++ b/src/main/java/de/tudbut/rendering/Maths3D.java @@ -0,0 +1,113 @@ +package de.tudbut.rendering; + +import de.tudbut.tools.ExtendedMath; +import de.tudbut.type.Vector3d; + +public class Maths3D { + public static boolean[] getRelation(Rectangle3D measuringObject, Rectangle3D toMeasure) { + boolean[] b = new boolean[6]; + Vector3d a0 = toMeasure.getPos(); + Vector3d a1 = toMeasure.getEndPoint(); + Vector3d b0 = measuringObject.getPos(); + Vector3d b1 = measuringObject.getEndPoint(); + + b[0] = a0.getX() < b1.getX(); + b[1] = a0.getY() < b1.getY(); + b[2] = a0.getZ() < b1.getZ(); + b[3] = a1.getX() > b0.getX(); + b[4] = a1.getY() > b0.getY(); + b[5] = a1.getZ() > b0.getZ(); + + for (int i = 0; i < b.length; i++) { + b[i] = !b[i]; + } + + return b; + } + + public static boolean collides(Rectangle3D rectangle0, Rectangle3D rectangle1) { + boolean b = true; + boolean[] rel = getRelation(rectangle0, rectangle1); + for (int i = 0; i < rel.length; i++) { + if (rel[i]) { + b = false; + break; + } + } + return b; + } + + public static void prepareVectorsForRectangle(RenderObject3D o, int renderOutputX, int renderOutputY, double var0, double fovMod) { + Vector3d pos1 = o.vectors[0]; + Vector3d pos2 = o.vectors[1]; + Vector3d pos3 = o.vectors[2]; + Vector3d pos4 = o.vectors[3]; + + if ((1 / fovMod) / (ExtendedMath.min(pos1.getZ(), pos2.getZ(), pos3.getZ(), pos4.getZ()) / 15) <= 0) + o.isClipped = true; + + includeZDirection(pos1, fovMod); + includeZDirection(pos2, fovMod); + includeZDirection(pos3, fovMod); + includeZDirection(pos4, fovMod); + + for (int i = 0; i < o.vectors.length; i++) { + Vector3d vec = o.vectors[i]; + vec.multiply(var0); + vec.set(vec.getX() + (double) renderOutputX / 2, vec.getY() + (double) renderOutputY / 2, vec.getZ()); + } + } + + public static void prepareVectorsForTriangle(RenderObject3D o, int renderOutputX, int renderOutputY, double var0, double fovMod) { + Vector3d pos1 = o.vectors[0]; + Vector3d pos2 = o.vectors[1]; + Vector3d pos3 = o.vectors[2]; + + if ((1 / fovMod) / (ExtendedMath.min(pos1.getZ(), pos2.getZ(), pos3.getZ()) / 15) <= 0) + o.isClipped = true; + + includeZDirection(pos1, fovMod); + includeZDirection(pos2, fovMod); + includeZDirection(pos3, fovMod); + + for (int i = 0; i < o.vectors.length; i++) { + Vector3d vec = o.vectors[i]; + vec.multiply(var0); + vec.set(vec.getX() + (double) renderOutputX / 2, vec.getY() + (double) renderOutputY / 2, vec.getZ()); + } + } + + public static void includeZDirection(Vector3d vec, double fovMod) { + vec.multiply((1 / fovMod) / (vec.getZ() / 15)); + } + + public static int[][] getFillingCoordinatesForRectangle(RenderObject3D o) { + int[][] r = new int[2][4]; + + r[0][0] = (int) o.vectors[0].getX(); + r[0][1] = (int) o.vectors[1].getX(); + r[0][2] = (int) o.vectors[2].getX(); + r[0][3] = (int) o.vectors[3].getX(); + + r[1][0] = (int) o.vectors[0].getY(); + r[1][1] = (int) o.vectors[1].getY(); + r[1][2] = (int) o.vectors[2].getY(); + r[1][3] = (int) o.vectors[3].getY(); + + return r; + } + + public static int[][] getFillingCoordinatesForTriangle(RenderObject3D o) { + int[][] r = new int[2][3]; + + r[0][0] = (int) o.vectors[0].getX(); + r[0][1] = (int) o.vectors[1].getX(); + r[0][2] = (int) o.vectors[2].getX(); + + r[1][0] = (int) o.vectors[0].getY(); + r[1][1] = (int) o.vectors[1].getY(); + r[1][2] = (int) o.vectors[2].getY(); + + return r; + } +} diff --git a/src/main/java/de/tudbut/rendering/Projection2D.java b/src/main/java/de/tudbut/rendering/Projection2D.java new file mode 100644 index 0000000..0c5d45b --- /dev/null +++ b/src/main/java/de/tudbut/rendering/Projection2D.java @@ -0,0 +1,247 @@ +package de.tudbut.rendering; + +import de.tudbut.type.Vector2d; +import de.tudbut.ui.windowgui.AdaptedGraphics; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicInteger; + +public class Projection2D { + final AtomicInteger x; + final AtomicInteger y; + final RenderOutputType type; + final ArrayList objects = new ArrayList<>(); + final ArrayList imageObjects = new ArrayList<>(); + int fgColor = 0x000000; + final int bgColor; + double multiplier; + Vector2d offset = new Vector2d(0,0); + + public Projection2D(AtomicInteger xSize, AtomicInteger ySize, RenderOutputType type, int backgroundColor) { + x = xSize; + y = ySize; + this.type = type; + bgColor = backgroundColor; + multiplier = 1; + } + + public Projection2D(AtomicInteger xSize, AtomicInteger ySize, RenderOutputType type, int backgroundColor, double multiplier) { + x = xSize; + y = ySize; + this.type = type; + bgColor = backgroundColor; + this.multiplier = multiplier + 1; + } + + public void setMultiplier(double multiplier) { + if (multiplier == 0) + throw new IllegalArgumentException("Multiplier may not be 0"); + + this.multiplier = multiplier; + } + + public void setOffset(Vector2d vector) { + this.offset = vector; + } + + public void drawTriangle(Vector2d pos1, Vector2d pos2, Vector2d pos3) { + if (pos1 == null) + throw new IllegalArgumentException(); + if (pos2 == null) + throw new IllegalArgumentException(); + if (pos3 == null) + throw new IllegalArgumentException(); + objects.add(new RenderObject2D(pos1, pos2, pos3, false, multiplier, fgColor)); + } + + public void drawImage(Vector2d pos, Image image, int newResolutionX, int newResolutionY) { + if (pos == null) + throw new IllegalArgumentException(); + + imageObjects.add(new Object[]{pos, image, newResolutionX, newResolutionY, true}); + } + + public void drawImage(Vector2d pos, Image image, int newResolutionX, int newResolutionY, boolean applyMultiplier) { + if (pos == null) + throw new IllegalArgumentException(); + + imageObjects.add(new Object[]{pos, image, newResolutionX, newResolutionY, applyMultiplier}); + } + + public void drawRectangle(Vector2d pos1, Vector2d pos2, Vector2d pos3, Vector2d pos4) { + if (pos1 == null) + throw new IllegalArgumentException(); + if (pos2 == null) + throw new IllegalArgumentException(); + if (pos3 == null) + throw new IllegalArgumentException(); + if (pos4 == null) + throw new IllegalArgumentException(); + objects.add(new RenderObject2D(pos1, pos2, pos3, pos4, false, multiplier, fgColor)); + } + + public void fillTriangle(Vector2d pos1, Vector2d pos2, Vector2d pos3) { + if (pos1 == null) + throw new IllegalArgumentException(); + if (pos2 == null) + throw new IllegalArgumentException(); + if (pos3 == null) + throw new IllegalArgumentException(); + objects.add(new RenderObject2D(pos1, pos2, pos3, true, multiplier, fgColor)); + } + + public void fillRectangle(Vector2d pos1, Vector2d pos2, Vector2d pos3, Vector2d pos4) { + if (pos1 == null) + throw new IllegalArgumentException(); + if (pos2 == null) + throw new IllegalArgumentException(); + if (pos3 == null) + throw new IllegalArgumentException(); + if (pos4 == null) + throw new IllegalArgumentException(); + objects.add(new RenderObject2D(pos1, pos2, pos3, pos4, true, multiplier, fgColor)); + } + + public void draw(RenderObject2D object) { + if (object == null) + throw new IllegalArgumentException(); + objects.add(object); + } + + public void draw(BufferedImage image, Vector2d pos, Vector2d endPos) { + if (image == null) + throw new IllegalArgumentException(); + objects.add(new RenderObject2D(image,pos,endPos,multiplier)); + } + + public void draw(Vector2d[] vectors) { + if (vectors == null) + throw new IllegalArgumentException(); + if (vectors.length == 3) { + objects.add(new RenderObject2D(vectors[0], vectors[1], vectors[2], false, multiplier, fgColor)); + } + else if (vectors.length == 4) { + objects.add(new RenderObject2D(vectors[0], vectors[1], vectors[2], vectors[3], false, multiplier, fgColor)); + } + else + throw new IllegalArgumentException(); + } + + public void setColor(int hexColor) { + fgColor = hexColor; + } + + public Object render() { + BufferedImage image = new BufferedImage(x.get(), y.get(), BufferedImage.TYPE_INT_ARGB); + Graphics g = image.createGraphics(); + g.setColor(new Color(bgColor, true)); + g.fillRect(0, 0, x.get(), y.get()); + + render(g); + + g.dispose(); + + objects.clear(); + imageObjects.clear(); + + if (type == RenderOutputType.ARRAY2D) { + int[][] ints = new int[x.get()][y.get()]; + + for (int i = 0; i < x.get(); i++) { + for (int j = 0; j < y.get(); j++) { + ints[i][j] = image.getRGB(i, j); + } + } + + return ints; + } + if (type == RenderOutputType.ARRAY) { + int[] ints = new int[x.get() * y.get()]; + + int c = 0; + for (int i = 0; i < x.get(); i++) { + for (int j = 0; j < y.get(); j++) { + ints[c] = image.getRGB(i, j); + + c++; // YEET + } + } + + return ints; + } + if (type == RenderOutputType.BUFFEREDIMAGE) { + return image; + } + return null; + } + + void render(Graphics g) { + + for (int j = 0, objectsSize = objects.size(); j < objectsSize; j++) { + RenderObject2D o = objects.get(j); + o = o.clone(); + for (int i = 0; i < o.vectors.length; i++) { + o.vectors[i].add(offset); + } + g.setColor(new Color(o.color)); + + if (o.type == RenderObjectType.RECTANGLE) { + Vector2d pos1 = o.vectors[0]; + Vector2d pos2 = o.vectors[1]; + Vector2d pos3 = o.vectors[2]; + Vector2d pos4 = o.vectors[3]; + + g.drawLine((int) pos1.getX(), (int) pos1.getY(), (int) pos2.getX(), (int) pos2.getY()); + g.drawLine((int) pos2.getX(), (int) pos2.getY(), (int) pos3.getX(), (int) pos3.getY()); + g.drawLine((int) pos3.getX(), (int) pos3.getY(), (int) pos4.getX(), (int) pos4.getY()); + g.drawLine((int) pos4.getX(), (int) pos4.getY(), (int) pos1.getX(), (int) pos1.getY()); + } + + if (o.type == RenderObjectType.TRIANGLE) { + Vector2d pos1 = o.vectors[0]; + Vector2d pos2 = o.vectors[1]; + Vector2d pos3 = o.vectors[2]; + + g.drawLine((int) pos1.getX(), (int) pos1.getY(), (int) pos2.getX(), (int) pos2.getY()); + g.drawLine((int) pos2.getX(), (int) pos2.getY(), (int) pos3.getX(), (int) pos3.getY()); + g.drawLine((int) pos3.getX(), (int) pos3.getY(), (int) pos1.getX(), (int) pos1.getY()); + } + + if (o.type == RenderObjectType.FULL_RECTANGLE) { + int[][] rpos = Maths2D.getFillingCoordinatesForRectangle(o); + + g.fillPolygon(rpos[0], rpos[1], 4); + } + + if (o.type == RenderObjectType.FULL_TRIANGLE) { + int[][] rpos = Maths2D.getFillingCoordinatesForTriangle(o); + + g.fillPolygon(rpos[0], rpos[1], 3); + } + + if (o.type == RenderObjectType.IMAGE) { + g.drawImage(Maths2D.prepareImage( + o.image, + o.vectors[0], + o.vectors[1] + ), (int) o.vectors[0].getX(), (int) o.vectors[0].getY(), null); + } + } + + for (Object[] object : imageObjects) { + Vector2d pos = (Vector2d) object[0]; + Image image = (Image) object[1]; + int nx = (Integer) object[2]; + int ny = (Integer) object[3]; + boolean applyMultiplier = (Boolean) object[4]; + pos.add(offset); + + + new AdaptedGraphics(g).drawImage((int) (pos.getX() * (applyMultiplier ? multiplier : 1)), (int) (pos.getY() * (applyMultiplier ? multiplier : 1)), Maths2D.distortImage(image, nx, ny, applyMultiplier ? multiplier : 1)); + } + } + + +} diff --git a/src/main/java/de/tudbut/rendering/Projection3D.java b/src/main/java/de/tudbut/rendering/Projection3D.java new file mode 100644 index 0000000..5bcd643 --- /dev/null +++ b/src/main/java/de/tudbut/rendering/Projection3D.java @@ -0,0 +1,256 @@ +package de.tudbut.rendering; + +import de.tudbut.type.Vector3d; +import de.tudbut.parsing.TudSort; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicInteger; + +public class Projection3D { + public final Vector3d offset = new Vector3d(0, 0, 0); + final AtomicInteger x; + final AtomicInteger y; + final RenderOutputType type; + final ArrayList objects = new ArrayList<>(); + int fgColor = 0x000000; + final int bgColor; + final double var0 = 20; + int zClip = -1; + double fovMod = 1; + + public Projection3D(AtomicInteger xSize, AtomicInteger ySize, RenderOutputType type, int backgroundColor) { + x = xSize; + y = ySize; + this.type = type; + bgColor = backgroundColor; + } + + public Projection3D(AtomicInteger xSize, AtomicInteger ySize, RenderOutputType type, int backgroundColor, int zClip) { + x = xSize; + y = ySize; + this.type = type; + bgColor = backgroundColor; + this.zClip = zClip; + } + + public void setFOVMod(double fovMod) { + this.fovMod = fovMod; + } + + public void drawTriangle(Vector3d pos1, Vector3d pos2, Vector3d pos3) { + if (pos1 == null) + throw new IllegalArgumentException(); + if (pos2 == null) + throw new IllegalArgumentException(); + if (pos3 == null) + throw new IllegalArgumentException(); + objects.add(new RenderObject3D(pos1, pos2, pos3, false, var0, fgColor)); + } + + public void drawRectangle(Vector3d pos1, Vector3d pos2, Vector3d pos3, Vector3d pos4) { + if (pos1 == null) + throw new IllegalArgumentException(); + if (pos2 == null) + throw new IllegalArgumentException(); + if (pos3 == null) + throw new IllegalArgumentException(); + if (pos4 == null) + throw new IllegalArgumentException(); + objects.add(new RenderObject3D(pos1, pos2, pos3, pos4, false, var0, fgColor)); + } + + public void fillTriangle(Vector3d pos1, Vector3d pos2, Vector3d pos3) { + if (pos1 == null) + throw new IllegalArgumentException(); + if (pos2 == null) + throw new IllegalArgumentException(); + if (pos3 == null) + throw new IllegalArgumentException(); + objects.add(new RenderObject3D(pos1, pos2, pos3, true, var0, fgColor)); + } + + public void fillRectangle(Vector3d pos1, Vector3d pos2, Vector3d pos3, Vector3d pos4) { + if (pos1 == null) + throw new IllegalArgumentException(); + if (pos2 == null) + throw new IllegalArgumentException(); + if (pos3 == null) + throw new IllegalArgumentException(); + if (pos4 == null) + throw new IllegalArgumentException(); + objects.add(new RenderObject3D(pos1, pos2, pos3, pos4, true, var0, fgColor)); + } + + public void draw(RenderObject3D object) { + if (object == null) + throw new IllegalArgumentException(); + objects.add(object); + } + + public void draw(Vector3d[] vectors) { + if (vectors == null) + throw new IllegalArgumentException(); + if (vectors.length == 3) { + objects.add(new RenderObject3D(vectors[0], vectors[1], vectors[2], false, var0, fgColor)); + } + else if (vectors.length == 4) { + objects.add(new RenderObject3D(vectors[0], vectors[1], vectors[2], vectors[3], false, var0, fgColor)); + } + else + throw new IllegalArgumentException(); + } + + public void setColor(int hexColor) { + fgColor = hexColor; + } + + public Object render() { + BufferedImage image = new BufferedImage(x.get(), y.get(), BufferedImage.TYPE_INT_RGB); + Graphics g = image.createGraphics(); + g.setColor(new Color(bgColor)); + g.fillRect(0, 0, x.get(), y.get()); + + render(g); + + g.dispose(); + + objects.clear(); + + if (type == RenderOutputType.ARRAY2D) { + int[][] ints = new int[x.get()][y.get()]; + + for (int i = 0; i < x.get(); i++) { + for (int j = 0; j < y.get(); j++) { + ints[i][j] = image.getRGB(i, j); + } + } + + return ints; + } + if (type == RenderOutputType.ARRAY) { + int[] ints = new int[x.get() * y.get()]; + + int c = 0; + for (int i = 0; i < x.get(); i++) { + for (int j = 0; j < y.get(); j++) { + ints[c] = image.getRGB(i, j); + + c++; // YEET + } + } + + return ints; + } + if (type == RenderOutputType.BUFFEREDIMAGE) { + return image; + } + return null; + } + + void render(Graphics g) { + + RenderObject3D[] objects = this.objects.toArray(new RenderObject3D[0]); + + try { + objects = TudSort.sortDouble(objects, t -> { + double i = 0; + int j = 0; + + for (Vector3d vector : t.vectors) { + i += Math.max(vector.getX(), -vector.getX()); + j++; + } + + for (Vector3d vector : t.vectors) { + i += Math.max(vector.getY(), -vector.getY()); + j++; + } + + for (Vector3d vector : t.vectors) { + i += vector.getZ() * 5; + j++; + } + + i = i / j; + + return -i; + }); + } catch (Throwable ignored) { } + + for (int i = 0, objectsLength = objects.length; i < objectsLength; i++) { + RenderObject3D o = objects[i]; + if (o == null) + continue; + o = o.clone(); + for (Vector3d vector : o.vectors) { + vector.add(offset); + } + if (o.color >= 0 && o.color <= 0xffffff) { + g.setColor(new Color(o.color, false)); + } else + g.setColor(new Color(o.color, true)); + + if (o.type == RenderObjectType.RECTANGLE) { + Maths3D.prepareVectorsForRectangle(o, x.get(), y.get(), var0, fovMod); + + if (o.isClipped) { + continue; + } + + Vector3d pos1 = o.vectors[0]; + Vector3d pos2 = o.vectors[1]; + Vector3d pos3 = o.vectors[2]; + Vector3d pos4 = o.vectors[3]; + + g.drawLine((int) (pos1.getX()), (int) (pos1.getY()), (int) (pos2.getX()), (int) (pos2.getY())); + g.drawLine((int) (pos2.getX()), (int) (pos2.getY()), (int) (pos3.getX()), (int) (pos3.getY())); + g.drawLine((int) (pos3.getX()), (int) (pos3.getY()), (int) (pos4.getX()), (int) (pos4.getY())); + g.drawLine((int) (pos4.getX()), (int) (pos4.getY()), (int) (pos1.getX()), (int) (pos1.getY())); + } + + if (o.type == RenderObjectType.TRIANGLE) { + Maths3D.prepareVectorsForTriangle(o, x.get(), y.get(), var0, fovMod); + + if (o.isClipped) { + continue; + } + + Vector3d pos1 = o.vectors[0]; + Vector3d pos2 = o.vectors[1]; + Vector3d pos3 = o.vectors[2]; + + g.drawLine((int) (pos1.getX()), (int) (pos1.getY()), (int) (pos2.getX()), (int) (pos2.getY())); + g.drawLine((int) (pos2.getX()), (int) (pos2.getY()), (int) (pos3.getX()), (int) (pos3.getY())); + g.drawLine((int) (pos3.getX()), (int) (pos3.getY()), (int) (pos1.getX()), (int) (pos1.getY())); + } + + if (o.type == RenderObjectType.FULL_RECTANGLE) { + Maths3D.prepareVectorsForRectangle(o, x.get(), y.get(), var0, fovMod); + + if (o.isClipped) { + continue; + } + + int[][] rpos = Maths3D.getFillingCoordinatesForRectangle(o); + + g.fillPolygon(rpos[0], rpos[1], 4); + } + + if (o.type == RenderObjectType.FULL_TRIANGLE) { + Maths3D.prepareVectorsForTriangle(o, x.get(), y.get(), var0, fovMod); + + if (o.isClipped) { + continue; + } + + int[][] rpos = Maths3D.getFillingCoordinatesForTriangle(o); + + g.fillPolygon(rpos[0], rpos[1], 3); + } + } + } + + +} diff --git a/src/main/java/de/tudbut/rendering/Rectangle2D.java b/src/main/java/de/tudbut/rendering/Rectangle2D.java new file mode 100644 index 0000000..eb1087b --- /dev/null +++ b/src/main/java/de/tudbut/rendering/Rectangle2D.java @@ -0,0 +1,30 @@ +package de.tudbut.rendering; + +import de.tudbut.type.Vector2d; +import de.tudbut.obj.RelativeVector2d; + +public class Rectangle2D { + Vector2d pos; + RelativeVector2d end; + + public Rectangle2D(Vector2d pos, Vector2d size) { + construct(pos, new RelativeVector2d(pos, size)); + } + + private void construct(Vector2d pos, RelativeVector2d size) { + this.pos = pos.clone(); + this.end = size.clone(); + } + + public Vector2d getPos() { + return pos.clone(); + } + + public Vector2d getSize() { + return end.getRelativePos().clone(); + } + + public RelativeVector2d getEndPoint() { + return end.clone(); + } +} diff --git a/src/main/java/de/tudbut/rendering/Rectangle3D.java b/src/main/java/de/tudbut/rendering/Rectangle3D.java new file mode 100644 index 0000000..07cdfc4 --- /dev/null +++ b/src/main/java/de/tudbut/rendering/Rectangle3D.java @@ -0,0 +1,44 @@ +package de.tudbut.rendering; + +import de.tudbut.type.Vector3d; + +public class Rectangle3D { + Vector3d pos; + Vector3d size; + + public Rectangle3D(Vector3d pos, Vector3d size) { + construct(pos, size); + } + + private void construct(Vector3d pos, Vector3d size) { + this.pos = pos.clone(); + this.size = size.clone(); + } + + public void sort() { + if(size.getX() <= 0) { + pos.setX(pos.getX() - size.getX()); + size.setX(-size.getX()); + } + if(size.getY() <= 0) { + pos.setY(pos.getY() - size.getY()); + size.setY(-size.getY()); + } + if(size.getZ() <= 0) { + pos.setZ(pos.getZ() - size.getZ()); + size.setZ(-size.getZ()); + } + } + + public Vector3d getPos() { + return pos; + } + + public Vector3d getSize() { + return size; + } + + public Vector3d getEndPoint() { + return pos.clone().add(size); + } +} diff --git a/src/main/java/de/tudbut/rendering/RenderAssistant.java b/src/main/java/de/tudbut/rendering/RenderAssistant.java new file mode 100644 index 0000000..0e7ecdb --- /dev/null +++ b/src/main/java/de/tudbut/rendering/RenderAssistant.java @@ -0,0 +1,183 @@ +package de.tudbut.rendering; + +import de.tudbut.type.Vector2d; +import de.tudbut.type.Vector3d; + +import java.awt.*; + +public class RenderAssistant { + @Deprecated + public static void renderToGraphics(Graphics g, Projection3D projection) { + if (g == null) + throw new IllegalArgumentException(); + + projection.render(g); + } + + public static void renderToGraphics(Graphics g, Projection2D projection) { + if (g == null) + throw new IllegalArgumentException(); + + projection.render(g); + } + + @Deprecated + public static void drawRectangle(Projection3D projection, double[] doubles) { + if (doubles.length != 4 * 3) + throw new IllegalArgumentException("Length must be 4 * 3!"); + if (projection == null) + throw new IllegalArgumentException("NULL"); + + projection.drawRectangle( + new Vector3d(doubles[0], doubles[1], doubles[2]), + new Vector3d(doubles[3], doubles[4], doubles[5]), + new Vector3d(doubles[6], doubles[7], doubles[8]), + new Vector3d(doubles[9], doubles[10], doubles[11]) + ); + } + + @Deprecated + public static void fillRectangle(Projection3D projection, double[] doubles) { + if (doubles.length != 4 * 3) + throw new IllegalArgumentException("Length must be 4 * 3!"); + if (projection == null) + throw new IllegalArgumentException("NULL"); + + projection.fillRectangle( + new Vector3d(doubles[0], doubles[1], doubles[2]), + new Vector3d(doubles[3], doubles[4], doubles[5]), + new Vector3d(doubles[6], doubles[7], doubles[8]), + new Vector3d(doubles[9], doubles[10], doubles[11]) + ); + } + + public static void drawRectangle(Projection2D projection, double[] doubles) { + if (doubles.length != 4 * 2) + throw new IllegalArgumentException("Length must be 4 * 2!"); + if (projection == null) + throw new IllegalArgumentException("NULL"); + + projection.drawRectangle( + new Vector2d(doubles[0], doubles[1]), + new Vector2d(doubles[2], doubles[3]), + new Vector2d(doubles[4], doubles[5]), + new Vector2d(doubles[6], doubles[7]) + ); + } + + public static void fillRectangle(Projection2D projection, double[] doubles) { + if (doubles.length != 4 * 2) + throw new IllegalArgumentException("Length must be 4 * 2!"); + if (projection == null) + throw new IllegalArgumentException("NULL"); + + projection.fillRectangle( + new Vector2d(doubles[0], doubles[1]), + new Vector2d(doubles[2], doubles[3]), + new Vector2d(doubles[4], doubles[5]), + new Vector2d(doubles[6], doubles[7]) + ); + } + + public static void drawTriangle(Projection3D projection, double[] doubles) { + if (doubles.length != 3 * 3) + throw new IllegalArgumentException("Length must be 3 * 3!"); + if (projection == null) + throw new IllegalArgumentException("NULL"); + + projection.drawTriangle( + new Vector3d(doubles[0], doubles[1], doubles[2]), + new Vector3d(doubles[3], doubles[4], doubles[5]), + new Vector3d(doubles[6], doubles[7], doubles[8]) + ); + } + + public static void fillTriangle(Projection3D projection, double[] doubles) { + if (doubles.length != 3 * 3) + throw new IllegalArgumentException("Length must be 3 * 3!"); + if (projection == null) + throw new IllegalArgumentException("NULL"); + + projection.fillTriangle( + new Vector3d(doubles[0], doubles[1], doubles[2]), + new Vector3d(doubles[3], doubles[4], doubles[5]), + new Vector3d(doubles[6], doubles[7], doubles[8]) + ); + } + + public static void drawTriangle(Projection2D projection, double[] doubles) { + if (doubles.length != 3 * 2) + throw new IllegalArgumentException("Length must be 3 * 2!"); + if (projection == null) + throw new IllegalArgumentException("NULL"); + + projection.drawTriangle( + new Vector2d(doubles[0], doubles[1]), + new Vector2d(doubles[2], doubles[3]), + new Vector2d(doubles[4], doubles[5]) + ); + } + + public static void fillTriangle(Projection2D projection, double[] doubles) { + if (doubles.length != 3 * 2) + throw new IllegalArgumentException("Length must be 3 * 2!"); + if (projection == null) + throw new IllegalArgumentException("NULL"); + + projection.fillTriangle( + new Vector2d(doubles[0], doubles[1]), + new Vector2d(doubles[2], doubles[3]), + new Vector2d(doubles[4], doubles[5]) + ); + } + + public static double[] addOffset(double[] doubles, double xOffset, double yOffset, double zOffset) { + byte currentCoord = 0; + + for (int i = 0; i < doubles.length; i++) { + if (currentCoord == 0) + doubles[i] = doubles[i] + xOffset; + if (currentCoord == 1) + doubles[i] = doubles[i] + yOffset; + if (currentCoord == 2) { + doubles[i] = doubles[i] + zOffset; + currentCoord = -1; + } + currentCoord++; + } + + return doubles; + } + + public static double[] addOffset(double[] doubles, double xOffset, double yOffset) { + byte currentCoord = 0; + + for (int i = 0; i < doubles.length; i++) { + if (currentCoord == 0) + doubles[i] = doubles[i] + xOffset; + if (currentCoord == 1) { + doubles[i] = doubles[i] + yOffset; + currentCoord = -1; + } + currentCoord++; + } + + return doubles; + } + + public static Vector3d[] addOffset(Vector3d[] vectors, double xOffset, double yOffset, double zOffset) { + for (Vector3d vector : vectors) { + vector.set(vector.getX() + xOffset, vector.getY() + yOffset, vector.getZ() + zOffset); + } + + return vectors; + } + + public static Vector2d[] addOffset(Vector2d[] vectors, double xOffset, double yOffset) { + for (Vector2d vector : vectors) { + vector.set(vector.getX() + xOffset, vector.getY() + yOffset); + } + + return vectors; + } +} diff --git a/src/main/java/de/tudbut/rendering/RenderObject2D.java b/src/main/java/de/tudbut/rendering/RenderObject2D.java new file mode 100644 index 0000000..c20f4ad --- /dev/null +++ b/src/main/java/de/tudbut/rendering/RenderObject2D.java @@ -0,0 +1,67 @@ +package de.tudbut.rendering; + +import de.tudbut.tools.Tools; +import de.tudbut.type.Vector2d; + +import java.awt.image.BufferedImage; + +public class RenderObject2D { + private final double multiplier; + final Vector2d[] vectors; + final RenderObjectType type; + int color; + BufferedImage image; + + public RenderObject2D(Vector2d pos1, Vector2d pos2, Vector2d pos3, boolean fill, double multiplier, int color) { + type = fill ? RenderObjectType.FULL_TRIANGLE : RenderObjectType.TRIANGLE; + vectors = new Vector2d[]{pos1, pos2, pos3}; + this.multiplier = multiplier; + this.color = color; + + updateMultiplier(); + } + + public RenderObject2D(Vector2d pos1, Vector2d pos2, Vector2d pos3, Vector2d pos4, boolean fill, double multiplier, int color) { + type = fill ? RenderObjectType.FULL_RECTANGLE : RenderObjectType.RECTANGLE; + vectors = new Vector2d[]{pos1, pos2, pos3, pos4}; + this.multiplier = multiplier; + this.color = color; + + updateMultiplier(); + } + + public RenderObject2D(BufferedImage image, Vector2d pos, Vector2d size, double multiplier) { + type = RenderObjectType.IMAGE; + vectors = new Vector2d[]{pos, size}; + this.multiplier = multiplier; + this.image = image; + + updateMultiplier(); + } + + private RenderObject2D(Vector2d[] vectors, RenderObjectType type, double multiplier, int color, BufferedImage image) { + this.vectors = vectors; + this.type = type; + this.multiplier = multiplier; + this.color = color; + this.image = image; + } + + void updateMultiplier() { + for (Vector2d vec : vectors) { + vec.multiply(multiplier); + } + } + + @Override + public RenderObject2D clone() { + Vector2d[] lvectors = new Vector2d[vectors.length]; + Tools.copyArray(vectors, lvectors, vectors.length); + + for (int i = 0; i < lvectors.length; i++) { + lvectors[i] = lvectors[i].clone(); + } + + return new RenderObject2D(lvectors, type, multiplier, color, image); + } +} diff --git a/src/main/java/de/tudbut/rendering/RenderObject3D.java b/src/main/java/de/tudbut/rendering/RenderObject3D.java new file mode 100644 index 0000000..6f2145c --- /dev/null +++ b/src/main/java/de/tudbut/rendering/RenderObject3D.java @@ -0,0 +1,54 @@ +package de.tudbut.rendering; + +import de.tudbut.tools.Tools; +import de.tudbut.type.Vector3d; + +public class RenderObject3D { + final double multiplier; + final Vector3d[] vectors; + final RenderObjectType type; + final int color; + boolean isClipped; + + public RenderObject3D(Vector3d pos1, Vector3d pos2, Vector3d pos3, boolean fill, double multiplier, int color) { + type = fill ? RenderObjectType.FULL_TRIANGLE : RenderObjectType.TRIANGLE; + vectors = new Vector3d[]{pos1, pos2, pos3}; + this.multiplier = multiplier; + this.color = color; + + updateMultiplier(); + } + + public RenderObject3D(Vector3d pos1, Vector3d pos2, Vector3d pos3, Vector3d pos4, boolean fill, double multiplier, int color) { + type = fill ? RenderObjectType.FULL_RECTANGLE : RenderObjectType.RECTANGLE; + vectors = new Vector3d[]{pos1, pos2, pos3, pos4}; + this.multiplier = multiplier; + this.color = color; + + updateMultiplier(); + } + + private RenderObject3D(Vector3d[] vectors, RenderObjectType type, double multiplier, int color) { + this.vectors = vectors; + this.type = type; + this.multiplier = multiplier; + this.color = color; + } + + @Deprecated + private void updateMultiplier() { + + } + + @Override + public RenderObject3D clone() { + Vector3d[] lvectors = new Vector3d[vectors.length]; + Tools.copyArray(vectors, lvectors, vectors.length); + + for (int i = 0; i < lvectors.length; i++) { + lvectors[i] = lvectors[i].clone(); + } + + return new RenderObject3D(lvectors, type, multiplier, color); + } +} diff --git a/src/main/java/de/tudbut/rendering/RenderObjectType.java b/src/main/java/de/tudbut/rendering/RenderObjectType.java new file mode 100644 index 0000000..3d1801b --- /dev/null +++ b/src/main/java/de/tudbut/rendering/RenderObjectType.java @@ -0,0 +1,9 @@ +package de.tudbut.rendering; + +public enum RenderObjectType { + TRIANGLE, + RECTANGLE, + FULL_TRIANGLE, + FULL_RECTANGLE, + IMAGE +} diff --git a/src/main/java/de/tudbut/rendering/RenderOutputType.java b/src/main/java/de/tudbut/rendering/RenderOutputType.java new file mode 100644 index 0000000..fe59744 --- /dev/null +++ b/src/main/java/de/tudbut/rendering/RenderOutputType.java @@ -0,0 +1,7 @@ +package de.tudbut.rendering; + +public enum RenderOutputType { + BUFFEREDIMAGE, + ARRAY2D, + ARRAY +} diff --git a/src/main/java/de/tudbut/rendering/tph/TPH300.java b/src/main/java/de/tudbut/rendering/tph/TPH300.java new file mode 100644 index 0000000..cd3c311 --- /dev/null +++ b/src/main/java/de/tudbut/rendering/tph/TPH300.java @@ -0,0 +1,63 @@ +package de.tudbut.rendering.tph; + +import de.tudbut.type.FInfo; +import de.tudbut.type.Vector3d; + +@FInfo(s = "TuddyProjection Helper 3D 00: Rotation") +public class TPH300 { + public static final int ID = 302; + public static final int X = ID + 1; + public static final int Y = ID + 2; + public static final int Z = ID + 3; + + public static Vector3d applyRotation(Vector3d vector, Vector3d rotPoint, int type, double angle) { + angle = (angle / 360) * Math.PI * 2; + type = type - ID; + switch (type) { + case 1: + Vector3d p1 = vector.clone(); + Vector3d c1 = rotPoint.clone(); + c1.negate(); + p1.add(c1); + p1.set( + p1.getX(), + p1.getY() * Math.cos(angle) + p1.getZ() * -Math.sin(angle), + p1.getY() * Math.sin(angle) + p1.getZ() * Math.cos(angle) + ); + c1.negate(); + p1.add(c1); + vector.set(p1); + return vector; + case 2: + Vector3d p2 = vector.clone(); + Vector3d c2 = rotPoint.clone(); + c2.negate(); + p2.add(c2); + p2.set( + p2.getX() * Math.cos(angle) + p2.getZ() * Math.sin(angle), + p2.getY(), + p2.getX() * -Math.sin(angle) + p2.getZ() * Math.cos(angle) + ); + c2.negate(); + p2.add(c2); + vector.set(p2); + return vector; + case 3: + Vector3d p3 = vector.clone(); + Vector3d c3 = rotPoint.clone(); + c3.negate(); + p3.add(c3); + p3.set( + p3.getX() * Math.cos(angle) + p3.getY() * -Math.sin(angle), + p3.getX() * Math.sin(angle) + p3.getY() * Math.cos(angle), + p3.getZ() + ); + c3.negate(); + p3.add(c3); + vector.set(p3); + return vector; + + } + return null; + } +} diff --git a/src/main/java/de/tudbut/rendering/tph/TPH301.java b/src/main/java/de/tudbut/rendering/tph/TPH301.java new file mode 100644 index 0000000..573366f --- /dev/null +++ b/src/main/java/de/tudbut/rendering/tph/TPH301.java @@ -0,0 +1,96 @@ +package de.tudbut.rendering.tph; + +import de.tudbut.type.FInfo; +import de.tudbut.type.Vector2d; +import de.tudbut.type.Vector3d; +import de.tudbut.io.DoubleBuffer; + +import static de.tudbut.tools.BufferManager.createResource; +import static de.tudbut.tools.BufferManager.getBufferFromID; +import static de.tudbut.rendering.tph.TPH300.*; + +@FInfo(s = "TuddyProjection Helper 3D 01: Cameras") +public class TPH301 { + public static final int ID = 301; + + public static long createCamera(Vector3d position, int rotX, int rotY) { + DoubleBuffer res = DoubleBuffer.create(6 * 8); + double[] doubles = (double[]) res.get(); + doubles[0] = position.getX(); + doubles[1] = position.getY(); + doubles[2] = position.getZ(); + doubles[3] = rotX; + doubles[4] = rotY; + return createResource(res); + } + + public static long createCamera(Vector3d position, int rotX, int rotY, int rotZ) { + DoubleBuffer res = DoubleBuffer.create(6 * 8); + double[] doubles = (double[]) res.get(); + doubles[0] = position.getX(); + doubles[1] = position.getY(); + doubles[2] = position.getZ(); + doubles[3] = rotX; + doubles[4] = rotY; + doubles[5] = rotZ; + return createResource(res); + } + + public static long createCamera(Vector3d position, Vector2d rot) { + DoubleBuffer res = DoubleBuffer.create(6 * 8); + double[] doubles = (double[]) res.get(); + doubles[0] = position.getX(); + doubles[1] = position.getY(); + doubles[2] = position.getZ(); + doubles[3] = rot.getX(); + doubles[4] = rot.getY(); + return createResource(res); + } + + public static long createCamera(Vector3d position, Vector3d rot) { + DoubleBuffer res = DoubleBuffer.create(6 * 8); + double[] doubles = (double[]) res.get(); + doubles[0] = position.getX(); + doubles[1] = position.getY(); + doubles[2] = position.getZ(); + doubles[3] = rot.getX(); + doubles[4] = rot.getY(); + doubles[5] = rot.getZ(); + return createResource(res); + } + + public static void setCameraPosition(Vector3d pos, long cam) { + double[] d = (double[]) getBufferFromID(cam).get(); + d[0] = pos.getX(); + d[1] = pos.getY(); + d[2] = pos.getZ(); + } + + public static void setCameraRotation(Vector2d rot, long cam) { + double[] d = (double[]) getBufferFromID(cam).get(); + d[3] = rot.getX(); + d[4] = rot.getY(); + } + + public static void setCameraRotation(Vector3d rot, long cam) { + double[] d = (double[]) getBufferFromID(cam).get(); + d[3] = rot.getX(); + d[4] = rot.getY(); + d[5] = rot.getZ(); + } + + public static Vector3d translate(Vector3d vec, long camera) { + DoubleBuffer cam = (DoubleBuffer) getBufferFromID(camera); + double[] d = (double[]) cam.get(); + Vector3d pos = new Vector3d(-d[0], d[1], d[2]); + double rotX = d[3]; + double rotY = d[4]; + double rotZ = d[5]; + + applyRotation(vec, pos.clone().negate(), Y, -rotX); + applyRotation(vec, pos.clone().negate(), X, rotY); + applyRotation(vec, pos.clone().negate(), Z, rotZ); + vec.add(pos); + return vec; + } +} diff --git a/src/main/java/de/tudbut/rendering/tph/TPH302.java b/src/main/java/de/tudbut/rendering/tph/TPH302.java new file mode 100644 index 0000000..4a8ed0e --- /dev/null +++ b/src/main/java/de/tudbut/rendering/tph/TPH302.java @@ -0,0 +1,37 @@ +package de.tudbut.rendering.tph; + +import de.tudbut.rendering.Projection3D; +import de.tudbut.type.Vector2d; +import de.tudbut.type.FInfo; +import de.tudbut.type.Vector3d; + +import java.awt.*; +import java.awt.image.BufferedImage; + +import static de.tudbut.rendering.tph.TPH301.*; +import static de.tudbut.rendering.tph.TPH300.*; + +@FInfo(s = "TuddyProjection Helper 3D 02: Textures") +public class TPH302 { + public static final int ID = 303; + + public static void renderTexturedRectangle(Vector3d offset, Vector2d size, Vector3d rot, Vector3d rotPoint, long camera, Projection3D projection3D, Image image) { + BufferedImage img = (BufferedImage) image; + + for (int y = 0; y < img.getHeight(); y++) { + for (int x = 0; x < img.getWidth(); x++) { + projection3D.setColor(img.getRGB(x,y)); + projection3D.fillRectangle( + translate(applyRotations(new Vector3d(offset.getX() + (size.getX() / img.getWidth()) * x, offset.getY() + (size.getY() / img.getHeight()) * y, offset.getZ()), rot, rotPoint), camera), + translate(applyRotations(new Vector3d(offset.getX() + (size.getX() / img.getWidth()) * (x + 1), offset.getY() + (size.getY() / img.getHeight()) * (y), offset.getZ()), rot, rotPoint), camera), + translate(applyRotations(new Vector3d(offset.getX() + (size.getX() / img.getWidth()) * (x + 1), offset.getY() + (size.getY() / img.getHeight()) * (y + 1), offset.getZ()), rot, rotPoint), camera), + translate(applyRotations(new Vector3d(offset.getX() + (size.getX() / img.getWidth()) * (x), offset.getY() + (size.getY() / img.getHeight()) * (y + 1), offset.getZ()), rot, rotPoint), camera) + ); + } + } + } + + private static Vector3d applyRotations(Vector3d vec, Vector3d rot, Vector3d rotPoint) { + return applyRotation(applyRotation(applyRotation(vec, rotPoint, Z, rot.getZ()), rotPoint, Y, rot.getY()), rotPoint, X, rot.getX()); + } +} diff --git a/src/main/java/de/tudbut/security/AccessKiller.java b/src/main/java/de/tudbut/security/AccessKiller.java new file mode 100644 index 0000000..e645802 --- /dev/null +++ b/src/main/java/de/tudbut/security/AccessKiller.java @@ -0,0 +1,133 @@ +package de.tudbut.security; + +import de.tudbut.tools.ReflectUtil; + +import java.lang.ref.SoftReference; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class AccessKiller { + + private static final Field reflectionData; + + static { + try { + reflectionData = getField(Class.class.getDeclaredField("reflectionData")); + } catch (NoSuchFieldException e) { + throw new InternalError(e); + } + } + + private static Field getField(Field f) { + ReflectUtil.forceAccessible(f); + return f; + } + + private static Object getReflectionData(Class clazz) throws ReflectiveOperationException { + // make sure ReflectionData is populated + clazz.getDeclaredMethods(); + clazz.getDeclaredFields(); + clazz.getDeclaredConstructors(); + clazz.getInterfaces(); + + SoftReference data = (SoftReference) reflectionData.get(clazz); + Object reflectionData = data.get(); + assert reflectionData != null; + return reflectionData; + } + + /** + * WARNING!!! Can only erase private fields!! + * @param clazz Class to kill fields of + * @param fieldsToKill Field names to kill, or empty to kill all. + */ + public static void killFieldAccess(Class clazz, String... fieldsToKill) { + List toKill = Arrays.asList(fieldsToKill); + try { + Object reflectionData = getReflectionData(clazz); + Field data = getField(reflectionData.getClass().getDeclaredField("declaredFields")); + List fields = new ArrayList<>(Arrays.asList((Field[]) data.get(reflectionData))); + if(!toKill.isEmpty()) { + for (int i = 0; i < fields.size(); i++) { + if (toKill.contains(fields.get(i).getName())) + fields.remove(i--); + } + } + else { + fields.clear(); + } + data.set(reflectionData, fields.toArray(new Field[0])); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * WARNING!!! Can only erase private methods!! + * @param clazz Class to kill methods of + * @param methodsToKill Method names to kill, or empty to kill all. + */ + public static void killMethodAccess(Class clazz, String... methodsToKill) { + List toKill = Arrays.asList(methodsToKill); + try { + Object reflectionData = getReflectionData(clazz); + Field data = getField(reflectionData.getClass().getDeclaredField("declaredMethods")); + List methods = new ArrayList<>(Arrays.asList((Method[]) data.get(reflectionData))); + if(!toKill.isEmpty()) { + for (int i = 0; i < methods.size(); i++) { + if (toKill.contains(methods.get(i).getName())) + methods.remove(i--); + } + } + else { + methods.clear(); + } + data.set(reflectionData, methods.toArray(new Method[0])); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * WARNING!!! Can only erase private constructors!! + * @param clazz Class to kill constructors of + */ + public static void killConstructorAccess(Class clazz) { + try { + Object reflectionData = getReflectionData(clazz); + Field data = getField(reflectionData.getClass().getDeclaredField("declaredConstructors")); + data.set(reflectionData, new Constructor[0]); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void killReflectionFor(Class... classes) { + for (Class clazz : classes) { + killConstructorAccess(clazz); + killMethodAccess(clazz); + killFieldAccess(clazz); + } + } + + /** + * Stops any code from making further changes to reflectionData. + * This also stops any further AccessKiller calls.
+ * Use with EXTREME caution!! + */ + public static void killClassReflection() { + killReflectionFor(Class.class); + } + + /** + * Kills access to possible ways to restore reflective access after it has been removed. + * This should prevent all other ways of accessing fields, but other ways may exist. + */ + public static void ensureKills() { + killMethodAccess(Class.class, "getDeclaredFields0", "getDeclaredMethods0", "getDeclaredConstructors0"); + } +} diff --git a/src/main/java/de/tudbut/security/DataKeeper.java b/src/main/java/de/tudbut/security/DataKeeper.java new file mode 100644 index 0000000..d44d9d1 --- /dev/null +++ b/src/main/java/de/tudbut/security/DataKeeper.java @@ -0,0 +1,137 @@ +package de.tudbut.security; + +import de.tudbut.obj.DoubleTypedObject; +import de.tudbut.tools.Lock; + +import java.util.LinkedList; +import java.util.Queue; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.function.Supplier; + +// Keeps some data as safe as possible, unable to be accessed even by reflection +public class DataKeeper { + public static boolean forgetAll = false; + + + public Lock forget = new Lock(true); + private final PermissionManager permissionManager; + private Supplier dataInsertion; + private final Strictness strictness; + private final Lock lock = new Lock(true); + private final Queue>, Lock>> nextFunctionToRun = new LinkedList<>(); + private final Thread keeper = new Thread(this::keep, "DataKeeper"); { keeper.start(); } + + static { initSecurity(); } + + private static void initSecurity() { + // this should prevent any reflection, but is not a 100% guarantee! + AccessKiller.killReflectionFor(DataKeeper.class, Accessor.class); + } + + public DataKeeper(PermissionManager permissionManager, Strictness strictness, T toKeep) { + // make sure reflection is killed for it + permissionManager.killReflection(); + + this.permissionManager = permissionManager; + dataInsertion = () -> toKeep; + this.strictness = strictness; + lock.unlock(); + } + + public Strictness getStrictness() { + return strictness.clone(); + } + + public void access(Consumer> accessor) { + if(!forget.isLocked()) { + throw new IllegalStateException("This DataKeeper has already forgotten its value."); + } + if(!permissionManager.checkCaller(strictness)) { + if(permissionManager.showErrors()) + throw new IllegalAccessError("The active PermissionManager does not allow you to access this DataKeeper."); + else + return; + } + Lock waitLock = new Lock(true); + nextFunctionToRun.add(new DoubleTypedObject<>(accessor, waitLock)); + lock.unlock(); + waitLock.waitHere(500); + } + + public void forget() { + forget.unlock(); + } + + public DataKeeper forgetIn(int ms) { + if(forget.timeLeft() > ms) + return this; + forget.lock(ms); + return this; + } + + private void keep() { + lock.waitHere(); + lock.lock(); + PermissionManager permissionManager = this.permissionManager.clone(); + AtomicReference data = new AtomicReference<>(dataInsertion.get()); + Strictness strictness = this.strictness.clone(); + dataInsertion = null; + while(!forgetAll && forget.isLocked()) { + lock.waitHere(); + lock.lock(500); + + DoubleTypedObject>, Lock> itm = nextFunctionToRun.poll(); + if(itm == null) + continue; + Consumer> toRun = itm.o; + Lock lock = itm.t; + // second layer of protection, crashes this time. + if(!permissionManager.checkLambda(strictness, toRun)) + permissionManager.crash(strictness); + + toRun.accept(new Accessor<>(permissionManager, strictness, data)); + lock.unlock(); + } + } + + // A very last, third layer of protection, not actually that necessary. + public static class Accessor { + // The accessor will only ever be in local variables, so it does + // not have to be reflection-safe. But it is anyway due to AccessKiller. + private final PermissionManager permissionManager; + private final AtomicReference value; + private final Strictness strictness; + + public Accessor(PermissionManager permissionManager, Strictness strictness, AtomicReference data) { + this.permissionManager = permissionManager; + this.strictness = strictness; + value = data; + } + + public T getValue() { + if(permissionManager.checkCaller(strictness)) + return value.get(); + else { + // crash soon + new Thread(() -> { + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + permissionManager.crash(strictness); + }).start(); + // generate a weird error + return (T) value.get().getClass().cast(new Object()); + } + } + + public T setValue(T newValue) { + // check is in getValue + T old = getValue(); + value.set(newValue); + return old; + } + } +} diff --git a/src/main/java/de/tudbut/security/ExtendedStrictness.java b/src/main/java/de/tudbut/security/ExtendedStrictness.java new file mode 100644 index 0000000..65ab896 --- /dev/null +++ b/src/main/java/de/tudbut/security/ExtendedStrictness.java @@ -0,0 +1,25 @@ +package de.tudbut.security; + +public class ExtendedStrictness implements Strictness { + static { + AccessKiller.killReflectionFor(ExtendedStrictness.class); + } + private final Strictness primary, secondary; + + public ExtendedStrictness(Strictness primary, Strictness secondary) { + this.primary = primary; + this.secondary = secondary; + } + + @Override + public Object getRawProperty(String name) { + if(primary.hasProperty(name)) + return primary.getRawProperty(name); + return secondary.getRawProperty(name); + } + + @Override + public Strictness clone() { + return new ExtendedStrictness(primary.clone(), secondary.clone()); + } +} diff --git a/src/main/java/de/tudbut/security/PermissionManager.java b/src/main/java/de/tudbut/security/PermissionManager.java new file mode 100644 index 0000000..f058604 --- /dev/null +++ b/src/main/java/de/tudbut/security/PermissionManager.java @@ -0,0 +1,71 @@ +package de.tudbut.security; + +import de.tudbut.tools.ReflectUtil; + +import java.lang.reflect.Method; +import java.lang.reflect.Field; +import java.util.Arrays; + +public interface PermissionManager extends Cloneable { + boolean checkCaller(Strictness strictnessLevel); + + boolean checkLambda(Strictness strictnessLevel, T lambda); + + default void crash(Strictness strictnessLevel) { + DataKeeper.forgetAll = true; + try { + Class shutdownClass = Class.forName("java.lang.Shutdown"); + Method exitMethod = shutdownClass.getDeclaredMethod("exit", int.class); + ReflectUtil.forceAccessible(exitMethod); + exitMethod.invoke(null, 1); + } catch (Exception ignored) {} + System.exit(1); + throw new Error(); + } + + default boolean showErrors() { + return true; + } + + default void killReflection() { + Class clazz = getClass(); + while(Arrays.stream(clazz.getInterfaces()).anyMatch(x -> x == PermissionManager.class)) { + AccessKiller.killReflectionFor(clazz); + clazz = clazz.getSuperclass(); + } + } + + PermissionManager clone(); + + default String getClassName(Class clazz) { + return getClassName(clazz, null, 0); + } + default String getClassName(Class clazz, boolean[] cache, int idx) { + if(cache != null && cache[0]) + return clazz.getName(); + try { + // Reset the name field so that it must be cached again + Field nameField = clazz.getClass().getDeclaredField("name"); + ReflectUtil.forceAccessible(nameField); + nameField.set(clazz, null); + // name is clean, getName can now be used. + if(cache != null) + cache[idx] = true; + return clazz.getName(); + } + catch(Exception e) { + // name can't be cleaned, getName can't be used. + if(cache != null) + cache[idx] = false; + try { + // Unable to reset the name field, invoking the native that gets the name directly + Method initClassName = clazz.getClass().getDeclaredMethod("initClassName"); + ReflectUtil.forceAccessible(initClassName); + return (String) initClassName.invoke(clazz); + } + catch(Exception e1) { + return null; + } + } + } +} diff --git a/src/main/java/de/tudbut/security/Strictness.java b/src/main/java/de/tudbut/security/Strictness.java new file mode 100644 index 0000000..46a898b --- /dev/null +++ b/src/main/java/de/tudbut/security/Strictness.java @@ -0,0 +1,30 @@ +package de.tudbut.security; + +public interface Strictness extends Cloneable { + + Object getRawProperty(String name); + + default T getProperty(String name) { + return (T) getRawProperty(name); + } + default boolean getBoolProperty(String name) { + Boolean b = getProperty(name); + if(b == null) + return false; + return b; + } + default String getStringProperty(String name) { + return getProperty(name); + } + default int getIntProperty(String name) { + return getProperty(name); + } + default boolean hasProperty(String name) { + return getRawProperty(name) != null; + } + default Strictness extend(Strictness newPrimary) { + return new ExtendedStrictness(newPrimary, this); + } + + Strictness clone(); +} diff --git a/src/main/java/de/tudbut/security/StrictnessBuilder.java b/src/main/java/de/tudbut/security/StrictnessBuilder.java new file mode 100644 index 0000000..f5b772e --- /dev/null +++ b/src/main/java/de/tudbut/security/StrictnessBuilder.java @@ -0,0 +1,68 @@ +package de.tudbut.security; + +import de.tudbut.parsing.TCN; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +public class StrictnessBuilder { + static { + AccessKiller.killReflectionFor(StrictnessBuilder.class); + } + + private final HashMap properties = new HashMap<>(); + + public static StrictnessBuilder create() { + return new StrictnessBuilder(); + } + + public static Strictness empty() { + return new StrictnessBuilder().build(); + } + + public StrictnessBuilder property(String s, Object o) { + properties.put(s, o); + return this; + } + + public StrictnessBuilder fromTCN(TCN tcn) { + properties.putAll(tcn.toMap()); + return this; + } + + public StrictnessBuilder tryFromStrictness(Strictness strictness) { + if(strictness instanceof StrictnessImpl) { + properties.putAll((((StrictnessImpl) strictness).properties)); + return this; + } + // error + return null; + } + + public Strictness build() { + return new StrictnessImpl((HashMap) properties.clone()); + } + + private static class StrictnessImpl implements Strictness { + static { + AccessKiller.killReflectionFor(StrictnessImpl.class); + } + + private final HashMap properties; + + public StrictnessImpl(HashMap properties) { + this.properties = properties; + } + + @Override + public Object getRawProperty(String name) { + return properties.get(name); + } + + @Override + public Strictness clone() { + return new StrictnessImpl((HashMap) properties.clone()); + } + } +} diff --git a/src/main/java/de/tudbut/security/permissionmanager/AllowAllRestriction.java b/src/main/java/de/tudbut/security/permissionmanager/AllowAllRestriction.java new file mode 100644 index 0000000..5409dc4 --- /dev/null +++ b/src/main/java/de/tudbut/security/permissionmanager/AllowAllRestriction.java @@ -0,0 +1,21 @@ +package de.tudbut.security.permissionmanager; + +import de.tudbut.security.PermissionManager; +import de.tudbut.security.Strictness; + +public class AllowAllRestriction implements PermissionManager { + @Override + public boolean checkCaller(Strictness strictnessLevel) { + return true; + } + + @Override + public boolean checkLambda(Strictness strictnessLevel, T lambda) { + return true; + } + + @Override + public PermissionManager clone() { + return this; // stateless + } +} diff --git a/src/main/java/de/tudbut/security/permissionmanager/CallClassRestriction.java b/src/main/java/de/tudbut/security/permissionmanager/CallClassRestriction.java new file mode 100644 index 0000000..7e9049c --- /dev/null +++ b/src/main/java/de/tudbut/security/permissionmanager/CallClassRestriction.java @@ -0,0 +1,65 @@ +package de.tudbut.security.permissionmanager; + +import de.tudbut.security.PermissionManager; +import de.tudbut.security.Strictness; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * Supported strictness properties: + * - Restriction.CallClass.MaxDistance (int): How far down the stack trace should the restriction look until it fails + * - Restriction.CallClass.RestrictLambda (bool): If the restriction should apply to lambdas. If true, ONLY classes in the + * allowlist pass, instead of allowing the allowed classes to call "through" others. + */ +public class CallClassRestriction extends Restriction { + + private final Set allow; + + public CallClassRestriction(PermissionManager parent, Class... allowFromClasses) { + super(parent); + allow = Collections.unmodifiableSet(Arrays.stream(allowFromClasses).map(this::getClassName).collect(Collectors.toSet())); + } + public CallClassRestriction(Class... allowFromClasses) { + this(null, allowFromClasses); + } + + @Override + public boolean checkCaller(Strictness strictnessLevel) { + StackTraceElement[] st = Thread.currentThread().getStackTrace(); + + if(strictnessLevel.hasProperty("Restriction.CallClass.MaxDistance")) { + int maxDist = strictnessLevel.getIntProperty("Restriction.CallClass.MaxDistance"); + if(st.length > maxDist) { + StackTraceElement[] elements = new StackTraceElement[maxDist]; + System.arraycopy(st, 0, elements, 0, maxDist); + st = elements; + } + } + + boolean isCalledByAllowed = false; + for (StackTraceElement element : st) { + if (allow.contains(element.getClassName())) { + isCalledByAllowed = true; + break; + } + } + return isCalledByAllowed && super.checkCaller(strictnessLevel); + } + + @Override + public boolean checkLambda(Strictness strictnessLevel, T lambda) { + boolean b = true; + if(strictnessLevel.getBoolProperty("Restriction.CallClass.RestrictLambda")) { + // might get more complex soon. + // is class, inner class of it, loaded by it, or lambda in it? + Class enclosingClass = lambda.getClass().getEnclosingClass(); + boolean[] cache = new boolean[2]; + b = allow.contains(getClassName(lambda.getClass(), cache, 0)) + || allow.contains(getClassName(lambda.getClass(), cache, 0).replaceAll("\\$\\$Lambda.*$", "")); + if (enclosingClass != null) + b = b || allow.contains(getClassName(enclosingClass)); + } + return b && super.checkLambda(strictnessLevel, lambda); + } +} diff --git a/src/main/java/de/tudbut/security/permissionmanager/ClassLoaderRestriction.java b/src/main/java/de/tudbut/security/permissionmanager/ClassLoaderRestriction.java new file mode 100644 index 0000000..edb1feb --- /dev/null +++ b/src/main/java/de/tudbut/security/permissionmanager/ClassLoaderRestriction.java @@ -0,0 +1,108 @@ +package de.tudbut.security.permissionmanager; + +import de.tudbut.security.PermissionManager; +import de.tudbut.security.Strictness; +import de.tudbut.tools.ReflectUtil; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * Only allows classes loaded by a certain class loader, and the classloader itself. + * + * Supported strictness properties: + * - Restriction.ClassLoader.MaxDistance (int): How far down the stack trace should the restriction look until it fails + * - Restriction.ClassLoader.RestrictLambda (bool): If the restriction should apply to lambdas. If true, ONLY classes in the + * allowlist pass, instead of allowing the allowed classes to call "through" others. + */ +public class ClassLoaderRestriction extends Restriction { + private final Set allow; + + public ClassLoaderRestriction(PermissionManager parent, ClassLoader... allowFromClassLoaders) { + super(parent); + this.allow = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(allowFromClassLoaders))); + } + + public ClassLoaderRestriction(ClassLoader... allowFromClassLoaders) { + this(null, allowFromClassLoaders); + } + + @Override + public boolean checkCaller(Strictness strictnessLevel) { + StackTraceElement[] st = Thread.currentThread().getStackTrace(); + + if(strictnessLevel.hasProperty("Restriction.ClassLoader.MaxDistance")) { + int maxDist = strictnessLevel.getIntProperty("Restriction.ClassLoader.MaxDistance"); + if(st.length > maxDist) { + StackTraceElement[] elements = new StackTraceElement[maxDist]; + System.arraycopy(st, 0, elements, 0, maxDist); + st = elements; + } + } + + boolean isCalledByAllowed = false; + for (StackTraceElement element : st) { + try { + Class cls = getClassObject(element.getClassName()); + // is the classloader or loaded by it? + if(allow.stream().anyMatch(x -> x.getClass() == cls) || allow.contains(cls.getClassLoader())) { + isCalledByAllowed = true; + break; + } + } catch (Exception e) { + // it'll just stay false + } + } + return isCalledByAllowed && super.checkCaller(strictnessLevel); + } + + private Class getClassObject(String className) throws ClassNotFoundException { + try { + Method findLoadedClass = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class); + ReflectUtil.forceAccessible(findLoadedClass); + for (ClassLoader allowed : allow) { + Class clazz = (Class) findLoadedClass.invoke(allowed, className); + if(clazz != null) { + return clazz; + } + } + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + return Class.forName(className); + } + + @Override + public boolean checkLambda(Strictness strictnessLevel, T lambda) { + boolean b = true; + if(strictnessLevel.getBoolProperty("Restriction.ClassLoader.RestrictLambda")) { + // might get more complex soon. + // is classloader, inner class of it, or loaded by it? + + + //noinspection SuspiciousMethodCalls + b = allow.contains(lambda) + || allow.contains(lambda.getClass().getClassLoader()); + + // is enclosed class (e.g. anonymous class) + Class enclosingClass = lambda.getClass().getEnclosingClass(); + if (enclosingClass != null) + b = b || allow.stream().anyMatch(x -> x.getClass() == enclosingClass); + + // is lambda in allowed class? + boolean[] cache = new boolean[1]; + String name = getClassName(lambda.getClass(), cache, 0).replaceAll("\\$\\$Lambda.*$", ""); + b = b || allow.stream().anyMatch(x -> getClassName(x.getClass(), cache, 0).equals(name)); // is lambda in classloader + try { + b = b || allow.contains(getClassObject(name).getClassLoader()); // is lambda in classloader-loaded class + } catch (Exception e) { + // it'll just stay false + } + } + return b && super.checkLambda(strictnessLevel, lambda); + } +} diff --git a/src/main/java/de/tudbut/security/permissionmanager/DenyAllRestriction.java b/src/main/java/de/tudbut/security/permissionmanager/DenyAllRestriction.java new file mode 100644 index 0000000..f0c013a --- /dev/null +++ b/src/main/java/de/tudbut/security/permissionmanager/DenyAllRestriction.java @@ -0,0 +1,21 @@ +package de.tudbut.security.permissionmanager; + +import de.tudbut.security.PermissionManager; +import de.tudbut.security.Strictness; + +public class DenyAllRestriction implements PermissionManager { + @Override + public boolean checkCaller(Strictness strictnessLevel) { + return false; + } + + @Override + public boolean checkLambda(Strictness strictnessLevel, T lambda) { + return false; + } + + @Override + public PermissionManager clone() { + return this; // stateless + } +} diff --git a/src/main/java/de/tudbut/security/permissionmanager/HideErrorRestriction.java b/src/main/java/de/tudbut/security/permissionmanager/HideErrorRestriction.java new file mode 100644 index 0000000..88ef947 --- /dev/null +++ b/src/main/java/de/tudbut/security/permissionmanager/HideErrorRestriction.java @@ -0,0 +1,14 @@ +package de.tudbut.security.permissionmanager; + +import de.tudbut.security.PermissionManager; + +public class HideErrorRestriction extends Restriction { + public HideErrorRestriction(PermissionManager parent) { + super(parent); + } + + @Override + public boolean showErrors() { + return false; // stateless + } +} diff --git a/src/main/java/de/tudbut/security/permissionmanager/PermissionAND.java b/src/main/java/de/tudbut/security/permissionmanager/PermissionAND.java new file mode 100644 index 0000000..0a020a3 --- /dev/null +++ b/src/main/java/de/tudbut/security/permissionmanager/PermissionAND.java @@ -0,0 +1,50 @@ +package de.tudbut.security.permissionmanager; + +import de.tudbut.security.PermissionManager; +import de.tudbut.security.Strictness; + +/** + * Equivalent to setting primary's parent to secondary. + */ +public class PermissionAND implements PermissionManager { + + private final PermissionManager primary, secondary; + + public PermissionAND(PermissionManager primary, PermissionManager secondary) { + this.primary = primary; + this.secondary = secondary; + } + + @Override + public boolean checkCaller(Strictness strictnessLevel) { + return primary.checkCaller(strictnessLevel) && secondary.checkCaller(strictnessLevel); + } + + @Override + public boolean checkLambda(Strictness strictnessLevel, T lambda) { + return primary.checkLambda(strictnessLevel, lambda) && secondary.checkLambda(strictnessLevel, lambda); + } + + @Override + public void crash(Strictness strictnessLevel) { + primary.crash(strictnessLevel); + secondary.crash(strictnessLevel); + } + + @Override + public boolean showErrors() { + return primary.showErrors() && secondary.showErrors(); + } + + @Override + public void killReflection() { + PermissionManager.super.killReflection(); + primary.killReflection(); + secondary.killReflection(); + } + + @Override + public PermissionManager clone() { + return new PermissionAND(primary.clone(), secondary.clone()); + } +} diff --git a/src/main/java/de/tudbut/security/permissionmanager/PermissionOR.java b/src/main/java/de/tudbut/security/permissionmanager/PermissionOR.java new file mode 100644 index 0000000..36bf983 --- /dev/null +++ b/src/main/java/de/tudbut/security/permissionmanager/PermissionOR.java @@ -0,0 +1,47 @@ +package de.tudbut.security.permissionmanager; + +import de.tudbut.security.PermissionManager; +import de.tudbut.security.Strictness; + +public class PermissionOR implements PermissionManager { + + private final PermissionManager primary, secondary; + + public PermissionOR(PermissionManager primary, PermissionManager secondary) { + this.primary = primary; + this.secondary = secondary; + } + + @Override + public boolean checkCaller(Strictness strictnessLevel) { + return primary.checkCaller(strictnessLevel) || secondary.checkCaller(strictnessLevel); + } + + @Override + public boolean checkLambda(Strictness strictnessLevel, T lambda) { + return primary.checkLambda(strictnessLevel, lambda) || secondary.checkLambda(strictnessLevel, lambda); + } + + @Override + public void crash(Strictness strictnessLevel) { + primary.crash(strictnessLevel); + secondary.crash(strictnessLevel); + } + + @Override + public boolean showErrors() { + return primary.showErrors() || secondary.showErrors(); + } + + @Override + public void killReflection() { + PermissionManager.super.killReflection(); + primary.killReflection(); + secondary.killReflection(); + } + + @Override + public PermissionManager clone() { + return new PermissionOR(primary.clone(), secondary.clone()); + } +} diff --git a/src/main/java/de/tudbut/security/permissionmanager/Restriction.java b/src/main/java/de/tudbut/security/permissionmanager/Restriction.java new file mode 100644 index 0000000..7d2ee26 --- /dev/null +++ b/src/main/java/de/tudbut/security/permissionmanager/Restriction.java @@ -0,0 +1,47 @@ +package de.tudbut.security.permissionmanager; + +import de.tudbut.security.PermissionManager; +import de.tudbut.security.Strictness; + +public abstract class Restriction implements PermissionManager { + + protected PermissionManager parent; + + public Restriction(PermissionManager parent) { + if(parent == null) + parent = new AllowAllRestriction(); + this.parent = parent; + } + + @Override + public boolean checkCaller(Strictness strictnessLevel) { + return parent.checkCaller(strictnessLevel); + } + + @Override + public boolean checkLambda(Strictness strictnessLevel, T lambda) { + return parent.checkLambda(strictnessLevel, lambda); + } + + @Override + public void crash(Strictness strictnessLevel) { + parent.crash(strictnessLevel); + } + + @Override + public void killReflection() { + parent.killReflection(); + PermissionManager.super.killReflection(); + } + + @Override + public PermissionManager clone() { + try { + Restriction cloned = (Restriction) super.clone(); + cloned.parent = parent.clone(); + return cloned; + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/de/tudbut/timer/AsyncCatcher.java b/src/main/java/de/tudbut/timer/AsyncCatcher.java new file mode 100644 index 0000000..a286bad --- /dev/null +++ b/src/main/java/de/tudbut/timer/AsyncCatcher.java @@ -0,0 +1,5 @@ +package de.tudbut.timer; + +public interface AsyncCatcher { + void run(Exception theException) throws Exception; +} diff --git a/src/main/java/de/tudbut/timer/AsyncRunnable.java b/src/main/java/de/tudbut/timer/AsyncRunnable.java new file mode 100644 index 0000000..d2865a0 --- /dev/null +++ b/src/main/java/de/tudbut/timer/AsyncRunnable.java @@ -0,0 +1,5 @@ +package de.tudbut.timer; + +public interface AsyncRunnable { + T run() throws Exception; +} diff --git a/src/main/java/de/tudbut/timer/AsyncTask.java b/src/main/java/de/tudbut/timer/AsyncTask.java new file mode 100644 index 0000000..4d92cd9 --- /dev/null +++ b/src/main/java/de/tudbut/timer/AsyncTask.java @@ -0,0 +1,111 @@ +package de.tudbut.timer; + +import de.tudbut.tools.Lock; + +import java.util.Date; +import java.util.concurrent.atomic.AtomicReference; + +import static de.tudbut.tools.BetterJ.t; + +public class AsyncTask { + private final AtomicReference backValue = new AtomicReference<>(); + private Exception exception = null; + private boolean isCatched = false; + private AsyncCatcher catcher; + private AsyncThenRunnable thenRunnable = null; + + // Has the program finished? (Useful to know if it hit the timeout or exited.) + private volatile boolean done = false; + // We want efficiency, don't use a while loop to wait! + private final Lock threadLock = new Lock(); + + // When to force termination + private long timeout = -1; + // Start of the execution + private long startTime = new Date().getTime(); + + public AsyncTask(AsyncRunnable runnable) { + threadLock.lock(); + Lock stopperLock = new Lock(true); + Lock sLock = new Lock(true); + Thread runner = t(() -> { + startTime = new Date().getTime(); + stopperLock.waitHere(); + sLock.unlock(); + + startTime = new Date().getTime(); + try { + backValue.set(runnable.run()); + } + catch (Exception e) { + exception = e; + if (!isCatched) + exception.printStackTrace(); + else { + try { + catcher.run(exception); + } + catch (Exception ex) { + ex.printStackTrace(); + } + } + } + + done = true; + done(); + }); + Thread stopper = t(() -> { + while (sLock.isLocked()) + stopperLock.unlock(); + while ((new Date().getTime() < startTime + timeout || timeout == -1) && !done) { + threadLock.waitHere(1); + } + if(!done) { + runner.stop(); + done(); + } + }); + } + + private void done() { + if (thenRunnable != null) { + try { + thenRunnable.run(backValue.get()); + } + catch (Exception e) { + exception = e; + if (!isCatched) + exception.printStackTrace(); + else { + try { + catcher.run(exception); + } + catch (Exception ex) { + ex.printStackTrace(); + } + } + } + } + done = true; + threadLock.unlock(); + } + + public void catchExceptions(AsyncCatcher catcher) { + this.catcher = catcher; + isCatched = true; + } + + public void then(AsyncThenRunnable runnable) { + this.thenRunnable = runnable; + } + + public T waitForFinish(int waitTimeout) { + threadLock.waitHere(waitTimeout); + + return backValue.get(); + } + + public void setTimeout(long timeout) { + this.timeout = timeout; + } +} diff --git a/src/main/java/de/tudbut/timer/AsyncThenRunnable.java b/src/main/java/de/tudbut/timer/AsyncThenRunnable.java new file mode 100644 index 0000000..a4fd20f --- /dev/null +++ b/src/main/java/de/tudbut/timer/AsyncThenRunnable.java @@ -0,0 +1,5 @@ +package de.tudbut.timer; + +public interface AsyncThenRunnable { + void run(T theValue) throws Exception; +} diff --git a/src/main/java/de/tudbut/timer/SyncQueue.java b/src/main/java/de/tudbut/timer/SyncQueue.java new file mode 100644 index 0000000..8bee814 --- /dev/null +++ b/src/main/java/de/tudbut/timer/SyncQueue.java @@ -0,0 +1,112 @@ +package de.tudbut.timer; + +import de.tudbut.tools.ThrowingRunnable; +import de.tudbut.tools.Queue; + +import java.util.ArrayList; +import java.util.Date; +import java.util.concurrent.atomic.AtomicBoolean; + +public class SyncQueue { + private final Queue queue = new Queue<>(); + private final Queue immediateQueue = new Queue<>(); + private final ArrayList timers = new ArrayList<>(); + + { + initAndRunAsynchronously(); + } + + public void add(ThrowingRunnable runnable) { + if(runnable == null) + throw new IllegalArgumentException(); + queue.add(runnable); + } + + public void addImmediate(ThrowingRunnable runnable) { + if(runnable == null) + throw new IllegalArgumentException(); + immediateQueue.add(runnable); + } + + public void addTimer(Ticker ticker, AtomicBoolean stop, int delay) { + addTimer(new Ticker() { + public int getDelay() { + return delay; + } + + public void run() { + ticker.run(); + } + + public boolean doRun() { + return !stop.get(); + } + }); + } + + public void addTimer(Ticker ticker, int delay) { + addTimer(new Ticker() { + public int getDelay() { + return delay; + } + + public void run() { + ticker.run(); + } + }); + } + + public void addTimer(Ticker ticker) { + timers.add(new Object[]{ticker, 0L}); + } + + private void initAndRun() { + while (true) { + if (immediateQueue.hasNext()) { + try { + immediateQueue.next().run(); + } catch (Exception exception) { + exception.printStackTrace(); + } + continue; + } + if (queue.hasNext()) { + try { + queue.next().run(); + } catch (Exception exception) { + exception.printStackTrace(); + } + } + if (immediateQueue.hasNext()) { + try { + immediateQueue.next().run(); + } catch (Exception exception) { + exception.printStackTrace(); + } + continue; + } + // An enhanced loop will throw an exception, fuck you intellij for this shit suggestion + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < timers.size(); i++) { + Object[] timer = timers.get(i); + if (new Date().getTime() - ((Long) timer[1]) >= ((Ticker) timer[0]).getDelay()) { + if (((Ticker) timer[0]).doRun()) { + ((Ticker) timer[0]).run(); + timer[1] = new Date().getTime(); + } + } + } + if (immediateQueue.hasNext()) { + try { + immediateQueue.next().run(); + } catch (Exception exception) { + exception.printStackTrace(); + } + } + } + } + + public void initAndRunAsynchronously() { + new Thread(this::initAndRun, "SyncQueue").start(); + } +} diff --git a/src/main/java/de/tudbut/timer/Ticker.java b/src/main/java/de/tudbut/timer/Ticker.java new file mode 100644 index 0000000..542ac53 --- /dev/null +++ b/src/main/java/de/tudbut/timer/Ticker.java @@ -0,0 +1,13 @@ +package de.tudbut.timer; + +public interface Ticker { + default int getDelay() { + return 0; + } + + void run(); + + default boolean doRun() { + return true; + } +} diff --git a/src/main/java/de/tudbut/timer/Ticks.java b/src/main/java/de/tudbut/timer/Ticks.java new file mode 100644 index 0000000..a079ae3 --- /dev/null +++ b/src/main/java/de/tudbut/timer/Ticks.java @@ -0,0 +1,58 @@ +package de.tudbut.timer; + +import de.tudbut.logger.Logger; +import de.tudbut.global.GlobalSyncQueue; + +import java.util.Date; +import java.util.concurrent.atomic.AtomicBoolean; + +public class Ticks { + private static final Logger logger = new Logger("Ticker"); + + public static long perSecound(int count) { + return 1000 / count; + } + + public static void onTick(int TPS, Ticker ticker) { + try { + int waitTicks = 1000 / TPS; + long ticksPassed = 0; + + while (true) { + long sAt = new Date().getTime(); + ticker.run(); + ticksPassed++; + try { + Thread.sleep(waitTicks - (new Date().getTime() - sAt)); + } + catch (Exception ignore) { + } + } + } + catch (Exception e) { + logger.error(e); + } + } + + public static void inQueue(int TPS, Ticker ticker, AtomicBoolean stop, SyncQueue queue) { + try { + int waitTicks = 1000 / TPS; + + queue.addTimer(ticker, stop, waitTicks); + } + catch (Exception e) { + logger.error(e); + } + } + + public static void inQueue(int TPS, Ticker ticker, AtomicBoolean stop) { + try { + int waitTicks = 1000 / TPS; + + GlobalSyncQueue.addTimer(ticker, stop, waitTicks); + } + catch (Exception e) { + logger.error(e); + } + } +} diff --git a/src/main/java/de/tudbut/tools/Application.java b/src/main/java/de/tudbut/tools/Application.java new file mode 100644 index 0000000..6d95cef --- /dev/null +++ b/src/main/java/de/tudbut/tools/Application.java @@ -0,0 +1,144 @@ +package de.tudbut.tools; + +import de.tudbut.logger.Logger; +import de.tudbut.window.Window; + +import javax.swing.*; +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; +import java.util.concurrent.atomic.AtomicBoolean; + + +public class Application { + protected Window window = null; + private boolean hasConfig = false; + private Config config; + private boolean main = false; + private String label = ""; + private Logger logger = null; + + + protected void activate(String name, boolean hasCfg, boolean hasWindow, boolean isMain) throws Exception { + this.hasConfig = hasCfg; + this.label = name; + this.main = isMain; + this.logger = new Logger(name); + + if (hasWindow) this.window = new Window(name, name, isMain).setSize(100, 100).open(); + + if (hasCfg) this.config = new Config(name.replaceAll(" ", "_") + ".config"); + } + + public String toString() { + return label; + } + + protected Config getConfig() { + if (this.hasConfig) return this.config; + else return null; + } + + protected void setConfig(Config c) { + this.config = c; + this.hasConfig = true; + } + + public Logger getLogger() { + return this.logger; + } + + protected String input(String name) { + return JOptionPane.showInputDialog(name); + } + + protected void output(String text) { + JOptionPane.showMessageDialog(null, text); + } + + @Deprecated + protected boolean askD(String question) { + this.logger.info("Showing question dialog"); + + AtomicBoolean received = new AtomicBoolean(false); + AtomicBoolean yes = new AtomicBoolean(false); + + Window askWindow = new Window(this.label, this.label + " - " + question, false).setText(question).setSize(400, 150).open(); + + + JButton askButton_yes = new JButton("Yes"); + JButton askButton_no = new JButton("No"); + askWindow.label.add(askButton_yes); + askWindow.label.add(askButton_no); + + askButton_yes.setLocation(50, 70); + askButton_no.setLocation(250, 70); + askButton_yes.setSize(100, 25); + askButton_no.setSize(100, 25); + + askWindow.frame.addWindowListener(new WindowListener() { + @Override + public void windowOpened(WindowEvent windowEvent) { + } + + @Override + public void windowClosing(WindowEvent windowEvent) { + } + + @Override + public void windowClosed(WindowEvent windowEvent) { + received.set(true); + yes.set(false); + } + + @Override + public void windowIconified(WindowEvent windowEvent) { + } + + @Override + public void windowDeiconified(WindowEvent windowEvent) { + } + + @Override + public void windowActivated(WindowEvent windowEvent) { + } + + @Override + public void windowDeactivated(WindowEvent windowEvent) { + } + }); + + askButton_yes.addActionListener((a) -> { + yes.set(true); + received.set(true); + }); + + askButton_no.addActionListener((a) -> { + yes.set(false); + received.set(true); + }); + + askWindow.label.updateUI(); + + while (true) { + if (received.get()) break; + } + + this.logger.info("Answer received"); + + if (yes.get()) { + askWindow.delete(); + return true; + } + else { + askWindow.delete(); + return false; + } + } + + protected boolean ask(String question) { + return JOptionPane.showConfirmDialog(null, + question, label, JOptionPane.YES_NO_OPTION + ) == + JOptionPane.YES_OPTION; + } +} diff --git a/src/main/java/de/tudbut/tools/ArrayGetter.java b/src/main/java/de/tudbut/tools/ArrayGetter.java new file mode 100644 index 0000000..3e32f0e --- /dev/null +++ b/src/main/java/de/tudbut/tools/ArrayGetter.java @@ -0,0 +1,14 @@ +package de.tudbut.tools; + +import java.lang.reflect.Array; + +public class ArrayGetter { + @SafeVarargs + public static T[] newGenericArray(int length, T... ts) { + return newArray(length, (Class) ts.getClass().getComponentType()); + } + + public static T[] newArray(int length, Class type) { + return (T[]) Array.newInstance(type, length); + } +} diff --git a/src/main/java/de/tudbut/tools/ArrayTools.java b/src/main/java/de/tudbut/tools/ArrayTools.java new file mode 100644 index 0000000..dd58026 --- /dev/null +++ b/src/main/java/de/tudbut/tools/ArrayTools.java @@ -0,0 +1,115 @@ +package de.tudbut.tools; + +import de.tudbut.obj.Mappable; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ArrayTools { + public static T[] replace(T[] array, T repl, T with) { array=array.clone();for (int i=0;i T[] arrayFromList(List list, T... empty) { + return list.toArray(empty); + } + + @SafeVarargs + public static O[] getFromArray(T[] array, Getter getter, O... ignore) throws RuntimeException { + O[] os = (O[]) ArrayGetter.newArray(array.length, ignore.getClass().getComponentType()); + for (int i = 0; i < array.length; i++) { + try { + os[i] = getter.get(array[i]); + } + catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + } + return os; + } + + public interface Getter { + O get(T t) throws Throwable; + } + + public static double[] convertToNative(Double[] doubles) { + double[] d = new double[doubles.length]; + for (int i = 0; i < d.length; i++) { + d[i] = doubles[i]; + } + return d; + } + + public static int[] convertToNative(Integer[] ints) { + int[] d = new int[ints.length]; + for (int i = 0; i < d.length; i++) { + d[i] = ints[i]; + } + return d; + } + + public static byte[] convertToNative(Byte[] bytes) { + byte[] d = new byte[bytes.length]; + for (int i = 0; i < d.length; i++) { + d[i] = bytes[i]; + } + return d; + } + + public static Map mapFrom(Mappable[] mappables) { + Map map = new HashMap<>(); + + for (int i = 0; i < mappables.length; i++) { + map.put(String.valueOf(i), Tools.mapToString(mappables[i].map())); + } + + map.put("len", String.valueOf(mappables.length)); + + return map; + } + + public static Map mapFrom(T[] objects, Getter getter) { + Map map = new HashMap<>(); + + for (int i = 0; i < objects.length; i++) { + try { + map.put(String.valueOf(i), getter.get(objects[i])); + } + catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + } + + map.put("len", String.valueOf(objects.length)); + + return map; + } + + public static T[] fromMap(Map map, Getter getter, T... ignore) { + int len = Integer.parseInt(map.get("len")); + + T[] t = ArrayGetter.newGenericArray(len, ignore); + + for (int i = 0; i < len; i++) { + try { + t[i] = getter.get(map.get(String.valueOf(i))); + } + catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + } + + return t; + } + + public static T[] fromValueArray(ValueArray valueArray, T... ignore) { + T[] t; + t = ArrayGetter.newGenericArray(valueArray.length, ignore); + + for (int i = 0; i < valueArray.length; i++) { + t[i] = valueArray.get(i); + } + + return t; + } +} diff --git a/src/main/java/de/tudbut/tools/AudioPlayer.java b/src/main/java/de/tudbut/tools/AudioPlayer.java new file mode 100644 index 0000000..e1a0710 --- /dev/null +++ b/src/main/java/de/tudbut/tools/AudioPlayer.java @@ -0,0 +1,65 @@ +package de.tudbut.tools; + +import de.tudbut.io.StreamReader; + +import javax.sound.sampled.*; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class AudioPlayer { + + public static AudioTrackPlayer startPlaying(InputStream stream) throws IOException { + AudioTrackPlayer r = new AudioTrackPlayer(); + + try { + AudioInputStream as = AudioSystem.getAudioInputStream(new ByteArrayInputStream(new StreamReader(stream).readAllAsBytes())); + DataLine.Info info = new DataLine.Info(Clip.class, as.getFormat()); + Clip clip = (Clip) AudioSystem.getLine(info); + clip.open(as); + clip.start(); + r.player = clip; + } + catch (UnsupportedAudioFileException | IOException | LineUnavailableException e) { + throw new AudioException(e); + } + + return r; + } + + public static class AudioException extends IOException { + + public AudioException(Throwable cause) { + super(cause); + } + } + + public static class AudioTrackPlayer { + private Clip player; + + public void setVolume(float vol) { + ((FloatControl) player.getControl(FloatControl.Type.VOLUME)).setValue(vol); + } + public void setMS(int ms) { + player.setFramePosition(ms); + } + public boolean isPlaying() { + return player.isActive(); + } + public boolean isPausedOrPlaying() { + return player.isOpen(); + } + public void start() { + if(!isPausedOrPlaying()) + setMS(0); + player.start(); + } + public void pause() { + player.stop(); + } + public void stop() { + player.stop(); + player.close(); + } + } +} diff --git a/src/main/java/de/tudbut/tools/BYMLFile.java b/src/main/java/de/tudbut/tools/BYMLFile.java new file mode 100644 index 0000000..76732a6 --- /dev/null +++ b/src/main/java/de/tudbut/tools/BYMLFile.java @@ -0,0 +1,36 @@ +package de.tudbut.tools; + +import de.tudbut.type.FileFormatException; + +import java.util.HashMap; +import java.util.Map; + +public class BYMLFile extends FileRW { + protected Map map = new HashMap<>(); + + public BYMLFile(String path) throws Exception { + super(path); + if (!path.endsWith(".byml") && !path.endsWith(".byaml")) { + throw new FileFormatException(); + } + else { + this.remap(); + } + } + + public void remap() throws Exception { + map = Tools.stringToMap(getContent().join(";").replaceAll(": ", ":").replaceAll("\\$\\$\\$", "\u0000").replaceAll("\"", "")); + } + + public void mapToObj(Object object) throws IllegalAccessException { + Tools.ObjectMapping.mapToObject(object, map); + } + + public void mapToSObj(Class clazz) throws IllegalAccessException { + Tools.ObjectMapping.mapToStaticObject(clazz, map); + } + + public String getValue(String key) { + return this.map.get(key); + } +} diff --git a/src/main/java/de/tudbut/tools/Bash.java b/src/main/java/de/tudbut/tools/Bash.java new file mode 100644 index 0000000..ba70bb7 --- /dev/null +++ b/src/main/java/de/tudbut/tools/Bash.java @@ -0,0 +1,129 @@ +package de.tudbut.tools; + +import de.tudbut.type.StringArray; + +import java.io.*; + +public class Bash { + private final File file; + private final FileRW frw; + private BufferedReader stdO; + private BufferedReader stdE; + private BufferedWriter stdI; + private Process p; + private final StringArray output = new StringArray(); + private final StringArray error = new StringArray(); + + public Bash(String tempFile) throws Exception { + this.file = new File(tempFile); + frw = new FileRW(this.file.getAbsolutePath()); + } + + public void run(String command, boolean enableSTDINPUT) throws Exception { + frw.setContent(command); + + this.p = Runtime.getRuntime().exec("bash " + file.getAbsolutePath()); + this.stdO = new BufferedReader(new + InputStreamReader( + p.getInputStream())); + this.stdE = new BufferedReader(new + InputStreamReader( + p.getErrorStream())); + this.stdI = new BufferedWriter(new + OutputStreamWriter( + p.getOutputStream())); + + if (!enableSTDINPUT) { + String s; + while ((s = this.stdO.readLine()) != null) + this.output.add(s); + while ((s = this.stdE.readLine()) != null) + this.error.add(s); + } + } + + public void run(StringArray command, boolean enableSTDINPUT) throws Exception { + frw.setContent(command); + + this.p = Runtime.getRuntime().exec("bash " + file.getAbsolutePath()); + this.stdO = new BufferedReader(new + InputStreamReader( + p.getInputStream())); + this.stdE = new BufferedReader(new + InputStreamReader( + p.getErrorStream())); + this.stdI = new BufferedWriter(new + OutputStreamWriter( + p.getOutputStream())); + + if (!enableSTDINPUT) { + String s; + while ((s = this.stdO.readLine()) != null) + this.output.add(s); + while ((s = this.stdE.readLine()) != null) + this.error.add(s); + } + } + + public void run(String command) throws Exception { + frw.setContent(command); + + this.p = Runtime.getRuntime().exec("bash " + file.getAbsolutePath()); + this.stdO = new BufferedReader(new + InputStreamReader( + p.getInputStream())); + this.stdE = new BufferedReader(new + InputStreamReader( + p.getErrorStream())); + this.stdI = new BufferedWriter(new + OutputStreamWriter( + p.getOutputStream())); + + String s; + while ((s = this.stdO.readLine()) != null) + this.output.add(s); + while ((s = this.stdE.readLine()) != null) + this.error.add(s); + } + + public void run(StringArray command) throws Exception { + frw.setContent(command); + + this.p = Runtime.getRuntime().exec("bash " + file.getAbsolutePath()); + this.stdO = new BufferedReader(new + InputStreamReader( + p.getInputStream())); + this.stdE = new BufferedReader(new + InputStreamReader( + p.getErrorStream())); + this.stdI = new BufferedWriter(new + OutputStreamWriter( + p.getOutputStream())); + + String s; + while ((s = this.stdO.readLine()) != null) + this.output.add(s); + while ((s = this.stdE.readLine()) != null) + this.error.add(s); + } + + public StringArray getOutput() { + return this.output; + } + + public StringArray getError() { + return this.error; + } + + public String getNextOutput() throws Exception { + return this.stdO.readLine(); + } + + public String getNextError() throws Exception { + return this.stdE.readLine(); + } + + public BufferedWriter getInputWriter() { + return this.stdI; + } +} diff --git a/src/main/java/de/tudbut/tools/BetterClassLoader.java b/src/main/java/de/tudbut/tools/BetterClassLoader.java new file mode 100644 index 0000000..9410a22 --- /dev/null +++ b/src/main/java/de/tudbut/tools/BetterClassLoader.java @@ -0,0 +1,51 @@ +package de.tudbut.tools; + +import de.tudbut.obj.TLClassLoader; + +import java.net.URL; +import java.net.URLClassLoader; +import java.net.URLStreamHandlerFactory; + +public class BetterClassLoader extends URLClassLoader implements TLClassLoader { + public BetterClassLoader(URL[] urls, ClassLoader classLoader) { + super(urls, classLoader); + } + + public BetterClassLoader(URL[] urls) { + super(urls); + } + + public BetterClassLoader(URL[] urls, ClassLoader classLoader, URLStreamHandlerFactory urlStreamHandlerFactory) { + super(urls, classLoader, urlStreamHandlerFactory); + } + + public BetterClassLoader(URLClassLoader loader) { + super(loader.getURLs()); + } + + public Class lc(String s) throws ClassNotFoundException { + setClassAssertionStatus(s, true); + return loadClass(s); + } + + public Class get(String s) throws ClassNotFoundException { + Class c; + if((c = findLoadedClass(s)) != null) { + return c; + } + else + return findClass(s); + } + + public Class friendlyGet(String s) { + try { + Class c; + if ((c = findLoadedClass(s)) != null) { + return c; + } else + return findClass(s); + } catch (ClassNotFoundException e) { + return null; + } + } +} diff --git a/src/main/java/de/tudbut/tools/BetterJ.java b/src/main/java/de/tudbut/tools/BetterJ.java new file mode 100644 index 0000000..3021f8f --- /dev/null +++ b/src/main/java/de/tudbut/tools/BetterJ.java @@ -0,0 +1,138 @@ +package de.tudbut.tools; + +import java.util.HashMap; +import java.util.Map; + +public class BetterJ { + public static ForLoop f() { + return new ForLoop(); + } + + public static Thread t(Runnable runnable) { + Thread thread = new Thread(runnable); + thread.start(); + return thread; + } + + public static void p(String t) { + System.out.println(t); + } + + public static void pn(String t) { + System.out.print(t); + } + + public static Map newMap() { + return new HashMap<>(); + } + + public static final class ForLoop { + private final int step = 1; + private final int to = 1; + private int from = 0; + + ForLoop() { + } + + public ForLoop1 from(int from) { + this.from = from; + return new ForLoop1(this); + } + + public interface ForLoopRunnable { + void run(int i); + } + + public static final class ForLoop1 { + protected int step; + protected int from; + protected int to; + + ForLoop1(ForLoop forLoop) { + this.from = forLoop.from; + this.to = forLoop.to; + this.step = forLoop.step; + } + + public ForLoop2 to(int to) { + this.to = to; + return new ForLoop2(this); + } + } + + public static final class ForLoop2 { + protected int step; + protected int from; + protected int to; + + ForLoop2(ForLoop1 forLoop) { + this.from = forLoop.from; + this.to = forLoop.to; + this.step = forLoop.step; + } + + public ForLoop3 inclusively() { + to += step; + return new ForLoop3(this); + } + + public ForLoop4 step(int step) { + this.step = step; + return new ForLoop4(this); + } + + public void r(ForLoopRunnable r) { + for (int i = from; i < to; i += step) { + r.run(i); + } + } + } + + public static final class ForLoop3 { + protected int step; + protected int from; + protected int to; + + ForLoop3(ForLoop2 forLoop) { + this.from = forLoop.from; + this.to = forLoop.to; + this.step = forLoop.step; + } + + public ForLoop4 step(int step) { + this.step = step; + return new ForLoop4(this); + } + + public void r(ForLoopRunnable r) { + for (int i = from; i < to; i += step) { + r.run(i); + } + } + } + + public static final class ForLoop4 { + protected int step; + protected int from; + protected int to; + + ForLoop4(ForLoop2 forLoop) { + this.from = forLoop.from; + this.to = forLoop.to; + this.step = forLoop.step; + } + + ForLoop4(ForLoop3 forLoop) { + this.from = forLoop.from; + this.to = forLoop.to; + this.step = forLoop.step; + } + + public void r(ForLoopRunnable r) { + for (int i = from; i < to; i += step) { + r.run(i); + } + } + } + } +} diff --git a/src/main/java/de/tudbut/tools/BufferManager.java b/src/main/java/de/tudbut/tools/BufferManager.java new file mode 100644 index 0000000..088b4fd --- /dev/null +++ b/src/main/java/de/tudbut/tools/BufferManager.java @@ -0,0 +1,33 @@ +package de.tudbut.tools; + +import de.tudbut.io.Buffer; + +import java.util.HashMap; +import java.util.Map; + +public class BufferManager { + private static final Map buffers = new HashMap<>(); + + public static long createResource(Buffer buffer) { + long s = newResourceID(); + buffers.put(s, buffer); + return s; + } + + public static Buffer getBufferFromID(long id) { + return buffers.get(id); + } + + private static long newResourceID() { + long s = ExtendedMath.randomLong(0, Long.MAX_VALUE - 1); + + while (buffers.containsKey(s)) + s = ExtendedMath.randomLong(0, Long.MAX_VALUE - 1); + + return s; + } + + public static void removeBuffer(long id) { + buffers.remove(id); + } +} diff --git a/src/main/java/de/tudbut/tools/CMD.java b/src/main/java/de/tudbut/tools/CMD.java new file mode 100644 index 0000000..d833627 --- /dev/null +++ b/src/main/java/de/tudbut/tools/CMD.java @@ -0,0 +1,139 @@ +package de.tudbut.tools; + +import de.tudbut.type.StringArray; + +import java.io.*; + +public class CMD extends Bash { + private final File file; + private final FileRW frw; + private BufferedReader stdO; + private BufferedReader stdE; + private BufferedWriter stdI; + private Process p; + private final StringArray output = new StringArray(); + private final StringArray error = new StringArray(); + + public CMD(String tempFile) throws Exception { + super(tempFile); + this.file = new File(tempFile); + frw = new FileRW(this.file.getAbsolutePath()); + } + + @Override + public void run(String command, boolean enableSTDINPUT) throws Exception { + frw.setContent(command); + + this.p = Runtime.getRuntime().exec(file.getAbsolutePath()); + this.stdO = new BufferedReader(new + InputStreamReader( + p.getInputStream())); + this.stdE = new BufferedReader(new + InputStreamReader( + p.getErrorStream())); + this.stdI = new BufferedWriter(new + OutputStreamWriter( + p.getOutputStream())); + + if (!enableSTDINPUT) { + String s; + while ((s = this.stdO.readLine()) != null) + this.output.add(s); + while ((s = this.stdE.readLine()) != null) + this.error.add(s); + } + } + + @Override + public void run(StringArray command, boolean enableSTDINPUT) throws Exception { + frw.setContent(command); + + this.p = Runtime.getRuntime().exec(file.getAbsolutePath()); + this.stdO = new BufferedReader(new + InputStreamReader( + p.getInputStream())); + this.stdE = new BufferedReader(new + InputStreamReader( + p.getErrorStream())); + this.stdI = new BufferedWriter(new + OutputStreamWriter( + p.getOutputStream())); + + if (!enableSTDINPUT) { + String s; + while ((s = this.stdO.readLine()) != null) + this.output.add(s); + while ((s = this.stdE.readLine()) != null) + this.error.add(s); + } + } + + @Override + public void run(String command) throws Exception { + frw.setContent(command); + + this.p = Runtime.getRuntime().exec(file.getAbsolutePath()); + this.stdO = new BufferedReader(new + InputStreamReader( + p.getInputStream())); + this.stdE = new BufferedReader(new + InputStreamReader( + p.getErrorStream())); + this.stdI = new BufferedWriter(new + OutputStreamWriter( + p.getOutputStream())); + + String s; + while ((s = this.stdO.readLine()) != null) + this.output.add(s); + while ((s = this.stdE.readLine()) != null) + this.error.add(s); + } + + @Override + public void run(StringArray command) throws Exception { + frw.setContent(command); + + this.p = Runtime.getRuntime().exec(file.getAbsolutePath()); + this.stdO = new BufferedReader(new + InputStreamReader( + p.getInputStream())); + this.stdE = new BufferedReader(new + InputStreamReader( + p.getErrorStream())); + this.stdI = new BufferedWriter(new + OutputStreamWriter( + p.getOutputStream())); + + String s; + while ((s = this.stdO.readLine()) != null) + this.output.add(s); + while ((s = this.stdE.readLine()) != null) + this.error.add(s); + } + + @Override + public StringArray getOutput() { + return this.output; + } + + @Override + public StringArray getError() { + return this.error; + } + + @Override + public String getNextOutput() throws Exception { + return this.stdO.readLine(); + } + + @Override + public String getNextError() throws Exception { + return this.stdE.readLine(); + } + + @Override + public BufferedWriter getInputWriter() { + return this.stdI; + } +} diff --git a/src/main/java/de/tudbut/tools/Cache.java b/src/main/java/de/tudbut/tools/Cache.java new file mode 100644 index 0000000..bfdd110 --- /dev/null +++ b/src/main/java/de/tudbut/tools/Cache.java @@ -0,0 +1,187 @@ +package de.tudbut.tools; + +import de.tudbut.parsing.TudSort; + +import java.util.*; + +/** + * Cache (Map with passively expiring values) + * @param + * @param + */ +public class Cache { + protected final ArrayList>[] entries = (ArrayList>[]) new ArrayList[50]; + { + for (int i = 0 ; i < entries.length ; i++) { + entries[i] = new ArrayList<>(); + } + } + + /** + * Add a key + * @param key Key + * @param ttl Time to live + * @param retriever Retriever + */ + public void add(K key, long ttl, CacheRetriever retriever) { + add(key, retriever.doRetrieve(null, key), ttl, retriever); + } + /** + * Add/Set a value to a key + * @param key Key + * @param val The value to set + * @param ttl Time to live + * @param retriever Re-Retriever + */ + public void add(K key, V val, long ttl, CacheRetriever retriever) { + boolean exists = false; + ArrayList> entries = this.entries[Math.abs(key.hashCode() % this.entries.length)]; + for (int i = 0; i < entries.size(); i++) { + Entry entry = entries.get(i); + if (key == entry.key || entry.key.equals(key)) { + exists = true; + if(val == null || retriever == null) { + entries.remove(i); + return; + } + entry.val = val; + entry.timer.lock((int) (entry.ttl = ttl)); + } + } + if(!exists) { + entries.add(new Entry<>(key, val, ttl, retriever)); + } + } + + /** + * Get the value associated to a key + * @param key Key + * @return The value + */ + public V get(K key) { + ArrayList> entries = this.entries[Math.abs(key.hashCode() % this.entries.length)]; + V v = null; + for (int i = 0 ; i < entries.size() ; i++) { + Entry entry = entries.get(i); + v = entry.get(); + if(v == null) + entries.remove(entry); + else if (key == entry.key || entry.key.equals(key)) + break; + else + v = null; + } + return v; + } + + /** + * + * @return All keys + */ + public Set keys() { + ArrayList keys = new ArrayList<>(); + for (int i = 0 ; i < entries.length ; i++) { + for (int j = 0 ; j < entries[i].size() ; j++) { + keys.add(entries[i].get(j).key); + } + } + return new LinkedHashSet(keys); + } + + /** + * + * @return All values + */ + public Set values() { + LinkedHashSet vals = new LinkedHashSet<>(); + for (int i = 0 ; i < entries.length ; i++) { + for (int j = 0 ; j < entries[i].size() ; j++) { + vals.add(entries[i].get(j).val); + } + } + return vals; + } + + public LinkedHashMap linkedHashMap() { + ArrayList> allEntries = new ArrayList<>(); + for (int i = 0 ; i < entries.length ; i++) { + allEntries.addAll(entries[i]); + } + Entry[] entries = TudSort.sort(allEntries.toArray(new Entry[0]), entry -> entry.i); + LinkedHashMap map = new LinkedHashMap<>(); + for (int i = 0 ; i < entries.length ; i++) { + map.put(entries[i].key, entries[i].val); + } + return map; + } + + /** + * Flip keys and values + * @return The flipped cache + */ + public Cache flip() { + Cache cache = new Cache<>(); + ArrayList> allEntries = new ArrayList<>(); + for (int i = 0 ; i < entries.length ; i++) { + for (int j = 0 ; j < entries[i].size() ; j++) { + allEntries.add(entries[i].get(j).flip()); + } + } + Entry[] entries = TudSort.sort(allEntries.toArray(new Entry[0]), entry -> entry.i); + for (int i = 0 ; i < entries.length ; i++) { + cache.add(entries[i].key, entries[i].val, entries[i].ttl, entries[i].retriever); + } + return cache; + } + + protected static class Entry { + protected K key; + protected V val; + protected Lock timer; + protected long ttl; + protected CacheRetriever retriever; + protected static int ni = 0; + protected int i = ni++; + + protected Entry() { + } + + protected V get() { + if(!timer.isLocked()) { + V v = retriever.doRetrieve(val, key); + timer.lock((int) ttl); + val = v; + } + return val; + } + + protected Entry(K key, V val, long ttl, CacheRetriever retriever) { + this.key = key; + this.val = val; + this.timer = new Lock(); + this.ttl = ttl; + this.timer.lock((int) ttl); + this.retriever = retriever; + } + + protected Entry flip() { + return new Entry<>(val, key, 0, new CacheRetriever() { }); + } + } + + public interface CacheRetriever { + default V retrieveFromOld(V old) { + return old; + } + default V retrieveFromKey(K key) { + return null; + } + default V doRetrieve(V old, K key) { + V v; + if((v = retrieveFromKey(key)) == null) { + return retrieveFromOld(old); + } + return v; + } + } +} diff --git a/src/main/java/de/tudbut/tools/CalibratingInterpolator.java b/src/main/java/de/tudbut/tools/CalibratingInterpolator.java new file mode 100644 index 0000000..f4ca669 --- /dev/null +++ b/src/main/java/de/tudbut/tools/CalibratingInterpolator.java @@ -0,0 +1,27 @@ +package de.tudbut.tools; + +public class CalibratingInterpolator extends FloatInterpolator { + + private float sensitivity = 1; + + @Override + public float get(float f) { + if(f < upper && f > lower) + setBounds( + ((lower * sensitivity) + f) / (sensitivity + 1), + ((upper * sensitivity) + f) / (sensitivity + 1) + ); + else { + setBounds(Math.min(lower, f), Math.max(upper, f)); + } + return super.get(f); + } + + public float getSensitivity() { + return 1 / sensitivity; + } + + public void setSensitivity(float sensitivity) { + this.sensitivity = 1 / sensitivity; + } +} diff --git a/src/main/java/de/tudbut/tools/Catcher.java b/src/main/java/de/tudbut/tools/Catcher.java new file mode 100644 index 0000000..7c67181 --- /dev/null +++ b/src/main/java/de/tudbut/tools/Catcher.java @@ -0,0 +1,38 @@ +package de.tudbut.tools; + +import java.util.concurrent.atomic.AtomicBoolean; + +import static de.tudbut.tools.BetterJ.t; + +public class Catcher { + public Exception currentException; + + public void run(ThrowingRunnable runnable) { + AtomicBoolean done = new AtomicBoolean(false); + t(() -> { + try { + runnable.run(); + } + catch (Exception e) { + currentException = e; + } + done.set(true); + }); + while (!done.get()) { + try { + Thread.sleep(0); + } + catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + public void throwNextHere() throws Exception { + while (true) { + if (currentException != null) { + throw currentException; + } + } + } +} diff --git a/src/main/java/de/tudbut/tools/ClassLoaderAdapter.java b/src/main/java/de/tudbut/tools/ClassLoaderAdapter.java new file mode 100644 index 0000000..29eaee0 --- /dev/null +++ b/src/main/java/de/tudbut/tools/ClassLoaderAdapter.java @@ -0,0 +1,28 @@ +package de.tudbut.tools; + +import de.tudbut.obj.TLClassLoader; + +public class ClassLoaderAdapter implements TLClassLoader { + private final ClassLoader cl; + + public ClassLoaderAdapter(ClassLoader classLoader) { + cl = classLoader; + } + + public Class lc(String s) throws ClassNotFoundException { + cl.setClassAssertionStatus(s, true); + return cl.loadClass(s); + } + + public Class get(String s) throws ClassNotFoundException { + return cl.loadClass(s); + } + + public Class friendlyGet(String s) { + try { + return cl.loadClass(s); + } catch (ClassNotFoundException e) { + return null; + } + } +} diff --git a/src/main/java/de/tudbut/tools/Config.java b/src/main/java/de/tudbut/tools/Config.java new file mode 100644 index 0000000..61b8daf --- /dev/null +++ b/src/main/java/de/tudbut/tools/Config.java @@ -0,0 +1,85 @@ +package de.tudbut.tools; + + +import de.tudbut.type.StringArray; + +import java.io.*; + +public class Config { + private final File configFile; + + public Config(String file) throws Exception { + this.configFile = new File(file); + if (!this.configFile.exists()) { + this.configFile.createNewFile(); + new BufferedWriter(new FileWriter(this.configFile)).write(" : ;"); + } + + + } + + + public String get(String cfgPath) throws Exception { + + FileReader reader = new FileReader(this.configFile); + BufferedReader breader = new BufferedReader(reader); + + String cfg = breader.readLine(); + + if (cfg == null) { + cfg = " : ;"; + } + + String[] cfgp1 = cfg.split(";"); + for (String x : cfgp1) { + String[] cfgp2 = x.split(":"); + if (cfgp2[0].equals(cfgPath)) { + return cfgp2[1].replaceAll("&a", ";").replaceAll("&b", ":").replaceAll("&d", "\n").replaceAll("&c", "&"); + } + } + return null; + } + + public void set(String cfgPath, String value) throws Exception { + FileReader reader = new FileReader(this.configFile); + BufferedReader breader = new BufferedReader(reader); + String cfg = breader.readLine(); + breader.close(); + reader.close(); + + FileWriter writer = new FileWriter(this.configFile); + BufferedWriter bwriter = new BufferedWriter(writer); + + if (cfg == null) { + cfg = " : ;"; + } + + String[] cfgp1 = cfg.split(";"); + for (String x : cfgp1) { + if (!x.split(":")[0].equals(cfgPath) && !x.equals(" : ")) bwriter.write(x + ";"); + } + + + bwriter.write(cfgPath); + bwriter.write(":"); + bwriter.write(value.replaceAll("&", "&c").replaceAll(":", "&b").replaceAll(";", "&a").replaceAll("\n", "&d")); + bwriter.write(";"); + bwriter.close(); + } + + public StringArray getList(String cfgPath) throws Exception { + StringArray r = new StringArray(); + for (int i = 0; i < Integer.parseInt(get(cfgPath + "[length]")); i++) { + r.add(get(cfgPath + "[" + i + "]")); + } + + return r; + } + + public void setList(String cfgPath, String[] list) throws Exception { + for (int i = 0; i < list.length; i++) { + set(cfgPath + "[" + i + "]", list[i]); + } + set(cfgPath + "[length]", String.valueOf(list.length)); + } +} \ No newline at end of file diff --git a/src/main/java/de/tudbut/tools/ConfigSaverTCN.java b/src/main/java/de/tudbut/tools/ConfigSaverTCN.java new file mode 100644 index 0000000..aca5cc3 --- /dev/null +++ b/src/main/java/de/tudbut/tools/ConfigSaverTCN.java @@ -0,0 +1,78 @@ +package de.tudbut.tools; + +import de.tudbut.parsing.TCN; +import de.tudbut.debug.Debug; +import de.tudbut.debug.DebugProfiler; +import de.tudbut.obj.Save; + +import java.lang.reflect.Field; + +public class ConfigSaverTCN { + + public static TCN saveConfig(Object object) { + DebugProfiler profiler = Debug.getDebugProfiler(ConfigSaverTCN.class, false); + + profiler.next("Create TCN"); + TCN map = new TCN(); + profiler.next("Get class"); + Class clazz = object.getClass(); + while (clazz != Object.class) { + profiler.next("Classes"); + Field[] fields = clazz.getDeclaredFields(); + try { + for (int i = 0; i < fields.length; i++) { + profiler.next("Fields"); + Field field = fields[i]; + + if(shouldSave(field)) { + // I want to access it + ReflectUtil.forceAccessible(field); + Object o = field.get(object); + ObjectSerializerTCN serializer = new ObjectSerializerTCN(o); + o = serializer.convertAll().done(); + map.set(field.getName(), o); + } + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + + clazz = clazz.getSuperclass(); + } + + profiler.endAll(); + return map; + } + + public static void loadConfig(Object object, TCN tcn) { + Class clazz = object.getClass(); + while (clazz != Object.class) { + Field[] fields = clazz.getDeclaredFields(); + try { + for (int i = 0; i < fields.length; i++) { + Field field = fields[i]; + + if(shouldSave(field)) { + // I want to access it + ReflectUtil.forceAccessible(field); + if (tcn.map.get(field.getName()) != null) { + Object o; + ObjectSerializerTCN serializer = new ObjectSerializerTCN(tcn.getSub(field.getName())); + o = serializer.convertAll().done(); + if (o != null) + field.set(object, o); + } + } + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + + clazz = clazz.getSuperclass(); + } + } + + public static boolean shouldSave(Field field) { + return field.getDeclaredAnnotation(Save.class) != null; + } +} diff --git a/src/main/java/de/tudbut/tools/ConfigSaverTCN2.java b/src/main/java/de/tudbut/tools/ConfigSaverTCN2.java new file mode 100644 index 0000000..602a1de --- /dev/null +++ b/src/main/java/de/tudbut/tools/ConfigSaverTCN2.java @@ -0,0 +1,263 @@ +package de.tudbut.tools; + +import java.lang.reflect.*; +import java.util.ArrayList; +import java.util.Arrays; + +import de.tudbut.parsing.TCN; +import de.tudbut.parsing.TCNArray; +import sun.misc.Unsafe; +import de.tudbut.obj.Save; +import de.tudbut.obj.Transient; + +public class ConfigSaverTCN2 { + + static ArrayList> tcnPrimitives = new ArrayList<>(Arrays.asList( + boolean.class, Boolean.class, + byte.class, Byte.class, + short.class, Short.class, + char.class, Character.class, + int.class, Integer.class, + float.class, Float.class, + long.class, Long.class, + double.class, Double.class, + String.class, + TCN.class, + TCNArray.class + )); + + public static Object write(Object object, boolean writeAll, boolean writeStatic) { + return write(object, writeAll, writeStatic, true); + } + + public static Object write(Object object, boolean writeAll, boolean writeStatic, boolean allowPrimitives) { + if(object == null) { + TCN tcn = new TCN(); + tcn.set("$", "null"); + return tcn; + } + Class objectClass = object.getClass(); + if(objectClass == Class.class && writeStatic) { + objectClass = (Class) object; + object = null; + } + if(tcnPrimitives.contains(objectClass) && allowPrimitives) { + if(object instanceof TCN) + ((TCN) object).set("$", "TCN"); + return object; // just write the object without any wrapping lol + } + + TCN tcn = new TCN(); + if(objectClass.isArray()) { + int len = Array.getLength(object); + tcn.set("$", "[]"); + tcn.set("*", objectClass.getComponentType().getName()); + tcn.set("length", len); + TCNArray tcnArray = new TCNArray(); + for(int i = 0; i < len; i++) { + tcnArray.add(write(Array.get(object, i), true, false, objectClass.getComponentType() != Object.class)); + } + tcn.set("items", tcnArray); + } + else { + tcn.set("$", objectClass.getName()); + if(objectClass.isEnum()) { + tcn.set("*", Arrays.asList(objectClass.getEnumConstants()).indexOf(object)); // Return the equivalent of .ordinal() + return tcn; + } + ArrayList fields = new ArrayList<>(); + + // Read fields + ArrayList> supers = getSupers(objectClass); // getSupers because interfaces can't have fields + for(Class superclass : supers) { + fields.addAll(Arrays.asList(superclass.getDeclaredFields())); + } + fields.addAll(Arrays.asList(objectClass.getDeclaredFields())); + + for(Field field : fields) { + boolean isStatic = (field.getModifiers() & Modifier.STATIC) != 0; + // Ignore field if writeAll is not set and it isn't @Save + if(!writeAll && field.getDeclaredAnnotation(Save.class) == null) + continue; + if(!writeStatic && isStatic) + continue; + if(isStatic && (field.getModifiers() & Modifier.FINAL) != 0) + continue; + if(field.getDeclaredAnnotation(Transient.class) != null) + continue; + + ReflectUtil.forceAccessible(field); // lovely java 18 bypass + Object o; + try { + o = field.get(isStatic ? null : object); + } catch (IllegalArgumentException | IllegalAccessException e) { + // These can't happen + System.err.println("ReflectUtil.forceAccessible silently failed. Exiting."); + throw new Error("ConfigSaverTCN2: ReflectUtil.forceAccessible failed"); + } + tcn.set(field.getName(), write(o, true, false, field.getType() != Object.class)); + } + } + + return tcn; + } + + public static Object convertPrimitive(Object object, Class type) { + if(object instanceof String) { + switch(type.getSimpleName()) { + case "boolean": + case "Boolean": + return Boolean.parseBoolean((String) object); + case "byte": + case "Byte": + return (byte) Integer.parseInt((String) object); + case "short": + case "Short": + return (short) Integer.parseInt((String) object); + case "char": + case "Character": + return ((String) object).charAt(0); + case "int": + case "Integer": + return Integer.parseInt((String) object); + case "float": + case "Float": + return Float.parseFloat((String) object); + case "long": + case "Long": + return Long.parseLong((String) object); + case "double": + case "Double": + return Double.parseDouble((String) object); + } + } + return object; + } + + public static Object read(Object object, Object toReadTo) throws ClassNotFoundException { + Class objectClass = object.getClass(); + if(tcnPrimitives.contains(objectClass) && (!(object instanceof TCN) || ((TCN) object).getString("$").equals("TCN"))) { + return object; // just write the object without any wrapping + } + + boolean forceAllow = ((Integer)(-1)).equals(toReadTo); + if(forceAllow) toReadTo = null; + + TCN tcn = (TCN) object; + if(tcn.getString("$") == null && toReadTo != null) tcn.set("$", toReadTo.getClass().getName()); + if(tcn.getString("$").equals("null")) + return null; + if(tcn.getString("$").equals("[]")) { + try { + objectClass = Class.forName(tcn.getString("*")); + } catch (ClassNotFoundException e) { + switch(tcn.getString("*")) { + case "boolean": + objectClass = boolean.class; + break; + case "byte": + objectClass = byte.class; + break; + case "short": + objectClass = short.class; + break; + case "char": + objectClass = char.class; + break; + case "int": + objectClass = int.class; + break; + case "float": + objectClass = float.class; + break; + case "long": + objectClass = long.class; + break; + case "double": + objectClass = double.class; + break; + } + } + TCNArray tcnArray = tcn.getArray("items"); + Object jArray = Array.newInstance(objectClass, tcn.getInteger("length")); + for(int i = 0; i < tcnArray.size(); i++) { + Array.set(jArray, i, convertPrimitive(read(tcnArray.get(i), null), objectClass)); + } + return jArray; + } + else { + objectClass = Class.forName(tcn.getString("$")); + Object instance = toReadTo; + if(objectClass.isEnum()) { + instance = objectClass.getEnumConstants()[tcn.getInteger("*")]; + return instance; + } + if(instance == null) { + try { + instance = ReflectUtil.theSafe.allocateInstance(objectClass); + } catch (InstantiationException e1) { + // This can't happen + throw new Error(e1); + } + } + ArrayList fields = new ArrayList<>(); + + // Read fields + ArrayList> supers = getSupers(objectClass); // getSupers because interfaces can't have fields + for(Class superclass : supers) { + fields.addAll(Arrays.asList(superclass.getDeclaredFields())); + } + fields.addAll(Arrays.asList(objectClass.getDeclaredFields())); + + for(Field field : fields) { + boolean isStatic = (field.getModifiers() & Modifier.STATIC) != 0; + if(field.getDeclaredAnnotation(Transient.class) != null) + continue; + if(isStatic && (field.getModifiers() & Modifier.FINAL) != 0) + continue; + ReflectUtil.forceAccessible(field); // lovely java 18 bypass + ReflectUtil.eraseFinality(field); // other lovely java 18 bypass + Object o = tcn.get(field.getName()); + if(o == null) { + if(toReadTo != null || isStatic || forceAllow) + continue; + else + throw new IllegalArgumentException("TCN is not complete. Try adding a toReadTo parameter."); + } + try { + field.set(isStatic ? null : instance, convertPrimitive(read(o, null), field.getType())); + } catch (IllegalAccessException e) { + e.printStackTrace(); + // These can't happen + System.err.println("ReflectUtil.forceAccessible silently failed. Exiting."); + throw new Error("ConfigSaverTCN2: ReflectUtil.forceAccessible failed"); + } + } + + return instance; + } + + } + + public static ArrayList> getSupers(Class primary) { + ArrayList> supers = new ArrayList<>(); + Class c = primary; + while(c != null) { + c = c.getSuperclass(); + if(c != null) supers.add(c); + } + return supers; + } + + public static ArrayList> getInterfaces(Class primary) { + ArrayList> supers = new ArrayList<>(); + Class c = primary; + while(c != null) { + supers.addAll(Arrays.asList(c.getInterfaces())); + c = c.getSuperclass(); + if(c != null) supers.add(c); + } + return supers; + } + +} diff --git a/src/main/java/de/tudbut/tools/DiscordRPC.java b/src/main/java/de/tudbut/tools/DiscordRPC.java new file mode 100644 index 0000000..d9d3b1c --- /dev/null +++ b/src/main/java/de/tudbut/tools/DiscordRPC.java @@ -0,0 +1,16 @@ +package de.tudbut.tools; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.net.Socket; +import java.net.SocketImpl; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.SocketChannel; +import java.nio.file.Paths; + +@Deprecated +public class DiscordRPC { + +} diff --git a/src/main/java/de/tudbut/tools/DiscoverClasses.java b/src/main/java/de/tudbut/tools/DiscoverClasses.java new file mode 100644 index 0000000..4c840e8 --- /dev/null +++ b/src/main/java/de/tudbut/tools/DiscoverClasses.java @@ -0,0 +1,107 @@ +package de.tudbut.tools; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class DiscoverClasses { + + ClassLoader loader; + String pkg; + Class annotation; + Class superClass; + ArrayList> interfaces = new ArrayList<>(); + + private DiscoverClasses() {} + + public static DiscoverClasses of(ClassLoader loader) { + DiscoverClasses discover = new DiscoverClasses(); + discover.loader = loader; + return discover; + } + + public DiscoverClasses in(String pkg) { + this.pkg = pkg; + return this; + } + + public DiscoverClasses with(Class annotation) { + this.annotation = annotation; + return this; + } + + public DiscoverClasses extending(Class superClass) { + if(superClass.isInterface()) throw new IllegalArgumentException("Interfaces can't be superclasses!"); + this.superClass = superClass; + return this; + } + + public DiscoverClasses implementing(Class iface) { + if(!iface.isInterface()) throw new IllegalArgumentException("Superclasses can't be interfaces!"); + this.interfaces.add(iface); + return this; + } + + public List> run() { + InputStream stream = loader.getResourceAsStream(pkg.replace('.', '/')); + if(stream == null) + return null; + BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); + List files = reader.lines() + .filter(line -> line.endsWith(".class") || !line.contains(".")) + .map(line -> pkg.replace('.', '/') + "/" + line) + .collect(Collectors.toList()); + for (int i = 0; i < files.size(); i++) { + String file = files.get(i); + if(!file.endsWith(".class")) { + try { + BufferedReader subReader = new BufferedReader(new InputStreamReader(loader.getResourceAsStream(file))); + List subFiles = subReader.lines() + .filter(line -> line.endsWith(".class") || !line.contains(".")) + .map(line -> file + "/" + line) + .collect(Collectors.toList()); + files.addAll(subFiles); + } catch (Exception e) {} + } + } + return files.stream() + .map(line -> { + try { + return loader.loadClass(line.replace('/', '.').substring(0, line.length() - ".class".length())); + } catch (ClassNotFoundException e) { + return null; + } + }) + .filter(Objects::nonNull) + .filter(x -> { + if(annotation != null) { + return x.getDeclaredAnnotation(annotation) != null; + } + return true; + }) + .filter(x -> { + if(superClass != null) { + return x.getSuperclass() == superClass; + } + return true; + }) + .filter(x -> { + if(!interfaces.isEmpty()) { + List> list = Arrays.asList(x.getInterfaces()); + for (Class iface : interfaces) { + if(!list.contains(iface)) + return false; + } + } + return true; + }) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/de/tudbut/tools/ExtendedMath.java b/src/main/java/de/tudbut/tools/ExtendedMath.java new file mode 100644 index 0000000..5d83434 --- /dev/null +++ b/src/main/java/de/tudbut/tools/ExtendedMath.java @@ -0,0 +1,259 @@ +package de.tudbut.tools; + +import de.tudbut.type.FInfo; + +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; + +public class ExtendedMath { + public static long[] solveSimple(String eq, char toSolveChar, int maxResults) throws InterruptedException { + eq = eq.replaceAll("=", "==").replaceAll("======", "==").replaceAll("====", "=="); + + + final boolean[] stop = {false}; + final long[] result = new long[maxResults]; + final char[] current = {0}; + + String finalEq = eq; + new Thread(() -> { + ScriptEngine engine = new ScriptEngineManager().getEngineByExtension("js"); + for (long i = -1; i > Long.MIN_VALUE; i--) { + if (stop[0]) + break; + try { + if (engine.eval("(" + finalEq.replaceAll(String.valueOf(toSolveChar), "(" + i + ")") + ")").equals(true)) { + if (current[0] == result.length) + stop[0] = true; + else { + result[current[0]] = i; + current[0]++; + if (current[0] == result.length) + stop[0] = true; + } + if (stop[0]) + break; + } + } + catch (ScriptException e) { + e.printStackTrace(); + break; + } + } + }).start(); + String finalEq1 = eq; + new Thread(() -> { + ScriptEngine engine = new ScriptEngineManager().getEngineByExtension("js"); + for (long i = 0; i < Long.MAX_VALUE; i++) { + if (stop[0]) + break; + try { + if (engine.eval("(" + finalEq1.replaceAll(String.valueOf(toSolveChar), "(" + i + ")") + ")").equals(true)) { + if (current[0] == result.length) + stop[0] = true; + else { + result[current[0]] = i; + current[0]++; + if (current[0] == result.length) + stop[0] = true; + } + if (stop[0]) + break; + } + } + catch (ScriptException e) { + e.printStackTrace(); + break; + } + } + }).start(); + + while (!stop[0]) { + Thread.sleep(0); + } + + return result; + } + + public static long solveSimple(String eq, char toSolveChar) throws InterruptedException { + return solveSimple(eq, toSolveChar, 1)[0]; + } + + public static double[] solveDouble(String eq, char toSolveChar, int precision, int maxResults) throws InterruptedException { + eq = eq.replaceAll("=", "==").replaceAll("======", "==").replaceAll("====", "=="); + + + final boolean[] stop = {false}; + final double[] result = new double[maxResults]; + final char[] current = {0}; + + String finalEq = eq; + new Thread(() -> { + ScriptEngine engine = new ScriptEngineManager().getEngineByExtension("js"); + for (long i = -1; i > Long.MIN_VALUE; i--) { + if (stop[0]) + break; + try { + if (engine.eval(finalEq.replaceAll(String.valueOf(toSolveChar), "(" + (double) i / (double) precision + ")")).equals(true)) { + if (current[0] == result.length) + stop[0] = true; + else { + result[current[0]] = (double) i / (double) precision; + current[0]++; + if (current[0] == result.length) + stop[0] = true; + } + if (stop[0]) + break; + } + } + catch (ScriptException e) { + e.printStackTrace(); + break; + } + } + }).start(); + String finalEq1 = eq; + new Thread(() -> { + ScriptEngine engine = new ScriptEngineManager().getEngineByExtension("js"); + for (long i = 0; i < Long.MAX_VALUE; i++) { + if (stop[0]) + break; + try { + if (engine.eval(finalEq1.replaceAll(String.valueOf(toSolveChar), "(" + (double) i / (double) precision + ")")).equals(true)) { + if (current[0] == result.length) + stop[0] = true; + else { + result[current[0]] = (double) i / (double) precision; + current[0]++; + if (current[0] == result.length) + stop[0] = true; + } + if (stop[0]) + break; + } + } + catch (ScriptException e) { + e.printStackTrace(); + break; + } + } + }).start(); + + while (!stop[0]) { + Thread.sleep(0); + } + + return result; + } + + + public static double solveDouble(String eq, char toSolveChar, int precision) throws InterruptedException { + return solveDouble(eq, toSolveChar, precision, 1)[0]; + } + + public static double solveDouble(String eq, char toSolveChar) throws InterruptedException { + return solveDouble(eq, toSolveChar, 100, 1)[0]; + } + + public static double[] solveDouble(String eq, char toSolveChar, long maxResults) throws InterruptedException { + return solveDouble(eq, toSolveChar, 100, (int) maxResults); + } + + public static int random(int lower, int upper) { + return (int) randomLong(lower, upper); + } + + public static long randomLong(long lower, long upper) { + upper ++; + return (long) (Math.floor(Math.random() * (upper - lower)) + lower); + } + + public static float randomFloat(float lower, float upper, int precision) { + return (float) randomLong((int) (lower * (precision)), (int) (upper * (precision))) / (precision); + } + + public static double min(double... doubles) { + double currentMin = doubles[0]; + + for (int i = 1; i < doubles.length; i++) { + currentMin = Math.min(currentMin, doubles[i]); + } + + return currentMin; + } + + public static int min(int... ints) { + int currentMin = ints[0]; + + for (int i = 1; i < ints.length; i++) { + currentMin = Math.min(currentMin, ints[i]); + } + + return currentMin; + } + + public static double max(double... doubles) { + double currentMax = doubles[0]; + + for (int i = 1; i < doubles.length; i++) { + currentMax = Math.max(currentMax, doubles[i]); + } + + return currentMax; + } + + public static int max(int... ints) { + int currentMax = ints[0]; + + for (int i = 1; i < ints.length; i++) { + currentMax = Math.max(currentMax, ints[i]); + } + + return currentMax; + } + + public static double highestMinusLowest(double d1, double d2) { + if (d1 > d2) { + return d1 - d2; + } + if (d2 > d1) { + return d2 - d1; + } + return 0; + } + + public static int highestMinusLowest(int i1, int i2) { + if (i1 > i2) { + return i1 - i2; + } + if (i2 > i1) { + return i2 - i1; + } + return 0; + } + + public static T[] flipArray(T[] array) { + T[] oldArray = array.clone(); + + for (int i = 0; i < array.length; i++) { + array[-i + array.length - 1] = oldArray[i]; + } + + return array; + } + + public static double removeSign(double d) { + return d < 0 ? -d : d; + } + + @FInfo(s = "only requires ~0.5 measurable ticks (-> new Date().toInstant().getNanos()) while Math.round takes ~5") + public static long fastRound(double d) { + return d - (long) d < 0.5 ? (long) d : (long) d + 1; + } + + @FInfo(s = "only requires ~0.5 measurable ticks (-> new Date().toInstant().getNanos()) while Math.round takes ~5") + public static int fastIntRound(double d) { + return d - (int) d < 0.5 ? (int) d : (int) d + 1; + } +} diff --git a/src/main/java/de/tudbut/tools/FileRW.java b/src/main/java/de/tudbut/tools/FileRW.java new file mode 100644 index 0000000..685985e --- /dev/null +++ b/src/main/java/de/tudbut/tools/FileRW.java @@ -0,0 +1,154 @@ +package de.tudbut.tools; + +import de.tudbut.io.StreamReader; +import de.tudbut.io.StreamWriter; +import de.tudbut.logger.LoggerSink; +import de.tudbut.type.StringArray; +import de.tudbut.global.DebugStateManager; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.concurrent.atomic.AtomicReference; + +public class FileRW { + + private final AtomicReference logger; + private final StringArray lines; + protected final File file; + + public FileRW(String path) throws IOException { + + this.file = new File(path); + logger = DebugStateManager.debugLoggerReference("FileRW " + path); + + logger.get().info("INIT"); + if (!this.file.exists()) { + logger.get().info("Creating file..."); + this.file.createNewFile(); + new BufferedWriter(new FileWriter(this.file)).write("\n"); + logger.get().info("Done."); + } + this.lines = new StringArray(); + logger.get().info("Reading file..."); + rereadFile(); + logger.get().info("Read file."); + } + + public StringArray getContent() { + return this.lines; + } + + public void setContent(String content) throws IOException { + logger.get().info("Writing file..."); + this.lines.clear(); + this.lines.set(content.split("\n")); + FileOutputStream fileWriter = new FileOutputStream(this.file); + new StreamWriter(fileWriter).writeChars(content.toCharArray()); + fileWriter.close(); + logger.get().info("Done."); + } + + public void safeSetContent(String content) throws IOException { + logger.get().info("Writing file safely..."); + this.lines.clear(); + this.lines.set(content.split("\n")); + File file = new File(this.file.getAbsolutePath() + ".tmp"); + FileOutputStream fileWriter = new FileOutputStream(file); + new StreamWriter(fileWriter).writeChars(content.toCharArray()); + fileWriter.close(); + Files.move(file.toPath(), this.file.toPath(), StandardCopyOption.REPLACE_EXISTING); + logger.get().info("Done."); + } + + public void setContent(StringArray content) throws IOException { + setContent(content.join("\n")); + } + + public void rereadFile() throws IOException { + StreamReader reader = new StreamReader(new FileInputStream(file)); + char[] chars = reader.readAllAsChars(); + String[] s = new String(chars).split("\n"); + lines.clear(); + for (String value : s) { + lines.add(value); + } + } +} + +/*import de.tudbut.logger.GlobalLogger; +import de.tudbut.logger.Logger; +import de.tudbut.type.StringArray; + +import java.io.*; + +public class FileRW { + private File file = null; + private Logger logger = null; + private StringArray lines = null; + + public FileRW(String path) throws Exception { + GlobalLogger.info("Initialising FileRW " + path); + this.file = new File(path); + this.logger = new Logger("FileRW " + path); + if(!this.file.exists()) { + this.logger.info("Creating file..."); + this.file.createNewFile(); + new BufferedWriter(new FileWriter(this.file)).write("\n"); + this.logger.info("Done!"); + } + FileReader fileReader = new FileReader(this.file); + BufferedReader reader = new BufferedReader(fileReader); + this.lines = new StringArray(new String[]{""}); + String _line = ""; + while((_line = reader.readLine()) != null) { + this.lines = this.lines.add(_line); + } + this.logger.info("FileRW " + path + " initialized."); + } + + public String getContent() throws Exception { + FileReader fileReader = new FileReader(this.file); + BufferedReader reader = new BufferedReader(fileReader); + this.lines = new StringArray(new String[]{""}); + String _line = ""; + String __lines = ""; + while((_line = reader.readLine()) != null) { + /*this.lines = this.lines.add(_line); + this.logger.info(this.lines.join("\n"));*//* + __lines = __lines + "\n" + _line; + } + return __lines; + } + + public String getLine(int line) throws Exception { + FileReader fileReader = new FileReader(this.file); + BufferedReader reader = new BufferedReader(fileReader); + + reader.close(); + return lines.asArray()[line]; + } + + public void setContent(String content) throws Exception { + FileWriter fileWriter = new FileWriter(this.file); + BufferedWriter writer = new BufferedWriter(fileWriter); + this.lines = new StringArray(content.split("\n")); + for (String line : lines.asArray()) { + writer.write(line); + writer.newLine(); + } + writer.close(); + } + + public void setLine(int line, String content) throws Exception { + FileWriter fileWriter = new FileWriter(this.file); + BufferedWriter writer = new BufferedWriter(fileWriter); + lines.asArray()[line] = content; + for (String line_ : lines.asArray()) { + writer.write(line_); + writer.newLine(); + } + writer.close(); + } +} +*/ \ No newline at end of file diff --git a/src/main/java/de/tudbut/tools/FloatInterpolator.java b/src/main/java/de/tudbut/tools/FloatInterpolator.java new file mode 100644 index 0000000..c0cbc81 --- /dev/null +++ b/src/main/java/de/tudbut/tools/FloatInterpolator.java @@ -0,0 +1,20 @@ +package de.tudbut.tools; + +public class FloatInterpolator { + + float diff, lower, upper; + + public void setBounds(float lower, float upper) { + if(lower > upper) + throw new IllegalArgumentException("Lower bound > Upper bound"); + + this.lower = lower; + this.upper = upper; + this.diff = upper - lower; + } + + public float get(float f) { + f = f - lower; + return f / diff; + } +} diff --git a/src/main/java/de/tudbut/tools/Hasher.java b/src/main/java/de/tudbut/tools/Hasher.java new file mode 100644 index 0000000..4a7380c --- /dev/null +++ b/src/main/java/de/tudbut/tools/Hasher.java @@ -0,0 +1,144 @@ +package de.tudbut.tools; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class Hasher { + public static final String TYPE_SHA256HEX = "sha256hex"; + public static final String TYPE_SHA512HEX = "sha512hex"; + public static final String TYPE_INT = "int"; + @SuppressWarnings("CanBeFinal") + public static String LETTERS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"§$%&/()=?#_-.:,;µ<>|^°{[]}\\ "; + + public static String sha256hex(String toHash) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance("SHA-256"); + } + catch (NoSuchAlgorithmException e) { + throw new RuntimeException("Impossible condition reached"); + } + return hash(toHash, digest); + } + + public static String sha512hex(String toHash) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance("SHA-512"); + } + catch (NoSuchAlgorithmException e) { + throw new RuntimeException("Impossible condition reached"); + } + return hash(toHash, digest); + } + + private static String hash(String toHash, MessageDigest digest) { + byte[] hash = digest.digest( + toHash.getBytes(StandardCharsets.UTF_8)); + StringBuilder hexString = new StringBuilder(); + for (byte b : hash) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) hexString.append('0'); + hexString.append(hex); + } + return hexString.toString(); + } + + public static String bruteforce(String type, String hash, int beginAt) { + switch (type) { + case TYPE_SHA256HEX: + return bf_sha256hex(hash, beginAt); + case TYPE_SHA512HEX: + return bf_sha512hex(hash, beginAt); + case TYPE_INT: + return bf_int(Integer.parseInt(hash), beginAt); + } + return null; + } + + private static String bf_int(int hash, int beginAt) { + for (int length = beginAt; true; length++) { + String s; + if ((s = bf_int_tryGenChar("", 0, length, hash)) != null) { + if (s.hashCode() == hash) + return s; + } + } + } + + private static String bf_int_tryGenChar(String str, int pos, int length, int hash) { + if (length == 0) { + if (str.hashCode() == hash) + return str; + } + else { + if (pos != 0) { + pos = 0; + } + for (int i = pos; i < LETTERS.toCharArray().length; i++) { + String s; + if ((s = bf_int_tryGenChar(str + LETTERS.charAt(i), i, length - 1, hash)) != null) { + return s; + } + } + } + return null; + } + + private static String bf_sha256hex(String hash, int beginAt) { + for (int length = beginAt; true; length++) { + String s; + if ((s = bf_sha256hex_tryGenChar("", 0, length, hash)) != null) { + return s; + } + } + } + + private static String bf_sha256hex_tryGenChar(String str, int pos, int length, String hash) { + if (length == 0) { + if (sha256hex(str).equals(hash)) + return str; + } + else { + if (pos != 0) { + pos = 0; + } + for (int i = pos; i < LETTERS.toCharArray().length; i++) { + String s; + if ((s = bf_sha256hex_tryGenChar(str + LETTERS.charAt(i), i, length - 1, hash)) != null) { + return s; + } + } + } + return null; + } + + private static String bf_sha512hex(String hash, int beginAt) { + for (int length = beginAt; true; length++) { + String s; + if ((s = bf_sha512hex_tryGenChar("", 0, length, hash)) != null) { + return s; + } + } + } + + private static String bf_sha512hex_tryGenChar(String str, int pos, int length, String hash) { + if (length == 0) { + if (sha512hex(str).equals(hash)) + return str; + } + else { + if (pos != 0) { + pos = 0; + } + for (int i = pos; i < LETTERS.toCharArray().length; i++) { + String s; + if ((s = bf_sha512hex_tryGenChar(str + LETTERS.charAt(i), i, length - 1, hash)) != null) { + return s; + } + } + } + return null; + } +} diff --git a/src/main/java/de/tudbut/tools/ImageUtils.java b/src/main/java/de/tudbut/tools/ImageUtils.java new file mode 100644 index 0000000..de48968 --- /dev/null +++ b/src/main/java/de/tudbut/tools/ImageUtils.java @@ -0,0 +1,442 @@ +package de.tudbut.tools; + +import de.tudbut.rendering.Maths2D; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.Arrays; + +import static java.lang.Math.*; + +public class ImageUtils { + + public static BufferedImage antiAlias(BufferedImage image, int amount) { + return antiAlias(image, amount, 20); + } + + public static BufferedImage antiAlias(BufferedImage image, int amount, float m) { + Color color = getAverageColor(image); + int i = 0; + i += color.getRed(); + i += color.getGreen(); + i += color.getBlue(); + i += color.getAlpha(); + if(i > (0xff * 2)) { + image = invert(image); + } + BufferedImage img = smoothen(getContrastColors(image), amount, m); + BufferedImage out = new BufferedImage(image.getWidth(), image.getHeight(), image.getColorModel().hasAlpha() ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); + for (int x = 0; x < image.getWidth(); x++) { + for (int y = 0; y < image.getHeight(); y++) { + Color c = new Color(image.getRGB(x,y), image.getColorModel().hasAlpha()); + Color sc = new Color(img.getRGB(x,y), true); + out.setRGB(x,y, new Color( + max(c.getRed(), sc.getRed()), + max(c.getGreen(), sc.getGreen()), + max(c.getBlue(), sc.getBlue()), + 255 + ).getRGB()); + } + } + if(i > (0xff * 2)) { + out = invert(out); + } + return out; + } + + public static BufferedImage whiteContrast(BufferedImage image, int amount) { + BufferedImage img = smoothen(getContrast(image), amount, 2); + BufferedImage out = new BufferedImage(image.getWidth(), image.getHeight(), image.getColorModel().hasAlpha() ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); + for (int x = 0; x < image.getWidth(); x++) { + for (int y = 0; y < image.getHeight(); y++) { + Color c = new Color(image.getRGB(x,y), image.getColorModel().hasAlpha()); + Color sc = new Color(img.getRGB(x,y), image.getColorModel().hasAlpha()); + out.setRGB(x,y, new Color( + min(abs(c.getRed() + sc.getRed()), 255), + min(abs(c.getGreen() + sc.getGreen()), 255), + min(abs(c.getBlue() + sc.getBlue()), 255), + 255 + ).getRGB()); + } + } + return out; + } + + public static BufferedImage whiteContrastColored(BufferedImage image, int amount) { + BufferedImage img = smoothen(getContrastColors(image), amount, 2); + BufferedImage out = new BufferedImage(image.getWidth(), image.getHeight(), image.getColorModel().hasAlpha() ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); + for (int x = 0; x < image.getWidth(); x++) { + for (int y = 0; y < image.getHeight(); y++) { + Color c = new Color(image.getRGB(x,y), image.getColorModel().hasAlpha()); + Color sc = new Color(img.getRGB(x,y), image.getColorModel().hasAlpha()); + out.setRGB(x,y, new Color( + min(abs(c.getRed() + sc.getRed()), 255), + min(abs(c.getGreen() + sc.getGreen()), 255), + min(abs(c.getBlue() + sc.getBlue()), 255), + 255 + ).getRGB()); + } + } + return out; + } + + public static BufferedImage contrast(BufferedImage image, int amount) { + BufferedImage img = smoothen(getContrast(image), amount); + BufferedImage out = new BufferedImage(image.getWidth(), image.getHeight(), image.getColorModel().hasAlpha() ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); + for (int x = 0; x < image.getWidth(); x++) { + for (int y = 0; y < image.getHeight(); y++) { + Color c = new Color(image.getRGB(x,y), image.getColorModel().hasAlpha()); + Color sc = new Color(img.getRGB(x,y), image.getColorModel().hasAlpha()); + out.setRGB(x,y, new Color( + abs(c.getRed() - sc.getRed()), + abs(c.getGreen() - sc.getGreen()), + abs(c.getBlue() - sc.getBlue()), + 255 + ).getRGB()); + } + } + return out; + } + + public static BufferedImage contrastColored(BufferedImage image, int smoothness, int amount) { + for (int i = 0 ; i < amount ; i++) { + image = contrastColored(image, smoothness); + } + return image; + } + + public static BufferedImage contrastColored(BufferedImage image, int smoothness) { + BufferedImage img = smoothen(getContrastColors(image), smoothness); + BufferedImage out = new BufferedImage(image.getWidth(), image.getHeight(), image.getColorModel().hasAlpha() ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); + for (int x = 0; x < image.getWidth(); x++) { + for (int y = 0; y < image.getHeight(); y++) { + Color c = new Color(image.getRGB(x,y), image.getColorModel().hasAlpha()); + Color sc = new Color(img.getRGB(x,y), image.getColorModel().hasAlpha()); + out.setRGB(x,y, new Color( + abs(c.getRed() - sc.getRed()), + abs(c.getGreen() - sc.getGreen()), + abs(c.getBlue() - sc.getBlue()), + 255 + ).getRGB()); + } + } + return out; + } + + public static BufferedImage smoothen(BufferedImage image, int amount, float m) { + float[][][] floats = imageToFloats(image); + float[][][] floats1 = new float[4][floats.length][floats[0].length]; + for (int x = 0 ; x < floats.length ; x++) { + for (int y = 0 ; y < floats[0].length ; y++) { + floats1[0][x][y] = floats[x][y][0]; + floats1[1][x][y] = floats[x][y][1]; + floats1[2][x][y] = floats[x][y][2]; + floats1[3][x][y] = floats[x][y][3]; + } + } + NoiseGenerator.smooth(new float[][][]{floats1[0]}, 0, 0, 0, 1, image.getWidth(), image.getHeight(), amount, m); + NoiseGenerator.smooth(new float[][][]{floats1[1]}, 0, 0, 0, 1, image.getWidth(), image.getHeight(), amount, m); + NoiseGenerator.smooth(new float[][][]{floats1[2]}, 0, 0, 0, 1, image.getWidth(), image.getHeight(), amount, m); + NoiseGenerator.smooth(new float[][][]{floats1[3]}, 0, 0, 0, 1, image.getWidth(), image.getHeight(), amount, m); + for (int x = 0 ; x < floats.length ; x++) { + for (int y = 0 ; y < floats[0].length ; y++) { + floats[x][y][0] = floats1[0][x][y]; + floats[x][y][1] = floats1[1][x][y]; + floats[x][y][2] = floats1[2][x][y]; + floats[x][y][3] = floats1[3][x][y]; + } + } + return floatsToImage(floats); + } + + public static BufferedImage smoothen(BufferedImage image, int amount) { + return smoothen(image, amount, 2); + } + + public static BufferedImage smoothenColors(BufferedImage image, int amount) { + return smoothenColors(image, amount, 2); + } + + public static BufferedImage smoothenColors(BufferedImage image, int amount, float m) { + float[][][] floats = imageToFloats(image); + NoiseGenerator.smooth(floats, 0, 0, 0, image.getWidth(), image.getHeight(), 4, amount, m); + return floatsToImage(floats); + } + + public static float[][][] imageToFloats(BufferedImage image) { + float[][][] floats = new float[image.getWidth()][image.getHeight()][4]; + for (int x = 0 ; x < floats.length ; x++) { + for (int y = 0 ; y < floats[0].length ; y++) { + float[] c = floats[x][y]; + Color color = new Color(image.getRGB(x,y), true); + c[0] = color.getRed() / 255f; + c[1] = color.getGreen() / 255f; + c[2] = color.getBlue() / 255f; + c[3] = color.getAlpha() / 255f; + } + } + return floats; + } + + public static BufferedImage floatsToImage(float[][][] floats) { + BufferedImage image = new BufferedImage(floats.length, floats[0].length, BufferedImage.TYPE_INT_ARGB); + for (int x = 0 ; x < floats.length ; x++) { + for (int y = 0 ; y < floats[0].length ; y++) { + float[] c = floats[x][y]; + Color color = new Color(c[0], c[1], c[2], c[3]); + image.setRGB(x,y, color.getRGB()); + } + } + return image; + } + + public static BufferedImage getContrast(BufferedImage image) { + BufferedImage out = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB); + for (int x = 0 ; x < image.getWidth() ; x++) { + for (int y = 0 ; y < image.getHeight() ; y++) { + Color c0 = new Color(image.getRGB(x, y), image.getColorModel().hasAlpha()); + int i = c0.getRed() + c0.getGreen() + c0.getBlue() + c0.getAlpha(); + for (int ox = -1 ; ox <= 1 ; ox++) { + for (int oy = -1 ; oy <= 1 ; oy++) { + int tx = x + ox; + int ty = y + oy; + if(tx >= 0 && ty >= 0) { + if(tx < image.getWidth() && ty < image.getHeight()) { + Color c1 = new Color(image.getRGB(tx, ty), image.getColorModel().hasAlpha()); + i += abs(c0.getAlpha() - c1.getAlpha()); + i += abs(c0.getRed() - c1.getRed()); + i += abs(c0.getGreen() - c1.getGreen()); + i += abs(c0.getBlue() - c1.getBlue()); + } + } + } + } + i /= 9; + i = min(i, 0xff); + Color color = new Color(i, i, i, 0xff); + out.setRGB(x, y, color.getRGB()); + } + } + return out; + } + + + public static BufferedImage getContrastColors(BufferedImage image) { + BufferedImage out = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB); + for (int x = 0 ; x < image.getWidth() ; x++) { + for (int y = 0 ; y < image.getHeight() ; y++) { + Color c0 = new Color(image.getRGB(x, y), image.getColorModel().hasAlpha()); + Color[] c = new Color[9]; + Arrays.fill(c, c0); + int i = 0; + for (int ox = -1 ; ox <= 1 ; ox++) { + for (int oy = -1 ; oy <= 1 ; oy++) { + int tx = x + ox; + int ty = y + oy; + if(tx >= 0 && ty >= 0) { + if(tx < image.getWidth() && ty < image.getHeight()) { + Color c1 = new Color(image.getRGB(tx, ty), image.getColorModel().hasAlpha()); + c[i++] = new Color( + abs(c0.getRed() - c1.getRed()), + abs(c0.getGreen() - c1.getGreen()), + abs(c0.getBlue() - c1.getBlue()), + abs(c0.getAlpha() - c1.getAlpha()) + ); + } + } + } + } + Color color = new Color( + min(( + c[0].getRed() + c[1].getRed() + c[2].getRed() + + c[3].getRed() + c[4].getRed() + c[5].getRed() + + c[6].getRed() + c[7].getRed() + c[8].getRed() + ) / 4.5f / 255, 1), + min(( + c[0].getGreen() + c[1].getGreen() + c[2].getGreen() + + c[3].getGreen() + c[4].getGreen() + c[5].getGreen() + + c[6].getGreen() + c[7].getGreen() + c[8].getGreen() + ) / 4.5f / 255, 1), + min(( + c[0].getBlue() + c[1].getBlue() + c[2].getBlue() + + c[3].getBlue() + c[4].getBlue() + c[5].getBlue() + + c[6].getBlue() + c[7].getBlue() + c[8].getBlue() + ) / 4.5f / 255, 1), + min(( + c[0].getAlpha() + c[1].getAlpha() + c[2].getAlpha() + + c[3].getAlpha() + c[4].getAlpha() + c[5].getAlpha() + + c[6].getAlpha() + c[7].getAlpha() + c[8].getAlpha() + ) / 4.5f / 255, 1) + ); + out.setRGB(x, y, color.getRGB()); + } + } + return out; + } + + public static BufferedImage getDifference(BufferedImage image0, BufferedImage image1) { + if(image0.getWidth() != image1.getWidth() || image0.getHeight() != image1.getHeight()) + throw new IllegalArgumentException("Width and Height don't match!"); + + BufferedImage out = new BufferedImage(image0.getWidth(), image0.getHeight(), BufferedImage.TYPE_INT_RGB); + for (int x = 0 ; x < image0.getWidth() ; x++) { + for (int y = 0 ; y < image0.getHeight() ; y++) { + Color c0 = new Color(image0.getRGB(x, y), image0.getColorModel().hasAlpha()); + Color c1 = new Color(image1.getRGB(x, y), image1.getColorModel().hasAlpha()); + Color color = new Color( + abs(c0.getRed() - c1.getRed()), + abs(c0.getGreen() - c1.getGreen()), + abs(c0.getBlue() - c1.getBlue()), + abs(c0.getAlpha() - c1.getAlpha()) + ); + out.setRGB(x, y, color.getRGB()); + } + } + return out; + } + + /** + * Returns the estimated similarity between two images to a computer + * @param image0 + * @param image1 + * @return The similarity, 0.0f to 1.0f + */ + public static float getSimilarity(BufferedImage image0, BufferedImage image1) { + float f; + int fullDiff = 0; + BufferedImage diff = getDifference(Maths2D.distortImage(image0, 2, 2, 1), Maths2D.distortImage(image1, 2, 2, 1)); + for (int x = 0 ; x < 2 ; x++) { + for (int y = 0 ; y < 2 ; y++) { + Color color = new Color(diff.getRGB(x,y)); + fullDiff += color.getRed(); + fullDiff += color.getGreen(); + fullDiff += color.getBlue(); + } + } + fullDiff = max(255 - fullDiff, 0); + f = fullDiff / 255f; + + for (int n = 4 ; n < 100 ; n+=4) { + fullDiff = 0; + diff = getDifference(Maths2D.distortImage(image0, n, n, 1), Maths2D.distortImage(image1, n, n, 1)); + for (int x = 0 ; x < n ; x++) { + for (int y = 0 ; y < n ; y++) { + Color color = new Color(diff.getRGB(x,y)); + int i = 0; + i += color.getRed(); + i += color.getGreen(); + i += color.getBlue(); + i /= 3; + fullDiff += i; + } + } + fullDiff /= n / 2f * n / 2f; + fullDiff = max(255 - fullDiff, 0); + f = f*n/4f + fullDiff / 255f; + f /= n / 4f + 1; + } + return f; + } + + public static Color getAverageColor(BufferedImage image) { + float r=0,g=0,b=0,a=0; + float[][][] floats = imageToFloats(image); + for (int x = 0 ; x < floats.length ; x++) { + for (int y = 0 ; y < floats[0].length ; y++) { + r += floats[x][y][0]; + g += floats[x][y][1]; + b += floats[x][y][2]; + a += floats[x][y][3]; + } + } + r /= image.getWidth() * image.getHeight(); + g /= image.getWidth() * image.getHeight(); + b /= image.getWidth() * image.getHeight(); + a /= image.getWidth() * image.getHeight(); + return new Color(r,g,b,a); + } + + /** + * Returns the estimated similarity between two images to a computer and human, + * this uses line and color theme detection to ensure the results staying one + * with the ones the human sees + * @param image0 + * @param image1 + */ + public static float getSimilarityV2(BufferedImage image0, BufferedImage image1) { + float f = 0; + Color c0; + Color c1; + int colorDiff; + + image0 = Maths2D.distortImage(image0, 128, 128, 1); + image1 = Maths2D.distortImage(image1, 128, 128, 1); + f += (1-getSimilarity(image0, image1)); + c0 = getAverageColor(image0); + c1 = getAverageColor(image1); + colorDiff = 0; + colorDiff += abs(c0.getRed() - c1.getRed()); + colorDiff += abs(c0.getGreen() - c1.getGreen()); + colorDiff += abs(c0.getBlue() - c1.getBlue()); + colorDiff += abs(c0.getAlpha() - c1.getAlpha()); + colorDiff = min(colorDiff, 0x80); + f += (colorDiff / 128f) * 3; + image0 = getContrastColors(image0); + image1 = getContrastColors(image1); + f += 1-getSimilarity(image0, image1); + c0 = getAverageColor(image0); + c1 = getAverageColor(image1); + colorDiff = 0; + colorDiff += abs(c0.getRed() - c1.getRed()); + colorDiff += abs(c0.getGreen() - c1.getGreen()); + colorDiff += abs(c0.getBlue() - c1.getBlue()); + colorDiff += abs(c0.getAlpha() - c1.getAlpha()); + colorDiff = min(colorDiff, 0x80); + f += (colorDiff / 128f) * 3; + f /= 8; + f = 1-f; + return f; + } + + public static BufferedImage invert(BufferedImage image) { + BufferedImage r = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB); + + for (int x = 0; x < r.getWidth(); x++) { + for (int y = 0; y < r.getHeight(); y++) { + Color color = new Color(image.getRGB(x,y)); + color = new Color( + 0xff - color.getRed(), + 0xff - color.getGreen(), + 0xff - color.getBlue(), + color.getAlpha() + ); + r.setRGB(x, y, color.getRGB()); + } + } + + return r; + } + + public static BufferedImage detailMap(BufferedImage image) { + return smoothen(getContrast(contrastColored(image, 2)), 10); + } + + public static BufferedImage compress(BufferedImage image, float amount, boolean alpha) { + BufferedImage out = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB); + + for (int x = 0 ; x < image.getWidth() ; x++) { + for (int y = 0 ; y < image.getHeight() ; y++) { + Color color = new Color(image.getRGB(x,y)); + color = new Color( + (int) (floor(color.getRed() / amount) * amount), + (int) (floor(color.getGreen() / amount) * amount), + (int) (floor(color.getBlue() / amount) * amount), + !alpha ? color.getAlpha() : (int) (Math.floor(color.getAlpha() / amount) * amount) + ); + out.setRGB(x, y, color.getRGB()); + } + } + + return out; + } +} diff --git a/src/main/java/de/tudbut/tools/JButtonList.java b/src/main/java/de/tudbut/tools/JButtonList.java new file mode 100644 index 0000000..7ef03cd --- /dev/null +++ b/src/main/java/de/tudbut/tools/JButtonList.java @@ -0,0 +1,31 @@ +package de.tudbut.tools; + +import javax.swing.*; +import java.awt.*; + +public class JButtonList { + public final JPanel pane; + public final JScrollPane scrollPane; + + public JButtonList(Container component) { + pane = new JPanel(); + pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS)); + pane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + scrollPane = new JScrollPane(pane); + component.add(scrollPane); + scrollPane.getVerticalScrollBar().setUnitIncrement(10); + } + + public void addButton(JButton button, JButtonListRunnable onClick) { + button.addActionListener(actionEvent -> onClick.run(button, pane, this)); + pane.add(button); + pane.add(Box.createRigidArea(new Dimension(0,5))); + pane.doLayout(); + scrollPane.updateUI(); + scrollPane.repaint(); + } + + public interface JButtonListRunnable { + void run(JButton button, JPanel pane, JButtonList buttonList); + } +} diff --git a/src/main/java/de/tudbut/tools/Keyboard.java b/src/main/java/de/tudbut/tools/Keyboard.java new file mode 100644 index 0000000..3e81102 --- /dev/null +++ b/src/main/java/de/tudbut/tools/Keyboard.java @@ -0,0 +1,113 @@ +package de.tudbut.tools; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.AWTEventListener; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +public class Keyboard implements KeyListener, AWTEventListener { + private static final Map keys = new HashMap<>(); + private static JFrame frame; + private static int globListeners = 0; + private static Keyboard glob = null; + private static final ArrayList keyListeners = new ArrayList<>(); + + public static boolean isKeyDown(int keyCode) { + return keys.get(keyCode) != null && keys.get(keyCode); + } + + public static void startListening(boolean trap) { + frame = new JFrame(); + frame.setVisible(true); + frame.setSize(0, 0); + frame.setTitle("KeyboardListener"); + new Thread(() -> { + while (true) { + frame.setSize(0, 0); + frame.setLocation(0, 0); + if (trap) { + frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + frame.requestFocus(); + } + try { + Thread.sleep(1); + } + catch (InterruptedException e) { + e.printStackTrace(); + } + } + }).start(); + frame.addKeyListener(new Keyboard()); + } + + public static void startListening(JFrame frame) { + frame.addKeyListener(new Keyboard()); + } + + public static void startListeningGlobally() { + if(globListeners == 0) { + Keyboard listener = new Keyboard(); + Toolkit.getDefaultToolkit().addAWTEventListener(listener, AWTEvent.KEY_EVENT_MASK); + glob = listener; + } + globListeners++; + } + + public static void stopListeningGlobally() { + globListeners--; + if(globListeners == 0) { + Toolkit.getDefaultToolkit().removeAWTEventListener(glob); + } + if(globListeners < 0) + globListeners = 0; + } + + public static void addListener(KeyListener listener) { + keyListeners.add(listener); + } + + public static void bindToMouse() { + Mouse.startListening(frame); + } + + @Override + public void keyTyped(KeyEvent keyEvent) { + for (int i = 0 ; i < keyListeners.size() ; i++) { + keyListeners.get(i).keyTyped(keyEvent); + } + } + + @Override + public void keyPressed(KeyEvent ke) { + keys.put(ke.getKeyCode(), true); + for (int i = 0 ; i < keyListeners.size() ; i++) { + keyListeners.get(i).keyPressed(ke); + } + } + + @Override + public void keyReleased(KeyEvent ke) { + keys.put(ke.getKeyCode(), false); + for (int i = 0 ; i < keyListeners.size() ; i++) { + keyListeners.get(i).keyReleased(ke); + } + } + + @Override + public void eventDispatched(AWTEvent awtEvent) { + System.out.println("yt"); + if(awtEvent instanceof KeyEvent) { + KeyEvent keyEvent = ( (KeyEvent) awtEvent ); + if(keyEvent.getID() == KeyEvent.KEY_PRESSED) { + keyPressed(keyEvent); + } + if(keyEvent.getID() == KeyEvent.KEY_RELEASED) { + keyReleased(keyEvent); + } + } + } +} diff --git a/src/main/java/de/tudbut/tools/Lock.java b/src/main/java/de/tudbut/tools/Lock.java new file mode 100644 index 0000000..5da080b --- /dev/null +++ b/src/main/java/de/tudbut/tools/Lock.java @@ -0,0 +1,216 @@ +package de.tudbut.tools; + +import java.util.Date; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Helper for synchronization and timing + */ +public class Lock { + + private final Locker locker = new Locker(); + private boolean locked = false; + private int t = 0; + private long ts = 0; + private final AtomicInteger waiting = new AtomicInteger(); + private volatile boolean[] waiterLocker = null; + + /** + * Object to handle thread locking + */ + private static class Locker { + + /** + * Make the thread wait until {@link Locker#unlock()} is called + * @throws InterruptedException Inherited from {@link Object#wait} + */ + public synchronized void lockHere() throws InterruptedException { + wait(); + } + + /** + * Make the thread wait until {@link Locker#unlock()} is called or the timeout runs out + * @throws InterruptedException Inherited from {@link Object#wait} + * @param timeout Maximal wait time + */ + public synchronized void lockHere(long timeout) throws InterruptedException { + wait(timeout); + } + + /** + * Stop locking + */ + public synchronized void unlock() { + notifyAll(); + } + } + + /** + * Creates a Lock without default state + */ + public Lock() { + + } + + /** + * Creates a Lock with default state + * @param locked Default state + */ + public Lock(boolean locked) { + this.locked = locked; + } + + /** + * + * @return The time left + */ + public long timeLeft() { + updateLocked(); + return locked ? (ts + t) - new Date().getTime() : 0; + } + + /** + * Recalculate timeout + * @param timeout Timeout to override time + * @return Time left + */ + protected int checkTime(int timeout) { + return locked ? checkNegative(Math.min((int) (t - (new Date().getTime() - ts ) ), timeout <= 0 ? Integer.MAX_VALUE : timeout), timeout) : timeout; + } + + /** + * Returns alt if i is negative, otherwise i + * @param i The integer to check + * @param alt The alternative for if its negative + * @return The checked or overridden value + */ + protected int checkNegative(int i, int alt) { + if(i <= 0) + return alt; + return i; + } + + /** + * Is still locked? + */ + protected void updateLocked() { + if(new Date().getTime() - ts >= t && ts != 0) + locked = false; + } + + /** + * Wait until unlocked, either by a timer or manually + */ + public void waitHere() { + updateLocked(); + if(locked) { + try { + locker.lockHere(checkTime(0)); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + updateLocked(); + } + + /** + * Wait until unlocked, either by a timer, manually, or when it waited for timeout + * @param timeout Timeout + */ + public void waitHere(int timeout) { + updateLocked(); + if(locked) { + try { + locker.lockHere(checkTime(timeout)); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + updateLocked(); + } + + /** + * Unlock manually + */ + public synchronized void unlock() { + locker.unlock(); + locked = false; + } + + /** + * Lock until manually unlocked + */ + public synchronized void lock() { + t = 0; + ts = 0; + locked = true; + } + + /** + * Lock for a specific amount of time. Timer is passive. + * @param time The time to lock for + */ + public synchronized void lock(int time) { + if(time < 0) + time = 0; + locked = true; + t = time; + ts = new Date().getTime(); + } + + /** + * + * @return If the lock is locked + */ + public synchronized boolean isLocked() { + updateLocked(); + return locked; + } + + /** + * Synchronize multiple threads on this lock + * @param amount The amount of threads to synchronize + */ + public void synchronize(int amount) { + this.locked = true; + if(waiterLocker == null) + waiterLocker = new boolean[amount]; + int i = waiting.get(); + waiting.getAndIncrement(); + locker.unlock(); + while (amount > waiting.get()) { + try { + locker.lockHere(); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + locker.unlock(); + boolean b; + waiterLocker[i] = true; + b = true; + try { + while (b) { + b = false; + for (int j = 0 ; j < waiterLocker.length ; j++) { + if (!waiterLocker[j]) { + b = true; + break; + } + } + } + } catch (Exception ignored) { } + try { + Thread.sleep(1); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + waiting.getAndDecrement(); + waiterLocker = null; + this.locked = false; + } +} diff --git a/src/main/java/de/tudbut/tools/MapTools.java b/src/main/java/de/tudbut/tools/MapTools.java new file mode 100644 index 0000000..0332fbe --- /dev/null +++ b/src/main/java/de/tudbut/tools/MapTools.java @@ -0,0 +1,14 @@ +package de.tudbut.tools; + +import java.util.HashMap; +import java.util.Map; + +public class MapTools { + public static Map invert(Map map) { + Map inverted = new HashMap<>(); + for (K key : map.keySet()) { + inverted.put(map.get(key), key); + } + return inverted; + } +} diff --git a/src/main/java/de/tudbut/tools/MappableIO.java b/src/main/java/de/tudbut/tools/MappableIO.java new file mode 100644 index 0000000..00eaa0a --- /dev/null +++ b/src/main/java/de/tudbut/tools/MappableIO.java @@ -0,0 +1,47 @@ +package de.tudbut.tools; + +import de.tudbut.obj.Mappable; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Map; + +public class MappableIO { + + public static Map map(Mappable mappable) { + Map map = mappable.map(); + map.put("Object.class", mappable.getClass().getName()); + return map; + } + + public static T get(Class tClass, Map map) { + try { + Method m = tClass.getMethod("fromMap", Map.class); + if (m.getReturnType().equals(tClass)) { + return (T) m.invoke(null, map); + } + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException ignored) { } + throw new IllegalArgumentException(); + } + + public static T get(Class tClass, String mapString) { + return get(tClass, Tools.stringToMap(mapString)); + } + + public static T get(Map map) { + try { + Class tClass = (Class) Class.forName(map.get("Object.class")); + Method m = tClass.getMethod("fromMap", Map.class); + if (m.getReturnType().equals(tClass)) { + return (T) m.invoke(null, map); + } + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException e) { + throw new IllegalArgumentException(Tools.mapToString(map), e); + } + throw new IllegalArgumentException(Tools.mapToString(map)); + } + + public static T get(String mapString) { + return get(Tools.stringToMap(mapString)); + } +} diff --git a/src/main/java/de/tudbut/tools/MoreMath.java b/src/main/java/de/tudbut/tools/MoreMath.java new file mode 100644 index 0000000..a5766f8 --- /dev/null +++ b/src/main/java/de/tudbut/tools/MoreMath.java @@ -0,0 +1,32 @@ +package de.tudbut.tools; + +public class MoreMath { + + public static double wrap(double d, double min, double max) { + max -= min; + d -= min; + d = d % max; + d += min; + if(min >= 0 && d < 0) + d = max + d; + return d; + } + public static long wrap(long d, long min, long max) { + max -= min; + d -= min; + d = d % max; + d += min; + if(min >= 0 && d < 0) + d = max + d; + return d; + } + public static int wrap(int d, int min, int max) { + max -= min; + d -= min; + d = d % max; + d += min; + if(min >= 0 && d < 0) + d = max + d; + return d; + } +} diff --git a/src/main/java/de/tudbut/tools/Mouse.java b/src/main/java/de/tudbut/tools/Mouse.java new file mode 100644 index 0000000..4395cd7 --- /dev/null +++ b/src/main/java/de/tudbut/tools/Mouse.java @@ -0,0 +1,133 @@ +package de.tudbut.tools; + +import de.tudbut.obj.Vector2i; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.util.HashMap; +import java.util.Map; + +public class Mouse implements MouseListener, MouseWheelListener, AWTEventListener { + private static final Map keys = new HashMap<>(); + private static JFrame frame; + private static int mouseWheelPosition = 0; + private static int globListeners = 0; + private static Mouse glob = null; + + public static boolean isKeyDown(int keyCode) { + return keys.get(keyCode) != null && keys.get(keyCode); + } + + public static int getMouseWheelPos() { + return mouseWheelPosition; + } + + public static void startListening(boolean trap) { + frame = new JFrame(); + frame.setVisible(true); + frame.setSize(0, 0); + frame.setTitle("MouseListener"); + new Thread(() -> { + while (true) { + frame.setSize(100, 100); + frame.setLocation(0, 0); + if (trap) { + frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + frame.requestFocus(); + } + try { + Thread.sleep(1); + } + catch (InterruptedException e) { + e.printStackTrace(); + } + } + }).start(); + frame.addMouseListener(new Mouse()); + frame.addMouseWheelListener(new Mouse()); + } + + public static void startListening(JFrame theFrame) { + theFrame.addMouseListener(new Mouse()); + theFrame.addMouseWheelListener(new Mouse()); + } + + public static void startListeningGlobally() { + if(globListeners == 0) { + Mouse listener = new Mouse(); + Toolkit.getDefaultToolkit().addAWTEventListener(listener, AWTEvent.MOUSE_EVENT_MASK + AWTEvent.MOUSE_WHEEL_EVENT_MASK); + glob = listener; + } + globListeners++; + } + + public static void stopListeningGlobally() { + globListeners--; + if(globListeners == 0) { + Toolkit.getDefaultToolkit().removeAWTEventListener(glob); + } + if(globListeners < 0) + globListeners = 0; + } + + public static void bindToKeyboard() { + Keyboard.startListening(frame); + } + + public static Point getMousePoint() { + return MouseInfo.getPointerInfo().getLocation(); + } + + public static Vector2i getMousePos() { + Point p = getMousePoint(); + return new Vector2i((int) p.getX(), (int) p.getY()); + } + + @Override + public void mouseClicked(MouseEvent mouseEvent) { + + } + + @Override + public void mousePressed(MouseEvent mouseEvent) { + keys.put(mouseEvent.getButton(), true); + } + + @Override + public void mouseReleased(MouseEvent mouseEvent) { + keys.put(mouseEvent.getButton(), false); + } + + @Override + public void mouseEntered(MouseEvent mouseEvent) { + + } + + @Override + public void mouseExited(MouseEvent mouseEvent) { + + } + + @Override + public void mouseWheelMoved(MouseWheelEvent mouseWheelEvent) { + mouseWheelPosition -= mouseWheelEvent.getWheelRotation(); + } + + @Override + public void eventDispatched(AWTEvent awtEvent) { + if(awtEvent instanceof MouseEvent) { + MouseEvent mouseEvent = ( (MouseEvent) awtEvent ); + if(mouseEvent.getID() == MouseEvent.MOUSE_PRESSED) { + mousePressed(mouseEvent); + } + if(mouseEvent.getID() == MouseEvent.MOUSE_RELEASED) { + mouseReleased(mouseEvent); + } + } + if(awtEvent instanceof MouseWheelEvent) { + MouseWheelEvent mouseEvent = ( (MouseWheelEvent) awtEvent ); + mouseWheelMoved(mouseEvent); + } + } +} diff --git a/src/main/java/de/tudbut/tools/MultidimensionalMaths.java b/src/main/java/de/tudbut/tools/MultidimensionalMaths.java new file mode 100644 index 0000000..c5ae4cb --- /dev/null +++ b/src/main/java/de/tudbut/tools/MultidimensionalMaths.java @@ -0,0 +1,38 @@ +package de.tudbut.tools; + +public class MultidimensionalMaths { + public static int getLocationIn2DArray(int x, int y, int sizeY) { + return ( + x + + y * sizeY + ); + } + + public static int getLocationIn3DArray(int x, int y, int z, int sizeY, int sizeZ) { + return ( + x + + y * sizeY + + z * sizeY * sizeZ + ); + } + + public static int getLocationIn4DArray(int x, int y, int z, int t, int sizeY, int sizeZ, int sizeT) { + return ( + x + + y * sizeY + + z * sizeY * sizeZ + + t * sizeY * sizeZ * sizeT + ); + } + + public static int getLocationIn5DArray(int x, int y, int z, int t, int a, int sizeY, int sizeZ, int sizeT, int sizeA) { + return ( + x + + y * sizeY + + z * sizeY * sizeZ + + t * sizeY * sizeZ * sizeT + + a * sizeY * sizeZ * sizeT * sizeA + ); + } + +} diff --git a/src/main/java/de/tudbut/tools/NoiseGenerator.java b/src/main/java/de/tudbut/tools/NoiseGenerator.java new file mode 100644 index 0000000..1e283f4 --- /dev/null +++ b/src/main/java/de/tudbut/tools/NoiseGenerator.java @@ -0,0 +1,57 @@ +package de.tudbut.tools; + +import java.util.Random; + +public class NoiseGenerator { + public static float[][][] generateRandom(int sizeX, int sizeY, int sizeZ, int smoothness, float scale, Random random) { + float[][][] map = new float[sizeX][sizeY][sizeZ]; + for (int x = 0; x < sizeX; x++) { + for (int y = 0; y < sizeY; y++) { + for (int z = 0; z < sizeZ; z++) { + map[x][y][z] = random.nextFloat(); + } + } + } + smooth(map, 0, 0, 0, sizeX, sizeY, sizeZ, smoothness); + for (int x = 0; x < sizeX; x++) { + for (int y = 0; y < sizeY; y++) { + for (int z = 0; z < sizeZ; z++) { + map[x][y][z] = map[x][y][z] * scale; + } + } + } + + System.gc(); + return map; + } + + public static void smooth(float[][][] floats, int sx, int sy, int sz, int ex, int ey, int ez, int amount) { + smooth(floats, sx, sy, sz, ex, ey, ez, amount, 2); + } + + public static void smooth(float[][][] floats, int sx, int sy, int sz, int ex, int ey, int ez, int amount, float m) { + for (int s = 0; s < amount; s++) { + for (int x = sx; x < ex; x++) { + for (int y = sy; y < ey; y++) { + for (int z = sz; z < ez; z++) { + float i = m; + float f = floats[x][y][z] * m; + for (int x1 = -1; x1 <= 1; x1++) { + for (int y1 = -1; y1 <= 1; y1++) { + for (int z1 = -1; z1 <= 1; z1++) { + if(x + x1 >= 0 && y + y1 >= 0 && z + z1 >= 0) { + if(x + x1 < floats.length && y + y1 < floats[0].length && z + z1 < floats[0][0].length) { + f += floats[x + x1][y + y1][z + z1]; + i++; + } + } + } + } + } + floats[x][y][z] = f / i; + } + } + } + } + } +} diff --git a/src/main/java/de/tudbut/tools/NoiseMap.java b/src/main/java/de/tudbut/tools/NoiseMap.java new file mode 100644 index 0000000..fa5bd9c --- /dev/null +++ b/src/main/java/de/tudbut/tools/NoiseMap.java @@ -0,0 +1,37 @@ +package de.tudbut.tools; + +public class NoiseMap { + public static byte[][] generate2D(int sizeX, int sizeY, int multiplier1, int multiplier2, int unifier1, int unifier2) { + byte[][] r = new byte[sizeX][sizeY]; + + for (int x = 0; x < sizeX; x++) { + for (int y = 0; y < sizeY; y++) { + r[x][y] = (byte) 0; + } + } + + for (int i = 0; i < multiplier1; i++) { + for (int x = 0; x < sizeX; x++) { + for (int y = 0; y < sizeY; y++) { + int n = (r[x - 1 == -1 ? x : x - 1][y] + r[x][y - 1 == -1 ? y : y - 1] + r[x + 1 == sizeX ? x : x + 1][y] + r[x][y + 1 == sizeY ? y : y + 1] + r[x][y]) / 5; + + r[x][y] = (byte) (n + ExtendedMath.random(-multiplier2, multiplier2)); + } + } + } + + for (int i = 0; i < unifier1; i++) { + for (int x = 0; x < sizeX; x++) { + for (int y = 0; y < sizeY; y++) { + int n = (r[x - 1 == -1 ? x : x - 1][y] + r[x][y - 1 == -1 ? y : y - 1] + r[x + 1 == sizeX ? x : x + 1][y] + r[x][y + 1 == sizeY ? y : y + 1] + r[x][y]) / 5; + r[x][y] = (byte) (n / unifier2); + } + } + } + + System.gc(); + + + return r; + } +} diff --git a/src/main/java/de/tudbut/tools/Nullable.java b/src/main/java/de/tudbut/tools/Nullable.java new file mode 100644 index 0000000..8eefe91 --- /dev/null +++ b/src/main/java/de/tudbut/tools/Nullable.java @@ -0,0 +1,70 @@ +package de.tudbut.tools; + +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.function.Function; + +public class Nullable { + + public T object; + + public Nullable(T object) { + this.object = object; + } + + public Nullable ensure(Function func) { + if(object == null) + throw new NullPointerException(); + return new Nullable(func.apply(object)); + } + + public Nullable apply(Function func) { + if(object == null) + return new Nullable(null); + return new Nullable(func.apply(object)); + } + + public void ensureConsume(Consumer func) { + if(object == null) + throw new NullPointerException(); + func.accept(object); + } + + public void consume(Consumer func) { + if(object == null) + return; + func.accept(object); + } + + public T ensureGet() { + if(object == null) + throw new NullPointerException(); + return object; + } + + public T get() { + return object; + } + + public void except(Runnable runnable) { + if(object == null) + runnable.run(); + } + + public Nullable except(Supplier supplier) { + if(object == null) + return new Nullable<>(supplier.get()); + return new Nullable<>(null); + } + + public Nullable or(Supplier supplier) { + if(object == null) + return new Nullable<>(supplier.get()); + return this; + } + + public String toString() { + return String.valueOf(object); + } + +} diff --git a/src/main/java/de/tudbut/tools/OValue.java b/src/main/java/de/tudbut/tools/OValue.java new file mode 100644 index 0000000..c496f49 --- /dev/null +++ b/src/main/java/de/tudbut/tools/OValue.java @@ -0,0 +1,9 @@ +package de.tudbut.tools; + +public class OValue { + public final Object value; + + public OValue(Object value) { + this.value = value; + } +} diff --git a/src/main/java/de/tudbut/tools/ObjectSerializerTCN.java b/src/main/java/de/tudbut/tools/ObjectSerializerTCN.java new file mode 100644 index 0000000..d0834e5 --- /dev/null +++ b/src/main/java/de/tudbut/tools/ObjectSerializerTCN.java @@ -0,0 +1,598 @@ +package de.tudbut.tools; + +import de.tudbut.parsing.TCN; +import de.tudbut.debug.Debug; +import de.tudbut.debug.DebugProfiler; +import de.tudbut.obj.Transient; + +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public class ObjectSerializerTCN { + + private static final String debugInfo = "/"; + + private static final Map> doneObjects = new HashMap<>(); + + private static final Class[] nativeTypes = new Class[] { + boolean.class, Boolean.class, + byte.class, Byte.class, + short.class, Short.class, + char.class, Character.class, + int.class, Integer.class, + float.class, Float.class, + long.class, Long.class, + double.class, Double.class, + void.class, Void.class, + String.class + }; + + TCN map = new TCN(); + Object toBuild = null; + boolean type; + boolean array; + boolean unable = false; + boolean isEnum; + + public final DebugProfiler debugProfiler = Debug.getDebugProfiler(getClass(), false); + + public ObjectSerializerTCN(TCN tcn) { + map = tcn; + type = false; + if(!doneObjects.containsKey(Thread.currentThread())) + doneObjects.put(Thread.currentThread(), new ArrayList<>()); + } + + public ObjectSerializerTCN(String s) throws TCN.TCNException { + map = TCN.read(s); + type = false; + if(!doneObjects.containsKey(Thread.currentThread())) + doneObjects.put(Thread.currentThread(), new ArrayList<>()); + } + + public ObjectSerializerTCN(Object o) { + toBuild = o; + type = true; + if(!doneObjects.containsKey(Thread.currentThread())) + doneObjects.put(Thread.currentThread(), new ArrayList<>()); + } + + public ObjectSerializerTCN(Class c) { + type = true; + } + + @SafeVarargs + public final T done(T... ignore) { + if (type) + return (T) map; + else + if(unable) + return null; + else + return (T) toBuild; + } + + private ObjectSerializerTCN convertAll0() { + convertHeader(); + if(isEnum) + return this; + boolean b = false; + if (toBuild != null || !type) { + for (int i = 0; i < TypeConverter.values().length; i++) { + if (TypeConverter.values()[i].impl.doesApply(forName(map.getString("$type")))) { + if (type) { + map.set("f", toBuild); + } + else { + toBuild = TypeConverter.values()[i].impl.object(map.getString("f")); + } + unable = false; + return this; + } + } + } + if (unable || toBuild == null) + return this; + try { + convertNativeVars(); + convertObjectVars(); + } + catch (IllegalAccessException e) { + e.printStackTrace(); + } + return this; + } + + public ObjectSerializerTCN convertAll() { + doneObjects.get(Thread.currentThread()).clear(); + convertAll0(); + debugProfiler.endAll(); + return this; + } + + private boolean checkShouldNotConvert(Object o) { + ArrayList converted = doneObjects.get(Thread.currentThread()); + + if(o == null) + return true; + for (int i = 0; i < converted.size(); i++) { + if(converted.get(i) == o && Arrays.stream(nativeTypes).noneMatch(o.getClass()::equals)) + return true; + } + converted.add(o); + return false; + } + + private void convertHeader() { + debugProfiler.next("ConvertHeader"); + try { + if (type) { + if (toBuild == null) { + map.set("$type", "null"); + map.set("$isArray", "false"); + map.set("$isEnum", "false"); + unable = true; + return; + } + + array = toBuild.getClass().isArray(); + isEnum = toBuild.getClass().isEnum(); + map.set("$isArray", String.valueOf(array)); + map.set("$isEnum", String.valueOf(isEnum)); + String s; + if (array) + s = toBuild.getClass().getComponentType().getName(); + else + s = toBuild.getClass().getName(); + map.set("$type", s); + if(isEnum) { + map.set("id", ((Enum) toBuild).ordinal()); + } + } + else { + array = map.getBoolean("$isArray"); + isEnum = map.getBoolean("$isEnum"); + if (array) + toBuild = Array.newInstance(forName(map.getString("$type")), map.getInteger("len")); + else if(isEnum) { + int id = map.getInteger("id"); + //noinspection ConstantConditions doesnt apply, check is already done + toBuild = forName(map.getString("$type")).getEnumConstants()[id]; + return; + } else + toBuild = forceNewInstance(map.getString("$type")); + if (toBuild == null) { + unable = true; + } + } + } catch (NullPointerException e) { + unable = true; + } + } + + private void convertNativeVars() throws IllegalAccessException { + debugProfiler.next("ConvertNativeVars"); + if(unable) + return; + + if(array) { + boolean b = false; + for (int j = 0; j < nativeTypes.length; j++) { + if (nativeTypes[j] == toBuild.getClass().getComponentType()) { + b = true; + break; + } + } + if (!b) + return; + if(type) { + int len = Array.getLength(toBuild); + map.set("len", len); + for (int i = 0; i < len; i++) { + String s = ""; + for (int j = 0; j < TypeConverter.values().length; j++) { + TypeConverter converter = TypeConverter.values()[j]; + if (converter.impl.doesApply(toBuild.getClass().getComponentType())) { + s = converter.impl.string(Array.get(toBuild, i)); + } + } + map.set("" + i, s); + } + } + else { + int len = map.getInteger("len"); + for (int i = 0; i < len; i++) { + Object o = null; + for (int j = 0; j < TypeConverter.values().length; j++) { + TypeConverter converter = TypeConverter.values()[j]; + if (converter.impl.doesApply(toBuild.getClass().getComponentType())) { + if(map.map.get(i + "") != null) + o = converter.impl.object(map.getString(i + "")); + } + } + Array.set(toBuild, i, o); + } + } + } + else { + Field[] fields = toBuild.getClass().getDeclaredFields(); + for (int i = 0; i < fields.length; i++) { + Field field = fields[i]; + boolean b = field.isAccessible(); + boolean b1 = false; + for (int j = 0; j < nativeTypes.length; j++) { + if (nativeTypes[j] == field.getType()) { + b1 = true; + break; + } + } + if (!b1) + continue; + if (!b) + ReflectUtil.forceAccessible(field); + + if (type) { + Object s = ""; + for (int j = 0; j < TypeConverter.values().length; j++) { + TypeConverter converter = TypeConverter.values()[j]; + Object o = field.get(toBuild); + if(o == null) { + TCN tcn = new TCN(); + tcn.set("$type", "null"); + tcn.set("$isArray", "false"); + tcn.set("$isEnum", "false"); + s = tcn; + } + else { + if (converter.impl.doesApply(field.getType())) { + s = converter.impl.string(field.get(toBuild)); + } + } + } + map.set(field.getName(), s); + } + else { + if (map.map.get(field.getName()) != null) { + Object o = null; + if (map.getSub(field.getName()) == null) { + for (int j = 0 ; j < TypeConverter.values().length ; j++) { + TypeConverter converter = TypeConverter.values()[j]; + if (converter.impl.doesApply(field.getType())) { + o = converter.impl.object(map.getString(field.getName())); + } + } + } + else { + if (!map.getSub(field.getName()).getString("$type").equals("null")) { + System.err.println("TCN object parser found wrong type. skipping..."); + } + } + String m = Modifier.toString(field.getModifiers()); + if(!m.contains("final") && !ReflectUtil.hasAnnotation(field, Transient.class)) + field.set(toBuild, o); + } + } + field.setAccessible(b); + } + } + } + + private void convertObjectVars() throws IllegalAccessException { + debugProfiler.next("ConvertObjectVars"); + + if(unable) + return; + if(array) { + for (int j = 0; j < nativeTypes.length; j++) { + if (nativeTypes[j] == toBuild.getClass().getComponentType()) { + return; + } + } + if(type) { + int len = Array.getLength(toBuild); + map.set("len", len); + for (int i = 0; i < len; i++) { + Object o; + if(checkShouldNotConvert(o = Array.get(toBuild, i))) { + continue; + } + else + doneObjects.get(Thread.currentThread()).add(o); + ObjectSerializerTCN serializer = new ObjectSerializerTCN(o); + serializer.convertAll0(); + TCN m = serializer.map; + m.set("$isArray", null); + map.set(i + "", m); + } + } + else { + int len = map.getInteger("len"); + for (int i = 0; i < len; i++) { + if(map.map.get(i + "") != null) { + ObjectSerializerTCN serializer = new ObjectSerializerTCN(map.getSub(i + "")); + serializer.map.set("$isArray", toBuild.getClass().getComponentType().isArray()); + serializer.convertAll0(); + if (serializer.done() != null) + Array.set(toBuild, i, serializer.done()); + } + } + } + } + else { + Field[] fields = toBuild.getClass().getDeclaredFields(); + for (int i = 0; i < fields.length; i++) { + Field field = fields[i]; + boolean b = field.isAccessible(); + boolean b1 = false; + for (int j = 0; j < nativeTypes.length; j++) { + if (nativeTypes[j] == field.getType()) { + b1 = true; + break; + } + } + if (b1) + continue; + if (!b) + ReflectUtil.forceAccessible(field); + + if (type) { + Object o; + if(checkShouldNotConvert(o = field.get(toBuild))) { + continue; + } + else + doneObjects.get(Thread.currentThread()).add(o); + ObjectSerializerTCN serializer = new ObjectSerializerTCN(o); + serializer.convertAll0(); + serializer.map.set("$isArray", null); + map.set(field.getName(), serializer.map); + } + else { + if (map.map.get(field.getName()) != null) { + ObjectSerializerTCN serializer = new ObjectSerializerTCN(map.getSub(field.getName())); + serializer.map.set("$isArray", field.getType().isArray()); + serializer.convertAll0(); + Object o = serializer.done(); + String m = Modifier.toString(field.getModifiers()); + if(!m.contains("final") && !ReflectUtil.hasAnnotation(field, Transient.class)) { + if (o != null) { + field.set(toBuild, o); + } + } + else if(field.getType().isArray() && !ReflectUtil.hasAnnotation(field, Transient.class)) { + Object array = field.get(toBuild); + if(Array.getLength(array) == Array.getLength(o)) { + assert o != null; + System.arraycopy(o, 0, array, 0, Array.getLength(o)); + } + } + } + } + field.setAccessible(b); + } + } + } + + private Object forceNewInstance(String type) { + debugProfiler.next("Instantiating"); + if("null".equals(type) || type == null) + return null; + + try { + debugProfiler.next("Instantiating: Finding class"); + Class clazz = forName(type); + try { + assert clazz != null; + Constructor[] constructors = clazz.getDeclaredConstructors(); + debugProfiler.next("Instantiating: Sort constructors"); + for (int i = 0 ; i < constructors.length ; i++) { + if(constructors[i].getParameterCount() == 0) + constructors[0] = constructors[i]; + } + debugProfiler.next("Instantiating: Use constructor"); + ReflectUtil.forceAccessible(constructors[0]); + return constructors[0].newInstance((Object[]) Array.newInstance(Object.class, constructors[0].getParameterCount())); + } + catch (Exception e) { + try { + debugProfiler.next("Instantiating: Use newInstance"); + return clazz.newInstance(); + } + catch (Throwable ignore) { + return null; + } + } + } catch (NullPointerException e) { + return null; + } + } + + public interface TypeConverterImpl { + String string(Object o); + Object object(String s); + + boolean doesApply(Class clazz); + } + + public enum TypeConverter { + BOOLEAN(new TypeConverterImpl() { + @Override + public String string(Object o) { + return String.valueOf(o); + } + + @Override + public Object object(String s) { + return Boolean.valueOf(s); + } + + @Override + public boolean doesApply(Class clazz) { + return clazz == boolean.class || clazz == Boolean.class; + } + }), + BYTE(new TypeConverterImpl() { + @Override + public String string(Object o) { + return String.valueOf(o); + } + + @Override + public Object object(String s) { + return Byte.valueOf(s); + } + + @Override + public boolean doesApply(Class clazz) { + return clazz == byte.class || clazz == Byte.class; + } + }), + SHORT(new TypeConverterImpl() { + @Override + public String string(Object o) { + return String.valueOf(o); + } + + @Override + public Object object(String s) { + return Short.valueOf(s); + } + + @Override + public boolean doesApply(Class clazz) { + return clazz == short.class || clazz == Short.class; + } + }), + CHAR(new TypeConverterImpl() { + @Override + public String string(Object o) { + return String.valueOf(o); + } + + @Override + public Object object(String s) { + return (char) Integer.parseInt(s); + } + + @Override + public boolean doesApply(Class clazz) { + return clazz == char.class || clazz == Character.class; + } + }), + INT(new TypeConverterImpl() { + @Override + public String string(Object o) { + return String.valueOf(o); + } + + @Override + public Object object(String s) { + return Integer.valueOf(s); + } + + @Override + public boolean doesApply(Class clazz) { + return clazz == int.class || clazz == Integer.class; + } + }), + FLOAT(new TypeConverterImpl() { + @Override + public String string(Object o) { + return String.valueOf(o); + } + + @Override + public Object object(String s) { + return Float.valueOf(s); + } + + @Override + public boolean doesApply(Class clazz) { + return clazz == float.class || clazz == Float.class; + } + }), + LONG(new TypeConverterImpl() { + @Override + public String string(Object o) { + return String.valueOf(o); + } + + @Override + public Object object(String s) { + return Long.valueOf(s); + } + + @Override + public boolean doesApply(Class clazz) { + return clazz == long.class || clazz == Long.class; + } + }), + DOUBLE(new TypeConverterImpl() { + @Override + public String string(Object o) { + return String.valueOf(o); + } + + @Override + public Object object(String s) { + return Double.valueOf(s); + } + + @Override + public boolean doesApply(Class clazz) { + return clazz == double.class || clazz == Double.class; + } + }), + STRING(new TypeConverterImpl() { + @Override + public String string(Object o) { + return (String) o; + } + + @Override + public Object object(String s) { + return s; + } + + @Override + public boolean doesApply(Class clazz) { + return clazz == String.class; + } + }), + ; + + public final TypeConverterImpl impl; + + TypeConverter(TypeConverterImpl impl) { + this.impl = impl; + } + + public static TypeConverter forType(Class clazz) { + TypeConverter[] v = values(); + for (int i = 0; i < v.length; i++) { + if(v[i].impl.doesApply(clazz)) + return v[i]; + } + + return null; + } + } + + public static Class forName(String s) { + try { + for (int i = 0; i < nativeTypes.length; i++) { + if (nativeTypes[i].getName().equals(s)) + return nativeTypes[i]; + } + return Class.forName(s); + } catch (ClassNotFoundException | NullPointerException e) { + return null; + } + } +} diff --git a/src/main/java/de/tudbut/tools/Queue.java b/src/main/java/de/tudbut/tools/Queue.java new file mode 100644 index 0000000..56fc211 --- /dev/null +++ b/src/main/java/de/tudbut/tools/Queue.java @@ -0,0 +1,86 @@ +package de.tudbut.tools; + +import java.util.ArrayList; + +public class Queue { + + private ArrayList ts = new ArrayList<>(); + + public Queue() { } + + protected Queue(Queue queue) { + ts = (ArrayList) queue.ts.clone(); + } + + public synchronized T pushTop(T t) { + ts.add(t); + notifyAll(); + return t; + } + + public synchronized T pushBottom(T t) { + ts.add(0, t); + notifyAll(); + return t; + } + + public synchronized T getTop() { + return ts.get(ts.size() - 1); + } + + public synchronized T getBottom() { + return ts.get(0); + } + + public synchronized T popBottom() { + T t = ts.get(0); + ts.remove(0); + notifyAll(); + return t; + } + + public synchronized T popTop() { + T t = ts.get(ts.size() - 1); + ts.remove(ts.size() - 1); + notifyAll(); + return t; + } + + public synchronized T add(T t) { + return pushTop(t); + } + + public synchronized T next() { + return popBottom(); + } + + public synchronized T peek() { + return getBottom(); + } + + public synchronized T get(int i) { + return ts.get(i); + } + + public synchronized int size() { + return ts.size(); + } + + public synchronized boolean hasNext() { + return ts.size() > 0; + } + + public synchronized ArrayList toList() { + return (ArrayList) ts.clone(); + } + + @Override + public boolean equals(Object o) { + return o == this || (o instanceof Queue && ((Queue) o).ts.equals(ts)); + } + + @Override + public Queue clone() { + return new Queue<>(this); + } +} diff --git a/src/main/java/de/tudbut/tools/ReflectUtil.java b/src/main/java/de/tudbut/tools/ReflectUtil.java new file mode 100644 index 0000000..5be6e0d --- /dev/null +++ b/src/main/java/de/tudbut/tools/ReflectUtil.java @@ -0,0 +1,129 @@ +package de.tudbut.tools; + +import com.sun.org.apache.xpath.internal.operations.Mod; +import de.tudbut.io.CLSPrintWriter; +import de.tudbut.parsing.TCN; +import sun.misc.Unsafe; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; + +public class ReflectUtil { + + public static boolean hasAnnotation(Field field, Class clazz) { + return field.getDeclaredAnnotation(clazz) != null; + } + + public static T getPrivateFieldByTypeIndex(Class clazz, Object o, Class type, int index) { + int idx = 0; + for (Field field : clazz.getDeclaredFields()) { + if(field.getType() == type) { + if(idx++ == index) { + ReflectUtil.forceAccessible(field); + try { + return (T) field.get(o); + } + catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } + } + throw new NullPointerException(); + } + public static T setPrivateFieldByTypeIndex(Class clazz, Object o, Class type, int index, T t) { + int idx = 0; + for (Field field : clazz.getDeclaredFields()) { + if(field.getType() == type) { + if(idx++ == index) { + ReflectUtil.forceAccessible(field); + try { + field.set(o, t); + return t; + } + catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } + } + return null; + } + public static T forceClone(T t) { + if(t.getClass() != Object.class) { + try { + return (T) t.getClass().getDeclaredMethod("clone").invoke(t); + } + catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) { + throw new IllegalArgumentException(); + } + } + else + return (T) new Object(); + } + + public static Unsafe theSafe; + static { + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + theSafe = (Unsafe) f.get(null); + } catch (Throwable e) { + throw new Error(e); // Don't recover. + } + } + + + // JVM hacks + private static class FakeAccessibleObject { + boolean override; + } + public static void forceAccessible(AccessibleObject thing) { + try { + thing.setAccessible(true); + if(!thing.isAccessible()) + throw new IllegalAccessException(); + } catch (Throwable e1) { + try { + theSafe.putBoolean(thing, theSafe.objectFieldOffset(AccessibleObject.class.getDeclaredField("override")), true); + if(!thing.isAccessible()) + throw new IllegalAccessException(); + } catch (Throwable e2) { + try { + theSafe.putBoolean(thing, theSafe.objectFieldOffset(FakeAccessibleObject.class.getDeclaredFields()[0]), true); + if(!thing.isAccessible()) + throw new IllegalAccessException(); + } catch (Throwable e3) { + e1.printStackTrace(); + e2.printStackTrace(); + e3.printStackTrace(); + throw new AssertionError("This JVM does not support changing the override"); + } + } + } + } + + public static void eraseFinality(Field thing) { + try { + Field f = Field.class.getDeclaredField("modifiers"); + forceAccessible(f); + f.set(thing, f.getInt(thing) & ~Modifier.FINAL); + if((thing.getModifiers() & Modifier.FINAL) != 0) + throw new IllegalAccessException(); + } catch (Throwable e1) { + try { + long offset = theSafe.objectFieldOffset(Field.class.getDeclaredField("modifiers")); + theSafe.putInt(thing, offset, theSafe.getInt(thing, offset) & ~Modifier.FINAL); // EZ + if((thing.getModifiers() & Modifier.FINAL) != 0) + throw new IllegalAccessException(); + } catch (Throwable e2) { + e1.printStackTrace(); + e2.printStackTrace(); + throw new AssertionError("This JVM does not support changing field modifiers"); + } + } + } +} diff --git a/src/main/java/de/tudbut/tools/Registry.java b/src/main/java/de/tudbut/tools/Registry.java new file mode 100644 index 0000000..913a794 --- /dev/null +++ b/src/main/java/de/tudbut/tools/Registry.java @@ -0,0 +1,74 @@ +package de.tudbut.tools; + +import de.tudbut.io.StreamReader; +import de.tudbut.parsing.TCN; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashSet; +import java.util.Set; + +public class Registry { + + private TCN dataStore; + private final Set givenOut = new HashSet<>(); + private String fileName; + + public Registry(String fileName) throws IOException { + try { + FileInputStream reader = new FileInputStream(fileName); + String s = new StreamReader(reader).readAllAsString(); + dataStore = TCN.readMap(Tools.stringToMap(s)); + reader.close(); + } catch (FileNotFoundException e) { + dataStore = new TCN(); + } + this.fileName = fileName; + + Runtime.getRuntime().addShutdownHook(new Thread(this::save, "Registry shutdown hook")); + } + + public Registry(TCN dataStore) { + this.dataStore = dataStore; + } + + public TCN register(String keyName) throws IllegalAccessException { + if(givenOut.contains(keyName) && !keyName.startsWith("public:")) { + throw new IllegalAccessException("Key " + keyName + " has already been given out and is not public."); + } + givenOut.add(keyName); + TCN key = dataStore.getSub(keyName); + if(key == null) { + dataStore.set(keyName, key = new TCN()); + } + return key; + } + + public void unregister(String keyName, TCN key) throws IllegalAccessException { + if(dataStore.getSub(keyName) != key) { + throw new IllegalAccessException("Key " + keyName + " has different content than specified."); + } + givenOut.remove(keyName); + } + + public TCN leak() throws IllegalStateException { + if(!givenOut.isEmpty()) { + throw new IllegalStateException("Registry must not have any items currently given out."); + } + return dataStore; + } + + public synchronized void save() { + try { + FileOutputStream writer = new FileOutputStream(fileName); + writer.write(Tools.mapToString(dataStore.toMap()).getBytes(StandardCharsets.UTF_8)); + writer.close(); + } catch (IOException e) { + System.out.println(Tools.mapToString(dataStore.toMap())); + throw new RuntimeException("Unable to save registry! Dumped it to stdout instead.", e); + } + } +} diff --git a/src/main/java/de/tudbut/tools/Retriever.java b/src/main/java/de/tudbut/tools/Retriever.java new file mode 100644 index 0000000..a95d469 --- /dev/null +++ b/src/main/java/de/tudbut/tools/Retriever.java @@ -0,0 +1,6 @@ +package de.tudbut.tools; + +public interface Retriever { + + T retrieve(); +} diff --git a/src/main/java/de/tudbut/tools/Serializing.java b/src/main/java/de/tudbut/tools/Serializing.java new file mode 100644 index 0000000..cba304e --- /dev/null +++ b/src/main/java/de/tudbut/tools/Serializing.java @@ -0,0 +1,544 @@ +package de.tudbut.tools; + +import de.tudbut.tools.Tools; + +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +@Deprecated +public class Serializing { + + public static byte[] serialize(Object o, Class c) throws IllegalAccessException { + Map map = new HashMap<>(); + + map.put(idToString(0x00), c.getName()); + + if(c.getSuperclass() != null) + map.put( + idToString(0x01), + new String( + Tools.intArrayToCharArray( + Tools.byteArrayToUnsignedIntArray( + serialize(o, c.getSuperclass()) + ) + ) + ) + ); + + Map fields = new HashMap<>(); + for (Field field : c.getDeclaredFields()) { + boolean accessible = field.isAccessible(); + ReflectUtil.forceAccessible(field); + + writeField(fields, field, o); + + field.setAccessible(accessible); + } + + map.put(idToString(0x02), prep(fields)); + + return Tools.charArrayToByteArray(prep(map).toCharArray()); + } + + @SuppressWarnings("DuplicatedCode") + private static void writeField(Map fields, Field field, Object o) throws IllegalAccessException { + + if(field.getType() == boolean.class) { + fields.put(field.getName(), writeBoolean(field.getBoolean(o))); + } + else + if(field.getType() == byte.class) { + fields.put(field.getName(), writeByte(field.getByte(o))); + } + else + if(field.getType() == short.class) { + fields.put(field.getName(), writeShort(field.getShort(o))); + } + else + if(field.getType() == char.class) { + fields.put(field.getName(), writeChar(field.getChar(o))); + } + else + if(field.getType() == int.class) { + fields.put(field.getName(), writeInt(field.getInt(o))); + } + else + if(field.getType() == long.class) { + fields.put(field.getName(), writeLong(field.getLong(o))); + } + else + if(field.getType() == double.class) { + fields.put(field.getName(), writeDouble(field.getDouble(o))); + } + else + if(field.getType() == float.class) { + fields.put(field.getName(), writeFloat(field.getFloat(o))); + } + else + if(field.getType().isArray()) { + Map arrayMap = new HashMap<>(); + writeArray(arrayMap, field.get(o)); + fields.put(field.getName(), prep(arrayMap)); + } + else + { + fields.put(field.getName(), writeObject(field.get(o))); + } + } + + private static void writeArray(Map map, Object o) throws IllegalAccessException { + Class c = o.getClass(); + + if(c.getComponentType() == boolean.class) { + boolean[] a = (boolean[]) o; + map.put(idToString(0x00), writeInt(a.length)); + for (int i = 0; i < a.length; i++) { + map.put(writeInt(i), writeBoolean(a[i])); + } + } + else + if(c.getComponentType() == byte.class) { + byte[] a = (byte[]) o; + map.put(idToString(0x00), writeInt(a.length)); + for (int i = 0; i < a.length; i++) { + map.put(writeInt(i), writeByte(a[i])); + } + } + else + if(c.getComponentType() == short.class) { + short[] a = (short[]) o; + map.put(idToString(0x00), writeInt(a.length)); + for (int i = 0; i < a.length; i++) { + map.put(writeInt(i), writeShort(a[i])); + } + } + else + if(c.getComponentType() == char.class) { + char[] a = (char[]) o; + map.put(idToString(0x00), writeInt(a.length)); + for (int i = 0; i < a.length; i++) { + map.put(writeInt(i), writeChar(a[i])); + } + } + else + if(c.getComponentType() == int.class) { + int[] a = (int[]) o; + map.put(idToString(0x00), writeInt(a.length)); + for (int i = 0; i < a.length; i++) { + map.put(writeInt(i), writeInt(a[i])); + } + } + else + if(c.getComponentType() == long.class) { + long[] a = (long[]) o; + map.put(idToString(0x00), writeInt(a.length)); + for (int i = 0; i < a.length; i++) { + map.put(writeInt(i), writeLong(a[i])); + } + } + else + if(c.getComponentType() == double.class) { + double[] a = (double[]) o; + map.put(idToString(0x00), writeInt(a.length)); + for (int i = 0; i < a.length; i++) { + map.put(writeInt(i), writeDouble(a[i])); + } + } + else + if(c.getComponentType() == float.class) { + float[] a = (float[]) o; + map.put(idToString(0x00), writeInt(a.length)); + for (int i = 0; i < a.length; i++) { + map.put(writeInt(i), writeFloat(a[i])); + } + } + else + if(c.getComponentType().isArray()) { + int len = Array.getLength(o); + map.put(idToString(0x00), writeInt(len)); + for (int i = 0; i < len; i++) { + Map arrayMap = new HashMap<>(); + writeArray(arrayMap, Array.get(o, i)); + map.put(writeInt(i), prep(arrayMap)); + } + } + else + { + Object[] a = (Object[]) o; + map.put(idToString(0x00), writeInt(a.length)); + for (int i = 0; i < a.length; i++) { + map.put(writeInt(i), writeObject(a[i])); + } + } + } + + private static String writeBoolean(boolean o) { + return idToString(o ? 0x01 : 0x00); + } + + public static String writeByte(byte o) { + return idToString(o); + } + + public static String writeShort(short o) { + return idToString( + o >> 8 * 1 & 0xff, + o >> 8 * 0 & 0xff + ); + } + + public static String writeChar(char o) { + return idToString( + o >> 8 * 1 & 0xff, + o >> 8 * 0 & 0xff + ); + } + + public static String writeInt(int o) { + return idToString( + o >> 8 * 3 & 0xff, + o >> 8 * 2 & 0xff, + o >> 8 * 1 & 0xff, + o >> 8 * 0 & 0xff + ); + } + + public static String writeLong(long o) { + return idToString( + o >> 8 * 7 & 0xff, + o >> 8 * 6 & 0xff, + o >> 8 * 5 & 0xff, + o >> 8 * 4 & 0xff, + o >> 8 * 3 & 0xff, + o >> 8 * 2 & 0xff, + o >> 8 * 1 & 0xff, + o >> 8 * 0 & 0xff + ); + } + + public static String writeDouble(double o) { + return writeLong(Double.doubleToLongBits(o)); + } + + public static String writeFloat(float o) { + return writeInt(Float.floatToIntBits(o)); + } + + private static String writeObject(Object o) throws IllegalAccessException { + return idToString(Tools.byteArrayToUnsignedIntArray(serialize(o, o.getClass()))); + } + + + public static Object deserialize(byte[] bytes) throws IllegalAccessException, InstantiationException, ClassNotFoundException { + return deserialize(new String(Tools.intArrayToCharArray(Tools.byteArrayToUnsignedIntArray(bytes))), null); + } + + private static Object deserialize(String s, Object o) throws ClassNotFoundException, IllegalAccessException, InstantiationException { + + Map map = prep(s); + + + + Class c = Class.forName(map.get(idToString(0x00))); + + if(o == null) + o = c.newInstance(); + + if(c.getSuperclass() != null) + deserialize(map.get(idToString(0x01)), o); + + Map fields = new HashMap<>(); + for (Field field : c.getDeclaredFields()) { + boolean accessible = field.isAccessible(); + ReflectUtil.forceAccessible(field); + + readField(fields, field, o); + + field.setAccessible(accessible); + } + + return o; + } + + private static void readField(Map fields, Field field, Object o) throws IllegalAccessException, ClassNotFoundException, InstantiationException { + + if(field.getType() == boolean.class) { + field.setBoolean(o, readBoolean(fields.get(field.getName()))); + } + else + if(field.getType() == byte.class) { + field.setByte(o, readByte(fields.get(field.getName()))); + } + else + if(field.getType() == short.class) { + field.setShort(o, readShort(fields.get(field.getName()))); + } + else + if(field.getType() == char.class) { + field.setChar(o, readChar(fields.get(field.getName()))); + } + else + if(field.getType() == int.class) { + field.setInt(o, readInt(fields.get(field.getName()))); + } + else + if(field.getType() == long.class) { + field.setLong(o, readLong(fields.get(field.getName()))); + } + else + if(field.getType() == double.class) { + field.setDouble(o, readDouble(fields.get(field.getName()))); + } + else + if(field.getType() == float.class) { + field.setFloat(o, readFloat(fields.get(field.getName()))); + } + else + if(field.getType().isArray()) { + field.set(o, readArray(field.getType(), prep(fields.get(field.getName())))); + } + else + { + field.set(o, deserialize(fields.get(field.getName()), null)); + } + } + + private static Object readArray(Class c, Map data) + throws IllegalAccessException, InstantiationException, ClassNotFoundException { + + Object r; + + if(c.getComponentType() == boolean.class) { + boolean[] a = new boolean[readInt(data.get(idToString(0x00)))]; + for (int i = 0; i < a.length; i++) { + a[i] = readBoolean(data.get(writeInt(i))); + } + r = a; + } + else + if(c.getComponentType() == byte.class) { + byte[] a = new byte[readInt(data.get(idToString(0x00)))]; + for (int i = 0; i < a.length; i++) { + a[i] = readByte(data.get(writeInt(i))); + } + r = a; + } + else + if(c.getComponentType() == short.class) { + short[] a = new short[readInt(data.get(idToString(0x00)))]; + for (int i = 0; i < a.length; i++) { + a[i] = readShort(data.get(writeInt(i))); + } + r = a; + } + else + if(c.getComponentType() == char.class) { + char[] a = new char[readInt(data.get(idToString(0x00)))]; + for (int i = 0; i < a.length; i++) { + a[i] = readChar(data.get(writeInt(i))); + } + r = a; + } + else + if(c.getComponentType() == int.class) { + int[] a = new int[readInt(data.get(idToString(0x00)))]; + for (int i = 0; i < a.length; i++) { + a[i] = readInt(data.get(writeInt(i))); + } + r = a; + } + else + if(c.getComponentType() == long.class) { + long[] a = new long[readInt(data.get(idToString(0x00)))]; + for (int i = 0; i < a.length; i++) { + a[i] = readLong(data.get(writeInt(i))); + } + r = a; + } + else + if(c.getComponentType() == double.class) { + double[] a = new double[readInt(data.get(idToString(0x00)))]; + for (int i = 0; i < a.length; i++) { + a[i] = readDouble(data.get(writeInt(i))); + } + r = a; + } + else + if(c.getComponentType() == float.class) { + float[] a = new float[readInt(data.get(idToString(0x00)))]; + for (int i = 0; i < a.length; i++) { + a[i] = readFloat(data.get(writeInt(i))); + } + r = a; + } + else + if(c.getComponentType().isArray()) { + Object[] a = (Object[]) Array.newInstance(c.getComponentType(), readInt(data.get(idToString(0x00)))); + for (int i = 0; i < a.length; i++) { + a[i] = readArray(c, prep(data.get(writeInt(i)))); + } + r = a; + } + else + { + Object[] a = (Object[]) Array.newInstance(c.getComponentType(), readInt(data.get(idToString(0x00)))); + for (int i = 0; i < a.length; i++) { + a[i] = deserialize(data.get(writeInt(i)), null); + } + r = a; + } + + return r; + } + + private static boolean readBoolean(String s) { + return s.equals(idToString(0x01)); + } + + private static byte readByte(String s) { + return (byte) stringToID(s); + } + + private static short readShort(String s) { + int[] o = stringToIDs(s); + return + (short) ( + (o[0] << 8 * 1) + + (o[1] << 8 * 0) + ) + ; + } + + private static char readChar(String s) { + int[] o = stringToIDs(s); + return + (char) ( + (o[0] << 8 * 1) + + (o[1] << 8 * 0) + ) + ; + } + + private static int readInt(String s) { + int[] o = stringToIDs(s); + return + (o[0] << 8 * 3) + + (o[1] << 8 * 2) + + (o[2] << 8 * 1) + + (o[3] << 8 * 0) + ; + } + + private static long readLong(String s) { + int[] o = stringToIDs(s); + return + ((long) o[0] << 8 * 7) + + ((long) o[1] << 8 * 6) + + ((long) o[2] << 8 * 5) + + ((long) o[3] << 8 * 4) + + ((long) o[4] << 8 * 3) + + ((long) o[5] << 8 * 2) + + ((long) o[6] << 8 * 1) + + ((long) o[7] << 8 * 0); + } + + private static double readDouble(String s) { + return Double.longBitsToDouble(readLong(s)); + } + + private static float readFloat(String s) { + return Float.intBitsToFloat(readInt(s)); + } + + + + private static int[] stringToIDs(String id) { + int[] ints = new int[id.length()]; + for (int i = 0; i < id.length(); i++) { + ints[i] = id.charAt(i) & 0xff; + } + return ints; + } + private static int stringToID(String id) { + return (int) id.charAt(0) & 0xff; + } + + private static String idToString(int... id) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < id.length; i++) { + builder.append((char) id[i]); + } + return builder.toString(); + } + + private static String idToString(long... id) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < id.length; i++) { + builder.append((char) id[i]); + } + return builder.toString(); + } + + private static String prep(Map map) { + StringBuilder r = new StringBuilder(); + + for (String key : map.keySet().toArray(new String[0])) { + + r + .append( + key + .replaceAll(idToString(0xfd), idToString(0xfd, 0x0d)) + .replaceAll(idToString(0xff), idToString(0xfd, 0x0f)) + .replaceAll(idToString(0xfe), idToString(0xfd, 0x0e)) + ) + .append(idToString(0xfe)) + .append( + map.get(key) + .replaceAll(idToString(0xfd), idToString(0xfd, 0x0d)) + .replaceAll(idToString(0xff), idToString(0xfd, 0x0f)) + .replaceAll(idToString(0xfe), idToString(0xfd, 0x0e)) + ) + .append(idToString(0xff)) + ; + } + + return r.toString(); + } + + private static Map prep(String string) { + + HashMap map = new HashMap<>(); + + for (int i = 0; i < string.split(idToString(0xff)).length; i++) { + String tile = string.split(idToString(0xff))[i]; + if (tile.contains(idToString(0xfe))) { + if (tile.split(idToString(0xfe)).length == 2) + map.put( + tile + .split(idToString(0xfe))[0] + .replaceAll(idToString(0xfd, 0x0e), idToString(0xfe)) + .replaceAll(idToString(0xfd, 0x0f), idToString(0xff)) + .replaceAll(idToString(0xfd, 0x0d), idToString(0xfd)), + tile + .split(idToString(0xfe))[1] + .replaceAll(idToString(0xfd, 0x0e), idToString(0xfe)) + .replaceAll(idToString(0xfd, 0x0f), idToString(0xff)) + .replaceAll(idToString(0xfd, 0x0d), idToString(0xfd)) + ); + else + map.put( + tile + .split(idToString(0xfe))[0] + .replaceAll(idToString(0xfd, 0x0e), idToString(0xfe)) + .replaceAll(idToString(0xfd, 0x0f), idToString(0xff)) + .replaceAll(idToString(0xfd, 0x0d), idToString(0xfd)) + , "" + ); + } + } + + return map; + } +} diff --git a/src/main/java/de/tudbut/tools/SimpleTimer.java b/src/main/java/de/tudbut/tools/SimpleTimer.java new file mode 100644 index 0000000..da9721b --- /dev/null +++ b/src/main/java/de/tudbut/tools/SimpleTimer.java @@ -0,0 +1,45 @@ +package de.tudbut.tools; + +import java.util.Date; + +public class SimpleTimer { + + private final Lock lock = new Lock(); + + private final int time; + private int timesRan = 0; + private long lastTimeTaken = 0; + + private long last; + public SimpleTimer(int delay) { + time = delay; + } + + private void waitHere() { + lock.waitHere(); + lock.lock(getDelayTime()); + last = new Date().getTime(); + } + + public int getTimesRan() { + return timesRan; + } + + public long getLastTimeTaken() { + return lastTimeTaken; + } + + public void run(Runnable runnable) { + timesRan++; + waitHere(); + try { + runnable.run(); + } finally { + lastTimeTaken = new Date().getTime() - last; + } + } + + private int getDelayTime() { + return time; + } +} diff --git a/src/main/java/de/tudbut/tools/Stack.java b/src/main/java/de/tudbut/tools/Stack.java new file mode 100644 index 0000000..e5b1f8e --- /dev/null +++ b/src/main/java/de/tudbut/tools/Stack.java @@ -0,0 +1,25 @@ +package de.tudbut.tools; + +public class Stack extends Queue { + + public Stack() { } + + protected Stack(Stack stack) { + super(stack); + } + + @Override + public synchronized T next() { + return popTop(); + } + + @Override + public synchronized T peek() { + return getTop(); + } + + @Override + public Stack clone() { + return new Stack<>(this); + } +} diff --git a/src/main/java/de/tudbut/tools/Stopwatch.java b/src/main/java/de/tudbut/tools/Stopwatch.java new file mode 100644 index 0000000..18aa7e5 --- /dev/null +++ b/src/main/java/de/tudbut/tools/Stopwatch.java @@ -0,0 +1,28 @@ +package de.tudbut.tools; + +import java.util.ArrayList; +import java.util.Date; + +public class Stopwatch { + + public Stopwatch() { + + } + + long sa = new Date().getTime(); + ArrayList laps = new ArrayList<>(); + + public long lap() { + long l = getPassedTime(); + laps.add(l); + return l; + } + + public long getLap(int idx) { + return laps.get(idx); + } + + public long getPassedTime() { + return new Date().getTime() - sa; + } +} diff --git a/src/main/java/de/tudbut/tools/StringTools.java b/src/main/java/de/tudbut/tools/StringTools.java new file mode 100644 index 0000000..d581d25 --- /dev/null +++ b/src/main/java/de/tudbut/tools/StringTools.java @@ -0,0 +1,30 @@ +package de.tudbut.tools; + +public class StringTools { + + public static String removeIndents(String s) { + while(s.contains("\n ")) { + s = s.replaceAll("\n ", "\n"); + } + while(s.contains("\n\t")) { + s = s.replaceAll("\n\t", "\n"); + } + return s; + } + + public static String multiply(String s, int i) { + StringBuilder builder = new StringBuilder(); + for (int j = 0; j < i; j++) { + builder.append(s); + } + return builder.toString(); + } + + public static String lengthify(String s, String m, int i) { + return s + multiply(m, (i - s.length()) / m.length()); + } + + public static String lengthifyStart(String s, String m, int i) { + return multiply(m, (i - s.length()) / m.length()) + s; + } +} diff --git a/src/main/java/de/tudbut/tools/ThreadPool.java b/src/main/java/de/tudbut/tools/ThreadPool.java new file mode 100644 index 0000000..336e679 --- /dev/null +++ b/src/main/java/de/tudbut/tools/ThreadPool.java @@ -0,0 +1,87 @@ +package de.tudbut.tools; + +import de.tudbut.type.Stoppable; +import de.tudbut.obj.NotSupportedException; + +import java.util.concurrent.atomic.AtomicInteger; + +public class ThreadPool implements Stoppable { + private final Thread[] threads; + private final Runnable[] toDo; + private final Lock[] locks; + private final Lock hasFree = new Lock(); + private final AtomicInteger freeThreads = new AtomicInteger(); + + public ThreadPool(final int amount, final String name, final boolean enableCrashRecovery) { + threads = new Thread[amount]; + toDo = new Runnable[amount]; + locks = new Lock[amount]; + for (int i = 0 ; i < locks.length ; i++) { + locks[i] = new Lock(); + locks[i].lock(); + } + updateFree(); + + for (int i = 0; i < threads.length; i++) { + final int threadID = i; + threads[i] = new Thread(() -> { + while (!isStopped()) { + freeThreads.getAndIncrement(); + updateFree(); + locks[threadID].waitHere(); + locks[threadID].lock(); + freeThreads.getAndDecrement(); + updateFree(); + + try { + toDo[threadID].run(); + } catch (Throwable e) { + if (enableCrashRecovery) { + new RuntimeException("Thread crash: " + threads[threadID].getName() + "!", e).fillInStackTrace().printStackTrace(); + System.err.println("Thread recovered: " + threads[threadID].getName() + "."); + } else { + throw new RuntimeException("Thread crash: " + threads[threadID].getName() + "!", e); + } + } + } + }, name + ":" + i); + threads[i].start(); + } + } + + private void updateFree() { + if (freeThreads.get() == 0) { + hasFree.lock(); + } + else { + hasFree.unlock(); + } + } + + public void run(Runnable runnable) { + boolean found = false; + while (!found) { + hasFree.waitHere(); + for (int i = 0; i < threads.length; i++) { + if(locks[i].isLocked()) { + toDo[i] = runnable; + locks[i].unlock(); + found = true; + break; + } + } + } + } + + public void start() throws NotSupportedException { + throw new NotSupportedException(); + } + + public int available() { + return freeThreads.get(); + } + + public boolean isBlocked() { + return hasFree.isLocked(); + } +} diff --git a/src/main/java/de/tudbut/tools/ThrowingRunnable.java b/src/main/java/de/tudbut/tools/ThrowingRunnable.java new file mode 100644 index 0000000..8893666 --- /dev/null +++ b/src/main/java/de/tudbut/tools/ThrowingRunnable.java @@ -0,0 +1,5 @@ +package de.tudbut.tools; + +public interface ThrowingRunnable { + void run() throws Exception; +} diff --git a/src/main/java/de/tudbut/tools/Time.java b/src/main/java/de/tudbut/tools/Time.java new file mode 100644 index 0000000..5348e21 --- /dev/null +++ b/src/main/java/de/tudbut/tools/Time.java @@ -0,0 +1,34 @@ +package de.tudbut.tools; + +public class Time { + + public static String ydhmsString(long seconds) { + String s = ""; + s += (seconds / 60 / 60 / 24 / 360) + ":"; + s += (seconds / 60 / 60 / 24 % 360) + ":"; + s += (seconds / 60 / 60 % 24) + ":"; + s += (seconds / 60 % 60) + ":"; + s += (seconds % 60); + return s; + } + + public static String[] ydhmsStrings(long seconds) { + return ydhmsString(seconds).split(":"); + } + + public static String ydhms(long seconds) { + String s = ""; + s += (seconds / 60 / 60 / 24 / 360) + "y "; + s += (seconds / 60 / 60 / 24 % 360) + "d "; + s += (seconds / 60 / 60 % 24) + "h "; + s += (seconds / 60 % 60) + "m "; + s += (seconds % 60) + "s"; + return s; + } + + public static long predictTimeLeft(long start, float progress) { + long current = System.currentTimeMillis(); + long diff = current - start; + return (long) (diff / progress) + start - current; + } +} diff --git a/src/main/java/de/tudbut/tools/Timer.java b/src/main/java/de/tudbut/tools/Timer.java new file mode 100644 index 0000000..14f803e --- /dev/null +++ b/src/main/java/de/tudbut/tools/Timer.java @@ -0,0 +1,72 @@ +package de.tudbut.tools; + +import java.util.Date; + +public class Timer { + + Lock lock = new Lock(); + + int time; + int timesRan = 0; + long lastTimeTaken = 0; + int needed = 0; + + long last; + public Timer(int delay) { + time = delay; + last = new Date().getTime(); + } + + private void waitHere() { + lock.waitHere(); + lock.lock(getDelayTime()); + last = new Date().getTime(); + } + + public int getTimesRan() { + return timesRan; + } + + public long getLastTimeTaken() { + return lastTimeTaken; + } + + public void run(Runnable runnable) { + timesRan++; + waitHere(); + try { + runnable.run(); + } finally { + lastTimeTaken = new Date().getTime() - last; + } + } + + public boolean isFastForward() { + return needed > 0; + } + + public boolean isSuperFastForward() { + return needed > time; + } + + private int getDelayTime() { + long last = this.last; + int time = this.time; + long l = new Date().getTime(); + if (l - last > time) { + needed += l - last - time; + } + if (needed > 0) { + needed -= time - (l - last); + if (needed > time * 2L) { + return 1; + } + else { + return time - needed; + } + } + else + needed = 0; + return time; + } +} diff --git a/src/main/java/de/tudbut/tools/Tools.java b/src/main/java/de/tudbut/tools/Tools.java new file mode 100644 index 0000000..f102bd7 --- /dev/null +++ b/src/main/java/de/tudbut/tools/Tools.java @@ -0,0 +1,532 @@ +package de.tudbut.tools; + +import de.tudbut.type.StringArray; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.lang.reflect.Field; +import java.sql.Time; +import java.util.*; +import de.tudbut.obj.TypedArray; + +public class Tools { + + public static String[] readf(String format, String s) { + // extracts parts of a string denoted by {} + try { + if(!format.contains("{}")) + return format.equals(s) ? new String[]{} : null; + if(format.contains("{}{}")) throw new IllegalArgumentException("Ambiguous argument: '{}{}' found in format string"); + String f = format; + int occurences = 0; + for(; f.indexOf("{}") != -1; occurences++) { + f = f.substring(f.indexOf("{}") + 2); + } + String[] result = new String[occurences]; + + String originalFormat = format; + for(int n = 0; n <= occurences; n++) { // This may throw if it doesn't match, but that's the same outcome + // shave off blanking space + int i = format.indexOf("{}"); + if(i == -1) i = format.length(); + if(!format.substring(0, i).equals(s.substring(0, i))) return null; // If the previous part didn't match, we can forget about it. + if(n == occurences) { + break; + } + format = format.substring(i + 2); + s = s.substring(i); + // populate braces + int x = format.indexOf("{}"); + if(x != -1) { + result[n] = s.substring(0, s.indexOf(format.substring(0, x))); + } + else { + result[n] = s.substring(0, s.length() - (originalFormat.length() - originalFormat.lastIndexOf("{}") - 2)); + } + s = s.substring(result[n].length()); + } + if(result[occurences - 1] == null) // this happens when a later part doesnt match; + return null; + + return result; + } catch(Exception e) { + return null; + } + } + + public static String readf1(String format, String s) { + // extracts parts of a string denoted by {} + String[] r = readf(format, s); + if(r == null) return null; + if(r.length == 0) return ""; + return r[0]; + } + + public static BufferedReader getStdInput() { + return new BufferedReader(new InputStreamReader(System.in)); + } + + public static String randomOutOfArray(StringArray stringArray) { + return stringArray.asArray()[(int) Math.floor(Math.random() * stringArray.asArray().length)]; + } + + public static T randomOutOfArray(T[] array) { + return array[(int) Math.floor(Math.random() * array.length)]; + } + + public static T randomOutOfArray(TypedArray array) { + return array.get((int) Math.floor(Math.random() * array.length())); + } + + public static String randomString(int length, String pool) { + StringBuilder r = new StringBuilder(); + + for (int i = 0; i < length; i++) { + r.append(pool.charAt(ExtendedMath.random(0, pool.length() - 1))); + } + + return r.toString(); + } + + public static void copyArray(Object array1, Object array2, int copyLength) { + System.arraycopy(array1, 0, array2, 0, copyLength); + } + + public static String randomAlphanumericString(int length) { + String alphabet = "abcdefghijklmnopqrstuvwxyz"; + String pool = alphabet + alphabet.toUpperCase() + "0123456789"; + + return randomString(length, pool); + } + + public static String randomReadableString(int length) { + String pool = "bcdfghjklmnpqrstvwxyz"; + String readablePool = "aeiou"; + + StringBuilder r = new StringBuilder(); + for (int i = 0; i < length; i++) { + r.append(pool.charAt(ExtendedMath.random(0, pool.length() - 1))) + .append(readablePool.charAt(ExtendedMath.random(0, readablePool.length() - 1))); + } + return r.substring(0, length); + } + + public static String getTime() { + return new Time(new Date().getTime()).toString(); + } + + public static String stringSwitch(Map switchMap, String value) { + if (switchMap.get(value) != null) { + return switchMap.get(value); + } + + return switchMap.get("__default"); + } + + public static Map toSwitchMap(Map alreadyExisting, String newKey, String newVal) { + alreadyExisting.put(newKey, newVal); + alreadyExisting.putIfAbsent("__default", ""); + return alreadyExisting; + } + + public static Map newSwitchMap(String defaultVal) { + HashMap r = new HashMap<>(); + + r.put("__default", defaultVal); + + return r; + } + + public static Map stringToMap(String mapStringParsable) { + LinkedHashMap map = new LinkedHashMap<>(); + + String[] splitTiles = mapStringParsable.split(";"); + for (int i = 0; i < splitTiles.length; i++) { + String tile = splitTiles[i]; + String[] splitTile = tile.split(":"); + if (tile.contains(":")) { + if (splitTile.length == 2) + map.put( + splitTile[0] + .replaceAll("%I", ":") + .replaceAll("%B", ";") + .replaceAll("%P", "%"), + splitTile[1].equals("%N") + ? null + : splitTile[1] + .replaceAll("%I", ":") + .replaceAll("%B", ";") + .replaceAll("%P", "%")); + else + map.put( + splitTile[0] + .replaceAll("%I", ":") + .replaceAll("%B", ";") + .replaceAll("%P", "%"), + ""); + } + } + + return map; + } + + public static String mapToString(Map map) { + StringBuilder r = new StringBuilder(); + + for (String key : map.keySet().toArray(new String[0])) { + + r.append(key.replaceAll("%", "%P").replaceAll(";", "%B").replaceAll(":", "%I")) + .append(":") + .append( + map.get(key) == null + ? "%N" + : map.get(key) + .replaceAll("%", "%P") + .replaceAll(";", "%B") + .replaceAll(":", "%I")) + .append(";"); + } + + return r.toString(); + } + + public static byte[] charArrayToByteArray(char[] chars) { + byte[] bytes = new byte[chars.length]; + + for (int i = 0; i < bytes.length; i++) { + bytes[i] = (byte) (int) chars[i]; + } + + return bytes; + } + + public static int[] charArrayToIntArray(char[] chars) { + int[] ints = new int[chars.length]; + + for (int i = 0; i < ints.length; i++) { + ints[i] = chars[i]; + } + + return ints; + } + + public static char[] intArrayToCharArray(int[] ints) { + char[] chars = new char[ints.length]; + + for (int i = 0; i < ints.length; i++) { + chars[i] = (char) ints[i]; + } + + return chars; + } + + public static int[] byteArrayToIntArray(byte[] bytes) { + int[] ints = new int[bytes.length]; + + for (int i = 0; i < ints.length; i++) { + ints[i] = Byte.toUnsignedInt(bytes[i]); + } + + return ints; + } + + public static int[] byteArrayToUnsignedIntArray(byte[] bytes) { + int[] ints = new int[bytes.length]; + + for (int i = 0; i < ints.length; i++) { + ints[i] = Byte.toUnsignedInt(bytes[i]); + } + + return ints; + } + + public static String wildcardToRegex(String s) { + String r = ""; + char[] charArray = s.toCharArray(); + for (int i = 0, charArrayLength = charArray.length; i < charArrayLength; i++) { + char c = charArray[i]; + r += ("[" + c + "]").replaceAll("\\^", "\\^"); + } + return "^" + + r.replaceAll("\\[\\\\]", "[\\\\}") + .replaceAll("\\[\\*]", "(.|\n)*") + .replaceAll("\\[\\?]", "[.\n]") + "$"; + } + + public static T firstNonNull(T... objects) { + for (int i = 0; i < objects.length; i++) { + if(objects[i] != null) + return objects[i]; + } + return null; + } + + public static class TFS { + + public static String createTFS(String sep) { + Map mainMap = new HashMap<>(); + mainMap.put("head", "\u0000\u0001" + sep + "\u0000\u0002\u0020\u0000\u0003/\u0000"); + return mapToString(mainMap); + } + + public static String getFromHead(String tfs, String key) { + Map tfsMap = stringToMap(tfs); + + for (String val : tfsMap.get("head").split("\\x{0000}")) { + if (val.startsWith(key)) return val.substring(1); + } + return null; + } + + public static String getPath(String tfs, String path) { + String p = Objects.requireNonNull(getFromHead(tfs, "\u0003")); + return path.startsWith("/") ? path : (p.equals("/") ? p : p + "/") + path; + } + + public static String getFile(String tfs, String path) { + Map tfsMap = stringToMap(tfs); + + return tfsMap.get(getPath(tfs, path)); + } + + public static String getFileContent(String file) { + return stringToMap(file).get("content"); + } + + public static String createFile(String tfs, String path, String content) + throws TFSException.TFSFileAlreadyExistsException { + if (getFile(tfs, path) == null) { + Map tfsMap = stringToMap(tfs); + Map fileMap = new HashMap<>(); + + fileMap.put("head", "\u0000"); + fileMap.put("content", content); + fileMap.put("mods", String.valueOf(new Date().getTime())); + fileMap.put("lastMod", String.valueOf(new Date().getTime())); + + tfsMap.put(getPath(tfs, path), mapToString(fileMap)); + return mapToString(tfsMap); + } else throw new TFSException.TFSFileAlreadyExistsException(); + } + + public static String modFile(String tfs, String path, String newContent) + throws TFSException.TFSFileNotFoundException { + if (getFile(tfs, path) != null) { + Map tfsMap = stringToMap(tfs); + Map fileMap = stringToMap(tfsMap.get(getPath(tfs, path))); + + fileMap.put("content", newContent); + fileMap.put("mods", fileMap.get("mods") + ";" + new Date().getTime()); + fileMap.put("lastMod", String.valueOf(new Date().getTime())); + + tfsMap.put(getPath(tfs, path), mapToString(fileMap)); + return mapToString(tfsMap); + } else throw new TFSException.TFSFileNotFoundException(); + } + + public static String cd(String tfs, String path) throws TFSException.TFSPathNotFromRootException { + if (path.startsWith(Objects.requireNonNull(getFromHead(tfs, "\u0001")))) { + Map tfsMap = stringToMap(tfs); + + StringBuilder newHead = new StringBuilder(); + for (String val : tfsMap.get("head").split("\\x{0000}")) { + if (val.startsWith("\u0003")) val = "\u0003" + path; + newHead.append(val).append("\u0000"); + } + tfsMap.put("head", newHead.toString()); + + return mapToString(tfsMap); + } else throw new TFSException.TFSPathNotFromRootException(); + } + + public static class TFSException extends Exception { + + public static class TFSFileAlreadyExistsException extends TFSException {} + + public static class TFSFileNotFoundException extends TFSException {} + + public static class TFSPathNotFromRootException extends TFSException {} + } + } + + public static class ObjectMapping { + public static Map objectToMap(Object o) throws IllegalAccessException { + Map map = new HashMap<>(); + + Class c = o.getClass(); + for (Field field : c.getFields()) { + if (field.getType() == String.class) { + map.put( + field.getName(), + "str\u0000" + + ((String) field.get(new Object())) + .replaceAll("\\x{0000}", "\u00010") + .replaceAll("\\x{0001}", "\u00011")); + } + + if (field.getType().isInstance(new HashMap())) { + map.put( + field.getName(), + "map\u0000" + + mapToString((Map) field.get(new Object())) + .replaceAll("\\x{0000}", "\u00010") + .replaceAll("\\x{0001}", "\u00011")); + } + + if (field.getType() == int.class) { + map.put(field.getName(), "int\u0000" + field.get(new Object())); + } + + if (field.getType() == long.class) { + map.put(field.getName(), "lon\u0000" + field.get(new Object())); + } + + if (field.getType() == double.class) { + map.put(field.getName(), "dou\u0000" + field.get(new Object())); + } + + if (field.getType() == float.class) { + map.put(field.getName(), "flo\u0000" + field.get(new Object())); + } + + if (field.getType() == boolean.class) { + map.put(field.getName(), "boo\u0000" + field.get(new Object())); + } + } + + return map; + } + + public static void mapToObject(Object o, Map map) throws IllegalAccessException { + Class c = o.getClass(); + for (String key : map.keySet()) { + String type = map.get(key).split("\\x{0000}")[0]; + String val = map.get(key) + .split("\\x{0000}")[1] + .replaceAll("\\x{0001}0", "\u0000") + .replaceAll("\\x{0001}1", "\u0001"); + + try { + Field field = c.getField(key); + + if (type.equals("str")) { + field.set(new Object(), val); + } + + if (type.equals("map")) { + field.set(new Object(), stringToMap(val)); + } + + if (type.equals("int")) { + field.set(new Object(), Integer.parseInt(val)); + } + + if (type.equals("lon")) { + field.set(new Object(), Long.parseLong(val)); + } + + if (type.equals("dou")) { + field.set(new Object(), Double.parseDouble(val)); + } + + if (type.equals("flo")) { + field.set(new Object(), Float.parseFloat(val)); + } + + if (type.equals("boo")) { + field.set(new Object(), Boolean.parseBoolean(val)); + } + } catch (NoSuchFieldException ignore) { + } + } + } + + public static Map staticObjectToMap(Class c) throws IllegalAccessException { + Map map = new HashMap<>(); + + for (Field field : c.getFields()) { + if (field.getType() == String.class && field.get(new Object()) != null) { + map.put( + field.getName(), + "str\u0000" + + ((String) field.get(new Object())) + .replaceAll("\\x{0000}", "\u00010") + .replaceAll("\\x{0001}", "\u00011")); + } + + if (field.getType().isInstance(new HashMap()) && field.get(new Object()) != null) { + map.put( + field.getName(), + "map\u0000" + + mapToString((Map) field.get(new Object())) + .replaceAll("\\x{0000}", "\u00010") + .replaceAll("\\x{0001}", "\u00011")); + } + + if (field.getType() == int.class) { + map.put(field.getName(), "int\u0000" + field.get(new Object())); + } + + if (field.getType() == long.class) { + map.put(field.getName(), "lon\u0000" + field.get(new Object())); + } + + if (field.getType() == double.class) { + map.put(field.getName(), "dou\u0000" + field.get(new Object())); + } + + if (field.getType() == float.class) { + map.put(field.getName(), "flo\u0000" + field.get(new Object())); + } + + if (field.getType() == boolean.class) { + map.put(field.getName(), "boo\u0000" + field.get(new Object())); + } + } + + return map; + } + + public static void mapToStaticObject(Class c, Map map) throws IllegalAccessException { + for (String key : map.keySet()) { + String type = map.get(key).split("\\x{0000}")[0]; + String val = map.get(key) + .split("\\x{0000}")[1] + .replaceAll("\\x{0001}0", "\u0000") + .replaceAll("\\x{0001}1", "\u0001"); + + try { + Field field = c.getField(key); + + if (type.equals("str")) { + field.set(new Object(), val); + } + + if (type.equals("map")) { + field.set(new Object(), stringToMap(val)); + } + + if (type.equals("int")) { + field.set(new Object(), Integer.parseInt(val)); + } + + if (type.equals("lon")) { + field.set(new Object(), Long.parseLong(val)); + } + + if (type.equals("dou")) { + field.set(new Object(), Double.parseDouble(val)); + } + + if (type.equals("flo")) { + field.set(new Object(), Float.parseFloat(val)); + } + + if (type.equals("boo")) { + field.set(new Object(), Boolean.parseBoolean(val)); + } + } catch (NoSuchFieldException ignore) { + } + } + } + } +} diff --git a/src/main/java/de/tudbut/tools/Tools2.java b/src/main/java/de/tudbut/tools/Tools2.java new file mode 100644 index 0000000..a08733d --- /dev/null +++ b/src/main/java/de/tudbut/tools/Tools2.java @@ -0,0 +1,144 @@ +package de.tudbut.tools; + +import de.tudbut.io.StreamReader; +import de.tudbut.obj.Partial; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +public class Tools2 { + + /** + * Java 9+ is unreliable when using java.awt.Toolkit#getScreenSize() + * @return The screen size, with all monitors. + */ + public static Rectangle fullScreenSize() { + GraphicsDevice[] screenDevices = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices(); + Rectangle r = new Rectangle(0, 0, 0, 0); + for (GraphicsDevice screenDevice : screenDevices) { + Rectangle bounds = screenDevice.getDefaultConfiguration().getBounds(); + r.width = Math.max(bounds.x + bounds.width, r.width); + r.height = Math.max(bounds.y + bounds.height, r.height); + } + return r; + } + + public synchronized static BufferedImage screenshot() throws AWTException { + return new Robot().createScreenCapture(fullScreenSize()); + } + + public static String getStringStackTrace(Throwable throwable) { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + throwable.printStackTrace(new PrintStream(stream)); + return stream.toString(); + } + + public synchronized static void addFilesToZIP(File zipFile, Partial.Listener done, File... files) throws IOException { + byte[] buf = new byte[StreamReader.BUFFER_SIZE]; + + ZipInputStream zin = new ZipInputStream(new ByteArrayInputStream(new StreamReader(new FileInputStream(zipFile)).readAllAsBytes())); + ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFile)); + + + ZipEntry entry = zin.getNextEntry(); + while (entry != null) { + try { + String name = entry.getName(); + boolean notInFiles = true; + for (File f : files) { + if (f.getName().equals(name)) { + notInFiles = false; + break; + } + } + if (notInFiles) { + out.putNextEntry(new ZipEntry(name)); + int len; + while ((len = zin.read(buf)) > 0) { + out.write(buf, 0, len); + } + } + entry = zin.getNextEntry(); + } catch (EOFException ignore) { + entry = null; + } + } + zin.close(); + for (File file : files) { + InputStream in = new FileInputStream(file); + out.putNextEntry(new ZipEntry(file.getName())); + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + out.closeEntry(); + in.close(); + done.onComplete(file); + } + + out.close(); + } + public synchronized static void addFilesToZIP(File zipFile, File... files) throws IOException { + addFilesToZIP(zipFile, file -> {}, files); + } + + public static void deleteDir(File dir) { + try { + File[] files = dir.listFiles(); + assert files != null; + for (File file : files) { + if (file.isDirectory()) { + deleteDir(file); + } else + file.delete(); + } + dir.delete(); + } catch (Exception ignore) { + } + } + + public static void copyDir(File dir, File dest) { + + Path sourcePath = dir.toPath(); + Path targetPath = dest.toPath(); + try(Stream filePaths = Files.walk(sourcePath)) { + filePaths.forEach(filePath -> { + try { + if (Files.isRegularFile(filePath)) { + Path newFile = targetPath.resolve(sourcePath.relativize(filePath)); + Files.copy(filePath, newFile); + }else{ + Path newDir = targetPath.resolve(sourcePath.relativize(filePath)); + Files.createDirectory(newDir); + } + } catch (IOException ignored) { + } + }); + } catch (IOException ignored) { + } + } + + public static double round(double d, int i) { + if(i < 0) + throw new IllegalArgumentException(); + + return Math.round(d * Math.pow(10, i)) / Math.pow(10, i); + } + + public static double roll(double d, double min, double max) { + while (d < min) { + d += max - min; + } + while (d >= max) { + d -= max - min; + } + return d; + } +} diff --git a/src/main/java/de/tudbut/tools/TrayApp.java b/src/main/java/de/tudbut/tools/TrayApp.java new file mode 100644 index 0000000..b25f5cb --- /dev/null +++ b/src/main/java/de/tudbut/tools/TrayApp.java @@ -0,0 +1,32 @@ +package de.tudbut.tools; + +import de.tudbut.rendering.Maths2D; + +import java.awt.*; +import java.awt.image.BufferedImage; + +public class TrayApp { + + private static SystemTray tray; + static { + if(SystemTray.isSupported()) + tray = SystemTray.getSystemTray(); + } + private final TrayIcon trayIcon; + + public TrayApp(BufferedImage icon) throws AWTException { + if(tray == null) + throw new AWTException("Tray not supported"); + trayIcon = new TrayIcon(Maths2D.distortImage(icon, tray.getTrayIconSize().width, tray.getTrayIconSize().height, 1)); + trayIcon.setImageAutoSize(true); + tray.add(trayIcon); + } + + public void setImage(BufferedImage image) { + trayIcon.setImage(Maths2D.distortImage(image, 32, 32, 1)); + } + + public void onClick(Runnable runnable) { + trayIcon.addActionListener((e) -> runnable.run()); + } +} diff --git a/src/main/java/de/tudbut/tools/Value.java b/src/main/java/de/tudbut/tools/Value.java new file mode 100644 index 0000000..9c07e4d --- /dev/null +++ b/src/main/java/de/tudbut/tools/Value.java @@ -0,0 +1,9 @@ +package de.tudbut.tools; + +public class Value { + public final T value; + + public Value(T value) { + this.value = value; + } +} diff --git a/src/main/java/de/tudbut/tools/ValueArray.java b/src/main/java/de/tudbut/tools/ValueArray.java new file mode 100644 index 0000000..d38e219 --- /dev/null +++ b/src/main/java/de/tudbut/tools/ValueArray.java @@ -0,0 +1,27 @@ +package de.tudbut.tools; + +import de.tudbut.type.FInfo; +import de.tudbut.obj.TypedArray; +import de.tudbut.obj.TypedList; + +public class ValueArray { + private final T[] values; + public final int length; + + public ValueArray(T[] values) { + this.values = values.clone(); + this.length = this.values.length; + } + + public T get(int i) { + return values[i]; + } + + @FInfo( + s="" + + "@return TypedArray, but castable to TypedList" + ) + public TypedArray toTypedArray() { + return new TypedList<>(values.clone()).lock(); + } +} diff --git a/src/main/java/de/tudbut/tools/VarTools.java b/src/main/java/de/tudbut/tools/VarTools.java new file mode 100644 index 0000000..0055df0 --- /dev/null +++ b/src/main/java/de/tudbut/tools/VarTools.java @@ -0,0 +1,30 @@ +package de.tudbut.tools; + +import de.tudbut.timer.AsyncRunnable; + +public class VarTools { + + public static O objectOrNull(T t, ArrayTools.Getter getter) { + try { + return getter.get(t); + } catch (Throwable throwable) { + return null; + } + } + + public static O objectOrNull(ArrayTools.Getter getter) { + try { + return getter.get(null); + } catch (Throwable throwable) { + return null; + } + } + + public static O objectOrNull(AsyncRunnable getter) { + try { + return getter.run(); + } catch (Throwable throwable) { + return null; + } + } +} diff --git a/src/main/java/de/tudbut/tools/YMLFile.java b/src/main/java/de/tudbut/tools/YMLFile.java new file mode 100644 index 0000000..7b8c3d1 --- /dev/null +++ b/src/main/java/de/tudbut/tools/YMLFile.java @@ -0,0 +1,54 @@ +package de.tudbut.tools; + +import de.tudbut.type.FileFormatException; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +public class YMLFile extends FileRW { + protected Map map = new HashMap<>(); + + public YMLFile(String path) throws Exception { + super(path); + if (!path.endsWith(".yml") && !path.endsWith(".yaml")) { + throw new FileFormatException(); + } + else { + this.remap(); + } + } + + public void remap() throws Exception { + map = new HashMap<>(); + + + String[] var1 = this.getContent().asArray(); + + for (String line : var1) { + if (!line.equals("")) { + String theLine = line.substring(line.split(": ")[0].length() + 2); + if (theLine.startsWith("\"") && theLine.endsWith("\"")) { + theLine = theLine.substring(1, theLine.length() - 1); + } + + this.map.put(line.split(": ")[0], theLine); + } + } + + } + + public void mapToObj(Object object) throws IllegalAccessException { + Field[] fields = object.getClass().getFields(); + int var4 = fields.length; + + for (Field field : fields) { + field.set(object, this.map.get(field.getName())); + } + + } + + public String getValue(String key) { + return this.map.get(key); + } +} diff --git a/src/main/java/de/tudbut/tools/bintools/BinFileRW.java b/src/main/java/de/tudbut/tools/bintools/BinFileRW.java new file mode 100644 index 0000000..2c3d963 --- /dev/null +++ b/src/main/java/de/tudbut/tools/bintools/BinFileRW.java @@ -0,0 +1,39 @@ +package de.tudbut.tools.bintools; + +import de.tudbut.io.StreamReader; +import de.tudbut.tools.FileRW; + +import java.io.BufferedOutputStream; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +public class BinFileRW extends FileRW { + int[] chars; + + public BinFileRW(String path) throws IOException { + super(path); + } + + public int[] getBinContent() throws IOException { + rereadFile(); + return chars; + } + + public void setBinContent(int[] content) throws IOException { + this.chars = content; + int[] nChars = new int[this.chars.length]; + System.arraycopy(chars, 0, nChars, 0, chars.length); + chars = nChars; + FileOutputStream fos = new FileOutputStream(file); + BufferedOutputStream fileWriter = new BufferedOutputStream(fos); + for (int ch : chars) { + fileWriter.write(ch); + } + fileWriter.close(); + } + + public void rereadFile() throws IOException { + chars = new StreamReader(new FileInputStream(file)).readAllAsUnsignedBytes(); + } +} diff --git a/src/main/java/de/tudbut/tools/bintools/encoding/Seed.java b/src/main/java/de/tudbut/tools/bintools/encoding/Seed.java new file mode 100644 index 0000000..0877331 --- /dev/null +++ b/src/main/java/de/tudbut/tools/bintools/encoding/Seed.java @@ -0,0 +1,25 @@ +package de.tudbut.tools.bintools.encoding; + +import de.tudbut.tools.Hasher; + +public class Seed { + private final String seed; + + public Seed(String s) { + seed = s; + } + + public static String random() { + try { + return Hasher.sha512hex(Math.random() + "_" + Math.random()); + } + catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + public String getSeed() { + return seed; + } +} diff --git a/src/main/java/de/tudbut/tools/bintools/encoding/TCryptV1.java b/src/main/java/de/tudbut/tools/bintools/encoding/TCryptV1.java new file mode 100644 index 0000000..d8fc342 --- /dev/null +++ b/src/main/java/de/tudbut/tools/bintools/encoding/TCryptV1.java @@ -0,0 +1,173 @@ +package de.tudbut.tools.bintools.encoding; + +import de.tudbut.type.ByteArrayList; + +@SuppressWarnings("DuplicatedCode") +public class TCryptV1 { + + protected Seed seed; + + public TCryptV1(String seed) { + if (seed == null) { + seed = Seed.random(); + } + + this.seed = new Seed(seed); + } + + public Value encrypt(byte[] content) { + ByteArrayList newContent = new ByteArrayList(); + + //System.out.println("Encrypting with seed: " + seed.getSeed()); + + for (int c = 0; c < content.length; c += seed.getSeed().length()) { + + for (int i = 0; i < seed.getSeed().length(); i++) { + + int add; + switch (seed.getSeed().charAt(i)) { + case '0': + add = 0; + break; + case '1': + add = 1; + break; + case '2': + add = 2; + break; + case '3': + add = 3; + break; + case '4': + add = 4; + break; + case '5': + add = 5; + break; + case '6': + add = 6; + break; + case '7': + add = 7; + break; + case '8': + add = 8; + break; + case '9': + add = 9; + break; + case 'a': + add = -1; + break; + case 'b': + add = -2; + break; + case 'c': + add = -3; + break; + case 'd': + add = -4; + break; + case 'e': + add = -5; + break; + case 'f': + add = -6; + break; + default: + throw new IllegalStateException("Unexpected value: " + seed.getSeed().charAt(i)); + } + + byte theByte; + + if ((c + i) >= content.length) + break; + else + theByte = content[c + i]; + + //System.out.println("Adding char: " + theChar + " -> " + (char) (theChar + (char) add)); + newContent.add((byte) (theByte + add)); + } + } + + return new Value(newContent.toByteArray()); + } + + public Value decrypt(byte[] content) { + ByteArrayList newContent = new ByteArrayList(); + + //System.out.println("Decrypting with seed: " + seed.getSeed()); + + for (int c = 0; c < content.length; c += seed.getSeed().length()) { + + for (int i = 0; i < seed.getSeed().length(); i++) { + + int rem; + switch (seed.getSeed().charAt(i)) { + case '0': + rem = 0; + break; + case '1': + rem = 1; + break; + case '2': + rem = 2; + break; + case '3': + rem = 3; + break; + case '4': + rem = 4; + break; + case '5': + rem = 5; + break; + case '6': + rem = 6; + break; + case '7': + rem = 7; + break; + case '8': + rem = 8; + break; + case '9': + rem = 9; + break; + case 'a': + rem = -1; + break; + case 'b': + rem = -2; + break; + case 'c': + rem = -3; + break; + case 'd': + rem = -4; + break; + case 'e': + rem = -5; + break; + case 'f': + rem = -6; + break; + default: + throw new IllegalStateException("Unexpected value: " + seed.getSeed().charAt(i)); + } + + byte theByte; + + if ((c + i) >= content.length) + break; + else + theByte = content[c + i]; + + //System.out.println("Adding char: " + theChar + " -> " + (char) (theChar + (char) add)); + newContent.add((byte) (theByte - rem)); + } + } + + return new Value(newContent.toByteArray()); + } +} diff --git a/src/main/java/de/tudbut/tools/bintools/encoding/TCryptV2.java b/src/main/java/de/tudbut/tools/bintools/encoding/TCryptV2.java new file mode 100644 index 0000000..a2f6401 --- /dev/null +++ b/src/main/java/de/tudbut/tools/bintools/encoding/TCryptV2.java @@ -0,0 +1,34 @@ +package de.tudbut.tools.bintools.encoding; + +import de.tudbut.tools.Tools; +import de.tudbut.tools.bintools.BinFileRW; + +import java.io.IOException; + +public class TCryptV2 extends TCryptV1 { + public TCryptV2(String seed) { + super(seed); + } + + public Value encryptFile(String file) throws IOException { + Value n = this.encrypt(Tools.charArrayToByteArray(Tools.intArrayToCharArray(new BinFileRW(file).getBinContent()))); + int[] r = new int[n.getB().length]; + + for (int i = 0; i < r.length; i++) { + r[i] = Byte.toUnsignedInt(n.getB()[i]); + } + + return new Value(r); + } + + public Value decryptFile(String file) throws IOException { + Value n = this.decrypt(Tools.charArrayToByteArray(Tools.intArrayToCharArray(new BinFileRW(file).getBinContent()))); + int[] r = new int[n.getB().length]; + + for (int i = 0; i < r.length; i++) { + r[i] = Byte.toUnsignedInt(n.getB()[i]); + } + + return new Value(r); + } +} diff --git a/src/main/java/de/tudbut/tools/bintools/encoding/Value.java b/src/main/java/de/tudbut/tools/bintools/encoding/Value.java new file mode 100644 index 0000000..29e4d03 --- /dev/null +++ b/src/main/java/de/tudbut/tools/bintools/encoding/Value.java @@ -0,0 +1,54 @@ +package de.tudbut.tools.bintools.encoding; + +import java.util.Objects; + +public class Value { + private final String stringValue; + private final char[] charValue; + private final int[] intValue; + private final byte[] byteValue; + + public Value(String s) { + stringValue = s; + byteValue = null; + this.intValue = null; + charValue = null; + } + + public Value(char[] cs) { + charValue = cs; + byteValue = null; + this.stringValue = null; + this.intValue = null; + } + + public Value(int[] is) { + intValue = is; + byteValue = null; + this.stringValue = null; + this.charValue = null; + } + + public Value(byte[] bs) { + byteValue = bs; + intValue = null; + this.stringValue = null; + this.charValue = null; + } + + public String getS() { + return stringValue; + } + + public char[] getC() { + return Objects.requireNonNull(charValue).clone(); + } + + public int[] getI() { + return Objects.requireNonNull(intValue).clone(); + } + + public byte[] getB() { + return Objects.requireNonNull(byteValue).clone(); + } +} diff --git a/src/main/java/de/tudbut/tools/encryption/Key.java b/src/main/java/de/tudbut/tools/encryption/Key.java new file mode 100644 index 0000000..414bd8e --- /dev/null +++ b/src/main/java/de/tudbut/tools/encryption/Key.java @@ -0,0 +1,145 @@ +package de.tudbut.tools.encryption; + +import de.tudbut.parsing.TCN; +import de.tudbut.tools.Hasher; +import de.tudbut.tools.ObjectSerializerTCN; +import de.tudbut.tools.Tools; + +import java.util.Objects; + +/** + * Key to encrypt objects and strings + */ +public class Key implements Cloneable { + + protected final String string; + + /** + * Generates a random Key + */ + public Key() { + string = Tools.randomAlphanumericString(1024); + } + + /** + * Constructs a Key + * @param s Key as string + */ + public Key(String s) { + string = s; + } + + /** + * Constructs a Key + * @param bytes Key as byte[] + */ + public Key(byte[] bytes) { + string = new String(bytes); + } + + /** + * Compares two keys + * @param other The key to compare to + * @return If other is equal to this + */ + public boolean equals(Key other) { + return string.equals(other.string); + } + + /** + * Hashes the Kay + * @return the hash + */ + public String toHashString() { + String[] strings = string.split("_"); + StringBuilder hash = new StringBuilder(); + for (int i = 0 ; i < strings.length ; i++) { + hash.append(Hasher.sha512hex(strings[i])); + } + return hash.toString(); + } + + /** + * Gets the bytes of the key + * @return the bytes of the key + */ + public byte[] toBytes() { + return string.getBytes(); + } + + /** + * Returns the key as string. USE {@link #toHashString} TO GET A HASH, THIS WILL RETURN THE ENCRYPTION KEY! + * @return the key as string + */ + @Override + public String toString() { + return string; + } + + /** + * Encrypts a string + * @param s string to encrypt + * @return encrypted string + */ + public String encryptString(String s) { + char[] bytes = s.toCharArray(); + char[] eb = string.toCharArray(); + int len = bytes.length; + int p = eb.length; + for (int i = 0 ; i < len ; i+=p) { + for (int j = 0 ; j < p && i + j < len ; j++) { + int idx = i + j; + bytes[idx] = (char) ((int) bytes[idx] + (int) eb[j]); + } + } + return new String(bytes); + } + + /** + * Decrypts a string + * @param s string to decrypt + * @return decrypted string + */ + public String decryptString(String s) { + char[] bytes = s.toCharArray(); + char[] eb = string.toCharArray(); + int len = bytes.length; + int p = eb.length; + for (int i = 0 ; i < len ; i+=p) { + for (int j = 0 ; j < p && i + j < len ; j++) { + int idx = i + j; + bytes[idx] = (char) ((int) bytes[idx] - (int) eb[j]); + } + } + return new String(bytes); + } + + /** + * Encrypts an object + * @param o object to encrypt + * @return encrypted string + */ + public String encryptObject(Object o) { + return encryptString(Tools.mapToString(Objects.requireNonNull(new ObjectSerializerTCN(o).convertAll().done(new TCN())).toMap())); + } + + /** + * Decrypts an object + * @param s string to decrypt + * @return decrypted object + */ + public T decryptObject(String s) { + return new ObjectSerializerTCN(TCN.readMap(Tools.stringToMap(decryptString(s)))).convertAll().done(); + } + + @SuppressWarnings("CloneDoesntDeclareCloneNotSupportedException") + @Override + protected Key clone() { + try { + return (Key) super.clone(); + } + catch (CloneNotSupportedException e) { + return new Key(string); + } + } +} diff --git a/src/main/java/de/tudbut/tools/encryption/KeyStream.java b/src/main/java/de/tudbut/tools/encryption/KeyStream.java new file mode 100644 index 0000000..4bc4ffe --- /dev/null +++ b/src/main/java/de/tudbut/tools/encryption/KeyStream.java @@ -0,0 +1,23 @@ +package de.tudbut.tools.encryption; + +public class KeyStream { + + private int encPos = 0, decPos = 0; + private final char[] key; + + public KeyStream(Key key) { + this.key = key.string.toCharArray(); + } + + public int encrypt(int i) { + return proc(i + key[encPos++ % key.length]); + } + + public int decrypt(int i) { + return proc(i - key[decPos++ % key.length]); + } + + private int proc(int i) { + return i & 0xff; + } +} diff --git a/src/main/java/de/tudbut/tools/encryption/RawKey.java b/src/main/java/de/tudbut/tools/encryption/RawKey.java new file mode 100644 index 0000000..5fab806 --- /dev/null +++ b/src/main/java/de/tudbut/tools/encryption/RawKey.java @@ -0,0 +1,89 @@ +package de.tudbut.tools.encryption; + +import java.lang.reflect.InvocationTargetException; +import java.nio.charset.StandardCharsets; + +/** + * Key to encrypt objects and strings + */ +public class RawKey extends Key { + + /** + * Generates a random Key + */ + public RawKey() { + super(); + } + + /** + * Constructs a Key + * @param s Key as string + */ + public RawKey(String s) { + super(s); + } + + /** + * Constructs a Key + * @param bytes Key as byte[] + */ + public RawKey(byte[] bytes) { + super(new String(bytes, StandardCharsets.ISO_8859_1)); + } + + /** + * Gets the bytes of the key + * @return the bytes of the key + */ + public byte[] toBytes() { + return string.getBytes(StandardCharsets.ISO_8859_1); + } + + /** + * Encrypts a string + * @param s string to encrypt + * @return encrypted string + */ + public String encryptString(String s) { + byte[] bytes = s.getBytes(StandardCharsets.ISO_8859_1); + byte[] eb = string.getBytes(StandardCharsets.ISO_8859_1); + int len = bytes.length; + int p = eb.length; + for (int i = 0 ; i < len ; i+=p) { + for (int j = 0 ; j < p && i + j < len ; j++) { + int idx = i + j; + bytes[idx] = (byte) ((int) bytes[idx] + (int) eb[j]); + } + } + return new String(bytes, StandardCharsets.ISO_8859_1); + } + + /** + * Decrypts a string + * @param s string to decrypt + * @return decrypted string + */ + public String decryptString(String s) { + byte[] bytes = s.getBytes(StandardCharsets.ISO_8859_1); + byte[] eb = string.getBytes(StandardCharsets.ISO_8859_1); + int len = bytes.length; + int p = eb.length; + for (int i = 0 ; i < len ; i+=p) { + for (int j = 0 ; j < p && i + j < len ; j++) { + int idx = i + j; + bytes[idx] = (byte) ((int) bytes[idx] - (int) eb[j]); + } + } + return new String(bytes, StandardCharsets.ISO_8859_1); + } + + @Override + protected RawKey clone() { + try { + return (RawKey) Object.class.getDeclaredMethod("clone").invoke(this); + } + catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + return new RawKey(string); + } + } +} diff --git a/src/main/java/de/tudbut/tools/encryption/SaferRawKey.java b/src/main/java/de/tudbut/tools/encryption/SaferRawKey.java new file mode 100644 index 0000000..59e2dcf --- /dev/null +++ b/src/main/java/de/tudbut/tools/encryption/SaferRawKey.java @@ -0,0 +1,59 @@ +package de.tudbut.tools.encryption; + +import java.nio.charset.StandardCharsets; + +public class SaferRawKey extends RawKey { + + public SaferRawKey() { + super(); + } + + public SaferRawKey(String s) { + super(s); + } + + public SaferRawKey(byte[] bytes) { + super(bytes); + } + + /** + * Encrypts a string + * @param s string to encrypt + * @return encrypted string + */ + public String encryptString(String s) { + byte[] bytes = s.getBytes(StandardCharsets.ISO_8859_1); + byte[] eb = string.getBytes(StandardCharsets.ISO_8859_1); + int len = bytes.length; + int p = eb.length; + for (int i = 0 ; i < len ; i+=p) { + for (int j = 0 ; j < p && i + j < len ; j++) { + int idx = i + j; + byte o = bytes[idx]; + bytes[idx] += eb[j]; + eb[(j + 1) % p] += o; + } + } + return new String(bytes, StandardCharsets.ISO_8859_1); + } + + /** + * Decrypts a string + * @param s string to decrypt + * @return decrypted string + */ + public String decryptString(String s) { + byte[] bytes = s.getBytes(StandardCharsets.ISO_8859_1); + byte[] eb = string.getBytes(StandardCharsets.ISO_8859_1); + int len = bytes.length; + int p = eb.length; + for (int i = 0 ; i < len ; i+=p) { + for (int j = 0 ; j < p && i + j < len ; j++) { + int idx = i + j; + bytes[idx] -= eb[j]; + eb[(j + 1) % p] += bytes[idx]; + } + } + return new String(bytes, StandardCharsets.ISO_8859_1); + } +} diff --git a/src/main/java/de/tudbut/type/ByteArrayList.java b/src/main/java/de/tudbut/type/ByteArrayList.java new file mode 100644 index 0000000..173b8d1 --- /dev/null +++ b/src/main/java/de/tudbut/type/ByteArrayList.java @@ -0,0 +1,19 @@ +package de.tudbut.type; + +import de.tudbut.tools.ArrayTools; + +import java.util.ArrayList; + +public class ByteArrayList extends ArrayList { + + public byte[] toByteArray() { + Byte[] a = ArrayTools.arrayFromList(this); + byte[] b = new byte[size()]; + + for (int i = 0; i < size(); i++) { + b[i] = a[i]; + } + + return b; + } +} diff --git a/src/main/java/de/tudbut/type/CharArrayList.java b/src/main/java/de/tudbut/type/CharArrayList.java new file mode 100644 index 0000000..85f1ef5 --- /dev/null +++ b/src/main/java/de/tudbut/type/CharArrayList.java @@ -0,0 +1,19 @@ +package de.tudbut.type; + +import de.tudbut.tools.ArrayTools; + +import java.util.ArrayList; + +public class CharArrayList extends ArrayList { + + public char[] toCharArray() { + Character[] a = ArrayTools.arrayFromList(this); + char[] b = new char[size()]; + + for (int i = 0; i < size(); i++) { + b[i] = a[i]; + } + + return b; + } +} diff --git a/src/main/java/de/tudbut/type/FInfo.java b/src/main/java/de/tudbut/type/FInfo.java new file mode 100644 index 0000000..f3e1a31 --- /dev/null +++ b/src/main/java/de/tudbut/type/FInfo.java @@ -0,0 +1,5 @@ +package de.tudbut.type; + +public @interface FInfo { + String s(); +} diff --git a/src/main/java/de/tudbut/type/FileFormatException.java b/src/main/java/de/tudbut/type/FileFormatException.java new file mode 100644 index 0000000..1214d9b --- /dev/null +++ b/src/main/java/de/tudbut/type/FileFormatException.java @@ -0,0 +1,5 @@ +package de.tudbut.type; + +public class FileFormatException extends Exception { + +} diff --git a/src/main/java/de/tudbut/type/INothing.java b/src/main/java/de/tudbut/type/INothing.java new file mode 100644 index 0000000..d5369ab --- /dev/null +++ b/src/main/java/de/tudbut/type/INothing.java @@ -0,0 +1,5 @@ +package de.tudbut.type; + +public interface INothing { + +} diff --git a/src/main/java/de/tudbut/type/IntArrayList.java b/src/main/java/de/tudbut/type/IntArrayList.java new file mode 100644 index 0000000..9186afa --- /dev/null +++ b/src/main/java/de/tudbut/type/IntArrayList.java @@ -0,0 +1,17 @@ +package de.tudbut.type; + +import java.util.ArrayList; + +public class IntArrayList extends ArrayList { + + public int[] toIntArray() { + Integer[] a = toArray(new Integer[0]); + int[] b = new int[size()]; + + for (int i = 0; i < size(); i++) { + b[i] = a[i]; + } + + return b; + } +} diff --git a/src/main/java/de/tudbut/type/Nothing.java b/src/main/java/de/tudbut/type/Nothing.java new file mode 100644 index 0000000..c24437c --- /dev/null +++ b/src/main/java/de/tudbut/type/Nothing.java @@ -0,0 +1,8 @@ +package de.tudbut.type; + +public class Nothing { + public static void voidObject(Object toVoid) { + // literally does nothing with it expect killing it + Runtime.getRuntime().gc(); + } +} diff --git a/src/main/java/de/tudbut/type/O.java b/src/main/java/de/tudbut/type/O.java new file mode 100644 index 0000000..cc357b3 --- /dev/null +++ b/src/main/java/de/tudbut/type/O.java @@ -0,0 +1,5 @@ +package de.tudbut.type; + +public @interface O { + String n(); +} diff --git a/src/main/java/de/tudbut/type/ShortArrayList.java b/src/main/java/de/tudbut/type/ShortArrayList.java new file mode 100644 index 0000000..0fc96b4 --- /dev/null +++ b/src/main/java/de/tudbut/type/ShortArrayList.java @@ -0,0 +1,19 @@ +package de.tudbut.type; + +import de.tudbut.tools.ArrayTools; + +import java.util.ArrayList; + +public class ShortArrayList extends ArrayList { + + public short[] toIntArray() { + Short[] a = ArrayTools.arrayFromList(this); + short[] b = new short[size()]; + + for (int i = 0; i < size(); i++) { + b[i] = a[i]; + } + + return b; + } +} diff --git a/src/main/java/de/tudbut/type/Stoppable.java b/src/main/java/de/tudbut/type/Stoppable.java new file mode 100644 index 0000000..95cc15a --- /dev/null +++ b/src/main/java/de/tudbut/type/Stoppable.java @@ -0,0 +1,25 @@ +package de.tudbut.type; + +import de.tudbut.obj.InstanceBoundMap; +import de.tudbut.obj.NotSupportedException; + +public interface Stoppable { + InstanceBoundMap vars = new InstanceBoundMap<>(); + + default void stop() { + vars.set(this, C.STOPPED, true); + } + + default void start() throws NotSupportedException { + throw new NotSupportedException(); + } + + default boolean isStopped() { + vars.setIfNull(this, C.STOPPED, false); + return vars.get(this, C.STOPPED); + } + + class C { + private static final String STOPPED = "stopped"; + } +} diff --git a/src/main/java/de/tudbut/type/StringArray.java b/src/main/java/de/tudbut/type/StringArray.java new file mode 100644 index 0000000..7b17bc9 --- /dev/null +++ b/src/main/java/de/tudbut/type/StringArray.java @@ -0,0 +1,55 @@ +package de.tudbut.type; + +import java.util.Arrays; +import java.util.List; + +public class StringArray { + private String[] array; + + public StringArray(String[] array) { + this.array = array; + } + + public StringArray() { + this.array = new String[]{}; + } + + public String join(String s) { + StringBuilder joinedString = null; + for (String arrayItem : this.array) { + if (joinedString == null) + joinedString = new StringBuilder(arrayItem); + else + joinedString.append(s).append(arrayItem); + } + if (joinedString != null) { + return joinedString.toString(); + } + return ""; + } + + public String[] asArray() { + return this.array; + } + + public List asList() { + return Arrays.asList(this.array); + } + + public void add(String s) { + String[] old = this.array.clone(); + this.array = new String[this.array.length + 1]; + + System.arraycopy(old, 0, this.array, 0, old.length); + + this.array[this.array.length - 1] = s; + } + + public void clear() { + this.array = new String[]{}; + } + + public void set(String[] array) { + this.array = array; + } +} diff --git a/src/main/java/de/tudbut/type/Token.java b/src/main/java/de/tudbut/type/Token.java new file mode 100644 index 0000000..63fdd15 --- /dev/null +++ b/src/main/java/de/tudbut/type/Token.java @@ -0,0 +1,38 @@ +package de.tudbut.type; + +import de.tudbut.tools.Tools; + +public class Token { + public static byte LENGTH = 64; + private final int[] ints = new int[LENGTH]; + + public Token(String token) { + if (token.length() == LENGTH) { + for (int i = 0; i < token.length(); i++) { + ints[i] = token.toCharArray()[i]; + } + } + else + throw new StringIndexOutOfBoundsException("Must be " + LENGTH); + } + + public static Token gen() { + StringBuilder pool = new StringBuilder(); + for (char i = 0; i < '\u00ff'; i++) { + pool.append(i); + } + + return new Token(Tools.randomString(LENGTH, pool.toString())); + } + + public String toString() { + StringBuilder string = new StringBuilder(); + + for (int i = 0; i < ints.length; i++) { + string.append((char) ints[i]); + } + + return string.toString(); + } +} + diff --git a/src/main/java/de/tudbut/type/Vector2d.java b/src/main/java/de/tudbut/type/Vector2d.java new file mode 100644 index 0000000..c9ecdd4 --- /dev/null +++ b/src/main/java/de/tudbut/type/Vector2d.java @@ -0,0 +1,92 @@ +package de.tudbut.type; + +import de.tudbut.tools.Tools; +import de.tudbut.obj.Mappable; + +import java.util.Map; + +public class Vector2d implements Mappable { + private double x, y; + + public Vector2d(double x, double y) { + this.x = x; + this.y = y; + } + + public Vector2d set(double x, double y) { + this.x = x; + this.y = y; + return this; + } + + public Vector2d set(Vector2d vec) { + return set(vec.x, vec.y); + } + + public Vector2d add(Vector2d vec) { + set(x + vec.x, y + vec.y); + return this; + } + + public Vector2d add(double x, double y) { + set(x + this.x, y + this.y); + return this; + } + + public double getX() { + return x; + } + + public void setX(double x) { + this.x = x; + } + + public double getY() { + return y; + } + + public void setY(double y) { + this.y = y; + } + + @Override + public Vector2d clone() { + return new Vector2d(x, y); + } + + public Vector2d multiply(double i) { + set(x * i, y * i); + return this; + } + public Vector2d multiply(double mx, double my) { + set(x * mx, y * my); + return this; + } + public Vector2d multiply(Vector2d vec) { + set(x * vec.x, y * vec.y); + return this; + } + + public Vector2d negate() { + set(-x, -y); + return this; + } + + public String toString() { + return "x:" + x + ";y:" + y; + } + + @Override + public Map map() { + return Tools.stringToMap("x:" + x + ";y:" + y); + } + + public static Vector2d fromMap(Map map) { + return + new Vector2d( + Double.parseDouble(map.get("x")), + Double.parseDouble(map.get("y")) + ) + ; + } +} diff --git a/src/main/java/de/tudbut/type/Vector3d.java b/src/main/java/de/tudbut/type/Vector3d.java new file mode 100644 index 0000000..661fd4f --- /dev/null +++ b/src/main/java/de/tudbut/type/Vector3d.java @@ -0,0 +1,182 @@ +package de.tudbut.type; + +import de.tudbut.tools.Tools; +import de.tudbut.obj.Mappable; + +import java.util.Map; + +public class Vector3d implements Mappable { + private double x, y, z; + + public Vector3d(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + public static Vector3d add(Vector3d vector1, Vector3d vector2) { + return new Vector3d(vector1.getX() + vector2.getX(), vector1.getY() + vector2.getY(), vector1.getZ() + vector2.getZ()); + } + + public static Vector3d subtract(Vector3d vector1, Vector3d vector2) { + return new Vector3d(vector1.getX() - vector2.getX(), vector1.getY() - vector2.getY(), vector1.getZ() - vector2.getZ()); + } + + public static Vector3d multiply(Vector3d vector1, Vector3d vector2) { + return new Vector3d(vector1.getX() * vector2.getX(), vector1.getY() * vector2.getY(), vector1.getZ() * vector2.getZ()); + } + + public static Vector3d divide(Vector3d vector1, Vector3d vector2) { + return new Vector3d(vector1.getX() / vector2.getX(), vector1.getY() / vector2.getY(), vector1.getZ() / vector2.getZ()); + } + + public static double length(Vector3d vector) { + return Math.sqrt(vector.getX() * vector.getX() + vector.getY() * vector.getY() + vector.getZ() * vector.getZ()); + } + + public static Vector3d normalize(Vector3d vector) { + double len = Vector3d.length(vector); + return Vector3d.divide(vector, new Vector3d(len, len, len)); + } + + public static double dot(Vector3d vector1, Vector3d vector2) { + return vector1.getX() * vector2.getX() + vector1.getY() * vector2.getY() + vector1.getZ() * vector2.getZ(); + } + + public Vector3d set(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + return this; + } + + public Vector3d set(Vector3d vector) { + return set(vector.x, vector.y, vector.z); + } + + public Vector3d add(Vector3d vector) { + set(x + vector.x, y + vector.y, z + vector.z); + return this; + } + + public Vector3d add(double x, double y, double z) { + set(x + this.x, y + this.y, z + this.z); + return this; + } + + @Override + public int hashCode() { + final long prime = 31; + long result = 1; + result = prime * result + Double.doubleToLongBits(x); + result = prime * result + Double.doubleToLongBits(y); + result = prime * result + Double.doubleToLongBits(z); + return (int) result / 4; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Vector3d other = (Vector3d) obj; + if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x)) + return false; + if (Double.doubleToLongBits(y) != Double.doubleToLongBits(other.y)) + return false; + return Double.doubleToLongBits(z) == Double.doubleToLongBits(other.z); + } + + public double getX() { + return x; + } + + public void setX(double x) { + this.x = x; + } + + public double getY() { + return y; + } + + public void setY(double y) { + this.y = y; + } + + public double getZ() { + return z; + } + + public void setZ(double z) { + this.z = z; + } + + public Vector3d multiply(double i) { + set(x * i, y * i, z * i); + return this; + } + + public Vector3d multiply(double x, double y, double z) { + set(this.x * x, this.y * y, this.z * z); + return this; + } + + public Vector3d multiply(Vector3d vec) { + set(this.x * vec.x, this.y * vec.y, this.z * vec.z); + return this; + } + + public Vector3d negate() { + set(-x, -y, -z); + return this; + } + + public boolean isGreaterThan(Vector3d vec) { + return + x > vec.x && + y > vec.y && + z > vec.z; + } + + public boolean isSmallerThan(Vector3d vec) { + return + x < vec.x && + y < vec.y && + z < vec.z; + } + + public boolean isGreaterOrEqualThan(Vector3d vec) { + return + x >= vec.x && + y >= vec.y && + z >= vec.z; + } + + public boolean isSmallerOrEqualThan(Vector3d vec) { + return + x <= vec.x && + y <= vec.y && + z <= vec.z; + } + + @Override + public Vector3d clone() { + return new Vector3d(x, y, z); + } + + public String toString() { + return "x:" + x + ";y:" + y + ";z:" + z; + } + + @Override + public Map map() { + return Tools.stringToMap("x:" + x + ";y:" + y + ";z:" + z); + } + + public static Vector3d fromMap(Map map) { + return new Vector3d(Double.parseDouble(map.get("x")), Double.parseDouble(map.get("y")), Double.parseDouble(map.get("z"))); + } +} diff --git a/src/main/java/de/tudbut/type/WIP.java b/src/main/java/de/tudbut/type/WIP.java new file mode 100644 index 0000000..04131fe --- /dev/null +++ b/src/main/java/de/tudbut/type/WIP.java @@ -0,0 +1,5 @@ +package de.tudbut.type; + +public @interface WIP { + boolean works() default false; +} diff --git a/src/main/java/de/tudbut/ui/consoleui/ConsoleUI.java b/src/main/java/de/tudbut/ui/consoleui/ConsoleUI.java new file mode 100644 index 0000000..0335739 --- /dev/null +++ b/src/main/java/de/tudbut/ui/consoleui/ConsoleUI.java @@ -0,0 +1,56 @@ +package de.tudbut.ui.consoleui; + +import de.tudbut.type.StringArray; +import de.tudbut.type.WIP; + +@WIP( + works = true +) +public class ConsoleUI { + public static String HORIZONTAL_LINE = "—"; + public static String VERTICAL_LINE = "│"; + + protected static StringArray create( + String title, String bgColor, + String fontColor, + String rColor, StringArray theText + ) { + StringBuilder x = new StringBuilder(); + for (int i = 0; i < title.length(); i++) { + x.append("─"); + } + for (int i1 = 0; i1 < theText.asArray().length; i1++) { + String s = theText.asArray()[i1]; + StringBuilder y = new StringBuilder(); + for (int i2 = 0; i2 < title.length() + 8 - s.length(); i2++) { + y.append(" "); + } + theText.asArray()[i1] = color(fontColor, bgColor) + s + y; + } + theText.asArray()[0] = "│" + theText.asArray()[0]; + return new StringArray( + ( + "" + + color(rColor, bgColor) + "┌────" + title + "────┐\u001b[0m\n" + + color(rColor, bgColor) + + theText.join( + color(rColor, bgColor) + + "│\u001b[0m\n" + color(rColor, bgColor) + + "│" + color(rColor, bgColor) + ) + + color(rColor, bgColor) + "│\u001b[0m\n" + + color(rColor, bgColor) + "└────" + x + "────┘\u001b[0m" + ).split("\n") + ); + } + + private static String color(String cUpper, String cLower) { + return "\u001b[38;5;" + cUpper + "m\u001b[48;5;" + cLower + "m"; + } + + protected static void clear() { + for (int i = 0; i < 2048; i++) { + System.out.print("\u001b[K\u001b[A\u001b[K"); + } + } +} diff --git a/src/main/java/de/tudbut/ui/windowgui/AdaptedGraphics.java b/src/main/java/de/tudbut/ui/windowgui/AdaptedGraphics.java new file mode 100644 index 0000000..ef13ec7 --- /dev/null +++ b/src/main/java/de/tudbut/ui/windowgui/AdaptedGraphics.java @@ -0,0 +1,30 @@ +package de.tudbut.ui.windowgui; + +import java.awt.*; + +public class AdaptedGraphics { + private final Graphics graphics; + + public AdaptedGraphics(Graphics graphics) { + this.graphics = graphics; + } + + public void setColor(int r, int g, int b) { + this.graphics.setColor(new Color(r, g, b)); + } + + public void setColor(int rgb) { + if(rgb >= 0 && rgb <= 0xffffff) + this.graphics.setColor(new Color(rgb, false)); + else + this.graphics.setColor(new Color(rgb, true)); + } + + public Graphics g() { + return graphics; + } + + public void drawImage(int x, int y, Image image) { + graphics.drawImage(image, x, y, null); + } +} diff --git a/src/main/java/de/tudbut/ui/windowgui/Design.java b/src/main/java/de/tudbut/ui/windowgui/Design.java new file mode 100644 index 0000000..2ea8274 --- /dev/null +++ b/src/main/java/de/tudbut/ui/windowgui/Design.java @@ -0,0 +1,6 @@ +package de.tudbut.ui.windowgui; + +@Deprecated +public class Design { + +} diff --git a/src/main/java/de/tudbut/ui/windowgui/FontRenderer.java b/src/main/java/de/tudbut/ui/windowgui/FontRenderer.java new file mode 100644 index 0000000..0c9be80 --- /dev/null +++ b/src/main/java/de/tudbut/ui/windowgui/FontRenderer.java @@ -0,0 +1,66 @@ +package de.tudbut.ui.windowgui; + +import de.tudbut.obj.Vector2i; + +import java.awt.*; +import java.awt.font.FontRenderContext; +import java.awt.font.GlyphVector; +import java.awt.image.BufferedImage; + +public class FontRenderer { + public Font myFont; + public FontRenderContext context; + + public FontRenderer(int size) { + myFont = new Font("serif", Font.PLAIN, size); + context = new FontRenderContext(null, true, false); + } + + public int getTextWidth(String text) { + int r = 0; + + for (int i = 0; i < text.split("\n").length; i++) + r += ((int) myFont.getStringBounds(text.split("\n")[i], context).getWidth()) + 4; + + return r; + } + + public int getTextHeight(String text) { + int r = 0; + + for (int i = 0; i < text.split("\n").length; i++) + r += ((int) myFont.getStringBounds(text.split("\n")[i], context).getHeight()) + 4; + + return r; + } + + private static Rectangle getStringBounds(String s, Font font, FontRenderContext context) { + GlyphVector gv = font.createGlyphVector(context, s); + return gv.getPixelBounds(context, 0, 0); + } + + public Image renderText(String text, int color) { + BufferedImage image = new BufferedImage(getTextWidth(text), getTextHeight(text), BufferedImage.TYPE_INT_ARGB); + Graphics2D graphics = ((Graphics2D) image.getGraphics()); + graphics.setRenderingHint( + RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON + ); + + graphics.setColor(new Color(color)); + graphics.setFont(myFont); + for (int i = 0; i < text.split("\n").length; i++) + graphics.drawString(text.split("\n")[i], 0, (myFont.getSize() + 4) * (i + 1)); + + return image; + } + + public Vector2i getCoordsForCentered(Vector2i coords, String text) { + return + new Vector2i( + coords.getX() - getTextWidth(text) / 2, + coords.getY() - getTextHeight(text) / 2 + ) + ; + } +} diff --git a/src/main/java/de/tudbut/ui/windowgui/GUIWindow.java b/src/main/java/de/tudbut/ui/windowgui/GUIWindow.java new file mode 100644 index 0000000..5caf405 --- /dev/null +++ b/src/main/java/de/tudbut/ui/windowgui/GUIWindow.java @@ -0,0 +1,7 @@ +package de.tudbut.ui.windowgui; + +import de.tudbut.type.WIP; + +@WIP(works = false) +public class GUIWindow { +} diff --git a/src/main/java/de/tudbut/ui/windowgui/RenderableWindow.java b/src/main/java/de/tudbut/ui/windowgui/RenderableWindow.java new file mode 100644 index 0000000..a1f3190 --- /dev/null +++ b/src/main/java/de/tudbut/ui/windowgui/RenderableWindow.java @@ -0,0 +1,197 @@ +package de.tudbut.ui.windowgui; + +import de.tudbut.tools.Keyboard; +import de.tudbut.tools.Mouse; +import de.tudbut.type.*; +import de.tudbut.obj.Vector2i; + +import javax.swing.*; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.Date; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +public class RenderableWindow { + public final BufferedImage image; + public final AtomicInteger xSize = new AtomicInteger(1); + public final AtomicInteger ySize = new AtomicInteger(1); + protected BufferedImage buffer; + protected BufferedImage bufferTMP; + private final AtomicInteger fps = new AtomicInteger(); + private final JFrame window; + private final JLabel label; + private RenderRunnable[] toRender = new RenderRunnable[1024]; + private RenderRunnable[] toRenderTMP = new RenderRunnable[1024]; + private final AtomicLong frameAt = new AtomicLong(new Date().getTime()); + private final AtomicLong lastFrameAt = new AtomicLong(new Date().getTime()); + private short nextRenderRunnable = 0; + private final int tryFps; + + public RenderableWindow(int x, int y, String name, int tryFps, boolean showFPS) { + this(x,y,name,tryFps,showFPS,true); + } + + public RenderableWindow(int x, int y, String name, int tryFps, boolean showFPS, boolean decorated) { + this.tryFps = tryFps; + window = new JFrame(name); + image = new BufferedImage(x, y, BufferedImage.TYPE_INT_ARGB); + window.setUndecorated(!decorated); + window.setVisible(true); + window.setSize(x, y); + label = new JLabel() { + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics g2 = g.create(); + g2.drawImage(buffer, 0, 0, window); + g2.drawImage(image, 0, 0, window); + g2.dispose(); + } + }; + window.add(label); + Keyboard.startListening(window); + Mouse.startListening(window); + Timer timer = new Timer(1000 / tryFps, e -> { + label.repaint(); + xSize.set(window.getSize().width); + ySize.set(window.getSize().height); + if (showFPS) + window.setTitle(name + " | " + fps.get() + " FPS"); + }); + timer.start(); + label.repaint(); + window.repaint(); + } + + public void calcInsets(int delay) { + new Thread(() -> { + try { + Thread.sleep(delay); + } + catch (InterruptedException e) { + e.printStackTrace(); + } + Vector2i sizeOnScreen = getSize().add(getInsets().multiply(2)); + window.setSize( + sizeOnScreen.getX(), + sizeOnScreen.getY() + ); + }).start(); + } + + public Vector2i getMousePos() { + Vector2i pos = Mouse.getMousePos(); + Insets insets = window.getInsets(); + return new Vector2i( + (int) - window.getLocation().getX() - insets.left, + (int) - window.getLocation().getY() - insets.top + ).add(pos); + } + + public boolean mouseIsBetween(Vector2i pos0, Vector2i pos1) { + Vector2i mousePos = getMousePos(); + return + mousePos.getX() > pos0.getX() && + mousePos.getY() > pos0.getY() && + mousePos.getX() < pos1.getX() && + mousePos.getY() < pos1.getY(); + } + + public boolean mouseInRangeFrom(Vector2i pos, Vector2i range) { + Vector2i mousePos = getMousePos(); + return + mousePos.getX() > pos.getX() && + mousePos.getY() > pos.getY() && + mousePos.getX() < pos.getX() + range.getX() && + mousePos.getY() < pos.getY() + range.getY(); + } + + public Vector2i getSizeOnScreen() { + return + new Vector2i(xSize.get(), ySize.get()) + .negate() + .add( + new Vector2i( + window.getInsets().left + window.getInsets().right, + window.getInsets().top + window.getInsets().bottom + ) + ) + .negate(); + } + + public Vector2i getInsets() { + return new Vector2i( + window.getInsets().left + window.getInsets().right, + window.getInsets().top + window.getInsets().bottom + ); + } + + public Vector2i getSize() { + return + new Vector2i(xSize.get(), ySize.get()); + } + + public int getWindowBarHeight() { + return window.getInsets().top; + } + + public int getFps() { + return fps.get(); + } + + public JFrame getWindow() { + return window; + } + + public JLabel getLabel() { + return label; + } + + public void render(RenderRunnable renderRunnable) { + toRenderTMP[nextRenderRunnable] = renderRunnable; + nextRenderRunnable++; + } + + public void prepareRender() { + nextRenderRunnable = 0; + if (toRenderTMP[0] != null) + toRender = toRenderTMP; + toRenderTMP = new RenderRunnable[1024]; + } + + public void doRender() { + BufferedImage i = new BufferedImage(xSize.get(), ySize.get(), BufferedImage.TYPE_INT_ARGB); + + Graphics g = i.getGraphics(); + AdaptedGraphics ag = new AdaptedGraphics(g); + + for (RenderRunnable r : toRender) { + if (r == null) + break; + + r.render(ag, g, image); + } + + bufferTMP = i; + } + + @FInfo(s = "aka. finishRender") + public void swapBuffers() { + frameAt.set(new Date().getTime()); + try { + fps.set((int) (1000 / (frameAt.get() - lastFrameAt.get()))); + } + catch (ArithmeticException ignore) { + fps.set(tryFps); + } + lastFrameAt.set(new Date().getTime()); + BufferedImage var1 = buffer; + buffer = bufferTMP; + bufferTMP = var1; + } + + public interface RenderRunnable { + void render(AdaptedGraphics ag, Graphics gr, BufferedImage img); + } +} diff --git a/src/main/java/de/tudbut/ui/windowgui/WindowGUI.java b/src/main/java/de/tudbut/ui/windowgui/WindowGUI.java new file mode 100644 index 0000000..da2f9f7 --- /dev/null +++ b/src/main/java/de/tudbut/ui/windowgui/WindowGUI.java @@ -0,0 +1,126 @@ +package de.tudbut.ui.windowgui; + +import de.tudbut.logger.Logger; +import de.tudbut.tools.Config; +import de.tudbut.window.Window; + +import javax.swing.*; +import java.awt.event.ActionListener; +import java.nio.file.Files; +import java.nio.file.Paths; + +@Deprecated +public class WindowGUI { + public static String PATH = "tmp/gui"; + private static long nextWindowID = 0; + private int nextActionListenerID = 0; + private final int nextDesignID = 0; + private final ActionListener[] listeners = new ActionListener[1023]; + private final Logger logger = new Logger("WindowGUI"); + //private static Design[] designs = new Design[1024]; + + protected Config create(String name) throws Exception { + if (!Files.exists(Paths.get(PATH))) { + Files.createDirectory(Paths.get("tmp")); + Files.createDirectory(Paths.get(PATH)); + } + + if (!Files.exists(Paths.get(PATH + "/" + nextWindowID))) { + Files.createDirectory(Paths.get(PATH + "/" + nextWindowID)); + Files.createDirectory(Paths.get(PATH + "/" + nextWindowID + "/button")); + } + Config r = new Config(PATH + "/" + nextWindowID + ".window"); + r.set("name", name); + r.set("id", String.valueOf(nextWindowID)); + //r.set("design", "null"); + r.set("nextButtonY", "0"); + r.set("nextButtonID", "0"); + r.set("buttons", "0"); + nextWindowID++; + return r; + } + + protected Config addButton( + Config window, String text, int sizeX, + int sizeY + ) throws Exception { + int nextButtonY = Integer.parseInt(window.get("nextButtonY")); + int nextButtonID = Integer.parseInt(window.get("nextButtonID")); + Config r = new Config(PATH + "/" + window.get("id") + "/button/" + nextButtonID + ".button"); + r.set("text", text); + r.set("id", nextButtonID + ""); + r.set("posX", "0"); + r.set("posY", nextButtonY + ""); + r.set("sizeX", sizeX + ""); + r.set("sizeY", sizeY + ""); + r.set("windowID", window.get("id")); + r.set("listenerID", "x"); + r.set("invisible", "0"); + window.set("buttons", (Integer.parseInt(window.get("buttons")) + 1) + ""); + nextButtonY += sizeY + 2; + nextButtonID++; + window.set("nextButtonY", nextButtonY + ""); + window.set("nextButtonID", nextButtonID + ""); + return r; + } + + protected void delButton(Config button, Config window) throws Exception { + int nextButtonY = Integer.parseInt(window.get("nextButtonY")); + nextButtonY -= Integer.parseInt(button.get("sizeY")) + 2; + button.set("invisible", "1"); + window.set("nextButtonY", nextButtonY + ""); + } + + protected void setButtonVisibility(Config button, boolean visible) + throws Exception { + button.set("invisible", !visible ? "1" : "0"); + } + + + protected void addButtonListener(Config button, ActionListener listener) throws Exception { + listeners[nextActionListenerID] = listener; + button.set("listenerID", nextActionListenerID + ""); + nextActionListenerID++; + } + + protected void render(Config window, Window w) throws Exception { + logger.info("Preparing render..."); + w.label.removeAll(); + w.frame.repaint(); + logger.info("Started render..."); + + JButton[] buttons = new JButton[Integer.parseInt(window.get("buttons"))]; + for (int i = 0; i < buttons.length; i++) { + Config config = new Config(PATH + "/" + window.get("id") + "/button/" + i + ".button"); + JButton button = new JButton(config.get("text")); + button.setLocation(Integer.parseInt(config.get("posX")), Integer.parseInt(config.get("posY"))); + button.setSize(Integer.parseInt(config.get("sizeX")), Integer.parseInt(config.get("sizeY"))); + if (!config.get("listenerID").equals("x")) + if (listeners[Integer.parseInt(config.get("listenerID"))] != null) + button.addActionListener(listeners[Integer.parseInt(config.get("listenerID"))]); + + if (config.get("invisible").equals("1")) { + button.setVisible(false); + button.setSize(0, 0); + button.setEnabled(false); + } + buttons[i] = button; + } + + logger.info("Finishing render..."); + + for (JButton button : buttons) { + w.label.add(button); + } + + w.frame.setTitle(window.get("name")); + + logger.info("Finished! Updating..."); + + w.label.updateUI(); + w.frame.repaint(); + + logger.info("Rendered!"); + + } +} diff --git a/src/main/java/de/tudbut/window/Window.java b/src/main/java/de/tudbut/window/Window.java new file mode 100644 index 0000000..1dcd32b --- /dev/null +++ b/src/main/java/de/tudbut/window/Window.java @@ -0,0 +1,146 @@ +package de.tudbut.window; + +import de.tudbut.logger.LoggerSink; +import de.tudbut.global.DebugStateManager; + +import javax.swing.*; +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; +import java.util.concurrent.atomic.AtomicReference; + +public class Window { + private final AtomicReference logger; + public JFrame frame = new JFrame(); + public JLabel label = new JLabel(); + private String text; + private final boolean isMain; + private int exitValue = 0; + private String name; + private String title; + private boolean isClosed = true; + private final boolean messages = true; + + public Window(String name, String label, boolean isMain) { + this.logger = DebugStateManager.debugLoggerReference(); + this.frame.setName(name); + this.label.setName(name); + this.frame.setTitle(label); + if (isMain) { + this.frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + this.isMain = true; + } + else { + this.frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + this.isMain = false; + } + this.label.setVerticalAlignment(1); + this.frame.getContentPane().add(this.label); + this.name = name; + this.title = label; + this.frame.addWindowListener(new WindowListener() { + @Override + public void windowOpened(WindowEvent windowEvent) { + } + + @Override + public void windowClosing(WindowEvent windowEvent) { + } + + @Override + public void windowClosed(WindowEvent windowEvent) { + Window.this.isClosed = true; + Window.this.logger.get().info("Window was closed"); + if (Window.this.isMain) Window.this.delete(); + else Window.this.close(); + } + + @Override + public void windowDeactivated(WindowEvent windowEvent) { + } + + @Override + public void windowIconified(WindowEvent windowEvent) { + } + + @Override + public void windowDeiconified(WindowEvent windowEvent) { + } + + @Override + public void windowActivated(WindowEvent windowEvent) { + } + + }); + this.logger.get().info("Created window"); + + new Timer(100, (ignore) -> { + frame.repaint(); + this.label.repaint(); + this.label.updateUI(); + frame.repaint(); + this.label.repaint(); + }); + } + + public Window setText(String string) { + text = string; + this.label.setText("" + string.replaceAll("[\\\\\\n]", "
") + ""); + this.logger.get().info("Set text to '" + string + "'"); + return this; + } + + public Window setExitValue(int v) { + this.exitValue = v; + this.logger.get().info("Set exit value to " + v); + return this; + } + + public Window addText(String string) { + text += string; + this.label.setText("" + + text.replaceAll("[\\\\\\n]", "
") + ""); + this.logger.get().info("Added text '" + string + "'"); + return this; + } + + public Window open() { + this.frame.setVisible(true); + this.logger.get().info("Opened window"); + this.isClosed = false; + return this; + } + + public Window close() { + this.frame.setVisible(false); + this.logger.get().info("Closed window"); + this.isClosed = true; + return this; + } + + public Window setSize(int x, int y) { + this.frame.setSize(x, y); + this.logger.get().info("Resized window"); + return this; + } + + public Window delete() { + this.frame.dispose(); + this.isClosed = true; + if (this.isMain) { + this.logger.get().info("Deleted window"); + logger.get().warn("EXITING - Window '" + this.name + "' was deleted"); + System.exit(this.exitValue); + } + return this; + } + + public Window restore() { + Window restoredWindow = new Window(this.name, this.title, this.isMain); + restoredWindow.setText(this.label.getText()).setSize(this.frame.getWidth(), this.frame.getHeight()); + return restoredWindow; + } + + public boolean isClosed() { + return this.isClosed; + } +} diff --git a/src/test/java/AsyncTest.java b/src/test/java/AsyncTest.java new file mode 100644 index 0000000..889f310 --- /dev/null +++ b/src/test/java/AsyncTest.java @@ -0,0 +1,109 @@ +import de.tudbut.async.Reject; +import de.tudbut.async.Resolve; +import de.tudbut.async.Task; +import de.tudbut.async.TaskQueue; +import de.tudbut.parsing.AsyncJSON; + +import static de.tudbut.async.Async.*; + +/** + * @author TudbuT + * @since 03 Jun 2022 + */ + +public class AsyncTest { + + public static void main(String[] args) throws IllegalAccessException, InterruptedException { + context(TaskQueue.main); + new Task<>((resolve, reject) -> { + if (5 / 0 == 0) { + resolve.call("works!"); + } + }, String.class).then(System.err::println).err(Throwable::printStackTrace).ok(); + new Task((resolve, reject) -> { + try { + if(5 / 0 == 0) { + resolve.call("works!"); + } + } catch (Exception e) { + reject.call(e); + } + }).then(System.err::println).err(Throwable::printStackTrace).ok(); + new Task<>((resolve, reject) -> { + if (5 / 0 == 0) { + throw new Resolve("works!"); + } + }, String.class).then(System.err::println).err(Throwable::printStackTrace).ok(); + new Task((resolve, reject) -> { + try { + if(5 / 0 == 0) { + throw new Resolve("works!"); + } + } catch (Exception e) { + throw new Reject(e); + } + }).then(System.err::println).err(Throwable::printStackTrace).ok(); + new Task((resolve, reject) -> { + try { + if(5 / 0 == 0) { + throw new Resolve("works!"); + } + } catch (Exception e) { + throw new Reject(e); + } + }).then(System.err::println).ok(); + new Task<>((resolve, reject) -> { + if (5 * 0 == 0) { + resolve.call("works!"); + } + }, String.class).then(System.err::println).err(Throwable::printStackTrace).ok(); + new Task((resolve, reject) -> { + try { + if(5 * 0 == 0) { + resolve.call("works!"); + } + } catch (Exception e) { + reject.call(e); + } + }).then(System.err::println).err(Throwable::printStackTrace).ok(); + new Task<>((resolve, reject) -> { + if (5 * 0 == 0) { + throw new Resolve("works!"); + } + }, String.class).then(System.err::println).err(Throwable::printStackTrace).ok(); + new Task((resolve, reject) -> { + try { + if(5 * 0 == 0) { + throw new Resolve("works!"); + } + } catch (Exception e) { + throw new Reject(e); + } + }).then(System.err::println).err(Throwable::printStackTrace).ok(); + + new Task((resolve, reject) -> { + new Task((resolve1, reject1) -> { + resolve.call("works!"); + }).ok().await(); + }).then(System.err::println).err(Throwable::printStackTrace).ok(); + new Task(((resolve, reject) -> { + resolve.call(500); + })) + .compose((i, res, rej) -> res.call(String.valueOf(i))) + .then(System.err::println) + .ok(); + new Task(((resolve, reject) -> { + resolve.call(500); + })) + .ok() + .compose((i, res, rej) -> res.call(String.valueOf(i))) + .then(System.err::println); + System.out.println(AsyncJSON.read("{\"test\":{\"a\":\"b\",\"c\":[\"d\"]}}").ok().await().toString()); + System.out.println(AsyncJSON.write(AsyncJSON.read("{\"test\":{\"a\":\"b\",\"c\":[\"d\"]}}").ok().await()).ok().await()); + + TaskQueue.main.finish(); + Thread.sleep(55); + System.err.println("\n>>> Output should be 5 exceptions and 5 'works!' and 1 '500' and 2 json objects!!!"); + System.exit(0); + } +} diff --git a/src/test/java/CSTCN2.java b/src/test/java/CSTCN2.java new file mode 100644 index 0000000..6f44d86 --- /dev/null +++ b/src/test/java/CSTCN2.java @@ -0,0 +1,50 @@ +import java.util.Arrays; + +import de.tudbut.parsing.JSON; +import de.tudbut.parsing.TCN; +import de.tudbut.tools.*; +import de.tudbut.tools.*; +import de.tudbut.parsing.*; +import de.tudbut.io.*; +import java.io.FileInputStream; + +public class CSTCN2 { + private final String t1 = null; + private final String[][] t2 = null; + private static int t3 = 0; + private static boolean bool = false; + + //public CSTCN2(String t1, String[][] t2) { + // this.t1 = t1; + // this.t2 = t2; + //} + + public String toString() { + return "t1=" + t1 + ",t2=" + t2 + ",t3=" + t3; + } + + public static void main(String[] args) throws Exception { + CSTCN2 obj = (CSTCN2)ConfigSaverTCN2.read(JSON.read(new StreamReader(new FileInputStream("test.json")).readAllAsString()), null); + System.out.println(JSON.writeReadable((TCN)ConfigSaverTCN2.write(obj, true, true))); + + String s; + + + s = "I would like info on the player Player.360."; + System.out.println(Arrays.toString(Tools.readf("I would like info on the player {}.", s))); // yes + System.out.println(Arrays.toString(Tools.readf("I would like info on the {} {}.", s))); // yes + System.out.println(Arrays.toString(Tools.readf("I would like {} on the {} {}.", s))); // yes + System.out.println(Arrays.toString(Tools.readf("{} would like {} on the {} {}.", s))); // yes + s = "I would like info on the player Player.360..."; + System.out.println(Arrays.toString(Tools.readf("I would like info on the player {}...", s))); // yes + System.out.println(Arrays.toString(Tools.readf("I would like info on the {} {}...", s))); // yes + System.out.println(Arrays.toString(Tools.readf("I would like {} on the {} {}...", s))); // yes + System.out.println(Arrays.toString(Tools.readf("{} would like {} on the {} {}.", s))); // yes + s = "I would like info on the on the player on the Player.360..."; + System.out.println(Arrays.toString(Tools.readf("I would like info on the player {}...", s))); // no + System.out.println(Arrays.toString(Tools.readf("I would like info on the {} {}...", s))); // yes + System.out.println(Arrays.toString(Tools.readf("I would like {} on the {} {}...", s))); // yes + s = "Cancel all tasks!"; + System.out.println(Tools.readf1("Cancel all tasks!", s)); + } +} diff --git a/src/test/java/ClassDiscoverTest.java b/src/test/java/ClassDiscoverTest.java new file mode 100644 index 0000000..232739b --- /dev/null +++ b/src/test/java/ClassDiscoverTest.java @@ -0,0 +1,15 @@ +import de.tudbut.tools.DiscoverClasses; +import de.tudbut.tools.Registry; +import de.tudbut.parsing.TCN; + +import java.io.IOException; + +public class ClassDiscoverTest { + public static void main(String[] args) throws IOException, IllegalAccessException { + Registry registry = new Registry("registrytest.tcnm"); + TCN data = registry.register("test"); + if(data.get("Package") == null) + data.set("Package", "de.tudbut.tools"); + System.out.println(DiscoverClasses.of(ClassLoader.getSystemClassLoader()).in(data.getString("Package")).run()); + } +} diff --git a/src/test/java/DiscordRPCTest.java b/src/test/java/DiscordRPCTest.java new file mode 100644 index 0000000..270a304 --- /dev/null +++ b/src/test/java/DiscordRPCTest.java @@ -0,0 +1,9 @@ +import java.io.IOException; + +public class DiscordRPCTest { + + public static void main(String[] args) throws InterruptedException, IOException, NoSuchFieldException, IllegalAccessException { + + + } +} diff --git a/src/test/java/KeyTest.java b/src/test/java/KeyTest.java new file mode 100644 index 0000000..5c68d25 --- /dev/null +++ b/src/test/java/KeyTest.java @@ -0,0 +1,10 @@ +import de.tudbut.tools.encryption.SaferRawKey; + +public class KeyTest { + + public static void main(String[] args) { + SaferRawKey key = new SaferRawKey("hi"); + System.out.println(key.encryptString("hello!")); + System.out.println(key.decryptString(key.encryptString("hello!"))); + } +} diff --git a/src/test/java/PBIC2Test.java b/src/test/java/PBIC2Test.java new file mode 100644 index 0000000..8f24fb5 --- /dev/null +++ b/src/test/java/PBIC2Test.java @@ -0,0 +1,53 @@ +import de.tudbut.net.http.HTTPRequest; +import de.tudbut.net.http.HTTPRequestType; +import de.tudbut.net.http.HTTPServer; +import de.tudbut.net.pbic2.PBIC2AEventHandler; +import de.tudbut.net.pbic2.PBIC2AListener; +import de.tudbut.net.pbic2.PBIC2Client; +import de.tudbut.net.pbic2.PBIC2Server; +import de.tudbut.tools.encryption.KeyStream; +import de.tudbut.tools.encryption.RawKey; + +import java.io.IOException; + +public class PBIC2Test { + + public static void main(String[] args) throws IOException { + + HTTPServer server = new HTTPServer(5000, null, Runnable::run); + RawKey key = new RawKey("hello"); + KeyStream stream0 = new KeyStream(key); + KeyStream stream1 = new KeyStream(key); + + server.addHandler(request -> { + PBIC2Server serv = new PBIC2Server(request, stream0::encrypt, stream0::decrypt); + for (int i = 0 ; i < 100000 ; i++) { + StringBuilder builder = new StringBuilder(); + double l = Math.random() * 1000; + System.out.println((int) l); + for (int j = 0 ; j < (int) l ; j++) { + builder.append((char) (Math.random() * Character.MAX_VALUE + 1)); + } + System.out.println(serv.writeMessageWithResponse(builder.toString()).length()); + Thread.sleep(100); + } + }); + server.listen(); + + HTTPRequest request = new HTTPRequest(HTTPRequestType.POST, "localhost", 5000, "/"); + PBIC2Client client = new PBIC2Client(request, stream1::encrypt, stream1::decrypt); + PBIC2AEventHandler handler = new PBIC2AEventHandler(); + handler.start(client, new PBIC2AListener() { + @Override + public void onMessage(String message) throws IOException { + System.out.println(message.length()); + client.writeMessage("hhhh"); + } + + @Override + public void onError(Throwable throwable) { + + } + }); + } +} diff --git a/src/test/java/RenderTest.java b/src/test/java/RenderTest.java new file mode 100644 index 0000000..a7f0d4c --- /dev/null +++ b/src/test/java/RenderTest.java @@ -0,0 +1,21 @@ +import de.tudbut.type.Vector3d; +import de.tudbut.rendering.Projection3D; +import de.tudbut.rendering.RenderOutputType; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; + +public class RenderTest { + + public static void main(String[] args) throws IOException { + + Projection3D d = new Projection3D(new AtomicInteger(100), new AtomicInteger(100), RenderOutputType.BUFFEREDIMAGE, 0xff80a0ff); + d.setColor(0xff000000); + d.fillRectangle(new Vector3d(1, 1, 10), new Vector3d(-1, 1, 10), new Vector3d(-1, -1, 10), new Vector3d(1, -1, 10)); + BufferedImage render = (BufferedImage) d.render(); + ImageIO.write(render, "png", new File("output.png")); + } +} diff --git a/src/test/java/SecurityTest.java b/src/test/java/SecurityTest.java new file mode 100644 index 0000000..82abf6d --- /dev/null +++ b/src/test/java/SecurityTest.java @@ -0,0 +1,43 @@ +import de.tudbut.security.AccessKiller; +import de.tudbut.security.DataKeeper; +import de.tudbut.security.StrictnessBuilder; +import de.tudbut.security.permissionmanager.CallClassRestriction; + +import java.lang.reflect.Field; + +public class SecurityTest { + + private static final DataKeeper secret = new DataKeeper<>(new CallClassRestriction(AllowedAccessClass.class), StrictnessBuilder.empty(), "hii"); + + public static void main(String[] args) throws InterruptedException { + Thread.sleep(500); + AccessKiller.killReflectionFor(SecurityTest.class); + AllowedAccessClass.print(); + System.out.println("THIS IS GOOD!"); + System.out.println(); + try { + Field secretField = SecurityTest.class.getDeclaredField("secret"); + secretField.setAccessible(true); + System.out.println(secretField.get(null)); + System.out.println("THIS SHOULD NOT HAVE HAPPENED!!"); + } catch (Throwable e) { + e.printStackTrace(System.out); + System.out.println("THIS IS GOOD!"); + } + System.out.println(); + try { + secret.access(x -> System.out.println("Evil access (NOT OK): " + x.getValue())); + } catch (Throwable e) { + e.printStackTrace(System.out); + System.out.println("THIS IS GOOD!"); + } + Thread.sleep(500); + System.exit(0); + } + + public static class AllowedAccessClass { + public static void print() { + secret.access(x -> System.out.println("Normal access (OK): " + x.getValue())); + } + } +} diff --git a/src/test/java/TCNDBTest.java b/src/test/java/TCNDBTest.java new file mode 100644 index 0000000..f7986c5 --- /dev/null +++ b/src/test/java/TCNDBTest.java @@ -0,0 +1,13 @@ +import de.tudbut.parsing.JSON; +import de.tudbut.parsing.TCN; + +public class TCNDBTest { + + public static void main(String[] args) { + TCN tcn = new TCN(); + tcn.set("{}{}{}\"\"", "{}}][{\"\"\""); + System.out.println(JSON.write(tcn)); + + + } +} diff --git a/tuddylib b/tuddylib new file mode 120000 index 0000000..096fa8b --- /dev/null +++ b/tuddylib @@ -0,0 +1 @@ +tuddylib \ No newline at end of file