From de07c29e805aca744c301e8fece050104a527d2e Mon Sep 17 00:00:00 2001 From: NeunEinser Date: Mon, 30 Nov 2020 00:12:27 +0100 Subject: [PATCH] initial commit --- .gitignore | 146 +++++++++++++++++++++++++ .idea/.gitignore | 3 + .idea/codeStyles/codeStyleConfig.xml | 5 + .idea/misc.xml | 6 + .idea/modules.xml | 8 ++ namespace to language placeholders.iml | 11 ++ src/Main.java | 140 ++++++++++++++++++++++++ 7 files changed, 319 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 namespace to language placeholders.iml create mode 100644 src/Main.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..189703d --- /dev/null +++ b/.gitignore @@ -0,0 +1,146 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/java,intellij,maven +# Edit at https://www.toptal.com/developers/gitignore?templates=java,intellij,maven + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### Maven ### +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +# https://github.com/takari/maven-wrapper#usage-without-binary-jar +.mvn/wrapper/maven-wrapper.jar + +# End of https://www.toptal.com/developers/gitignore/api/java,intellij,maven diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml 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/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..91063aa --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..9b7baa4 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/namespace to language placeholders.iml b/namespace to language placeholders.iml new file mode 100644 index 0000000..c90834f --- /dev/null +++ b/namespace to language placeholders.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/Main.java b/src/Main.java new file mode 100644 index 0000000..45228f7 --- /dev/null +++ b/src/Main.java @@ -0,0 +1,140 @@ +import java.math.BigInteger; +import java.util.ArrayList; + +public class Main { + public static void main(String[] args) { + if (args.length != 2) { + System.err.println("Usage: java -jar (encode|decode) "); + System.exit(-1); + } + + switch (args[0]) { + case "encode" -> encode(args[1]); + case "decode" -> decode(args[1]); + default -> System.err.printf("invalid command %s. Expected either \"encode\" or \"decode\".%n", args[0]); + } + + final var namespace = args[0]; + } + + private static void encode(final String namespace) { + if (!namespace.matches("^[a-z0-9_.-]+$")) { + System.err.printf("invalid namespace %s%n", namespace); + System.exit(-1); + } + + final boolean fullCharSet = !namespace.matches("^[a-z_]+$"); + + BigInteger value = BigInteger.ZERO; + final int base = fullCharSet ? 39 : 27; + final BigInteger multiplier = BigInteger.valueOf(base); + + for (char curChar : namespace.toCharArray()) { + //The add one is here to create a consistent offset. In case a namespace starts with 'a', we don't want + // the first digit as 0. + value = value.multiply(multiplier) + .add(BigInteger.valueOf(getCharValue(curChar) + 1)); + } + + // ceil((value.bitLength() + 1) / 31) + // Int division always floors, but ceil can be expressed with ((n - 1) / m) +1 + // Since n = value.bitLength() + 1, the + 1 with the - 1 cancel each other out. + // The + 1 is needed because we use an additional bit to store the value of fullCharSet + final int length = value.bitLength() / 31 + 1; + + final int[] placeholders = new int[length]; + final BigInteger twoPow31 = BigInteger.valueOf(0x80_00_00_00L); + + for (int i = length -1; i >= 0; i--) { + final var divMod = value.divideAndRemainder(twoPow31); + + placeholders[i] = divMod[1].intValueExact(); + value = divMod[0]; + + if (i == 0 && fullCharSet) { + placeholders[0] |= 0x40_00_00_00; + } + } + + final var result = new StringBuilder(); + for (int placeholder : placeholders) { + result.append("%") + .append(Integer.toUnsignedString(placeholder)) + .append("$s"); + } + + System.out.println(result); + } + + private static int getCharValue(char c) { + if (c >= 'a') { + return c - 'a'; + } else if (c == '_') { + return 26; + } else if (c >= '0') { + return c - '0' + 27; + } else if (c == '-') { + return 37; + } else { + return 38; + } + } + + private static void decode(final String encodedNamespace) { + if (!encodedNamespace.matches("^(?:%[1-9][0-9]+\\$s)+$")) { + System.err.printf("invalid encoded namespace %s%n", encodedNamespace); + System.exit(-1); + } + + final String trimmed = encodedNamespace.substring(1, encodedNamespace.length() - 2); + final String[] placeholderStrings = trimmed.split("\\$s%"); + final int[] placeholders = new int[placeholderStrings.length]; + + for (int i = 0; i < placeholders.length; i++) { + placeholders[i] = Integer.parseInt(placeholderStrings[i]); + } + boolean fullCharSet = (placeholders[0] & 0x40_00_00_00) > 0; + placeholders[0] &= 0xBF_FF_FF_FF; + + final BigInteger twoPow31 = BigInteger.valueOf(0x80_00_00_00L); + BigInteger value = BigInteger.ZERO; + + for (int placeholder : placeholders) { + value = value.multiply(twoPow31); + value = value.add(BigInteger.valueOf(placeholder)); + } + + final int base = fullCharSet ? 39 : 27; + final BigInteger divisor = BigInteger.valueOf(base); + final var characters = new ArrayList(); + + while (value.compareTo(BigInteger.ZERO) > 0) { + value = value.subtract(BigInteger.ONE); + final BigInteger[] divMod = value.divideAndRemainder(divisor); + + value = divMod[0]; + characters.add(getCharFromValue(divMod[1].intValueExact())); + } + + final var chars = new char[characters.size()]; + for(int i = 0; i < chars.length; i++) { + chars[i] = characters.get(chars.length - i - 1); + } + + System.out.println(new String(chars)); + } + + private static char getCharFromValue(int value) { + if (value < 26) { + return (char) ('a' + value); + } else if (value == 26) { + return '_'; + } else if (value < 37) { + return (char) ('0' + value - 27); + } else if (value == 37) { + return '-'; + } else { + return '.'; + } + } +}