Initial commit

This commit is contained in:
David Schultz 2023-03-23 19:43:29 -05:00
commit 7048bdc21e
12 changed files with 434 additions and 0 deletions

38
.classpath Normal file
View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="test" value="true"/>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
<attributes>
<attribute name="test" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry exported="true" kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry exported="true" kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER">
<attributes>
<attribute name="module" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
target/

23
.project Normal file
View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>irctokens</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@ -0,0 +1,16 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
org.eclipse.jdt.core.compiler.codegen.targetPlatform=17
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=17
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
org.eclipse.jdt.core.compiler.release=disabled
org.eclipse.jdt.core.compiler.source=17

View File

@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 David Schultz
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, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
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.

12
pom.xml Normal file
View File

@ -0,0 +1,12 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>me.zpld</groupId>
<artifactId>irctokens</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>irctokens</name>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
</project>

View File

@ -0,0 +1,6 @@
package me.zpld.irctokens;
public class Constants {
public static final String[] TAG_UNESCAPED = {"\\", " ", ";", "\r", "\n"};
public static final String[] TAG_ESCAPED = {"\\\\", "\\s", "\\:", "\\r", "\\n"};
}

View File

@ -0,0 +1,54 @@
package me.zpld.irctokens;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
public class Formatting {
private static String escapeTag(String tag) {
for (int i = 0; i < Constants.TAG_UNESCAPED.length; i++)
tag = tag.replaceAll(Constants.TAG_UNESCAPED[i], Constants.TAG_ESCAPED[i]);
return tag;
}
public static String format(HashMap<String, String> tags, String source, String command, List<String> params) throws Exception {
List<String> outs = new ArrayList<>();
if (tags != null && tags.size() > 0) {
List<String> sTags = new ArrayList<>();
List<String> tagKeys = Arrays.asList((String[])tags.keySet().toArray());
Collections.sort(tagKeys);
for (String key : tagKeys) {
String value = tags.get(key);
if (value != null)
sTags.add(String.format("%s=%s", key, escapeTag(value)));
else
sTags.add(key);
}
outs.add("@" + String.join(";", sTags));
}
if (source != null)
outs.add(":"+source);
outs.add(command);
if (params.size() > 0) {
String last = params.get(params.size()-1);
for (String param : params.subList(0, params.size()-1)) {
if (param.indexOf(" ") >= 0)
throw new Exception("Non-last params cannot have spaces");
else if (param.startsWith(":"))
throw new Exception("Non-last params cannot start with colon");
}
outs.addAll(params.subList(0, params.size()-1));
if (last == null)
last = ":";
else if (last.indexOf(" ") >= 0 || last.startsWith(":"))
last = ":" + last;
outs.add(last);
}
return String.join(" ", outs);
}
}

View File

@ -0,0 +1,122 @@
package me.zpld.irctokens;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
public class Line {
private HashMap<String, String> tags;
private String source;
private String command;
private List<String> params;
public Line(HashMap<String, String> tags, String source, String command, List<String> params) {
this.tags = tags;
this.source = source;
this.command = command;
this.params = params;
}
public Line(String command, String[] params) {
this(null, null, command, Arrays.asList(params));
}
@Override
public String toString() {
return String.format(
"Line(tags=%s, source=%s, command=%s, params=%s)",
tags, source, command, params
);
}
public HashMap<String, String> getTags() {
return tags;
}
public String getSource() {
return source;
}
public String getCommand() {
return command;
}
public List<String> getParams() {
return params;
}
public String getFormatted() {
try {
return Formatting.format(tags, source, command, params);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private static String unescapeTag(String tag) {
String unescaped = "";
char[] escaped = tag.toCharArray();
int i = 0;
while (i < escaped.length) {
char current = escaped[i];
if (current == '\\') {
if (i < escaped.length - 1) {
char next = escaped[i + 1];
String duo = "" + current + next;
if (Arrays.asList(Constants.TAG_ESCAPED).contains(duo)) {
int index = Arrays.asList(Constants.TAG_ESCAPED).indexOf(duo);
unescaped += Constants.TAG_UNESCAPED[index];
i += 2;
} else {
unescaped += next;
i += 2;
}
} else {
i++;
}
} else {
unescaped += current;
i++;
}
}
return unescaped;
}
public static Line tokenise(String line) throws Exception {
HashMap<String, String> tags = new HashMap<>();
if (line.charAt(0) == '@') {
String[] tagParts = line.split(" ", 2);
String tags_s = tagParts[0];
line = tagParts.length > 1 ? tagParts[1] : "";
for (String part : tags_s.substring(1).split(";")) {
String[] kv = part.split("=", 2);
String key = kv[0];
String value = unescapeTag(kv.length > 1 ? kv[1] : "");
tags.put(key, value);
}
}
String[] lineParts = line.split(" :", 2);
line = lineParts[0];
String trailing = lineParts.length > 1 ? lineParts[1] : "";
List<String> params = new ArrayList<>(Arrays.asList(line.split(" ")));
String source = null;
if (params.size() > 0 && params.get(0).charAt(0) == ':') {
source = params.remove(0).substring(1);
}
if (params.size() < 1)
throw new Exception("Cannot tokenise command-less line");
String command = params.remove(0);
if (trailing.length() > 0)
params.add(trailing);
return new Line(tags, source, command, params);
}
}

View File

@ -0,0 +1,68 @@
package me.zpld.irctokens;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class StatefulDecoder {
private Charset encoding;
private Charset fallback;
private byte[] buffer;
public StatefulDecoder(String encoding, String fallback) {
this.encoding = Charset.forName(encoding);
this.fallback = Charset.forName(fallback);
this.clear();
}
public StatefulDecoder() {
this("UTF-8", "ISO-8859-1");
}
public void clear() {
this.buffer = new byte[0];
}
public byte[] pending() {
return this.buffer;
}
public List<Line> push(byte[] data) {
if (data == null || data.length == 0) {
return null;
}
this.buffer = Arrays.copyOf(this.buffer, this.buffer.length + data.length);
System.arraycopy(data, 0, this.buffer, this.buffer.length - data.length, data.length);
List<Line> lines = new ArrayList<>();
int start = 0;
int end = 0;
while (end < this.buffer.length) {
if (this.buffer[end] == '\n') {
byte[] lineBytes = Arrays.copyOfRange(this.buffer, start, end);
Line line = tokenise(lineBytes, this.encoding, this.fallback);
lines.add(line);
start = end + 1;
}
end++;
}
this.buffer = Arrays.copyOfRange(this.buffer, start, end);
return lines;
}
private Line tokenise(byte[] lineBytes, Charset encoding, Charset fallback) {
String lineStr;
try {
lineStr = new String(lineBytes, encoding.name());
} catch (Exception ex) {
lineStr = new String(lineBytes, fallback);
}
try {
return Line.tokenise(lineStr);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}

View File

@ -0,0 +1,69 @@
package me.zpld.irctokens;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class StatefulEncoder {
private String encoding;
private byte[] buffer;
private List<Line> bufferedLines;
public StatefulEncoder(String encoding) {
this.encoding = encoding;
clear();
}
public StatefulEncoder() {
this("UTF-8");
}
public void clear() {
buffer = new byte[0];
bufferedLines = new ArrayList<>();
}
public byte[] pending() {
return buffer;
}
public void push(Line line) {
String formattedLine = line.getFormatted() + "\r\n";
byte[] encodedLine;
try {
encodedLine = formattedLine.getBytes(encoding);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return;
}
buffer = concat(buffer, encodedLine);
bufferedLines.add(line);
}
public List<Line> pop(int byte_count) {
int sent = 0;
int index = 0;
while (sent < byte_count && index < buffer.length) {
if (buffer[index] == '\n') {
sent++;
}
index++;
}
byte[] sentBytes = new byte[index];
System.arraycopy(buffer, 0, sentBytes, 0, index);
buffer = Arrays.copyOfRange(buffer, index, buffer.length);
List<Line> sentLines = new ArrayList<>();
for (int i = 0; i < sent; i++) {
sentLines.add(bufferedLines.remove(0));
}
return sentLines;
}
private byte[] concat(byte[] a, byte[] b) {
byte[] result = new byte[a.length + b.length];
System.arraycopy(a, 0, result, 0, a.length);
System.arraycopy(b, 0, result, a.length, b.length);
return result;
}
}