diff --git a/helpers/java/helpers.java b/helpers/java/helpers.java new file mode 100644 index 0000000..d293828 --- /dev/null +++ b/helpers/java/helpers.java @@ -0,0 +1,746 @@ +package org.example; + +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicReference; + +@SuppressWarnings({"unchecked", "rawtypes"}) +public class Helpers { + + // tmp most of these methods are going to be re-implemented in the future to be more generic and efficient + + public static Object normalizeIntIfNeeded(Object a) { + if (a == null) return null; + if (a instanceof Integer) { + return Long.valueOf(((Integer) a).longValue()); + } + return a; + } + + // C# had "ref object a" + returns new value; in Java we mimic with AtomicReference + public static Object postFixIncrement(AtomicReference a) { + Object val = a.get(); + if (val instanceof Long) { + a.set(((Long) val) + 1L); + } else if (val instanceof Integer) { + a.set(((Integer) val) + 1); + } else if (val instanceof Double) { + a.set(((Double) val) + 1.0); + } else if (val instanceof String) { + a.set(((String) val) + 1); + } else { + return null; + } + return a.get(); + } + + public static Object postFixDecrement(AtomicReference a) { + Object val = a.get(); + if (val instanceof Long) { + a.set(((Long) val) - 1L); + } else if (val instanceof Integer) { + a.set(((Integer) val) - 1); + } else if (val instanceof Double) { + a.set(((Double) val) - 1.0); + } else { + return null; + } + return a.get(); + } + + public static Object prefixUnaryNeg(AtomicReference a) { + Object val = a.get(); + if (val instanceof Long) { + a.set(-((Long) val)); + } else if (val instanceof Integer) { + a.set(-((Integer) val)); + } else if (val instanceof Double) { + a.set(-((Double) val)); + } else if (val instanceof String) { + return null; + } else { + return null; + } + return a.get(); + } + + public static Object prefixUnaryPlus(AtomicReference a) { + Object val = a.get(); + if (val instanceof Long) { + a.set(+((Long) val)); + } else if (val instanceof Integer) { + a.set(+((Integer) val)); + } else if (val instanceof Double) { + a.set(+((Double) val)); + } else if (val instanceof String) { + return null; + } else { + return null; + } + return a.get(); + } + + public static Object plusEqual(Object a, Object value) { + a = normalizeIntIfNeeded(a); + value = normalizeIntIfNeeded(value); + + if (value == null) return null; + if (a instanceof Long && value instanceof Long) { + return (Long) a + (Long) value; + } else if (a instanceof Integer && value instanceof Integer) { + return (Integer) a + (Integer) value; + } else if (a instanceof Double && value instanceof Double) { + return (Double) a + (Double) value; + } else if (a instanceof String && value instanceof String) { + return ((String) a) + ((String) value); + } else { + return null; + } + } + + // NOTE: In C# this used JsonHelper.Deserialize((string)json). + // In Java, wire up your preferred JSON lib and return Map/List accordingly. + public Object parseJson(Object json) { + // placeholder: return the string itself (or plug in Jackson/Gson here) + return (json instanceof String) ? (String) json : null; + } + + public static boolean isTrue(Object value) { + if (value == null) return false; + + value = normalizeIntIfNeeded(value); + + if (value instanceof Boolean) { + return (Boolean) value; + } else if (value instanceof Long) { + return ((Long) value) != 0L; + } else if (value instanceof Integer) { + return ((Integer) value) != 0; + } else if (value instanceof Double) { + return ((Double) value) != 0.0; + } else if (value instanceof String) { + return !((String) value).isEmpty(); + } else if (value instanceof List) { + return !((List) value).isEmpty(); + } else if (value instanceof Map) { + // C# returned true for any IDictionary; we can mirror that or check emptiness. + return true; + } else { + return false; + } + } + + public static boolean isNumber(Object number) { + if (number == null) return false; + try { + Double.parseDouble(String.valueOf(number)); + return true; + } catch (Exception e) { + return false; + } + } + + public static boolean isEqual(Object a, Object b) { + try { + if (a == null && b == null) return true; + if (a == null || b == null) return false; + + // If types differ and neither is numeric, they're not equal + if (!a.getClass().equals(b.getClass()) && !(isNumber(a) && isNumber(b))) { + return false; + } + + if (IsInteger(a) && IsInteger(b)) { + return toLong(a).equals(toLong(b)); + } + if ((a instanceof Long) && (b instanceof Long)) { + return ((Long) a).longValue() == ((Long) b).longValue(); + } + if (a instanceof Double || b instanceof Double) { + return toDouble(a) == toDouble(b); + } + if (a instanceof Float || b instanceof Float) { + return toFloat(a) == toFloat(b); + } + if (a instanceof String && b instanceof String) { + return ((String) a).equals((String) b); + } + if (a instanceof Boolean && b instanceof Boolean) { + return ((Boolean) a).booleanValue() == ((Boolean) b).booleanValue(); + } + // decimal cases mapped via BigDecimal compare + if (isNumber(a) && isNumber(b)) { + return new BigDecimal(String.valueOf(a)).compareTo(new BigDecimal(String.valueOf(b))) == 0; + } + return false; + } catch (Exception ignored) { + return false; + } + } + + public static boolean isGreaterThan(Object a, Object b) { + if (a != null && b == null) return true; + if (a == null || b == null) return false; + + a = normalizeIntIfNeeded(a); + b = normalizeIntIfNeeded(b); + + if (a instanceof Long && b instanceof Long) { + return ((Long) a) > ((Long) b); + } else if (a instanceof Integer && b instanceof Integer) { + return ((Integer) a) > ((Integer) b); + } else if (isNumber(a) || isNumber(b)) { + return toDouble(a) > toDouble(b); + } else if (a instanceof String && b instanceof String) { + return ((String) a).compareTo((String) b) > 0; + } else { + return false; + } + } + + public static boolean isLessThan(Object a, Object b) { + return !isGreaterThan(a, b) && !isEqual(a, b); + } + + public static boolean isGreaterThanOrEqual(Object a, Object b) { + return isGreaterThan(a, b) || isEqual(a, b); + } + + public static boolean isLessThanOrEqual(Object a, Object b) { + return isLessThan(a, b) || isEqual(a, b); + } + + public static Object mod(Object a, Object b) { + if (a == null || b == null) return null; + a = normalizeIntIfNeeded(a); + b = normalizeIntIfNeeded(b); + if (a instanceof String || a instanceof Long || a instanceof Integer || a instanceof Double) { + return toDouble(a) % toDouble(b); + } + return null; + } + + public static Object add(Object a, Object b) { + a = normalizeIntIfNeeded(a); + b = normalizeIntIfNeeded(b); + + if (a instanceof Long && b instanceof Long) { + return ((Long) a) + ((Long) b); + } else if (a instanceof Double || b instanceof Double) { + return toDouble(a) + toDouble(b); + } else if (a instanceof String && b instanceof String) { + return ((String) a) + ((String) b); + } else { + return null; + } + } + + public static String add(String a, String b) { + return a + b; + } + + public static String add(String a, Object b) { + return a + String.valueOf(b); + } + + public static Object subtract(Object a, Object b) { + a = normalizeIntIfNeeded(a); + b = normalizeIntIfNeeded(b); + + if (a instanceof Long && b instanceof Long) { + return ((Long) a) - ((Long) b); + } else if (a instanceof Integer && b instanceof Integer) { + return ((Integer) a) - ((Integer) b); + } else if (a instanceof Double || b instanceof Double) { + return toDouble(a) - toDouble(b); + } else { + return null; + } + } + + public static int subtract(int a, int b) { return a - b; } + + public float subtract(float a, float b) { return a - b; } + + public static Object divide(Object a, Object b) { + a = normalizeIntIfNeeded(a); + b = normalizeIntIfNeeded(b); + if (a == null || b == null) return null; + + if (a instanceof Long && b instanceof Long) { + // C# integer division; keep behavior + return ((Long) a) / ((Long) b); + } else if (a instanceof Double && b instanceof Double) { + return ((Double) a) / ((Double) b); + } else { + return toDouble(a) / toDouble(b); + } + } + + public static Object multiply(Object a, Object b) { + a = normalizeIntIfNeeded(a); + b = normalizeIntIfNeeded(b); + if (a == null || b == null) return null; + + if (a instanceof Long && b instanceof Long) { + return ((Long) a) * ((Long) b); + } + double res = toDouble(a) * toDouble(b); + if (IsInteger(res)) { + return (long) res; + } else { + return res; + } + } + + public static int getArrayLength(Object value) { + if (value == null) return 0; + + if (value instanceof List) { + return ((List) value).size(); + } else if (value instanceof String) { + return ((String) value).length(); // fallback + } else if (value.getClass().isArray()) { + return java.lang.reflect.Array.getLength(value); + } else { + return 0; + } + } + + public static boolean IsInteger(Object value) { + if (value == null) return false; + + if (value instanceof Byte || value instanceof Short || + value instanceof Integer || value instanceof Long || + value instanceof java.util.concurrent.atomic.AtomicInteger || + value instanceof java.util.concurrent.atomic.AtomicLong) { + return true; + } + + if (value instanceof Float || value instanceof Double || value instanceof BigDecimal) { + BigDecimal d = new BigDecimal(String.valueOf(value)); + return d.stripTrailingZeros().scale() <= 0; + } + return false; + } + + public static Object mathMin(Object a, Object b) { + if (a == null || b == null) return null; + double first = toDouble(a); + double second = toDouble(b); + return (first < second) ? a : b; + } + + public static Object mathMax(Object a, Object b) { + if (a == null || b == null) return null; + double first = toDouble(a); + double second = toDouble(b); + return (first > second) ? a : b; + } + + public static int getIndexOf(Object str, Object target) { + if (str instanceof List) { + return ((List) str).indexOf(target); + } else if (str instanceof String && target instanceof String) { + return ((String) str).indexOf((String) target); + } else { + return -1; + } + } + + public static Object parseInt(Object a) { + try { + return toLong(a); + } catch (Exception ignored) { + return null; + } + } + + public static Object parseFloat(Object a) { + try { + return toDouble(a); + } catch (Exception ignored) { + return null; + } + } + + // generic getValue to replace elementAccesses + public Object getValue(Object a, Object b) { return GetValue(a, b); } + + public static Object GetValue(Object value2, Object key) { + if (value2 == null || key == null) return null; + + // Strings: index access + if (value2 instanceof String) { + String str = (String) value2; + int idx = toInt(key); + if (idx < 0 || idx >= str.length()) return null; + return String.valueOf(str.charAt(idx)); + } + + Object value = value2; + if (value2.getClass().isArray()) { + // Convert to List + int len = Array.getLength(value2); + List list = new ArrayList<>(len); + for (int i = 0; i < len; i++) list.add(Array.get(value2, i)); + value = list; + } + + if (value instanceof Map) { + Map m = (Map) value; + if (key instanceof String && m.containsKey(key)) { + return m.get(key); + } + return null; + } else if (value instanceof List) { + int idx = toInt(key); + List list = (List) value; + if (idx < 0 || idx >= list.size()) return null; + return list.get(idx); + } else if (key instanceof String) { + // Try Java field or getter + String name = (String) key; + try { + // Field + Field f = value.getClass().getField(name); + f.setAccessible(true); + return f.get(value2); + } catch (Exception ignored) {} + try { + // Getter + String mName = "get" + Character.toUpperCase(name.charAt(0)) + name.substring(1); + Method m = value.getClass().getMethod(mName); + return m.invoke(value2); + } catch (Exception ignored) {} + return null; + } else { + return null; + } + } + + public CompletableFuture> promiseAll(Object promisesObj) { return PromiseAll(promisesObj); } + + public static CompletableFuture> PromiseAll(Object promisesObj) { + List promises = (List) promisesObj; + List> futures = new ArrayList<>(); + for (Object p : promises) { + if (p instanceof CompletableFuture) { + futures.add((CompletableFuture) p); + } + } + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .thenApply(v -> { + List out = new ArrayList<>(futures.size()); + for (CompletableFuture f : futures) { + try { + out.add(f.get()); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + return out; + }); + } + + public static String toStringOrNull(Object value) { + if (value == null) return null; + return (String) value; + } + + public void throwDynamicException(Object exception, Object message) { + Exception ex = NewException((Class) exception, (String) message); + // In C#, this constructed but didn't throw; mimic behavior: + // If you actually want to throw: + // throw ex; + } + + // This function is the salient bit here + public Object newException(Object exception, Object message) { + return NewException((Class) exception, (String) message); + } + + public static Exception NewException(Class exception, String message) { + try { + Constructor ctor = exception.getConstructor(String.class); + return (Exception) ctor.newInstance(message); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static Object toFixed(Object number, Object decimals) { + double n = toDouble(number); + int d = toInt(decimals); + BigDecimal bd = new BigDecimal(Double.toString(n)).setScale(d, RoundingMode.HALF_UP); + return bd.doubleValue(); + } + + public static Object callDynamically(Object obj, Object methodName, Object[] args) { + if (args == null) args = new Object[]{}; + if (args.length == 0) { + // C# code injected a null arg to help binder; Java doesn't need it. + // But to mirror behavior, we won't add a null here. + } + String name = (String) methodName; + Method m = findMethod(obj.getClass(), name, args.length); + try { + m.setAccessible(true); + return m.invoke(obj, args); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static Object callDynamicallyAsync(Object obj, Object methodName, Object[] args) { + if (args == null) args = new Object[]{}; + String name = (String) methodName; + Method m = findMethod(obj.getClass(), name, args.length); + try { + m.setAccessible(true); + Object res = m.invoke(obj, args); + if (res instanceof CompletableFuture) { + return ((CompletableFuture) res).get(); + } + return res; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public boolean inOp(Object obj, Object key) { return InOp(obj, key); } + + public static boolean InOp(Object obj, Object key) { + if (obj == null || key == null) return false; + + if (obj instanceof List) { + return ((List) obj).contains(key); + } else if (obj instanceof Map) { + if (key instanceof String) { + return ((Map) obj).containsKey(key); + } else return false; + } else { + return false; + } + } + + public String slice(Object str2, Object idx1, Object idx2) { return Slice(str2, idx1, idx2); } + + public static String Slice(Object str2, Object idx1, Object idx2) { + if (str2 == null) return null; + String str = (String) str2; + int start = (idx1 != null) ? toInt(idx1) : -1; + + if (idx2 == null) { + if (start < 0) { + int innerStart = str.length() + start; + innerStart = Math.max(innerStart, 0); + return str.substring(innerStart); + } else { + if (start > str.length()) return ""; + return str.substring(start); + } + } else { + int end = toInt(idx2); + if (start < 0) start = str.length() + start; + if (end < 0) end = str.length() + end; + if (start < 0) start = 0; + if (end > str.length()) end = str.length(); + if (start > end) start = end; + return str.substring(start, end); + } + } + + public static Object concat(Object a, Object b) { + if (a == null && b == null) return null; + if (a == null) return b; + if (b == null) return a; + + if (a instanceof List && b instanceof List) { + List result = new ArrayList((List) a); + result.addAll((List) b); + return result; + } else if (a instanceof List && !(b instanceof List)) { + List result = new ArrayList((List) a); + result.add(b); + return result; + } else if (!(a instanceof List) && b instanceof List) { + List result = new ArrayList(); + result.add(a); + result.addAll((List) b); + return result; + } else { + throw new IllegalStateException("Unsupported types for concatenation."); + } + } + + // --------- helpers --------- + + private static Method findMethod(Class cls, String name, int argCount) { + // try exact arg count first + for (Method m : cls.getDeclaredMethods()) { + if (m.getName().equals(name) && m.getParameterCount() == argCount) { + return m; + } + } + // search up the hierarchy + Class cur = cls.getSuperclass(); + while (cur != null) { + for (Method m : cur.getDeclaredMethods()) { + if (m.getName().equals(name) && m.getParameterCount() == argCount) { + return m; + } + } + cur = cur.getSuperclass(); + } + // fallback: first by name + for (Method m : cls.getDeclaredMethods()) { + if (m.getName().equals(name)) return m; + } + throw new RuntimeException("Method not found: " + name + " with " + argCount + " args on " + cls.getName()); + } + + private static Long toLong(Object o) { + if (o instanceof Long) return (Long) o; + if (o instanceof Integer) return ((Integer) o).longValue(); + if (o instanceof Double) return ((Double) o).longValue(); + if (o instanceof Float) return ((Float) o).longValue(); + if (o instanceof BigDecimal) return ((BigDecimal) o).longValue(); + if (o instanceof String) return Long.parseLong((String) o); + return Long.parseLong(String.valueOf(o)); + } + + private static int toInt(Object o) { + if (o instanceof Integer) return (Integer) o; + if (o instanceof Long) return ((Long) o).intValue(); + if (o instanceof Double) return ((Double) o).intValue(); + if (o instanceof Float) return ((Float) o).intValue(); + if (o instanceof BigDecimal) return ((BigDecimal) o).intValue(); + if (o instanceof String) return Integer.parseInt((String) o); + return Integer.parseInt(String.valueOf(o)); + } + + private static double toDouble(Object o) { + if (o instanceof Double) return (Double) o; + if (o instanceof Float) return ((Float) o).doubleValue(); + if (o instanceof Long) return ((Long) o).doubleValue(); + if (o instanceof Integer) return ((Integer) o).doubleValue(); + if (o instanceof BigDecimal) return ((BigDecimal) o).doubleValue(); + if (o instanceof String) return Double.parseDouble((String) o); + return Double.parseDouble(String.valueOf(o)); + } + + private static float toFloat(Object o) { + if (o instanceof Float) return (Float) o; + if (o instanceof Double) return ((Double) o).floatValue(); + if (o instanceof Long) return ((Long) o).floatValue(); + if (o instanceof Integer) return ((Integer) o).floatValue(); + if (o instanceof BigDecimal) return ((BigDecimal) o).floatValue(); + if (o instanceof String) return Float.parseFloat((String) o); + return Float.parseFloat(String.valueOf(o)); + } + + + public static String replaceAll(Object baseString, Object search, Object replacement) { + if (baseString == null) { + return null; + } + String s = String.valueOf(baseString); + String find = (search == null) ? "" : String.valueOf(search); + if (find.isEmpty()) { + // Avoid weird behavior of replacing "" (would insert between every char) + return s; + } + String repl = (replacement == null) ? "" : String.valueOf(replacement); + return s.replace(find, repl); // literal (non-regex) replacement + } + + public static String replace(Object baseString, Object search, Object replacement) { + if (baseString == null) { + return null; + } + String s = String.valueOf(baseString); + String find = (search == null) ? "" : String.valueOf(search); + String repl = (replacement == null) ? "" : String.valueOf(replacement); + return s.replaceFirst(find, repl); // literal (non-regex) replacement + } + + public static Object getArg(Object[] v, int index, Object def) { + if (v.length <= index) { + return def; + } + return v[index]; + } + + + @SuppressWarnings("unchecked") + public static void addElementToObject(Object target, Object... args) { + if (target instanceof Map map) { + if (args.length != 2) + throw new IllegalArgumentException("Map requires (key, value)"); + ((Map) map).put(args[0], args[1]); + return; + } + + if (target instanceof List list) { + List l = (List) list; + if (args.length == 1) { + l.add(args[0]); // append + return; + } + if (args.length == 2 && args[0] instanceof Integer idx) { + int i = idx; + if (i < 0 || i > l.size()) { + throw new IndexOutOfBoundsException("Index " + i + " out of bounds [0," + l.size() + "]"); + } + l.add(i, args[1]); + return; + } + throw new IllegalArgumentException( + "List requires (value) to append or (index(Integer), value) to insert"); + } + + throw new IllegalArgumentException("Target is neither Map nor List: " + typeName(target)); + } + + private static String typeName(Object o) { + return (o == null) ? "null" : o.getClass().getName(); + } + + public static Object opNeg(Object value) { + if (value == null) { + return null; + } + + if (value instanceof Byte) { + byte v = (Byte) value; + return (byte) -v; + } + if (value instanceof Short v) { + return (short) -v; + } + if (value instanceof Integer v) { + return -v; + } + if (value instanceof Long v) { + return -v; + } + if (value instanceof Float v) { + return -v; + } + if (value instanceof Double v) { + return -v; + } + + return null; + } +} diff --git a/java-demo/.gitattributes b/java-demo/.gitattributes new file mode 100644 index 0000000..f91f646 --- /dev/null +++ b/java-demo/.gitattributes @@ -0,0 +1,12 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + +# Binary files should be left untouched +*.jar binary + diff --git a/java-demo/.gitignore b/java-demo/.gitignore new file mode 100644 index 0000000..1b6985c --- /dev/null +++ b/java-demo/.gitignore @@ -0,0 +1,5 @@ +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build diff --git a/java-demo/app/build.gradle.kts b/java-demo/app/build.gradle.kts new file mode 100644 index 0000000..4ca6030 --- /dev/null +++ b/java-demo/app/build.gradle.kts @@ -0,0 +1,43 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This generated file contains a sample Java application project to get you started. + * For more details on building Java & JVM projects, please refer to https://docs.gradle.org/8.12/userguide/building_java_projects.html in the Gradle documentation. + */ + +plugins { + // Apply the application plugin to add support for building a CLI application in Java. + application +} + +repositories { + // Use Maven Central for resolving dependencies. + mavenCentral() +} + +dependencies { + // Use JUnit Jupiter for testing. + testImplementation(libs.junit.jupiter) + + testRuntimeOnly("org.junit.platform:junit-platform-launcher") + + // This dependency is used by the application. + implementation(libs.guava) +} + +// Apply a specific Java toolchain to ease working on different environments. +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + +application { + // Define the main class for the application. + mainClass = "org.example.App" +} + +tasks.named("test") { + // Use JUnit Platform for unit tests. + useJUnitPlatform() +} diff --git a/java-demo/app/src/main/java/org/example/App.java b/java-demo/app/src/main/java/org/example/App.java new file mode 100644 index 0000000..ad4eb40 --- /dev/null +++ b/java-demo/app/src/main/java/org/example/App.java @@ -0,0 +1,151 @@ +/* + * This source file was generated by the Gradle 'init' task + */ +package org.example; + +import java.util.concurrent.CompletableFuture; + + +public class App { + + public String a = "a"; + public String b = "b"; + public boolean c = false; + + public void aa() + { + var a = 1; + Object b = 2; + System.out.println("HEREEEE::" + a); + var data = new java.util.HashMap() {{ + put( "1", 1 ); + put( "2", 2 ); + }}; + var lista = new java.util.ArrayList(java.util.Arrays.asList(1, 3, 4, "5")); + Helpers.GetValue(data, "1"); + Aux.fix(); + // System.out.println(a); + // System.out.println(data["1"]); + // System.out.println(lista[2]); + // for (var i = 0; isLessThan(i, getArrayLength(lista)); i++) + // { + // System.out.println(lista[i]); + // } + } + + public void test() { + System.out.println("test"); + } + + public void retryingTask(int... retries) { + int n = (retries.length == 0) ? 3 : retries[0]; // default = 3 + System.out.println("n: " + n); + // ... + } + + public String getGreeting() { + var i = 1; + i += 1; + var a = "aaa"; + a += "bbb"; + // var c = new HashMap(); + return "Hello World222!" + String.valueOf(i) + a; + } + + public CompletableFuture testAsync() { + return CompletableFuture.supplyAsync(() -> { + try { + Thread.sleep(1000); // Simulate a delay + throw new RuntimeException("Simulated exception"); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + return "Async Result"; + }); + } + + public void teste() + { + Object aaa = (this.fWithParams(3)).join(); + System.out.println(aaa); + var c = this.fWithParams(5); + } + + public java.util.concurrent.CompletableFuture fWithParams(Object a) + { + return java.util.concurrent.CompletableFuture.supplyAsync(() -> { + return 42; + }); + + } + + public java.util.concurrent.CompletableFuture teste(Object a) + { + return java.util.concurrent.CompletableFuture.supplyAsync(() -> { + System.out.println(a); + throw new Error("test") ; + }); + + } + + public Object getVar(Object v) { + return v; + } + + public void teste33(String a) + { + var x = 1; + x = 2; + Object newObj = new java.util.HashMap() {{ + put( "a", App.this.getVar(x) ); + put( "a", a ); + // put( "b", this.getValue(this.getValue(this.getValue(2))) ); + }}; + throw new Error("test") ; + } + + public java.util.concurrent.CompletableFuture fetchData(Object input3) + { + final Object input2 = input3; + return java.util.concurrent.CompletableFuture.supplyAsync(() -> { + Object input = input2; + input = 3; + return Helpers.add(input, 2); + }); + + } + + public java.util.concurrent.CompletableFuture fetchData(Object input2, Object... optionalArgs) + { + final Object input3 = input2; + return java.util.concurrent.CompletableFuture.supplyAsync(() -> { + Object input = input3; + System.out.println(input); + return null; + }); + + } + + public static void main(String[] args) { + // this.retryingTask(); + + var base = 3; + System.out.println(new App().getGreeting()); + var app = new App(); + app.teste33("example"); + // app.retryingTask(); + try { + var res = app.testAsync().join(); + System.out.println("res: " + res); + } catch (Exception e) { + System.out.println("Error: " + e.getMessage()); + } + + // System.out.println(app.getGreeting()); + // app.test(); + // app.aa(); + // for (var i = 0; i < 5; ((int) i)++) { + // System.out.println("i: " + i); + // } + } +} diff --git a/java-demo/app/src/main/java/org/example/Aux.java b/java-demo/app/src/main/java/org/example/Aux.java new file mode 100644 index 0000000..6b0bda7 --- /dev/null +++ b/java-demo/app/src/main/java/org/example/Aux.java @@ -0,0 +1,8 @@ +package org.example; + +public class Aux { + + public static void fix() { + System.out.println("fix"); + } +} \ No newline at end of file diff --git a/java-demo/app/src/main/java/org/example/Helpers.java b/java-demo/app/src/main/java/org/example/Helpers.java new file mode 100644 index 0000000..d293828 --- /dev/null +++ b/java-demo/app/src/main/java/org/example/Helpers.java @@ -0,0 +1,746 @@ +package org.example; + +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicReference; + +@SuppressWarnings({"unchecked", "rawtypes"}) +public class Helpers { + + // tmp most of these methods are going to be re-implemented in the future to be more generic and efficient + + public static Object normalizeIntIfNeeded(Object a) { + if (a == null) return null; + if (a instanceof Integer) { + return Long.valueOf(((Integer) a).longValue()); + } + return a; + } + + // C# had "ref object a" + returns new value; in Java we mimic with AtomicReference + public static Object postFixIncrement(AtomicReference a) { + Object val = a.get(); + if (val instanceof Long) { + a.set(((Long) val) + 1L); + } else if (val instanceof Integer) { + a.set(((Integer) val) + 1); + } else if (val instanceof Double) { + a.set(((Double) val) + 1.0); + } else if (val instanceof String) { + a.set(((String) val) + 1); + } else { + return null; + } + return a.get(); + } + + public static Object postFixDecrement(AtomicReference a) { + Object val = a.get(); + if (val instanceof Long) { + a.set(((Long) val) - 1L); + } else if (val instanceof Integer) { + a.set(((Integer) val) - 1); + } else if (val instanceof Double) { + a.set(((Double) val) - 1.0); + } else { + return null; + } + return a.get(); + } + + public static Object prefixUnaryNeg(AtomicReference a) { + Object val = a.get(); + if (val instanceof Long) { + a.set(-((Long) val)); + } else if (val instanceof Integer) { + a.set(-((Integer) val)); + } else if (val instanceof Double) { + a.set(-((Double) val)); + } else if (val instanceof String) { + return null; + } else { + return null; + } + return a.get(); + } + + public static Object prefixUnaryPlus(AtomicReference a) { + Object val = a.get(); + if (val instanceof Long) { + a.set(+((Long) val)); + } else if (val instanceof Integer) { + a.set(+((Integer) val)); + } else if (val instanceof Double) { + a.set(+((Double) val)); + } else if (val instanceof String) { + return null; + } else { + return null; + } + return a.get(); + } + + public static Object plusEqual(Object a, Object value) { + a = normalizeIntIfNeeded(a); + value = normalizeIntIfNeeded(value); + + if (value == null) return null; + if (a instanceof Long && value instanceof Long) { + return (Long) a + (Long) value; + } else if (a instanceof Integer && value instanceof Integer) { + return (Integer) a + (Integer) value; + } else if (a instanceof Double && value instanceof Double) { + return (Double) a + (Double) value; + } else if (a instanceof String && value instanceof String) { + return ((String) a) + ((String) value); + } else { + return null; + } + } + + // NOTE: In C# this used JsonHelper.Deserialize((string)json). + // In Java, wire up your preferred JSON lib and return Map/List accordingly. + public Object parseJson(Object json) { + // placeholder: return the string itself (or plug in Jackson/Gson here) + return (json instanceof String) ? (String) json : null; + } + + public static boolean isTrue(Object value) { + if (value == null) return false; + + value = normalizeIntIfNeeded(value); + + if (value instanceof Boolean) { + return (Boolean) value; + } else if (value instanceof Long) { + return ((Long) value) != 0L; + } else if (value instanceof Integer) { + return ((Integer) value) != 0; + } else if (value instanceof Double) { + return ((Double) value) != 0.0; + } else if (value instanceof String) { + return !((String) value).isEmpty(); + } else if (value instanceof List) { + return !((List) value).isEmpty(); + } else if (value instanceof Map) { + // C# returned true for any IDictionary; we can mirror that or check emptiness. + return true; + } else { + return false; + } + } + + public static boolean isNumber(Object number) { + if (number == null) return false; + try { + Double.parseDouble(String.valueOf(number)); + return true; + } catch (Exception e) { + return false; + } + } + + public static boolean isEqual(Object a, Object b) { + try { + if (a == null && b == null) return true; + if (a == null || b == null) return false; + + // If types differ and neither is numeric, they're not equal + if (!a.getClass().equals(b.getClass()) && !(isNumber(a) && isNumber(b))) { + return false; + } + + if (IsInteger(a) && IsInteger(b)) { + return toLong(a).equals(toLong(b)); + } + if ((a instanceof Long) && (b instanceof Long)) { + return ((Long) a).longValue() == ((Long) b).longValue(); + } + if (a instanceof Double || b instanceof Double) { + return toDouble(a) == toDouble(b); + } + if (a instanceof Float || b instanceof Float) { + return toFloat(a) == toFloat(b); + } + if (a instanceof String && b instanceof String) { + return ((String) a).equals((String) b); + } + if (a instanceof Boolean && b instanceof Boolean) { + return ((Boolean) a).booleanValue() == ((Boolean) b).booleanValue(); + } + // decimal cases mapped via BigDecimal compare + if (isNumber(a) && isNumber(b)) { + return new BigDecimal(String.valueOf(a)).compareTo(new BigDecimal(String.valueOf(b))) == 0; + } + return false; + } catch (Exception ignored) { + return false; + } + } + + public static boolean isGreaterThan(Object a, Object b) { + if (a != null && b == null) return true; + if (a == null || b == null) return false; + + a = normalizeIntIfNeeded(a); + b = normalizeIntIfNeeded(b); + + if (a instanceof Long && b instanceof Long) { + return ((Long) a) > ((Long) b); + } else if (a instanceof Integer && b instanceof Integer) { + return ((Integer) a) > ((Integer) b); + } else if (isNumber(a) || isNumber(b)) { + return toDouble(a) > toDouble(b); + } else if (a instanceof String && b instanceof String) { + return ((String) a).compareTo((String) b) > 0; + } else { + return false; + } + } + + public static boolean isLessThan(Object a, Object b) { + return !isGreaterThan(a, b) && !isEqual(a, b); + } + + public static boolean isGreaterThanOrEqual(Object a, Object b) { + return isGreaterThan(a, b) || isEqual(a, b); + } + + public static boolean isLessThanOrEqual(Object a, Object b) { + return isLessThan(a, b) || isEqual(a, b); + } + + public static Object mod(Object a, Object b) { + if (a == null || b == null) return null; + a = normalizeIntIfNeeded(a); + b = normalizeIntIfNeeded(b); + if (a instanceof String || a instanceof Long || a instanceof Integer || a instanceof Double) { + return toDouble(a) % toDouble(b); + } + return null; + } + + public static Object add(Object a, Object b) { + a = normalizeIntIfNeeded(a); + b = normalizeIntIfNeeded(b); + + if (a instanceof Long && b instanceof Long) { + return ((Long) a) + ((Long) b); + } else if (a instanceof Double || b instanceof Double) { + return toDouble(a) + toDouble(b); + } else if (a instanceof String && b instanceof String) { + return ((String) a) + ((String) b); + } else { + return null; + } + } + + public static String add(String a, String b) { + return a + b; + } + + public static String add(String a, Object b) { + return a + String.valueOf(b); + } + + public static Object subtract(Object a, Object b) { + a = normalizeIntIfNeeded(a); + b = normalizeIntIfNeeded(b); + + if (a instanceof Long && b instanceof Long) { + return ((Long) a) - ((Long) b); + } else if (a instanceof Integer && b instanceof Integer) { + return ((Integer) a) - ((Integer) b); + } else if (a instanceof Double || b instanceof Double) { + return toDouble(a) - toDouble(b); + } else { + return null; + } + } + + public static int subtract(int a, int b) { return a - b; } + + public float subtract(float a, float b) { return a - b; } + + public static Object divide(Object a, Object b) { + a = normalizeIntIfNeeded(a); + b = normalizeIntIfNeeded(b); + if (a == null || b == null) return null; + + if (a instanceof Long && b instanceof Long) { + // C# integer division; keep behavior + return ((Long) a) / ((Long) b); + } else if (a instanceof Double && b instanceof Double) { + return ((Double) a) / ((Double) b); + } else { + return toDouble(a) / toDouble(b); + } + } + + public static Object multiply(Object a, Object b) { + a = normalizeIntIfNeeded(a); + b = normalizeIntIfNeeded(b); + if (a == null || b == null) return null; + + if (a instanceof Long && b instanceof Long) { + return ((Long) a) * ((Long) b); + } + double res = toDouble(a) * toDouble(b); + if (IsInteger(res)) { + return (long) res; + } else { + return res; + } + } + + public static int getArrayLength(Object value) { + if (value == null) return 0; + + if (value instanceof List) { + return ((List) value).size(); + } else if (value instanceof String) { + return ((String) value).length(); // fallback + } else if (value.getClass().isArray()) { + return java.lang.reflect.Array.getLength(value); + } else { + return 0; + } + } + + public static boolean IsInteger(Object value) { + if (value == null) return false; + + if (value instanceof Byte || value instanceof Short || + value instanceof Integer || value instanceof Long || + value instanceof java.util.concurrent.atomic.AtomicInteger || + value instanceof java.util.concurrent.atomic.AtomicLong) { + return true; + } + + if (value instanceof Float || value instanceof Double || value instanceof BigDecimal) { + BigDecimal d = new BigDecimal(String.valueOf(value)); + return d.stripTrailingZeros().scale() <= 0; + } + return false; + } + + public static Object mathMin(Object a, Object b) { + if (a == null || b == null) return null; + double first = toDouble(a); + double second = toDouble(b); + return (first < second) ? a : b; + } + + public static Object mathMax(Object a, Object b) { + if (a == null || b == null) return null; + double first = toDouble(a); + double second = toDouble(b); + return (first > second) ? a : b; + } + + public static int getIndexOf(Object str, Object target) { + if (str instanceof List) { + return ((List) str).indexOf(target); + } else if (str instanceof String && target instanceof String) { + return ((String) str).indexOf((String) target); + } else { + return -1; + } + } + + public static Object parseInt(Object a) { + try { + return toLong(a); + } catch (Exception ignored) { + return null; + } + } + + public static Object parseFloat(Object a) { + try { + return toDouble(a); + } catch (Exception ignored) { + return null; + } + } + + // generic getValue to replace elementAccesses + public Object getValue(Object a, Object b) { return GetValue(a, b); } + + public static Object GetValue(Object value2, Object key) { + if (value2 == null || key == null) return null; + + // Strings: index access + if (value2 instanceof String) { + String str = (String) value2; + int idx = toInt(key); + if (idx < 0 || idx >= str.length()) return null; + return String.valueOf(str.charAt(idx)); + } + + Object value = value2; + if (value2.getClass().isArray()) { + // Convert to List + int len = Array.getLength(value2); + List list = new ArrayList<>(len); + for (int i = 0; i < len; i++) list.add(Array.get(value2, i)); + value = list; + } + + if (value instanceof Map) { + Map m = (Map) value; + if (key instanceof String && m.containsKey(key)) { + return m.get(key); + } + return null; + } else if (value instanceof List) { + int idx = toInt(key); + List list = (List) value; + if (idx < 0 || idx >= list.size()) return null; + return list.get(idx); + } else if (key instanceof String) { + // Try Java field or getter + String name = (String) key; + try { + // Field + Field f = value.getClass().getField(name); + f.setAccessible(true); + return f.get(value2); + } catch (Exception ignored) {} + try { + // Getter + String mName = "get" + Character.toUpperCase(name.charAt(0)) + name.substring(1); + Method m = value.getClass().getMethod(mName); + return m.invoke(value2); + } catch (Exception ignored) {} + return null; + } else { + return null; + } + } + + public CompletableFuture> promiseAll(Object promisesObj) { return PromiseAll(promisesObj); } + + public static CompletableFuture> PromiseAll(Object promisesObj) { + List promises = (List) promisesObj; + List> futures = new ArrayList<>(); + for (Object p : promises) { + if (p instanceof CompletableFuture) { + futures.add((CompletableFuture) p); + } + } + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .thenApply(v -> { + List out = new ArrayList<>(futures.size()); + for (CompletableFuture f : futures) { + try { + out.add(f.get()); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + return out; + }); + } + + public static String toStringOrNull(Object value) { + if (value == null) return null; + return (String) value; + } + + public void throwDynamicException(Object exception, Object message) { + Exception ex = NewException((Class) exception, (String) message); + // In C#, this constructed but didn't throw; mimic behavior: + // If you actually want to throw: + // throw ex; + } + + // This function is the salient bit here + public Object newException(Object exception, Object message) { + return NewException((Class) exception, (String) message); + } + + public static Exception NewException(Class exception, String message) { + try { + Constructor ctor = exception.getConstructor(String.class); + return (Exception) ctor.newInstance(message); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static Object toFixed(Object number, Object decimals) { + double n = toDouble(number); + int d = toInt(decimals); + BigDecimal bd = new BigDecimal(Double.toString(n)).setScale(d, RoundingMode.HALF_UP); + return bd.doubleValue(); + } + + public static Object callDynamically(Object obj, Object methodName, Object[] args) { + if (args == null) args = new Object[]{}; + if (args.length == 0) { + // C# code injected a null arg to help binder; Java doesn't need it. + // But to mirror behavior, we won't add a null here. + } + String name = (String) methodName; + Method m = findMethod(obj.getClass(), name, args.length); + try { + m.setAccessible(true); + return m.invoke(obj, args); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static Object callDynamicallyAsync(Object obj, Object methodName, Object[] args) { + if (args == null) args = new Object[]{}; + String name = (String) methodName; + Method m = findMethod(obj.getClass(), name, args.length); + try { + m.setAccessible(true); + Object res = m.invoke(obj, args); + if (res instanceof CompletableFuture) { + return ((CompletableFuture) res).get(); + } + return res; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public boolean inOp(Object obj, Object key) { return InOp(obj, key); } + + public static boolean InOp(Object obj, Object key) { + if (obj == null || key == null) return false; + + if (obj instanceof List) { + return ((List) obj).contains(key); + } else if (obj instanceof Map) { + if (key instanceof String) { + return ((Map) obj).containsKey(key); + } else return false; + } else { + return false; + } + } + + public String slice(Object str2, Object idx1, Object idx2) { return Slice(str2, idx1, idx2); } + + public static String Slice(Object str2, Object idx1, Object idx2) { + if (str2 == null) return null; + String str = (String) str2; + int start = (idx1 != null) ? toInt(idx1) : -1; + + if (idx2 == null) { + if (start < 0) { + int innerStart = str.length() + start; + innerStart = Math.max(innerStart, 0); + return str.substring(innerStart); + } else { + if (start > str.length()) return ""; + return str.substring(start); + } + } else { + int end = toInt(idx2); + if (start < 0) start = str.length() + start; + if (end < 0) end = str.length() + end; + if (start < 0) start = 0; + if (end > str.length()) end = str.length(); + if (start > end) start = end; + return str.substring(start, end); + } + } + + public static Object concat(Object a, Object b) { + if (a == null && b == null) return null; + if (a == null) return b; + if (b == null) return a; + + if (a instanceof List && b instanceof List) { + List result = new ArrayList((List) a); + result.addAll((List) b); + return result; + } else if (a instanceof List && !(b instanceof List)) { + List result = new ArrayList((List) a); + result.add(b); + return result; + } else if (!(a instanceof List) && b instanceof List) { + List result = new ArrayList(); + result.add(a); + result.addAll((List) b); + return result; + } else { + throw new IllegalStateException("Unsupported types for concatenation."); + } + } + + // --------- helpers --------- + + private static Method findMethod(Class cls, String name, int argCount) { + // try exact arg count first + for (Method m : cls.getDeclaredMethods()) { + if (m.getName().equals(name) && m.getParameterCount() == argCount) { + return m; + } + } + // search up the hierarchy + Class cur = cls.getSuperclass(); + while (cur != null) { + for (Method m : cur.getDeclaredMethods()) { + if (m.getName().equals(name) && m.getParameterCount() == argCount) { + return m; + } + } + cur = cur.getSuperclass(); + } + // fallback: first by name + for (Method m : cls.getDeclaredMethods()) { + if (m.getName().equals(name)) return m; + } + throw new RuntimeException("Method not found: " + name + " with " + argCount + " args on " + cls.getName()); + } + + private static Long toLong(Object o) { + if (o instanceof Long) return (Long) o; + if (o instanceof Integer) return ((Integer) o).longValue(); + if (o instanceof Double) return ((Double) o).longValue(); + if (o instanceof Float) return ((Float) o).longValue(); + if (o instanceof BigDecimal) return ((BigDecimal) o).longValue(); + if (o instanceof String) return Long.parseLong((String) o); + return Long.parseLong(String.valueOf(o)); + } + + private static int toInt(Object o) { + if (o instanceof Integer) return (Integer) o; + if (o instanceof Long) return ((Long) o).intValue(); + if (o instanceof Double) return ((Double) o).intValue(); + if (o instanceof Float) return ((Float) o).intValue(); + if (o instanceof BigDecimal) return ((BigDecimal) o).intValue(); + if (o instanceof String) return Integer.parseInt((String) o); + return Integer.parseInt(String.valueOf(o)); + } + + private static double toDouble(Object o) { + if (o instanceof Double) return (Double) o; + if (o instanceof Float) return ((Float) o).doubleValue(); + if (o instanceof Long) return ((Long) o).doubleValue(); + if (o instanceof Integer) return ((Integer) o).doubleValue(); + if (o instanceof BigDecimal) return ((BigDecimal) o).doubleValue(); + if (o instanceof String) return Double.parseDouble((String) o); + return Double.parseDouble(String.valueOf(o)); + } + + private static float toFloat(Object o) { + if (o instanceof Float) return (Float) o; + if (o instanceof Double) return ((Double) o).floatValue(); + if (o instanceof Long) return ((Long) o).floatValue(); + if (o instanceof Integer) return ((Integer) o).floatValue(); + if (o instanceof BigDecimal) return ((BigDecimal) o).floatValue(); + if (o instanceof String) return Float.parseFloat((String) o); + return Float.parseFloat(String.valueOf(o)); + } + + + public static String replaceAll(Object baseString, Object search, Object replacement) { + if (baseString == null) { + return null; + } + String s = String.valueOf(baseString); + String find = (search == null) ? "" : String.valueOf(search); + if (find.isEmpty()) { + // Avoid weird behavior of replacing "" (would insert between every char) + return s; + } + String repl = (replacement == null) ? "" : String.valueOf(replacement); + return s.replace(find, repl); // literal (non-regex) replacement + } + + public static String replace(Object baseString, Object search, Object replacement) { + if (baseString == null) { + return null; + } + String s = String.valueOf(baseString); + String find = (search == null) ? "" : String.valueOf(search); + String repl = (replacement == null) ? "" : String.valueOf(replacement); + return s.replaceFirst(find, repl); // literal (non-regex) replacement + } + + public static Object getArg(Object[] v, int index, Object def) { + if (v.length <= index) { + return def; + } + return v[index]; + } + + + @SuppressWarnings("unchecked") + public static void addElementToObject(Object target, Object... args) { + if (target instanceof Map map) { + if (args.length != 2) + throw new IllegalArgumentException("Map requires (key, value)"); + ((Map) map).put(args[0], args[1]); + return; + } + + if (target instanceof List list) { + List l = (List) list; + if (args.length == 1) { + l.add(args[0]); // append + return; + } + if (args.length == 2 && args[0] instanceof Integer idx) { + int i = idx; + if (i < 0 || i > l.size()) { + throw new IndexOutOfBoundsException("Index " + i + " out of bounds [0," + l.size() + "]"); + } + l.add(i, args[1]); + return; + } + throw new IllegalArgumentException( + "List requires (value) to append or (index(Integer), value) to insert"); + } + + throw new IllegalArgumentException("Target is neither Map nor List: " + typeName(target)); + } + + private static String typeName(Object o) { + return (o == null) ? "null" : o.getClass().getName(); + } + + public static Object opNeg(Object value) { + if (value == null) { + return null; + } + + if (value instanceof Byte) { + byte v = (Byte) value; + return (byte) -v; + } + if (value instanceof Short v) { + return (short) -v; + } + if (value instanceof Integer v) { + return -v; + } + if (value instanceof Long v) { + return -v; + } + if (value instanceof Float v) { + return -v; + } + if (value instanceof Double v) { + return -v; + } + + return null; + } +} diff --git a/java-demo/app/src/test/java/org/example/AppTest.java b/java-demo/app/src/test/java/org/example/AppTest.java new file mode 100644 index 0000000..f5ce33d --- /dev/null +++ b/java-demo/app/src/test/java/org/example/AppTest.java @@ -0,0 +1,14 @@ +/* + * This source file was generated by the Gradle 'init' task + */ +package org.example; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class AppTest { + @Test void appHasAGreeting() { + App classUnderTest = new App(); + assertNotNull(classUnderTest.getGreeting(), "app should have a greeting"); + } +} diff --git a/java-demo/gradle.properties b/java-demo/gradle.properties new file mode 100644 index 0000000..377538c --- /dev/null +++ b/java-demo/gradle.properties @@ -0,0 +1,5 @@ +# This file was generated by the Gradle 'init' task. +# https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties + +org.gradle.configuration-cache=true + diff --git a/java-demo/gradle/libs.versions.toml b/java-demo/gradle/libs.versions.toml new file mode 100644 index 0000000..aae5ec4 --- /dev/null +++ b/java-demo/gradle/libs.versions.toml @@ -0,0 +1,10 @@ +# This file was generated by the Gradle 'init' task. +# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format + +[versions] +guava = "33.3.1-jre" +junit-jupiter = "5.11.1" + +[libraries] +guava = { module = "com.google.guava:guava", version.ref = "guava" } +junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" } diff --git a/java-demo/gradle/wrapper/gradle-wrapper.jar b/java-demo/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..a4b76b9 Binary files /dev/null and b/java-demo/gradle/wrapper/gradle-wrapper.jar differ diff --git a/java-demo/gradle/wrapper/gradle-wrapper.properties b/java-demo/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..cea7a79 --- /dev/null +++ b/java-demo/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/java-demo/gradlew b/java-demo/gradlew new file mode 100755 index 0000000..f3b75f3 --- /dev/null +++ b/java-demo/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original 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. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# 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 ;; #( + MSYS* | 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 + if ! command -v java >/dev/null 2>&1 + then + 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 +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# 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"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/java-demo/gradlew.bat b/java-demo/gradlew.bat new file mode 100644 index 0000000..9d21a21 --- /dev/null +++ b/java-demo/gradlew.bat @@ -0,0 +1,94 @@ +@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 +@rem SPDX-License-Identifier: Apache-2.0 +@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=. +@rem This is normally unused +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% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +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% equ 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! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/java-demo/settings.gradle.kts b/java-demo/settings.gradle.kts new file mode 100644 index 0000000..d1554fa --- /dev/null +++ b/java-demo/settings.gradle.kts @@ -0,0 +1,14 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * For more detailed information on multi-project builds, please refer to https://docs.gradle.org/8.12/userguide/multi_project_builds.html in the Gradle documentation. + */ + +plugins { + // Apply the foojay-resolver plugin to allow automatic download of JDKs + id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" +} + +rootProject.name = "java-demo" +include("app") diff --git a/manual.ts b/manual.ts index a0f380c..1223d33 100644 --- a/manual.ts +++ b/manual.ts @@ -81,6 +81,9 @@ const config = [ language: "go", async: true }, + { + language: "java", + } ] const result = transpiler.transpileDifferentLanguagesByPath(config as any, file); @@ -96,6 +99,7 @@ const PHP_SYNC_OUTPUT = "./out/output-sync.php"; const PYTHON_OUTPUT = "./out/output.py"; const PYTHON_SYNC_OUTPUT = "./out/output-sync.py"; const CSHARP_OUTPUT = "./out/output.cs"; +const JAVA_OUTPUT = "./out/output.java"; const GO_OUTPUT = "./out/output.go"; const go = result[3].content; @@ -109,6 +113,8 @@ writeFileSync(PYTHON_OUTPUT, pythonAsync ?? ""); writeFileSync(CSHARP_OUTPUT, csharp); writeFileSync(GO_OUTPUT, go); +writeFileSync(JAVA_OUTPUT, result[4].content); + console.log("TRANSPILED!!"); diff --git a/package-lock.json b/package-lock.json index 3760af9..0f17a10 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ast-transpiler", - "version": "0.0.66", + "version": "0.0.71", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "ast-transpiler", - "version": "0.0.66", + "version": "0.0.71", "license": "MIT", "dependencies": { "colorette": "^2.0.19", @@ -25,7 +25,8 @@ "test-jest": "^1.0.1", "ts-jest": "^29.0.3", "ts-node": "^10.9.1", - "tsup": "^6.4.0" + "tsup": "^6.4.0", + "tsx": "^4.20.6" }, "engines": { "node": ">=16.0.0" @@ -626,6 +627,23 @@ "node": ">=12" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", + "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/android-arm": { "version": "0.15.13", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.13.tgz", @@ -642,6 +660,159 @@ "node": ">=12" } }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz", + "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz", + "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz", + "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz", + "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz", + "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz", + "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz", + "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz", + "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz", + "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/linux-loong64": { "version": "0.15.13", "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.13.tgz", @@ -658,6 +829,244 @@ "node": ">=12" } }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz", + "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz", + "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz", + "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz", + "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz", + "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz", + "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz", + "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz", + "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz", + "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz", + "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz", + "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz", + "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz", + "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz", + "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint/eslintrc": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", @@ -3200,11 +3609,12 @@ "dev": true }, "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -3258,6 +3668,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-tsconfig": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.12.0.tgz", + "integrity": "sha512-LScr2aNr2FbjAjZh2C6X6BxRx1/x+aTDExct/xyq2XKbYOiG5c0aK7pMsSuyc0brz3ibr/lbQiHD9jzt4lccJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -4972,6 +5395,16 @@ "node": ">=8" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/resolve.exports": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", @@ -5588,6 +6021,102 @@ "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" } }, + "node_modules/tsx": { + "version": "4.20.6", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", + "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz", + "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz", + "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", + "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.10", + "@esbuild/android-arm": "0.25.10", + "@esbuild/android-arm64": "0.25.10", + "@esbuild/android-x64": "0.25.10", + "@esbuild/darwin-arm64": "0.25.10", + "@esbuild/darwin-x64": "0.25.10", + "@esbuild/freebsd-arm64": "0.25.10", + "@esbuild/freebsd-x64": "0.25.10", + "@esbuild/linux-arm": "0.25.10", + "@esbuild/linux-arm64": "0.25.10", + "@esbuild/linux-ia32": "0.25.10", + "@esbuild/linux-loong64": "0.25.10", + "@esbuild/linux-mips64el": "0.25.10", + "@esbuild/linux-ppc64": "0.25.10", + "@esbuild/linux-riscv64": "0.25.10", + "@esbuild/linux-s390x": "0.25.10", + "@esbuild/linux-x64": "0.25.10", + "@esbuild/netbsd-arm64": "0.25.10", + "@esbuild/netbsd-x64": "0.25.10", + "@esbuild/openbsd-arm64": "0.25.10", + "@esbuild/openbsd-x64": "0.25.10", + "@esbuild/openharmony-arm64": "0.25.10", + "@esbuild/sunos-x64": "0.25.10", + "@esbuild/win32-arm64": "0.25.10", + "@esbuild/win32-ia32": "0.25.10", + "@esbuild/win32-x64": "0.25.10" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -6306,6 +6835,13 @@ "@jridgewell/trace-mapping": "0.3.9" } }, + "@esbuild/aix-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", + "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==", + "dev": true, + "optional": true + }, "@esbuild/android-arm": { "version": "0.15.13", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.13.tgz", @@ -6313,6 +6849,69 @@ "dev": true, "optional": true }, + "@esbuild/android-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz", + "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz", + "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz", + "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz", + "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz", + "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz", + "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz", + "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz", + "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz", + "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==", + "dev": true, + "optional": true + }, "@esbuild/linux-loong64": { "version": "0.15.13", "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.13.tgz", @@ -6320,6 +6919,104 @@ "dev": true, "optional": true }, + "@esbuild/linux-mips64el": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz", + "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz", + "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz", + "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz", + "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz", + "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz", + "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz", + "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz", + "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz", + "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==", + "dev": true, + "optional": true + }, + "@esbuild/openharmony-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz", + "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz", + "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz", + "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz", + "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz", + "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", + "dev": true, + "optional": true + }, "@eslint/eslintrc": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", @@ -8140,9 +8837,9 @@ "dev": true }, "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "optional": true }, @@ -8176,6 +8873,15 @@ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true }, + "get-tsconfig": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.12.0.tgz", + "integrity": "sha512-LScr2aNr2FbjAjZh2C6X6BxRx1/x+aTDExct/xyq2XKbYOiG5c0aK7pMsSuyc0brz3ibr/lbQiHD9jzt4lccJw==", + "dev": true, + "requires": { + "resolve-pkg-maps": "^1.0.0" + } + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -9457,6 +10163,12 @@ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true }, + "resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true + }, "resolve.exports": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", @@ -9878,6 +10590,67 @@ "tslib": "^1.8.1" } }, + "tsx": { + "version": "4.20.6", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", + "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", + "dev": true, + "requires": { + "esbuild": "~0.25.0", + "fsevents": "~2.3.3", + "get-tsconfig": "^4.7.5" + }, + "dependencies": { + "@esbuild/android-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz", + "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz", + "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==", + "dev": true, + "optional": true + }, + "esbuild": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", + "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", + "dev": true, + "requires": { + "@esbuild/aix-ppc64": "0.25.10", + "@esbuild/android-arm": "0.25.10", + "@esbuild/android-arm64": "0.25.10", + "@esbuild/android-x64": "0.25.10", + "@esbuild/darwin-arm64": "0.25.10", + "@esbuild/darwin-x64": "0.25.10", + "@esbuild/freebsd-arm64": "0.25.10", + "@esbuild/freebsd-x64": "0.25.10", + "@esbuild/linux-arm": "0.25.10", + "@esbuild/linux-arm64": "0.25.10", + "@esbuild/linux-ia32": "0.25.10", + "@esbuild/linux-loong64": "0.25.10", + "@esbuild/linux-mips64el": "0.25.10", + "@esbuild/linux-ppc64": "0.25.10", + "@esbuild/linux-riscv64": "0.25.10", + "@esbuild/linux-s390x": "0.25.10", + "@esbuild/linux-x64": "0.25.10", + "@esbuild/netbsd-arm64": "0.25.10", + "@esbuild/netbsd-x64": "0.25.10", + "@esbuild/openbsd-arm64": "0.25.10", + "@esbuild/openbsd-x64": "0.25.10", + "@esbuild/openharmony-arm64": "0.25.10", + "@esbuild/sunos-x64": "0.25.10", + "@esbuild/win32-arm64": "0.25.10", + "@esbuild/win32-ia32": "0.25.10", + "@esbuild/win32-x64": "0.25.10" + } + } + } + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/package.json b/package.json index 73a7754..cd0fbcd 100644 --- a/package.json +++ b/package.json @@ -22,12 +22,12 @@ "scripts": { "build": "npm run lint && tsup src/transpiler.ts --format cjs,esm --dts --clean --splitting --shims --sourcemap", "build2": "npm run lint && tsc", - "start": "node --loader ts-node/esm manual.ts", - "start-py": "node --loader ts-node/esm src/pythonTranspiler.ts", + "start": "tsx manual.ts", + "start-py": "tsx src/pythonTranspiler.ts", "test-ci": "jest --ci --coverage", "test": "jest --verbose", "testv": "jest --verbose --coverage", - "integration": "node --loader ts-node/esm tests/integration/test.ts", + "integration": "tsx tests/integration/test.ts", "lint": "eslint src/ --ext .ts", "publishPackage": "sh publish.sh && git push && git push --tags && npm publish" }, @@ -43,7 +43,8 @@ "test-jest": "^1.0.1", "ts-jest": "^29.0.3", "ts-node": "^10.9.1", - "tsup": "^6.4.0" + "tsup": "^6.4.0", + "tsx": "^4.20.6" }, "dependencies": { "colorette": "^2.0.19", diff --git a/src/baseTranspiler.ts b/src/baseTranspiler.ts index 668c622..5ea2d5e 100644 --- a/src/baseTranspiler.ts +++ b/src/baseTranspiler.ts @@ -2,6 +2,7 @@ import ts from 'typescript'; import { IFileImport, IFileExport, TranspilationError, IMethodType, IParameterType } from './types.js'; import { unCamelCase } from "./utils.js"; import { Logger } from "./logger.js"; +import { timingSafeEqual } from 'crypto'; class BaseTranspiler { NUM_LINES_BETWEEN_CLASS_MEMBERS = 1; @@ -176,6 +177,7 @@ class BaseTranspiler { CallExpressionReplacements = {}; ReservedKeywordsReplacements = {}; + ReassignedVars = {}; PropertyAccessRequiresParenthesisRemoval = []; VariableTypeReplacements = {}; ArgTypeReplacements = {}; @@ -183,6 +185,8 @@ class BaseTranspiler { FuncModifiers = {}; defaultPropertyAccess = 'public'; + currentClassName = ""; + uncamelcaseIdentifiers; asyncTranspiling; requiresReturnType; @@ -474,13 +478,19 @@ class BaseTranspiler { return `${this.COMPARISON_WRAPPER_OPEN}${leftVar}, ${rightVar}${this.COMPARISON_WRAPPER_CLOSE}`; } } - + let prefixes = ""; // check if boolean operators || and && because of the falsy values if (operatorToken.kind === ts.SyntaxKind.BarBarToken || operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) { leftVar = this.printCondition(left, 0); rightVar = this.printCondition(right, identation); } else { leftVar = this.printNode(left, 0); + + + // if (right.kind === ts.SyntaxKind.ObjectLiteralExpression) { + prefixes = this.getBinaryExpressionPrefixes(node, identation) ?? ""; + // } + rightVar = this.printNode(right, identation); } @@ -488,7 +498,11 @@ class BaseTranspiler { operator = customOperator ? customOperator : operator; - return leftVar +" "+ operator + " " + rightVar.trim(); + return prefixes + leftVar +" "+ operator + " " + rightVar.trim(); + } + + getBinaryExpressionPrefixes(node, identation) { + return undefined; } transformPropertyAcessExpressionIfNeeded (node) { @@ -1326,17 +1340,27 @@ class BaseTranspiler { return parsedMembers.join("\n"); } + getCustomClassName(node) { + return node.name.escapedText; + } + + getClassModifier(node) { + return ""; + } + printClassDefinition(node, identation) { - const className = node.name.escapedText; + const className = this.getCustomClassName(node); + this.currentClassName = className; const heritageClauses = node.heritageClauses; + const classModifier = this.getClassModifier(node); let classInit = ""; const classOpening = this.getBlockOpen(identation); if (heritageClauses !== undefined) { const classExtends = heritageClauses[0].types[0].expression.escapedText; - classInit = this.getIden(identation) + "class " + className + " " + this.EXTENDS_TOKEN + " " + classExtends + classOpening; + classInit = this.getIden(identation) + classModifier + "class " + className + " " + this.EXTENDS_TOKEN + " " + classExtends + classOpening; } else { - classInit = this.getIden(identation) + "class " + className + classOpening; + classInit = this.getIden(identation) + classModifier + "class " + className + classOpening; } return classInit; } @@ -1413,7 +1437,6 @@ class BaseTranspiler { } printObjectLiteralExpression(node, identation) { - const objectBody = this.printObjectLiteralBody(node, identation); const formattedObjectBody = objectBody ? "\n" + objectBody + "\n" + this.getIden(identation) : objectBody; return this.OBJECT_OPENING + formattedObjectBody + this.OBJECT_CLOSING; @@ -1501,13 +1524,20 @@ class BaseTranspiler { if (this.id === "C#") { const cast = ts.isStringLiteralLike(argumentExpression) ? "" : '(string)'; return `((IDictionary)${expressionAsString})[${cast}${argumentAsString}]`; + } else if (this.id === "Java") { + return `((java.util.HashMap)${expressionAsString}).get(${argumentAsString})`; } // if (this.id === "Go") { // return `AddElementToObject(${expressionAsString}, ${argumentAsString})`; // } } - return `((${this.ARRAY_KEYWORD})${expressionAsString})[Convert.ToInt32(${argumentAsString})]`; + if (this.id === "C#") { + return `((${this.ARRAY_KEYWORD})${expressionAsString})[Convert.ToInt32(${argumentAsString})]`; + } else if (this.id === "Java") { + return `((java.util.List)${expressionAsString}).get(Helpers.toInt(${argumentAsString}))`; + } + } return expressionAsString + "[" + argumentAsString + "]"; @@ -1694,16 +1724,23 @@ class BaseTranspiler { if (this.isCJSModuleExportsExpressionStatement(node)) { return ""; // remove module.exports = ... } + + const expressionStatementPrefixes = this.getExpressionStatementPrefixesIfAny(node, identation) ?? ""; + const exprStm = this.printNode(node.expression, identation); // skip empty statements if (exprStm.length === 0) { return ""; } - const expStatement = this.getIden(identation) + exprStm + this.LINE_TERMINATOR; + const expStatement = expressionStatementPrefixes + this.getIden(identation) + exprStm + this.LINE_TERMINATOR; return this.printNodeCommentsIfAny(node, identation, expStatement); } + getExpressionStatementPrefixesIfAny(node, identation) { + return undefined; + } + printPropertyDeclaration(node, identation) { const modifiers = this.printPropertyAccessModifiers(node); const name = this.printNode(node.name, 0); @@ -1737,6 +1774,10 @@ class BaseTranspiler { return undefined; } + printThisKeyword(node, identation) { + return this.THIS_TOKEN; + } + printNode(node, identation = 0): string { try { @@ -1789,7 +1830,7 @@ class BaseTranspiler { } else if ((ts as any).isBooleanLiteral(node)) { return this.printBooleanLiteral(node); } else if (ts.SyntaxKind.ThisKeyword === node.kind) { - return this.THIS_TOKEN; + return this.printThisKeyword(node, identation); } else if (ts.SyntaxKind.SuperKeyword === node.kind) { return this.SUPER_TOKEN; }else if (ts.isTryStatement(node)){ diff --git a/src/javaTranspiler.ts b/src/javaTranspiler.ts new file mode 100644 index 0000000..60b02bb --- /dev/null +++ b/src/javaTranspiler.ts @@ -0,0 +1,1660 @@ +import { BaseTranspiler } from "./baseTranspiler.js"; +import ts, { TypeChecker } from "typescript"; + +const parserConfig = { + EXTENDS_TOKEN: "extends", + PROMISE_TYPE_KEYWORD: "java.util.concurrent.CompletableFuture", + ARRAY_KEYWORD: "java.util.List", + OBJECT_KEYWORD: "java.util.Map", + STRING_KEYWORD: "String", + BOOLEAN_KEYWORD: "boolean", + DEFAULT_PARAMETER_TYPE: "Object", + DEFAULT_RETURN_TYPE: "Object", + DEFAULT_TYPE: "Object", + ELSEIF_TOKEN: "else if", + // Objects in Java: we'll use double-brace initialization so property puts work + OBJECT_OPENING: "new java.util.HashMap() {{", + OBJECT_CLOSING: "}}", + // Arrays in Java: emit Arrays.asList(...) wrapped by ArrayList + ARRAY_OPENING_TOKEN: "new java.util.ArrayList(java.util.Arrays.asList(", + ARRAY_CLOSING_TOKEN: "))", + // For object literal properties we'll emit: put(key, value); + PROPERTY_ASSIGNMENT_TOKEN: ",", + VAR_TOKEN: "Object", // Java 10+ local var + METHOD_TOKEN: "", + PROPERTY_ASSIGNMENT_OPEN: "put(", + PROPERTY_ASSIGNMENT_CLOSE: ");", + SUPER_TOKEN: "super", + SUPER_CALL_TOKEN: "super", + FALSY_WRAPPER_OPEN: "Helpers.isTrue(", + FALSY_WRAPPER_CLOSE: ")", + COMPARISON_WRAPPER_OPEN: "Helpers.isEqual(", + COMPARISON_WRAPPER_CLOSE: ")", + UKNOWN_PROP_WRAPPER_OPEN: "this.call(", + UNKOWN_PROP_WRAPPER_CLOSE: ")", + UKNOWN_PROP_ASYNC_WRAPPER_OPEN: "this.callAsync(", + UNKOWN_PROP_ASYNC_WRAPPER_CLOSE: ")", + DYNAMIC_CALL_OPEN: "Helpers.callDynamically(", + EQUALS_EQUALS_WRAPPER_OPEN: "Helpers.isEqual(", + EQUALS_EQUALS_WRAPPER_CLOSE: ")", + DIFFERENT_WRAPPER_OPEN: "!Helpers.isEqual(", + DIFFERENT_WRAPPER_CLOSE: ")", + GREATER_THAN_WRAPPER_OPEN: "Helpers.isGreaterThan(", + GREATER_THAN_WRAPPER_CLOSE: ")", + GREATER_THAN_EQUALS_WRAPPER_OPEN: "Helpers.isGreaterThanOrEqual(", + GREATER_THAN_EQUALS_WRAPPER_CLOSE: ")", + LESS_THAN_WRAPPER_OPEN: "Helpers.isLessThan(", + LESS_THAN_WRAPPER_CLOSE: ")", + LESS_THAN_EQUALS_WRAPPER_OPEN: "Helpers.isLessThanOrEqual(", + LESS_THAN_EQUALS_WRAPPER_CLOSE: ")", + PLUS_WRAPPER_OPEN: "Helpers.add(", + PLUS_WRAPPER_CLOSE: ")", + MINUS_WRAPPER_OPEN: "Helpers.subtract(", + MINUS_WRAPPER_CLOSE: ")", + ARRAY_LENGTH_WRAPPER_OPEN: "Helpers.getArrayLength(", + ARRAY_LENGTH_WRAPPER_CLOSE: ")", + DIVIDE_WRAPPER_OPEN: "Helpers.divide(", + DIVIDE_WRAPPER_CLOSE: ")", + MULTIPLY_WRAPPER_OPEN: "Helpers.multiply(", + MULTIPLY_WRAPPER_CLOSE: ")", + INDEXOF_WRAPPER_OPEN: "Helpers.getIndexOf(", + INDEXOF_WRAPPER_CLOSE: ")", + MOD_WRAPPER_OPEN: "Helpers.mod(", + MOD_WRAPPER_CLOSE: ")", + FUNCTION_TOKEN: "", + ELEMENT_ACCESS_WRAPPER_OPEN: 'Helpers.GetValue(', + ELEMENT_ACCESS_WRAPPER_CLOSE: ')', + INFER_VAR_TYPE: false, + INFER_ARG_TYPE: false, +}; + +export class JavaTranspiler extends BaseTranspiler { + binaryExpressionsWrappers; + + varListFromObjectLiterals = {}; + + constructor(config = {}) { + config["parser"] = Object.assign({}, parserConfig, config["parser"] ?? {}); + super(config); + + this.requiresParameterType = true; + this.requiresReturnType = true; + this.asyncTranspiling = true; + this.supportsFalsyOrTruthyValues = false; + this.requiresCallExpressionCast = true; + this.id = "Java"; + + this.initConfig(); + this.applyUserOverrides(config); + } + + initConfig() { + this.LeftPropertyAccessReplacements = { + // 'this': '$this', + }; + + this.RightPropertyAccessReplacements = { + // Java list/string methods (lowerCamelCase) + push: "add", + indexOf: "indexOf", + toUpperCase: "toUpperCase", + toLowerCase: "toLowerCase", + toString: "toString", + }; + + this.FullPropertyAccessReplacements = { + "JSON.parse": "parseJson", + "console.log": "System.out.println", + "Number.MAX_SAFE_INTEGER": "Long.MAX_VALUE", + "Math.min": "Math.min", + "Math.max": "Math.max", + "Math.log": "Math.log", + "Math.abs": "Math.abs", + "Math.floor": "Math.floor", + "Math.pow": "Math.pow", + // 'Promise.all' handled via promiseAll wrapper + }; + + this.CallExpressionReplacements = { + 'parseInt': "Helpers.parseInt", + "parseFloat": "Helpers.parseFloat", + // Add ad-hoc function call rewrites here if you need them + }; + + + this.ReservedKeywordsReplacements = { + string: "str", + object: "obj", + params: "parameters", + // base: "bs", + internal: "intern", + event: "eventVar", + fixed: "fixedVar", + final: "finalVar", + // add Java keywords if you need to avoid collisions (e.g., enum, assert) + }; + + this.VariableTypeReplacements = { + string: "String", + Str: "String", + number: "double", + Int: "long", + Num: "double", + Dict: "java.util.Map", + Strings: "java.util.List", + List: "java.util.List", + boolean: "boolean", + object: "Object", + }; + + this.ArgTypeReplacements = { + string: "String", + Str: "String", + number: "double", + Int: "long", + Num: "double", + Dict: "java.util.Map", + Strings: "java.util.List", + List: "java.util.List", + boolean: "boolean", + object: "Object", + }; + + this.binaryExpressionsWrappers = { + [ts.SyntaxKind.EqualsEqualsToken]: [ + this.EQUALS_EQUALS_WRAPPER_OPEN, + this.EQUALS_EQUALS_WRAPPER_CLOSE, + ], + [ts.SyntaxKind.EqualsEqualsEqualsToken]: [ + this.EQUALS_EQUALS_WRAPPER_OPEN, + this.EQUALS_EQUALS_WRAPPER_CLOSE, + ], + [ts.SyntaxKind.ExclamationEqualsToken]: [ + this.DIFFERENT_WRAPPER_OPEN, + this.DIFFERENT_WRAPPER_CLOSE, + ], + [ts.SyntaxKind.ExclamationEqualsEqualsToken]: [ + this.DIFFERENT_WRAPPER_OPEN, + this.DIFFERENT_WRAPPER_CLOSE, + ], + [ts.SyntaxKind.GreaterThanToken]: [ + this.GREATER_THAN_WRAPPER_OPEN, + this.GREATER_THAN_WRAPPER_CLOSE, + ], + [ts.SyntaxKind.GreaterThanEqualsToken]: [ + this.GREATER_THAN_EQUALS_WRAPPER_OPEN, + this.GREATER_THAN_EQUALS_WRAPPER_CLOSE, + ], + [ts.SyntaxKind.LessThanToken]: [ + this.LESS_THAN_WRAPPER_OPEN, + this.LESS_THAN_WRAPPER_CLOSE, + ], + [ts.SyntaxKind.LessThanEqualsToken]: [ + this.LESS_THAN_EQUALS_WRAPPER_OPEN, + this.LESS_THAN_EQUALS_WRAPPER_CLOSE, + ], + [ts.SyntaxKind.PlusToken]: [ + this.PLUS_WRAPPER_OPEN, + this.PLUS_WRAPPER_CLOSE, + ], + [ts.SyntaxKind.MinusToken]: [ + this.MINUS_WRAPPER_OPEN, + this.MINUS_WRAPPER_CLOSE, + ], + [ts.SyntaxKind.AsteriskToken]: [ + this.MULTIPLY_WRAPPER_OPEN, + this.MULTIPLY_WRAPPER_CLOSE, + ], + [ts.SyntaxKind.PercentToken]: [ + this.MOD_WRAPPER_OPEN, + this.MOD_WRAPPER_CLOSE, + ], + [ts.SyntaxKind.SlashToken]: [ + this.DIVIDE_WRAPPER_OPEN, + this.DIVIDE_WRAPPER_CLOSE, + ], + }; + } + + getBlockOpen(identation) { + return "\n" + this.getIden(identation) + this.BLOCK_OPENING_TOKEN + "\n"; + } + + + getCustomClassName(node) { + return this.capitalize(node.name.escapedText); + } + + getClassModifier(node) { + return "public "; + } + + printSuperCallInsideConstructor(_node, _identation) { + // Java allows "super(...)" as the first line; we already inject it when needed. + return ""; + } + + printNumericLiteral(node) { + const javaMax = 2147483647; + const nodeText = node.text; + if (Number(nodeText) > javaMax && Number.isInteger(Number(nodeText))) { + return `${nodeText}L`; + } + return node.text; + } + + printIdentifier(node) { + let idValue = node.text ?? node.escapedText; + + if (this.ReservedKeywordsReplacements[idValue]) { + idValue = this.ReservedKeywordsReplacements[idValue]; + } + + if (idValue === "undefined") { + return this.UNDEFINED_TOKEN; + } + + // keep the same class-reference typeof-guarding logic as your original file + const type = (global as any).checker.getTypeAtLocation(node); + const symbol = type?.symbol; + if (symbol !== undefined) { + const decl = symbol?.declarations ?? []; + let isBuiltIn = undefined; + if (decl.length > 0) { + isBuiltIn = + decl[0].getSourceFile().fileName.indexOf("typescript") > -1; + } + + if (isBuiltIn !== undefined && !isBuiltIn) { + const isInsideNewExpression = + node?.parent?.kind === ts.SyntaxKind.NewExpression; + const isInsideCatch = + node?.parent?.kind === ts.SyntaxKind.ThrowStatement; + const isLeftSide = + node?.parent?.name === node || node?.parent?.left === node; + const isCallOrPropertyAccess = + node?.parent?.kind === ts.SyntaxKind.PropertyAccessExpression || + node?.parent?.kind === ts.SyntaxKind.ElementAccessExpression; + if (!isLeftSide && !isCallOrPropertyAccess && !isInsideCatch && !isInsideNewExpression) { + const symbol = (global as any).checker.getSymbolAtLocation(node); + let isClassDeclaration = false; + if (symbol) { + const first = symbol.declarations[0]; + if (first.kind === ts.SyntaxKind.ClassDeclaration) { + isClassDeclaration = true; + } + if (first.kind === ts.SyntaxKind.ImportSpecifier) { + const importedSymbol = (global as any).checker.getAliasedSymbol(symbol); + if ( + importedSymbol?.declarations[0]?.kind === + ts.SyntaxKind.ClassDeclaration + ) { + isClassDeclaration = true; + } + } + } + if (isClassDeclaration) { + return `${idValue}.class`; + } + } + } + } + + return this.transformIdentifier(node, idValue); + } + + printConstructorDeclaration(node, identation) { + const classNode = node.parent; + const className = this.printNode(classNode.name, 0); + const args = this.printMethodParameters(node); + const constructorBody = this.printFunctionBody(node, identation); + + // find super call inside constructor and extract params + let superCallParams = ""; + let hasSuperCall = false; + node.body?.statements.forEach((statement) => { + if (ts.isExpressionStatement(statement)) { + const expression = statement.expression; + if (ts.isCallExpression(expression)) { + const expressionText = expression.expression.getText().trim(); + if (expressionText === "super") { + hasSuperCall = true; + superCallParams = expression.arguments + .map((a) => { + return this.printNode(a, identation).trim(); + }) + .join(", "); + } + } + } + }); + + const header = + this.getIden(identation) + className + "(" + args + ")"; + if (!hasSuperCall) { + return header + constructorBody; + } + // In Java, super(...) must be the first statement inside the body. + const injected = this.injectLeadingInBody(constructorBody, `super(${superCallParams});`); + return header + injected; + } + + injectLeadingInBody(body, firstLine) { + // body is "{\n ...\n}" + const lines = body.split("\n"); + if (lines.length >= 2) { + lines.splice(1, 0, this.getIden(1) + firstLine); + } + return lines.join("\n"); + } + + printDynamicCall(node, identation) { + // Use reflection helper exactly like before; Java runtime should provide callDynamically(Object, String, Object[]) + const elementAccess = node.expression; + if (elementAccess?.kind === ts.SyntaxKind.ElementAccessExpression) { + const parsedArg = + node.arguments?.length > 0 + ? node.arguments + .map((n) => this.printNode(n, identation).trimStart()) + .join(", ") + : ""; + const target = this.printNode(elementAccess.expression, 0); + const propName = this.printNode(elementAccess.argumentExpression, 0); + const argsArray = `new Object[] { ${parsedArg} }`; + const open = this.DYNAMIC_CALL_OPEN; + return `${open}${target}, ${propName}, ${argsArray})`; + } + return undefined; + } + + getExpressionStatementPrefixesIfAny(node, identation) { + // return undefined; + const finalVars = []; + if (node.expression?.kind === ts.SyntaxKind.CallExpression) { + const objectLiterals = this.getObjectLiteralFromCallExpressionArguments(node.expression); + for (let i = 0; i < objectLiterals.length; i++) { + const objLiteral = objectLiterals[i]; + const objVariables = this.getVarListFromObjectLiteralAndUpdateInPlace(objLiteral); + if (objVariables.length > 0) { + finalVars.push(...objVariables); + } + } + + if (finalVars.length > 0) { + return finalVars.map( (v, i) => `${this.getIden( i > 0 ? identation : 0)}final Object ${this.getFinalVarName(v)} = ${this.getOriginalVarName(v)};`).join('\n') + "\n" + this.getIden(identation); + + } + } + + return undefined; + } + + // printElementAccessExpressionExceptionIfAny(node) { + // const tsKind = ts.SyntaxKind; + // if (node.expression.kind === tsKind.CallExpression) { + // const callExp = node.expression; + // const calleeText = callExp.expression.getText(); + // if (calleeText.endsWith('.split') || calleeText.toLowerCase().includes('split')) { + // // print Split call normally (should already close with )) + // let splitCall = this.printNode(callExp, 0).trim(); + // if (!splitCall.endsWith(')')) { + // splitCall += ')'; + // } + // const idxArg = this.printNode(node.argumentExpression, 0); + // return `GetValue(${splitCall}, ${idxArg})`; + // } + // } + // // default: no exception + // return undefined; + // } + + printWrappedUnknownThisProperty(node) { + const type = (global as any).checker.getResolvedSignature(node); + if (type?.declaration === undefined) { + let parsedArguments = node.arguments + ?.map((a) => this.printNode(a, 0)) + .join(", "); + parsedArguments = parsedArguments ? parsedArguments : ""; + const propName = node.expression?.name.escapedText; + const isAsyncDecl = node?.parent?.kind === ts.SyntaxKind.AwaitExpression; + const argsArray = `new Object[] { ${parsedArguments} }`; + const open = this.DYNAMIC_CALL_OPEN; + const statement = `${open}this, "${propName}", ${argsArray})`; + // If your Java runtime returns CompletableFuture, you can await it where appropriate. + return statement; + } + return undefined; + } + + printOutOfOrderCallExpressionIfAny(node, identation) { + if (node.expression.kind === ts.SyntaxKind.PropertyAccessExpression) { + const expressionText = node.expression.getText().trim(); + const args = node.arguments; + if (args.length === 1) { + const parsedArg = this.printNode(args[0], 0); + switch (expressionText) { + case "Math.abs": + return `Herlpers.mathAbs(Double.parseDouble((${parsedArg}).toString()))`; + } + } else if (args.length === 2) { + const parsedArg1 = this.printNode(args[0], 0); + const parsedArg2 = this.printNode(args[1], 0); + switch (expressionText) { + case "Math.min": + return `Helpers.mathMin(${parsedArg1}, ${parsedArg2})`; + case "Math.max": + return `Helpers.mathMax(${parsedArg1}, ${parsedArg2})`; + case "Math.pow": + return `Helpers.mathPow(Double.parseDouble(${parsedArg1}.toString()), Double.parseDouble(${parsedArg2}.toString()))`; + } + } + const leftSide = node.expression?.expression; + const leftSideText = leftSide ? this.printNode(leftSide, 0) : undefined; + + // wrap unknown property this.X calls + if ( + leftSideText === this.THIS_TOKEN || + leftSide.getFullText().indexOf("(this as any)") > -1 + ) { + const res = this.printWrappedUnknownThisProperty(node); + if (res) return res; + } + } + + // dynamic call: obj[prop](...) + if (node.expression.kind === ts.SyntaxKind.ElementAccessExpression) { + return this.printDynamicCall(node, identation); + } + + return undefined; + } + + handleTypeOfInsideBinaryExpression(node, _identation) { + const left = node.left; + const right = node.right.text; + const op = node.operatorToken.kind; + const expression = left.expression; + + const isDifferentOperator = + op === ts.SyntaxKind.ExclamationEqualsEqualsToken || + op === ts.SyntaxKind.ExclamationEqualsToken; + const notOperator = isDifferentOperator ? this.NOT_TOKEN : ""; + + const target = this.printNode(expression, 0); + switch (right) { + case "string": + return `${notOperator}(${target} instanceof String)`; + case "number": + return `${notOperator}(${target} instanceof Long || ${target} instanceof Integer || ${target} instanceof Float || ${target} instanceof Double)`; + case "boolean": + return `${notOperator}(${target} instanceof Boolean)`; + case "object": + return `${notOperator}(${target} instanceof java.util.Map)`; + case "function": + // no universal Function type in Java; treat as any Method/Callable + return `${notOperator}(${target} instanceof java.util.concurrent.Callable)`; + } + return undefined; + } + + getVarMethodIfAny(node) { + // should return the name of the method this node belongs to, if any + let current = node?.parent; + while (current) { + if (ts.isMethodDeclaration(current) || ts.isFunctionDeclaration(current)) { + return this.printNode(current.name, 0); + } + current = current.parent; + } + return 'outsideAnyMethod'; + } + + getVarClassIfAny(node) { + // should return the name of the class this node belongs to, if any + let current = node?.parent; + while (current) { + if (ts.isClassDeclaration(current)) { + return this.printNode(current.name, 0); + } + current = current.parent; + } + return ''; + } + + getVarKey(node) { + + const varName = node?.escapedText ?? node?.name?.escapedText; + if (!varName) { + return ''; + } + return `${this.getVarClassIfAny(node)}-${this.getVarMethodIfAny(node)}-${varName}`; + } + + printCustomBinaryExpressionIfAny(node, identation) { + const left = node.left; + const right = node.right; + const op = node.operatorToken.kind; + + if (left.kind === ts.SyntaxKind.Identifier) { + this.ReassignedVars[this.getVarKey(left)] = true; + } + + if (left.kind === ts.SyntaxKind.TypeOfExpression) { + const typeOfExpression = this.handleTypeOfInsideBinaryExpression( + node, + identation + ); + if (typeOfExpression) return typeOfExpression; + } + + // destructuring: [a,b] = this.method() + if ( + op === ts.SyntaxKind.EqualsToken && + left.kind === ts.SyntaxKind.ArrayLiteralExpression + ) { + const arrayBindingPatternElements = left.elements; + const parsedArrayBindingElements = arrayBindingPatternElements.map((e) => { + this.ReassignedVars[this.getVarKey(e)] = true; + return this.printNode(e, 0); + }); + const syntheticName = parsedArrayBindingElements.join("") + "Variable"; + + let arrayBindingStatement = + `var ${syntheticName} = ${this.printNode(right, 0)};\n`; + + parsedArrayBindingElements.forEach((e, index) => { + const statement = + this.getIden(identation) + + `${e} = ((java.util.List) ${syntheticName}).get(${index})`; + if (index < parsedArrayBindingElements.length - 1) { + arrayBindingStatement += statement + ";\n"; + } else { + arrayBindingStatement += statement; + } + }); + + return arrayBindingStatement; + } + + // --------------------------------------------------------------- + // setter for element-access assignments: a[b] = v + // --------------------------------------------------------------- + if (op === ts.SyntaxKind.EqualsToken && + left.kind === ts.SyntaxKind.ElementAccessExpression) { + // Collect base container and all keys (inner-most key is last). + const keys: any[] = []; + let baseExpr: any = null; + let cur: any = left; + while (ts.isElementAccessExpression(cur)) { + keys.unshift(cur.argumentExpression); // prepend + const expr = cur.expression; + if (!ts.isElementAccessExpression(expr)) { + baseExpr = expr; + break; + } + cur = expr; + } + + const containerStr = this.printNode(baseExpr, 0); + const keyStrs = keys.map(k => this.printNode(k, 0)); + + // Build GetValue(GetValue( ... )) chain for all but the last key. + let acc = containerStr; + for (let i = 0; i < keyStrs.length - 1; i++) { + acc = `${this.ELEMENT_ACCESS_WRAPPER_OPEN}${acc}, ${keyStrs[i]}${this.ELEMENT_ACCESS_WRAPPER_CLOSE}`; + } + + let prefixes = this.getBinaryExpressionPrefixes(node, identation); + prefixes = prefixes ? prefixes : ""; + + + const lastKey = keyStrs[keyStrs.length - 1]; + const rhs = this.printNode(right, 0); + + + + return `${prefixes}Helpers.addElementToObject(${acc}, ${lastKey}, ${rhs})`; + } + + if (op === ts.SyntaxKind.InKeyword) { + return `Helpers.inOp(${this.printNode(right, 0)}, ${this.printNode(left, 0)})`; + } + + const leftText = this.printNode(left, 0); + const rightText = this.printNode(right, 0); + + if (op === ts.SyntaxKind.PlusEqualsToken) { + return `${leftText} = Helpers.add(${leftText}, ${rightText})`; + } + + if (op === ts.SyntaxKind.MinusEqualsToken) { + return `${leftText} = Helpers.subtract(${leftText}, ${rightText})`; + } + + if (op in this.binaryExpressionsWrappers) { + const wrapper = this.binaryExpressionsWrappers[op]; + const open = wrapper[0]; + const close = wrapper[1]; + return `${open}${leftText}, ${rightText}${close}`; + } + + return undefined; + } + + + getObjectLiteralFromCallExpressionArguments(node) { + const res = []; + if (!node?.arguments) { + return res; + } + const args = node.arguments; + + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + if (arg.kind === ts.SyntaxKind.ObjectLiteralExpression) { + res.push(arg); + } else if (arg.kind === ts.SyntaxKind.CallExpression) { + const innerCallExp = arg; + const innerObjLiterals = this.getObjectLiteralFromCallExpressionArguments(innerCallExp); + res.push(...innerObjLiterals); + } + } + return res; + } + + getBinaryExpressionPrefixes(node, identation) { + let right = node?.right; + if (right?.kind === ts.SyntaxKind.AwaitExpression) { + // un pack await this.x() to this.x(), we don't care about await here + right = right.expression; + } + if (!right) { + return undefined; + } + if (right.kind === ts.SyntaxKind.ObjectLiteralExpression) { + const objVariables = this.getVarListFromObjectLiteralAndUpdateInPlace(right); + if (objVariables.length > 0) { + return objVariables.map( (v, i) => `${this.getIden( i > 0 ? identation : 0)}final Object ${this.getFinalVarName(v)} = ${this.getOriginalVarName(v)};`).join('\n') + "\n" + this.getIden(identation); + } + } else if (right.kind === ts.SyntaxKind.CallExpression) { + // search arguments recursively for object literals + // eg: a[x] = this.extend(this.extend(this.extend({'a':b}, c))) + const objectLiterals = this.getObjectLiteralFromCallExpressionArguments(right); + if (objectLiterals.length > 0) { + let finalVars = ''; + for (let i = 0; i < objectLiterals.length; i++) { + const objLiteral = objectLiterals[i]; + const objVariables = this.getVarListFromObjectLiteralAndUpdateInPlace(objLiteral); + if (objVariables.length > 0) { + finalVars += objVariables.map( (v, j) => `${this.getIden( j > 0 || i > 0 ? identation : 0)}final Object ${this.getFinalVarName(v)} = ${this.getOriginalVarName(v)};`).join('\n'); + } + } + return finalVars + "\n" + this.getIden(identation); + } + } + return undefined; + } + + getFinalVarName(varName: string): string { + if (this.ReservedKeywordsReplacements[varName]) { + varName = this.ReservedKeywordsReplacements[varName]; + } + if (varName.startsWith('final')) { + return varName; + } + return `final${this.capitalize(varName)}`; + } + + getOriginalVarName(name: string): string { + if (this.ReservedKeywordsReplacements[name]) { + name = this.ReservedKeywordsReplacements[name]; + } + return name; + } + + getObjectLiteralId(node): string { + const start = node.getStart(); + const end = node.getEnd(); + return `${start}-${end}`; + } + + createNewNodeForFinalVar(originalName: string): ts.Identifier { + const newNode = ts.factory.createIdentifier(this.getFinalVarName(originalName)); + newNode.getFullText = () => this.getFinalVarName(originalName); + return newNode; + } + + getVarListFromObjectLiteralAndUpdateInPlace(node): string[] { + // in java if we use an anonymous object literal put, we can't refer non final variables + // so here we collect them and then we add the wrapper final variables, eg: finalX = X; + // and we update the node in place to use finalX instead of X + let res = []; + + const nodeId = this.getObjectLiteralId(node); + + if (nodeId in this.varListFromObjectLiterals) { + return this.varListFromObjectLiterals[nodeId]; + } + + node.properties.forEach( (prop) => { + if (prop.initializer?.kind === ts.SyntaxKind.Identifier && prop.initializer.escapedText !== 'undefined' && !prop.initializer.escapedText.startsWith('null')) { + // if (this.ReassignedVars[prop.initializer.escapedText]) { + if (this.ReassignedVars[this.getVarKey(prop.initializer)]) { + res.push(prop.initializer.escapedText); + const finalName = this.getFinalVarName(prop.initializer.escapedText); + const newNode = ts.factory.createIdentifier(finalName); + prop.initializer = newNode; + // prop.initializer.escapedText = finalName; + } + } else if (prop.initializer?.kind === ts.SyntaxKind.CallExpression) { + // check if any of the args is an identifier that was reassigned + const callArgs = prop.initializer?.arguments ?? []; + const callExp = prop.initializer; + // transverseCallExpressionArguments(callExp); + // callArgs.forEach( (arg,i) => { + // if (arg.kind === ts.SyntaxKind.Identifier) { + // // if (this.ReassignedVars[arg.escapedText]) { + // if (this.ReassignedVars[this.getVarKey(arg)]) { + // res.push(arg.escapedText); + // const newNode = this.createNewNodeForFinalVar(arg.escapedText); + // arg = newNode; + // callExp.arguments[i] = newNode; + // } + // } else if (arg.kind === ts.SyntaxKind.CallExpression) { + // const innerCallExp = arg; + // innerCallExp.arguments.forEach( (innerArg,j) => { + // if (innerArg.kind === ts.SyntaxKind.Identifier) { + // if (this.ReassignedVars[this.getVarKey(innerArg)]) { + // res.push(innerArg.escapedText); + // const newNode = this.createNewNodeForFinalVar(innerArg.escapedText); + // innerArg = newNode; + // innerCallExp.arguments[j] = newNode; + // } + // } + // }); + // } + // }); + + const transverseCallExpressionArguments = (callExpression) => { + callExpression.arguments.forEach( (arg, i) => { + if (arg.kind === ts.SyntaxKind.Identifier) { + if (this.ReassignedVars[this.getVarKey(arg)]) { + res.push(arg.escapedText); + const newNode = this.createNewNodeForFinalVar(arg.escapedText); + arg = newNode; + callExpression.arguments[i] = newNode; + } + } else if (arg.kind === ts.SyntaxKind.CallExpression) { + const innerCallExp = arg; + transverseCallExpressionArguments(innerCallExp); + } + }); + }; + + transverseCallExpressionArguments(callExp); + + // handle side.toUpperCase() scenarios + if (callExp.expression?.kind === ts.SyntaxKind.PropertyAccessExpression) { + const propAccess = callExp.expression; + const leftSide = propAccess.expression; + if (leftSide.kind === ts.SyntaxKind.Identifier) { + if (this.ReassignedVars[this.getVarKey(leftSide)]) { + res.push(leftSide.escapedText); + const newNode = this.createNewNodeForFinalVar(leftSide.escapedText); + leftSide.escapedText = newNode.escapedText; + propAccess.expression = newNode; + } + } + } + } else if (prop.initializer?.kind === ts.SyntaxKind.BinaryExpression) { + // handle scenarios like : 'a': b + b +x + const binExp = prop.initializer; + const checkNode = (n) => { + if (!n) { + return n; + } + if (n.kind === ts.SyntaxKind.Identifier) { + if (this.ReassignedVars[this.getVarKey(n)]) { + res.push(n.escapedText); + const newNode = this.createNewNodeForFinalVar(n.escapedText); + return newNode; + } + } + return n; + }; + const traverseBinaryExpression = (be) => { + be.left = checkNode(be.left); + be.right = checkNode(be.right); + if (be?.left?.kind === ts.SyntaxKind.BinaryExpression) { + traverseBinaryExpression(be.left); + } + if (be?.right?.kind === ts.SyntaxKind.BinaryExpression) { + traverseBinaryExpression(be.right); + } + }; + traverseBinaryExpression(binExp); + + } else if (prop.initializer?.kind === ts.SyntaxKind.ElementAccessExpression) { + let left = prop.initializer.expression; + const right = prop.initializer.argumentExpression; + if (left.kind === ts.SyntaxKind.ElementAccessExpression) { + // handle x[a][b][c]... recursively + // let currentLeft = left; + while (left.kind === ts.SyntaxKind.ElementAccessExpression) { + // const innerLeft = currentLeft.expression; + left = left.expression; + } + + } + if (this.ReassignedVars[this.getVarKey(left)]) { + const leftName = left.escapedText; + const newLeftName = this.getFinalVarName(leftName); + // const newLeftNode = ts.factory.createIdentifier(newLeftName); + left.escapedText = newLeftName; + // finalVars = finalVars + `final Object ${newLeftName} = ${leftName};\n`; + res.push(leftName); + + } + if (right.kind === ts.SyntaxKind.Identifier) { + if (this.ReassignedVars[this.getVarKey(right)]) { + const rightName = right.escapedText; + const newRightName = this.getFinalVarName(rightName); + right.escapedText = newRightName; + res.push(rightName); + } + } + } + else if (prop.initializer?.kind === ts.SyntaxKind.ObjectLiteralExpression) { + const innerVars = this.getVarListFromObjectLiteralAndUpdateInPlace(prop.initializer); + res = res.concat(innerVars); + } + }); + this.varListFromObjectLiterals[nodeId] = [...new Set(res)]; + return [...new Set(res)]; + } + + printVariableDeclarationList(node, identation) { + const declaration = node.declarations[0]; + + let finalVars = ''; + if (declaration.initializer?.kind === ts.SyntaxKind.ObjectLiteralExpression) { + // iterator over object and collect variables + const varsList = this.getVarListFromObjectLiteralAndUpdateInPlace(declaration.initializer); + finalVars = varsList.map( v=> `final Object ${this.getFinalVarName(v)} = ${this.getOriginalVarName(v)};`).join('\n' + this.getIden(identation)); + // console.log('Collected vars:', varsList); + } else if (declaration.initializer?.kind === ts.SyntaxKind.CallExpression) { + const callExp = declaration.initializer; + const args = callExp.arguments ?? []; + let varObj = []; + args.forEach( (arg) => { + if (arg.kind === ts.SyntaxKind.ObjectLiteralExpression) { + const objVariables = this.getVarListFromObjectLiteralAndUpdateInPlace(arg); + varObj = varObj.concat(objVariables); + } + }); + if (varObj.length > 0) { + finalVars = varObj.map( v=> `final Object ${this.getFinalVarName(v)} = ${this.getOriginalVarName(v)};`).join('\n' + this.getIden(identation)); + } + } + + if ( + this.removeVariableDeclarationForFunctionExpression && + declaration?.initializer && + ts.isFunctionExpression(declaration.initializer) + ) { + return this.printNode(declaration.initializer, identation).trimEnd(); + } + + // array destructuring in variable declaration + if (declaration?.name.kind === ts.SyntaxKind.ArrayBindingPattern) { + const arrayBindingPattern = declaration.name; + const arrayBindingPatternElements = arrayBindingPattern.elements; + const parsedArrayBindingElements = arrayBindingPatternElements.map((e) => + this.printNode(e.name, 0) + ); + const syntheticName = parsedArrayBindingElements.join("") + "Variable"; + + let arrayBindingStatement = + `${this.getIden(identation)}var ${syntheticName} = ${this.printNode( + declaration.initializer, + 0 + )};\n`; + + parsedArrayBindingElements.forEach((e, index) => { + const statement = + this.getIden(identation) + + `var ${e} = ((java.util.List) ${syntheticName}).get(${index})`; + if (index < parsedArrayBindingElements.length - 1) { + arrayBindingStatement += statement + ";\n"; + } else { + arrayBindingStatement += statement; + } + }); + + return arrayBindingStatement; + } + + const isNew = + declaration?.initializer && + declaration.initializer.kind === ts.SyntaxKind.NewExpression; + const varToken = isNew ? "var " : this.VAR_TOKEN + " "; + + // handle `let x;` + if (!declaration.initializer) { + return ( + this.getIden(identation) + + "Object " + + this.printNode(declaration.name) + + " = " + + this.UNDEFINED_TOKEN + ); + } + + const parsedValue = this.printNode(declaration.initializer, identation).trimStart(); + if (parsedValue === this.UNDEFINED_TOKEN) { + let specificVarToken = "Object"; + if (this.INFER_VAR_TYPE) { + const variableType = (global as any).checker.typeToString( + (global as any).checker.getTypeAtLocation(declaration) + ); + if (this.VariableTypeReplacements[variableType]) { + specificVarToken = this.VariableTypeReplacements[variableType]; + } + } + return ( + this.getIden(identation) + + specificVarToken + + " " + + this.printNode(declaration.name) + + " = " + + parsedValue + ); + } + finalVars = finalVars.length > 0 ? this.getIden(identation) + finalVars + "\n" : finalVars; + return ( + finalVars + + this.getIden(identation) + + varToken + + this.printNode(declaration.name) + + " = " + + parsedValue + ); + } + + printThisKeyword(node, identation) { + + let current = node?.parent; + while (current) { + if (current.kind === ts.SyntaxKind.PropertyAssignment) { + const className = this.currentClassName; + return `${this.capitalize(className)}.this`; + } + current = current?.parent; + } + // if this.x() is inside a object a object literal and we need to add the class name + return this.THIS_TOKEN; + } + + transformPropertyAcessExpressionIfNeeded(node) { + const expression = node.expression; + + const leftSide = this.printNode(expression, 0); + const rightSide = node.name.escapedText; + + let rawExpression = undefined; + + switch (rightSide) { + case "length": { + const type = (global.checker as TypeChecker).getTypeAtLocation( + expression + ); + this.warnIfAnyType(node, (type as any).flags, leftSide, "length"); + rawExpression = this.isStringType((type as any).flags) + ? `((String)${leftSide}).length()` + : `${this.ARRAY_LENGTH_WRAPPER_OPEN}${leftSide}${this.ARRAY_LENGTH_WRAPPER_CLOSE}`; + break; + } + case "push": + rawExpression = `((java.util.List)${leftSide}).add`; + break; + } + return rawExpression; + } + + printCustomDefaultValueIfNeeded(node) { + if ( + ts.isArrayLiteralExpression(node) || + ts.isObjectLiteralExpression(node) || + ts.isStringLiteral(node) || + (ts as any).isBooleanLiteral(node) + ) { + return this.UNDEFINED_TOKEN; + } + + if (ts.isNumericLiteral(node)) { + return this.UNDEFINED_TOKEN; + } + + if ( + node?.escapedText === "undefined" && + (global as any).checker.getTypeAtLocation(node?.parent)?.flags === + ts.TypeFlags.Number + ) { + return this.UNDEFINED_TOKEN; + } + + return undefined; + } + + printFunctionBody(node, identation) { + // keep your existing default param initializer logic, but swap C# types for Java + const funcParams = node.parameters ?? []; + const isAsync = this.isAsyncFunction(node); + const initParams = []; + // if (funcParams.length > 0) { + const body = node.body.statements; + const first = body.length > 0 ? body[0] : []; + const remaining = body.length > 0 ? body.slice(1) : []; + let firstStatement = this.printNode(first, identation + 1); + + const remainingString = remaining + .map((statement) => this.printNode(statement, identation + 1)) + .join("\n"); + let offSetIndex = 0; + funcParams.forEach((param, i) => { + const initializer = param.initializer; + if (initializer) { + const index = i + offSetIndex; + // index = index < 0 ? 0 : i - 1; + const paramName = this.printNode(param.name, 0); + initParams.push(`Object ${paramName} = Helpers.getArg(optionalArgs, ${index}, ${this.printNode(initializer, 0)});`); + } else { + offSetIndex--; + } + }); + + if (initParams.length > 0) { + const defaultInitializers = + initParams.map((l) => this.getIden(identation + 1) + l).join("\n") + + "\n"; + const bodyParts = firstStatement.split("\n"); + const commentPart = bodyParts.filter((line) => this.isComment(line)); + const isComment = commentPart.length > 0; + if (isComment) { + const commentPartString = commentPart + .map((c) => this.getIden(identation + 1) + c.trim()) + .join("\n"); + const firstStmNoComment = bodyParts + .filter((line) => !this.isComment(line)) + .join("\n"); + firstStatement = + commentPartString + "\n" + defaultInitializers + firstStmNoComment; + } else { + firstStatement = defaultInitializers + firstStatement; + } + } + const blockOpen = this.getBlockOpen(identation); + const blockClose = this.getBlockClose(identation); + firstStatement = remainingString.length > 0 ? firstStatement + "\n" : firstStatement; + + if (isAsync) { + const finalWrapperVars = this.printFinalOutsideMethodVariableWrappersIfAny(node, identation) + "\n"; + const insideWrappers = this.printInsideMethodVariableWrappersIfAny(node, identation + 1) + "\n"; + const body = (firstStatement + remainingString).split("\n").map(line => this.getIden(identation) + line).join("\n"); + const asyncBody = this.getIden(identation + 1) + "return java.util.concurrent.CompletableFuture.supplyAsync(() -> {\n" + + insideWrappers + + body + "\n" + + this.getIden(identation + 1) + "});\n"; + return blockOpen + finalWrapperVars + asyncBody + blockClose; + + } + return blockOpen + firstStatement + remainingString + blockClose; + // } + + return super.printFunctionBody(node, identation); + } + + printInstanceOfExpression(node, identation) { + const left = node.left.escapedText; + const right = node.right.escapedText; + return this.getIden(identation) + `${left} instanceof ${right}`; + } + + printAwaitExpression(node, identation) { + const expression = this.printNode(node.expression, identation); + return `(${expression}).join()`; + } + + printAsExpression(node, identation) { + const type = node.type; + + if (type.kind === ts.SyntaxKind.AnyKeyword) { + return `((${this.VariableTypeReplacements['object']})${this.printNode( + node.expression, + identation + )})`; + } + + if (type.kind === ts.SyntaxKind.StringKeyword) { + return `((String)${this.printNode(node.expression, identation)})`; + } + + if (type.kind === ts.SyntaxKind.ArrayType) { + if (type.elementType.kind === ts.SyntaxKind.AnyKeyword) { + return `(java.util.List)(${this.printNode( + node.expression, + identation + )})`; + } + if (type.elementType.kind === ts.SyntaxKind.StringKeyword) { + return `(java.util.List)(${this.printNode( + node.expression, + identation + )})`; + } + } + + return this.printNode(node.expression, identation); + } + + printParameter(node, defaultValue = true) { + const name = this.printNode(node.name, 0); + const initializer = node.initializer; + + let type = this.printParameterType(node) || ""; + + if (defaultValue) { + if (initializer) { + const customDefaultValue = + this.printCustomDefaultValueIfNeeded(initializer); + const def = customDefaultValue + ? customDefaultValue + : this.printNode(initializer, 0); + type = def === "null" && type !== "Object" ? type + " " : type + " "; + return type + name + this.SPACE_DEFAULT_PARAM + "=" + this.SPACE_DEFAULT_PARAM + def; + } + return type + " " + name; + } + return name; + } + + printMethodParameters(node) { + const isAsyncMethod = this.isAsyncFunction(node); + const params = node.parameters.map(param => { + const isReassignedVar = this.ReassignedVars[this.getVarKey(param)]; + let printedParam = this.printParameter(param); + if (isAsyncMethod && isReassignedVar) { + const paramName = param.name.escapedText; + printedParam = printedParam.replace(paramName, `${paramName}2`); + // we have to rename the param, to then create the final wrapper nad finally inside method body bind the original name + } + return printedParam; + }); + const hasOptionalParameter = node.parameters.some(p => p.initializer !== undefined || p.questionToken !== undefined); + if (!hasOptionalParameter) { + return params.join(", "); + } + const paramsWithOptional = params.filter(param => param.indexOf('=') === -1); + paramsWithOptional.push('Object... optionalArgs'); + return paramsWithOptional.join(", "); + } + + printArrayLiteralExpression(node) { + // For Java: new ArrayList<>(Arrays.asList(elem1, elem2, ...)) + const elements = node.elements.map((e) => this.printNode(e)).join(", "); + return `${this.ARRAY_OPENING_TOKEN}${elements}${this.ARRAY_CLOSING_TOKEN}`; + } + + + printFinalOutsideMethodVariableWrappersIfAny(node, identation) { + const parameters = node?.parameters; + const finalVarWrappers = []; + + if (parameters) { + const isAsyncMethod = this.isAsyncFunction(node); + parameters.forEach(param => { + const isOptionalParam = param.initializer !== undefined || param.questionToken !== undefined; + if (!isOptionalParam) { + const isReassignedVar = this.ReassignedVars[this.getVarKey(param)]; + if (isAsyncMethod && isReassignedVar) { + const paramName = param.name.escapedText; + finalVarWrappers.push(this.getIden(identation + 1) + `final Object ${paramName}3 = ${paramName}2;`); + } + } + + }); + } + return finalVarWrappers.join("\n"); + } + + printInsideMethodVariableWrappersIfAny(node, identation) { + const parameters = node?.parameters; + const finalVarWrappers = []; + + if (parameters) { + const isAsyncMethod = this.isAsyncFunction(node); + parameters.forEach(param => { + const isOptionalParam = param.initializer !== undefined || param.questionToken !== undefined; + if (!isOptionalParam) { + const isReassignedVar = this.ReassignedVars[this.getVarKey(param)]; + if (isAsyncMethod && isReassignedVar) { + const paramName = param.name.escapedText; + finalVarWrappers.push(this.getIden(identation + 1) + `Object ${paramName} = ${paramName}3;`); + } + } + + }); + } + return finalVarWrappers.join("\n"); + } + + printMethodDeclaration(node, identation) { + + + const funcBody = this.printFunctionBody(node, identation); // print body first to get var reassignments filled + + let methodDef = this.printMethodDefinition(node, identation); + + methodDef += funcBody; + + return methodDef; + } + + printMethodDefinition(node, identation) { + let name = node.name.escapedText; + name = this.transformMethodNameIfNeeded(name); + + let returnType = this.printFunctionType(node); + + // let modifiers = this.printModifiers(node); + const defaultAccess = this.METHOD_DEFAULT_ACCESS ? this.METHOD_DEFAULT_ACCESS + " " : ""; + const modifiers = defaultAccess; + // modifiers = modifiers ? modifiers + " " : defaultAccess; + // modifiers = + // modifiers.indexOf("public") === -1 && + // modifiers.indexOf("private") === -1 && + // modifiers.indexOf("protected") === -1 + // ? defaultAccess + modifiers + // : modifiers; + + let parsedArgs = undefined; + + // const methodOverride = (this.getMethodOverride(node) as any); + // const isOverride = methodOverride !== undefined; + + // if (isOverride && (returnType === "Object" || returnType === "java.util.concurrent.CompletableFuture")) { + // returnType = this.printFunctionType(methodOverride); + // } + + // if (isOverride && node.parameters.length > 0) { + // const first = node.parameters[0]; + // const firstType = this.getType(first); + + // if (firstType === undefined) { + // const currentArgs = node.parameters; + // const parentArgs = methodOverride.parameters; + // parsedArgs = ""; + // parentArgs.forEach((param, index) => { + // const originalName = this.printNode(currentArgs[index].name, 0); + // const parsedArg = this.printParameteCustomName(param, originalName); + // parsedArgs += parsedArg; + // if (index < parentArgs.length - 1) { + // parsedArgs += ", "; + // } + // }); + // } + // } + + parsedArgs = parsedArgs ? parsedArgs : this.printMethodParameters(node); + + returnType = returnType ? returnType + " " : returnType; + + const methodToken = this.METHOD_TOKEN ? this.METHOD_TOKEN + " " : ""; + const signature = + this.getIden(identation) + + modifiers + + returnType + + methodToken + + name + + "(" + + parsedArgs + + ")"; + + return this.printNodeCommentsIfAny(node, identation, signature); + } + + printArrayIsArrayCall(_node, _identation, parsedArg = undefined) { + return `((${parsedArg} instanceof java.util.List) || (${parsedArg}.getClass().isArray()))`; + } + + printObjectKeysCall(_node, _identation, parsedArg = undefined) { + return `new java.util.ArrayList(((java.util.Map)${parsedArg}).keySet())`; + } + + printObjectValuesCall(_node, _identation, parsedArg = undefined) { + return `new java.util.ArrayList(((java.util.Map)${parsedArg}).values())`; + } + + printJsonParseCall(_node, _identation, parsedArg = undefined) { + return `Helpers.parseJson(${parsedArg})`; + } + + printJsonStringifyCall(_node, _identation, parsedArg = undefined) { + return `Helpers.json(${parsedArg})`; + } + + printPromiseAllCall(_node, _identation, parsedArg = undefined) { + return `Helpers.promiseAll(${parsedArg})`; + } + + printMathFloorCall(_node, _identation, parsedArg = undefined) { + return `(Math.floor(Double.parseDouble((${parsedArg}).toString())))`; + } + + printMathRoundCall(_node, _identation, parsedArg = undefined) { + return `Math.round(Double.parseDouble(${parsedArg}.toString()))`; + } + + printMathCeilCall(_node, _identation, parsedArg = undefined) { + return `Math.ceil(Double.parseDouble(${parsedArg}.toString()))`; + } + + printNumberIsIntegerCall(_node, _identation, parsedArg = undefined) { + return `((${parsedArg} instanceof Integer) || (${parsedArg} instanceof Long))`; + } + + printArrayPushCall(_node, _identation, name = undefined, parsedArg = undefined) { + return `((java.util.List)${name}).add(${parsedArg})`; + } + + printIncludesCall(_node, _identation, name = undefined, parsedArg = undefined) { + return `${name}.contains(${parsedArg})`; + } + + printIndexOfCall(_node, _identation, name = undefined, parsedArg = undefined) { + return `${this.INDEXOF_WRAPPER_OPEN}${name}, ${parsedArg}${this.INDEXOF_WRAPPER_CLOSE}`; + } + + printSearchCall(_node, _identation, name = undefined, parsedArg = undefined) { + return `((String)${name}).indexOf(${parsedArg})`; + } + + printStartsWithCall(_node, _identation, name = undefined, parsedArg = undefined) { + return `((String)${name}).startsWith(((String)${parsedArg}))`; + } + + printEndsWithCall(_node, _identation, name = undefined, parsedArg = undefined) { + return `((String)${name}).endsWith(((String)${parsedArg}))`; + } + + printTrimCall(_node, _identation, name = undefined) { + return `((String)${name}).trim()`; + } + + printJoinCall(_node, _identation, name = undefined, parsedArg = undefined) { + // assumes List + return `String.join((String)${parsedArg}, (java.util.List)${name})`; + } + + printSplitCall(_node, _identation, name = undefined, parsedArg = undefined) { + return `new java.util.ArrayList(java.util.Arrays.asList(((String)${name}).split((String)${parsedArg})))`; + } + + printConcatCall(_node, _identation, name = undefined, parsedArg = undefined) { + return `Helpers.concat(${name}, ${parsedArg})`; + } + + printToFixedCall(_node, _identation, name = undefined, parsedArg = undefined) { + return `toFixed(${name}, ${parsedArg})`; + } + + printToStringCall(_node, _identation, name = undefined) { + return `String.valueOf(${name})`; + } + + printToUpperCaseCall(_node, _identation, name = undefined) { + return `((String)${name}).toUpperCase()`; + } + + printToLowerCaseCall(_node, _identation, name = undefined) { + return `((String)${name}).toLowerCase()`; + } + + printShiftCall(_node, _identation, name = undefined) { + return `((java.util.List)${name}).get(0)`; + } + + printReverseCall(_node, _identation, name = undefined) { + return `java.util.Collections.reverse((java.util.List)${name})`; + } + + printPopCall(_node, _identation, name = undefined) { + return `((java.util.List)${name}).get(((java.util.List)${name}).size()-1)`; + } + + printAssertCall(_node, _identation, parsedArgs) { + return `assert ${parsedArgs}`; + } + + printSliceCall(_node, _identation, name = undefined, parsedArg = undefined, parsedArg2 = undefined) { + if (parsedArg2 === undefined) { + parsedArg2 = "null"; + } + return `Helpers.slice(${name}, ${parsedArg}, ${parsedArg2})`; + } + + printReplaceCall(_node, _identation, name = undefined, parsedArg = undefined, parsedArg2 = undefined) { + return `Helpers.replace((String)${name}, (String)${parsedArg}, (String)${parsedArg2})`; + } + + printReplaceAllCall(_node, _identation, name = undefined, parsedArg = undefined, parsedArg2 = undefined) { + return `Helpers.replaceAll((String)${name}, (String)${parsedArg}, (String)${parsedArg2})`; + } + + printPadEndCall(_node, _identation, name, parsedArg, parsedArg2) { + // You can point this to a runtime helper if you have one + return `Helpers.padEnd((String)${name}, ((Number)${parsedArg}).intValue(), ((String)${parsedArg2}).charAt(0))`; + } + + printPadStartCall(_node, _identation, name, parsedArg, parsedArg2) { + return `Helpers.padStart((String)${name}, ((Number)${parsedArg}).intValue(), ((String)${parsedArg2}).charAt(0))`; + } + + printDateNowCall(_node, _identation) { + return "System.currentTimeMillis()"; + } + + printLengthProperty(node, _identation, _name = undefined) { + const leftSide = this.printNode(node.expression, 0); + const type = (global.checker as TypeChecker).getTypeAtLocation(node.expression); + this.warnIfAnyType(node, (type as any).flags, leftSide, "length"); + return this.isStringType((type as any).flags) + ? `((String)${leftSide}).length()` + : `${this.ARRAY_LENGTH_WRAPPER_OPEN}${leftSide}${this.ARRAY_LENGTH_WRAPPER_CLOSE}`; + } + + // For ++/--, prefer native Java operators rather than the C# ref-helpers + printPostFixUnaryExpression(node, identation) { + const { operand, operator } = node; + const leftSide = this.printNode(operand, 0); + const op = this.PostFixOperators[operator]; + if (op === "--") { + return `${leftSide}--`; + } + return `${leftSide}++`; + } + + printPrefixUnaryExpression(node, identation) { + const { operand, operator } = node; + if (operator === ts.SyntaxKind.ExclamationToken) { + return this.PrefixFixOperators[operator] + this.printCondition(node.operand, 0); + } + const leftSide = this.printNode(operand, 0); + if (operator === ts.SyntaxKind.PlusToken) { + return `+(${leftSide})`; + } else if (operator === ts.SyntaxKind.MinusToken) { + return `Helpers.opNeg(${leftSide})`; + } + return super.printPrefixUnaryExpression(node, identation); + } + + printConditionalExpression(node, _identation) { + const condition = this.printCondition(node.condition, 0); + const whenTrue = this.printNode(node.whenTrue, 0); + const whenFalse = this.printNode(node.whenFalse, 0); + return `((${condition})) ? ${whenTrue} : ${whenFalse}`; + } + + printDeleteExpression(node, _identation) { + const object = this.printNode(node.expression.expression, 0); + const key = this.printNode(node.expression.argumentExpression, 0); + return `((java.util.Map)${object}).remove((String)${key})`; + } + + printThrowStatement(node, identation) { + if (node.expression.kind === ts.SyntaxKind.Identifier) { + return ( + this.getIden(identation) + + this.THROW_TOKEN + + " " + + this.printNode(node.expression, 0) + + this.LINE_TERMINATOR + ); + } + if (node.expression.kind === ts.SyntaxKind.NewExpression) { + const expression = node.expression; + const argumentsExp = expression?.arguments ?? []; + const parsedArg = argumentsExp.map((n) => this.printNode(n, 0)).join(",") ?? ""; + const newExpression = this.printNode(expression.expression, 0); + if (expression.expression.kind === ts.SyntaxKind.Identifier) { + const id = expression.expression; + const symbol = (global as any).checker.getSymbolAtLocation(expression.expression); + if (symbol) { + const declarations = + (global as any).checker.getDeclaredTypeOfSymbol(symbol).symbol?.declarations ?? []; + const isClassDeclaration = declarations.find( + (l) => + l.kind === ts.SyntaxKind.InterfaceDeclaration || + l.kind === ts.SyntaxKind.ClassDeclaration + ); + if (isClassDeclaration) { + return ( + this.getIden(identation) + + `${this.THROW_TOKEN} ${this.NEW_TOKEN} ${id.escapedText}((String)${parsedArg}) ${this.LINE_TERMINATOR}` + ); + } else { + return ( + this.getIden(identation) + + `Helpers.throwDynamicException(${id.escapedText}, ${parsedArg});return null;` + ); + } + } + return ( + this.getIden(identation) + + `${this.THROW_TOKEN} ${this.NEW_TOKEN} ${newExpression}(${parsedArg}) ${this.LINE_TERMINATOR}` + ); + } else if (expression.expression.kind === ts.SyntaxKind.ElementAccessExpression) { + return this.getIden(identation) + `Helpers.throwDynamicException(${newExpression}, ${parsedArg});`; + } + return super.printThrowStatement(node, identation); + } + } + + csModifiers = {}; + + printPropertyAccessModifiers(node) { + let modifiers = this.printModifiers(node); + if (modifiers === "") { + modifiers = this.defaultPropertyAccess; + } + // add type + let typeText = "Object"; + if (node.type) { + typeText = this.getType(node); + if (!typeText) { + if (node.type.kind === ts.SyntaxKind.AnyKeyword) { + typeText = this.OBJECT_KEYWORD + " "; + } + } + } + return modifiers + " " + typeText + " "; + } + + + printObjectLiteralExpression(node, identation) { + const objectBody = this.printObjectLiteralBody(node, identation); + const formattedObjectBody = objectBody ? "\n" + objectBody + "\n" + this.getIden(identation) : objectBody; + return this.OBJECT_OPENING + formattedObjectBody + this.OBJECT_CLOSING; + } + + printObjectLiteralBody(node, identation) { + const body = node.properties.map((p) => this.printNode(p, identation+1)).join("\n"); + // body = body.replaceAll('this.', `${name}.this.`); + return body; + } + + printForStatement(node, identation) { + const initializer = this.printNode(node.initializer, 0).replace('Object ', 'var '); + const condition = this.printNode(node.condition, 0); + const incrementor = this.printNode(node.incrementor, 0); + + const forStm = this.getIden(identation) + + this.FOR_TOKEN + " " + + this.CONDITION_OPENING + + initializer + "; " + condition + "; " + incrementor + + this.CONDITION_CLOSE + + this.printBlock(node.statement, identation); + return this.printNodeCommentsIfAny(node, identation, forStm); + } + + printReturnStatement(node, identation) { + const leadingComment = this.printLeadingComments(node, identation); + let trailingComment = this.printTraillingComment(node, identation); + trailingComment = trailingComment ? " " + trailingComment : trailingComment; + let exp = node.expression; + if (exp && exp.kind === ts.SyntaxKind.AsExpression && (exp.expression.kind === ts.SyntaxKind.ObjectLiteralExpression || ts.SyntaxKind.CallExpression)) { + exp = exp.expression; // go over something like return {} as SomeType + } + let finalVars = ''; + if (exp && exp?.kind === ts.SyntaxKind.ObjectLiteralExpression) { + const varsList = this.getVarListFromObjectLiteralAndUpdateInPlace(exp); + finalVars = varsList.map( v=> `final Object ${this.getFinalVarName(v)} = ${this.getOriginalVarName(v)};`).join('\n' + this.getIden(identation)); + } else if (exp && exp?.kind === ts.SyntaxKind.CallExpression) { + // const callExpr = exp; + // const callExprArgs = exp.arguments; + // if (callExprArgs && callExprArgs.length > 0) { + // callExprArgs.forEach( (arg, i) => { + // if (arg.kind === ts.SyntaxKind.ObjectLiteralExpression) { + // const varsList = this.getVarListFromObjectLiteralAndUpdateInPlace(arg); + // finalVars = finalVars + varsList.map( v=> `final Object ${this.getFinalVarName(v)} = ${this.getOriginalVarName(v)};`).join('\n' + this.getIden(identation)); + // } + // }); + // } + const objectsFromCall = this.getObjectLiteralFromCallExpressionArguments(exp); + for (const objLiteral of objectsFromCall) { + const varsList = this.getVarListFromObjectLiteralAndUpdateInPlace(objLiteral); + if (varsList.length > 0) { + finalVars = finalVars + varsList.map( v=> `final Object ${this.getFinalVarName(v)} = ${this.getOriginalVarName(v)};`).join('\n' + this.getIden(identation)); + } + } + } else if (exp && exp?.kind === ts.SyntaxKind.ArrayLiteralExpression) { + const elements = exp?.elements ?? []; + for (const element of elements) { + if (element.kind === ts.SyntaxKind.CallExpression) { + const objectsFromCall = this.getObjectLiteralFromCallExpressionArguments(element); + for (const objLiteral of objectsFromCall) { + const varsList = this.getVarListFromObjectLiteralAndUpdateInPlace(objLiteral); + if (varsList.length > 0) { + finalVars = finalVars + varsList.map( v=> `final Object ${this.getFinalVarName(v)} = ${this.getOriginalVarName(v)};`).join('\n' + this.getIden(identation)); + } + } + } + } + } + let rightPart = exp ? (' ' + this.printNode(exp, identation)) : ''; + rightPart = rightPart.trim(); + rightPart = rightPart ? ' ' + rightPart + this.LINE_TERMINATOR : this.LINE_TERMINATOR; + finalVars = finalVars.length > 0 ? this.getIden(identation) + finalVars + "\n" : finalVars; + return leadingComment + finalVars + this.getIden(identation) + this.RETURN_TOKEN + rightPart + trailingComment; + } +} diff --git a/src/transpiler.ts b/src/transpiler.ts index f2f0f81..f92da4d 100644 --- a/src/transpiler.ts +++ b/src/transpiler.ts @@ -7,6 +7,7 @@ import * as path from "path"; import { Logger } from './logger.js'; import { Languages, TranspilationMode, IFileExport, IFileImport, ITranspiledFile, IInput } from './types.js'; import { GoTranspiler } from './goTranspiler.js'; +import { JavaTranspiler } from './javaTranspiler.js'; const __dirname_mock = currentPath; @@ -49,12 +50,14 @@ export default class Transpiler { phpTranspiler: PhpTranspiler; csharpTranspiler: CSharpTranspiler; goTranspiler: GoTranspiler; + javaTranspiler: JavaTranspiler; constructor(config = {}) { this.config = config; const phpConfig = config["php"] || {}; const pythonConfig = config["python"] || {}; const csharpConfig = config["csharp"] || {}; const goConfig = config["go"] || {}; + const javaConfig = config["java"] || {}; if ("verbose" in config) { Logger.setVerboseMode(Boolean(config['verbose'])); @@ -64,6 +67,7 @@ export default class Transpiler { this.phpTranspiler = new PhpTranspiler(phpConfig); this.csharpTranspiler = new CSharpTranspiler(csharpConfig); this.goTranspiler = new GoTranspiler(goConfig); + this.javaTranspiler = new JavaTranspiler(javaConfig); } setVerboseMode(verbose: boolean) { @@ -129,6 +133,9 @@ export default class Transpiler { case Languages.Go: transpiledContent = this.goTranspiler.printNode(global.src, -1); break; + case Languages.Java: + transpiledContent = this.javaTranspiler.printNode(global.src, -1); + break; } let imports = []; let exports = []; @@ -229,6 +236,14 @@ export default class Transpiler { return this.transpile(Languages.CSharp, TranspilationMode.ByPath, path); } + transpileJava(content): ITranspiledFile { + return this.transpile(Languages.Java, TranspilationMode.ByContent, content); + } + + transpileJavaByPath(path): ITranspiledFile { + return this.transpile(Languages.Java, TranspilationMode.ByPath, path); + } + transpileGoByPath(path): ITranspiledFile { return this.transpile(Languages.Go, TranspilationMode.ByPath, path); } @@ -282,6 +297,8 @@ export default class Transpiler { return Languages.CSharp; case "go": return Languages.Go; + case "java": + return Languages.Java; } } } diff --git a/src/types.ts b/src/types.ts index 7ba9aaf..2302bfa 100644 --- a/src/types.ts +++ b/src/types.ts @@ -41,7 +41,8 @@ enum Languages { Python, Php, CSharp, - Go + Go, + Java } enum TranspilationMode { diff --git a/tests/integration/java/.gitattributes b/tests/integration/java/.gitattributes new file mode 100644 index 0000000..f91f646 --- /dev/null +++ b/tests/integration/java/.gitattributes @@ -0,0 +1,12 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + +# Binary files should be left untouched +*.jar binary + diff --git a/tests/integration/java/.gitignore b/tests/integration/java/.gitignore new file mode 100644 index 0000000..1b6985c --- /dev/null +++ b/tests/integration/java/.gitignore @@ -0,0 +1,5 @@ +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build diff --git a/tests/integration/java/app/build.gradle.kts b/tests/integration/java/app/build.gradle.kts new file mode 100644 index 0000000..a06000a --- /dev/null +++ b/tests/integration/java/app/build.gradle.kts @@ -0,0 +1,43 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This generated file contains a sample Java application project to get you started. + * For more details on building Java & JVM projects, please refer to https://docs.gradle.org/8.12/userguide/building_java_projects.html in the Gradle documentation. + */ + +plugins { + // Apply the application plugin to add support for building a CLI application in Java. + application +} + +repositories { + // Use Maven Central for resolving dependencies. + mavenCentral() +} + +dependencies { + // Use JUnit Jupiter for testing. + testImplementation(libs.junit.jupiter) + + testRuntimeOnly("org.junit.platform:junit-platform-launcher") + + // This dependency is used by the application. + implementation(libs.guava) +} + +// Apply a specific Java toolchain to ease working on different environments. +java { + toolchain { + languageVersion = JavaLanguageVersion.of(20) + } +} + +application { + // Define the main class for the application. + mainClass = "org.example.App" +} + +tasks.named("test") { + // Use JUnit Platform for unit tests. + useJUnitPlatform() +} diff --git a/tests/integration/java/app/src/main/java/org/example/App.java b/tests/integration/java/app/src/main/java/org/example/App.java new file mode 100644 index 0000000..26f2635 --- /dev/null +++ b/tests/integration/java/app/src/main/java/org/example/App.java @@ -0,0 +1,11 @@ +/* + * This source file was generated by the Gradle 'init' task + */ +package org.example; + +public class App { + public static void main(String[] args) { + var test = new Test(); + test.test(); + } +} diff --git a/tests/integration/java/app/src/main/java/org/example/Helpers.java b/tests/integration/java/app/src/main/java/org/example/Helpers.java new file mode 100644 index 0000000..8d49383 --- /dev/null +++ b/tests/integration/java/app/src/main/java/org/example/Helpers.java @@ -0,0 +1,708 @@ +package org.example; + +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicReference; + +@SuppressWarnings({"unchecked", "rawtypes"}) +public class Helpers { + + // tmp most of these methods are going to be re-implemented in the future to be more generic and efficient + + public static Object normalizeIntIfNeeded(Object a) { + if (a == null) return null; + if (a instanceof Integer) { + return Long.valueOf(((Integer) a).longValue()); + } + return a; + } + + // C# had "ref object a" + returns new value; in Java we mimic with AtomicReference + public static Object postFixIncrement(AtomicReference a) { + Object val = a.get(); + if (val instanceof Long) { + a.set(((Long) val) + 1L); + } else if (val instanceof Integer) { + a.set(((Integer) val) + 1); + } else if (val instanceof Double) { + a.set(((Double) val) + 1.0); + } else if (val instanceof String) { + a.set(((String) val) + 1); + } else { + return null; + } + return a.get(); + } + + public static Object postFixDecrement(AtomicReference a) { + Object val = a.get(); + if (val instanceof Long) { + a.set(((Long) val) - 1L); + } else if (val instanceof Integer) { + a.set(((Integer) val) - 1); + } else if (val instanceof Double) { + a.set(((Double) val) - 1.0); + } else { + return null; + } + return a.get(); + } + + public static Object prefixUnaryNeg(AtomicReference a) { + Object val = a.get(); + if (val instanceof Long) { + a.set(-((Long) val)); + } else if (val instanceof Integer) { + a.set(-((Integer) val)); + } else if (val instanceof Double) { + a.set(-((Double) val)); + } else if (val instanceof String) { + return null; + } else { + return null; + } + return a.get(); + } + + public static Object prefixUnaryPlus(AtomicReference a) { + Object val = a.get(); + if (val instanceof Long) { + a.set(+((Long) val)); + } else if (val instanceof Integer) { + a.set(+((Integer) val)); + } else if (val instanceof Double) { + a.set(+((Double) val)); + } else if (val instanceof String) { + return null; + } else { + return null; + } + return a.get(); + } + + public static Object plusEqual(Object a, Object value) { + a = normalizeIntIfNeeded(a); + value = normalizeIntIfNeeded(value); + + if (value == null) return null; + if (a instanceof Long && value instanceof Long) { + return (Long) a + (Long) value; + } else if (a instanceof Integer && value instanceof Integer) { + return (Integer) a + (Integer) value; + } else if (a instanceof Double && value instanceof Double) { + return (Double) a + (Double) value; + } else if (a instanceof String && value instanceof String) { + return ((String) a) + ((String) value); + } else { + return null; + } + } + + // NOTE: In C# this used JsonHelper.Deserialize((string)json). + // In Java, wire up your preferred JSON lib and return Map/List accordingly. + public Object parseJson(Object json) { + // placeholder: return the string itself (or plug in Jackson/Gson here) + return (json instanceof String) ? (String) json : null; + } + + public static boolean isTrue(Object value) { + if (value == null) return false; + + value = normalizeIntIfNeeded(value); + + if (value instanceof Boolean) { + return (Boolean) value; + } else if (value instanceof Long) { + return ((Long) value) != 0L; + } else if (value instanceof Integer) { + return ((Integer) value) != 0; + } else if (value instanceof Double) { + return ((Double) value) != 0.0; + } else if (value instanceof String) { + return !((String) value).isEmpty(); + } else if (value instanceof List) { + return !((List) value).isEmpty(); + } else if (value instanceof Map) { + // C# returned true for any IDictionary; we can mirror that or check emptiness. + return true; + } else { + return false; + } + } + + public static boolean isNumber(Object number) { + if (number == null) return false; + try { + Double.parseDouble(String.valueOf(number)); + return true; + } catch (Exception e) { + return false; + } + } + + public static boolean isEqual(Object a, Object b) { + try { + if (a == null && b == null) return true; + if (a == null || b == null) return false; + + // If types differ and neither is numeric, they're not equal + if (!a.getClass().equals(b.getClass()) && !(isNumber(a) && isNumber(b))) { + return false; + } + + if (IsInteger(a) && IsInteger(b)) { + return toLong(a).equals(toLong(b)); + } + if ((a instanceof Long) && (b instanceof Long)) { + return ((Long) a).longValue() == ((Long) b).longValue(); + } + if (a instanceof Double || b instanceof Double) { + return toDouble(a) == toDouble(b); + } + if (a instanceof Float || b instanceof Float) { + return toFloat(a) == toFloat(b); + } + if (a instanceof String && b instanceof String) { + return ((String) a).equals((String) b); + } + if (a instanceof Boolean && b instanceof Boolean) { + return ((Boolean) a).booleanValue() == ((Boolean) b).booleanValue(); + } + // decimal cases mapped via BigDecimal compare + if (isNumber(a) && isNumber(b)) { + return new BigDecimal(String.valueOf(a)).compareTo(new BigDecimal(String.valueOf(b))) == 0; + } + return false; + } catch (Exception ignored) { + return false; + } + } + + public static boolean isGreaterThan(Object a, Object b) { + if (a != null && b == null) return true; + if (a == null || b == null) return false; + + a = normalizeIntIfNeeded(a); + b = normalizeIntIfNeeded(b); + + if (a instanceof Long && b instanceof Long) { + return ((Long) a) > ((Long) b); + } else if (a instanceof Integer && b instanceof Integer) { + return ((Integer) a) > ((Integer) b); + } else if (isNumber(a) || isNumber(b)) { + return toDouble(a) > toDouble(b); + } else if (a instanceof String && b instanceof String) { + return ((String) a).compareTo((String) b) > 0; + } else { + return false; + } + } + + public static boolean isLessThan(Object a, Object b) { + return !isGreaterThan(a, b) && !isEqual(a, b); + } + + public static boolean isGreaterThanOrEqual(Object a, Object b) { + return isGreaterThan(a, b) || isEqual(a, b); + } + + public static boolean isLessThanOrEqual(Object a, Object b) { + return isLessThan(a, b) || isEqual(a, b); + } + + public static Object mod(Object a, Object b) { + if (a == null || b == null) return null; + a = normalizeIntIfNeeded(a); + b = normalizeIntIfNeeded(b); + if (a instanceof String || a instanceof Long || a instanceof Integer || a instanceof Double) { + return toDouble(a) % toDouble(b); + } + return null; + } + + public static Object add(Object a, Object b) { + a = normalizeIntIfNeeded(a); + b = normalizeIntIfNeeded(b); + + if (a instanceof Long && b instanceof Long) { + return ((Long) a) + ((Long) b); + } else if (a instanceof Double || b instanceof Double) { + return toDouble(a) + toDouble(b); + } else if (a instanceof String && b instanceof String) { + return ((String) a) + ((String) b); + } else { + return null; + } + } + + public static String add(String a, String b) { + return a + b; + } + + public static String add(String a, Object b) { + return a + String.valueOf(b); + } + + public static Object subtract(Object a, Object b) { + a = normalizeIntIfNeeded(a); + b = normalizeIntIfNeeded(b); + + if (a instanceof Long && b instanceof Long) { + return ((Long) a) - ((Long) b); + } else if (a instanceof Integer && b instanceof Integer) { + return ((Integer) a) - ((Integer) b); + } else if (a instanceof Double || b instanceof Double) { + return toDouble(a) - toDouble(b); + } else { + return null; + } + } + + public static int subtract(int a, int b) { return a - b; } + + public float subtract(float a, float b) { return a - b; } + + public static Object divide(Object a, Object b) { + a = normalizeIntIfNeeded(a); + b = normalizeIntIfNeeded(b); + if (a == null || b == null) return null; + + if (a instanceof Long && b instanceof Long) { + // C# integer division; keep behavior + return ((Long) a) / ((Long) b); + } else if (a instanceof Double && b instanceof Double) { + return ((Double) a) / ((Double) b); + } else { + return toDouble(a) / toDouble(b); + } + } + + public static Object multiply(Object a, Object b) { + a = normalizeIntIfNeeded(a); + b = normalizeIntIfNeeded(b); + if (a == null || b == null) return null; + + if (a instanceof Long && b instanceof Long) { + return ((Long) a) * ((Long) b); + } + double res = toDouble(a) * toDouble(b); + if (IsInteger(res)) { + return (long) res; + } else { + return res; + } + } + + public static int getArrayLength(Object value) { + if (value == null) return 0; + + if (value instanceof List) { + return ((List) value).size(); + } else if (value instanceof String) { + return ((String) value).length(); // fallback + } else if (value.getClass().isArray()) { + return java.lang.reflect.Array.getLength(value); + } else { + return 0; + } + } + + public static boolean IsInteger(Object value) { + if (value == null) return false; + + if (value instanceof Byte || value instanceof Short || + value instanceof Integer || value instanceof Long || + value instanceof java.util.concurrent.atomic.AtomicInteger || + value instanceof java.util.concurrent.atomic.AtomicLong) { + return true; + } + + if (value instanceof Float || value instanceof Double || value instanceof BigDecimal) { + BigDecimal d = new BigDecimal(String.valueOf(value)); + return d.stripTrailingZeros().scale() <= 0; + } + return false; + } + + public static Object mathMin(Object a, Object b) { + if (a == null || b == null) return null; + double first = toDouble(a); + double second = toDouble(b); + return (first < second) ? a : b; + } + + public static Object mathMax(Object a, Object b) { + if (a == null || b == null) return null; + double first = toDouble(a); + double second = toDouble(b); + return (first > second) ? a : b; + } + + public static int getIndexOf(Object str, Object target) { + if (str instanceof List) { + return ((List) str).indexOf(target); + } else if (str instanceof String && target instanceof String) { + return ((String) str).indexOf((String) target); + } else { + return -1; + } + } + + public static Object parseInt(Object a) { + try { + return toLong(a); + } catch (Exception ignored) { + return null; + } + } + + public static Object parseFloat(Object a) { + try { + return toDouble(a); + } catch (Exception ignored) { + return null; + } + } + + // generic getValue to replace elementAccesses + public Object getValue(Object a, Object b) { return GetValue(a, b); } + + public static Object GetValue(Object value2, Object key) { + if (value2 == null || key == null) return null; + + // Strings: index access + if (value2 instanceof String) { + String str = (String) value2; + int idx = toInt(key); + if (idx < 0 || idx >= str.length()) return null; + return String.valueOf(str.charAt(idx)); + } + + Object value = value2; + if (value2.getClass().isArray()) { + // Convert to List + int len = Array.getLength(value2); + List list = new ArrayList<>(len); + for (int i = 0; i < len; i++) list.add(Array.get(value2, i)); + value = list; + } + + if (value instanceof Map) { + Map m = (Map) value; + if (key instanceof String && m.containsKey(key)) { + return m.get(key); + } + return null; + } else if (value instanceof List) { + int idx = toInt(key); + List list = (List) value; + if (idx < 0 || idx >= list.size()) return null; + return list.get(idx); + } else if (key instanceof String) { + // Try Java field or getter + String name = (String) key; + try { + // Field + Field f = value.getClass().getField(name); + f.setAccessible(true); + return f.get(value2); + } catch (Exception ignored) {} + try { + // Getter + String mName = "get" + Character.toUpperCase(name.charAt(0)) + name.substring(1); + Method m = value.getClass().getMethod(mName); + return m.invoke(value2); + } catch (Exception ignored) {} + return null; + } else { + return null; + } + } + + public CompletableFuture> promiseAll(Object promisesObj) { return PromiseAll(promisesObj); } + + public static CompletableFuture> PromiseAll(Object promisesObj) { + List promises = (List) promisesObj; + List> futures = new ArrayList<>(); + for (Object p : promises) { + if (p instanceof CompletableFuture) { + futures.add((CompletableFuture) p); + } + } + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .thenApply(v -> { + List out = new ArrayList<>(futures.size()); + for (CompletableFuture f : futures) { + try { + out.add(f.get()); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + return out; + }); + } + + public static String toStringOrNull(Object value) { + if (value == null) return null; + return (String) value; + } + + public void throwDynamicException(Object exception, Object message) { + Exception ex = NewException((Class) exception, (String) message); + // In C#, this constructed but didn't throw; mimic behavior: + // If you actually want to throw: + // throw ex; + } + + // This function is the salient bit here + public Object newException(Object exception, Object message) { + return NewException((Class) exception, (String) message); + } + + public static Exception NewException(Class exception, String message) { + try { + Constructor ctor = exception.getConstructor(String.class); + return (Exception) ctor.newInstance(message); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static Object toFixed(Object number, Object decimals) { + double n = toDouble(number); + int d = toInt(decimals); + BigDecimal bd = new BigDecimal(Double.toString(n)).setScale(d, RoundingMode.HALF_UP); + return bd.doubleValue(); + } + + public static Object callDynamically(Object obj, Object methodName, Object[] args) { + if (args == null) args = new Object[]{}; + if (args.length == 0) { + // C# code injected a null arg to help binder; Java doesn't need it. + // But to mirror behavior, we won't add a null here. + } + String name = (String) methodName; + Method m = findMethod(obj.getClass(), name, args.length); + try { + m.setAccessible(true); + return m.invoke(obj, args); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static Object callDynamicallyAsync(Object obj, Object methodName, Object[] args) { + if (args == null) args = new Object[]{}; + String name = (String) methodName; + Method m = findMethod(obj.getClass(), name, args.length); + try { + m.setAccessible(true); + Object res = m.invoke(obj, args); + if (res instanceof CompletableFuture) { + return ((CompletableFuture) res).get(); + } + return res; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public boolean inOp(Object obj, Object key) { return InOp(obj, key); } + + public static boolean InOp(Object obj, Object key) { + if (obj == null || key == null) return false; + + if (obj instanceof List) { + return ((List) obj).contains(key); + } else if (obj instanceof Map) { + if (key instanceof String) { + return ((Map) obj).containsKey(key); + } else return false; + } else { + return false; + } + } + + public String slice(Object str2, Object idx1, Object idx2) { return Slice(str2, idx1, idx2); } + + public static String Slice(Object str2, Object idx1, Object idx2) { + if (str2 == null) return null; + String str = (String) str2; + int start = (idx1 != null) ? toInt(idx1) : -1; + + if (idx2 == null) { + if (start < 0) { + int innerStart = str.length() + start; + innerStart = Math.max(innerStart, 0); + return str.substring(innerStart); + } else { + if (start > str.length()) return ""; + return str.substring(start); + } + } else { + int end = toInt(idx2); + if (start < 0) start = str.length() + start; + if (end < 0) end = str.length() + end; + if (start < 0) start = 0; + if (end > str.length()) end = str.length(); + if (start > end) start = end; + return str.substring(start, end); + } + } + + public static Object concat(Object a, Object b) { + if (a == null && b == null) return null; + if (a == null) return b; + if (b == null) return a; + + if (a instanceof List && b instanceof List) { + List result = new ArrayList((List) a); + result.addAll((List) b); + return result; + } else if (a instanceof List && !(b instanceof List)) { + List result = new ArrayList((List) a); + result.add(b); + return result; + } else if (!(a instanceof List) && b instanceof List) { + List result = new ArrayList(); + result.add(a); + result.addAll((List) b); + return result; + } else { + throw new IllegalStateException("Unsupported types for concatenation."); + } + } + + // --------- helpers --------- + + private static Method findMethod(Class cls, String name, int argCount) { + // try exact arg count first + for (Method m : cls.getDeclaredMethods()) { + if (m.getName().equals(name) && m.getParameterCount() == argCount) { + return m; + } + } + // search up the hierarchy + Class cur = cls.getSuperclass(); + while (cur != null) { + for (Method m : cur.getDeclaredMethods()) { + if (m.getName().equals(name) && m.getParameterCount() == argCount) { + return m; + } + } + cur = cur.getSuperclass(); + } + // fallback: first by name + for (Method m : cls.getDeclaredMethods()) { + if (m.getName().equals(name)) return m; + } + throw new RuntimeException("Method not found: " + name + " with " + argCount + " args on " + cls.getName()); + } + + private static Long toLong(Object o) { + if (o instanceof Long) return (Long) o; + if (o instanceof Integer) return ((Integer) o).longValue(); + if (o instanceof Double) return ((Double) o).longValue(); + if (o instanceof Float) return ((Float) o).longValue(); + if (o instanceof BigDecimal) return ((BigDecimal) o).longValue(); + if (o instanceof String) return Long.parseLong((String) o); + return Long.parseLong(String.valueOf(o)); + } + + private static int toInt(Object o) { + if (o instanceof Integer) return (Integer) o; + if (o instanceof Long) return ((Long) o).intValue(); + if (o instanceof Double) return ((Double) o).intValue(); + if (o instanceof Float) return ((Float) o).intValue(); + if (o instanceof BigDecimal) return ((BigDecimal) o).intValue(); + if (o instanceof String) return Integer.parseInt((String) o); + return Integer.parseInt(String.valueOf(o)); + } + + private static double toDouble(Object o) { + if (o instanceof Double) return (Double) o; + if (o instanceof Float) return ((Float) o).doubleValue(); + if (o instanceof Long) return ((Long) o).doubleValue(); + if (o instanceof Integer) return ((Integer) o).doubleValue(); + if (o instanceof BigDecimal) return ((BigDecimal) o).doubleValue(); + if (o instanceof String) return Double.parseDouble((String) o); + return Double.parseDouble(String.valueOf(o)); + } + + private static float toFloat(Object o) { + if (o instanceof Float) return (Float) o; + if (o instanceof Double) return ((Double) o).floatValue(); + if (o instanceof Long) return ((Long) o).floatValue(); + if (o instanceof Integer) return ((Integer) o).floatValue(); + if (o instanceof BigDecimal) return ((BigDecimal) o).floatValue(); + if (o instanceof String) return Float.parseFloat((String) o); + return Float.parseFloat(String.valueOf(o)); + } + + + public static String replaceAll(Object baseString, Object search, Object replacement) { + if (baseString == null) { + return null; + } + String s = String.valueOf(baseString); + String find = (search == null) ? "" : String.valueOf(search); + if (find.isEmpty()) { + // Avoid weird behavior of replacing "" (would insert between every char) + return s; + } + String repl = (replacement == null) ? "" : String.valueOf(replacement); + return s.replace(find, repl); // literal (non-regex) replacement + } + + public static Object getArg(Object[] v, int index, Object def) { + if (v.length <= index) { + return def; + } + return v[index]; + } + + + @SuppressWarnings("unchecked") + public static void addElementToObject(Object target, Object... args) { + if (target instanceof Map map) { + if (args.length != 2) + throw new IllegalArgumentException("Map requires (key, value)"); + ((Map) map).put(args[0], args[1]); + return; + } + + if (target instanceof List list) { + List l = (List) list; + if (args.length == 1) { + l.add(args[0]); // append + return; + } + if (args.length == 2 && args[0] instanceof Integer idx) { + int i = idx; + if (i < 0 || i > l.size()) { + throw new IndexOutOfBoundsException("Index " + i + " out of bounds [0," + l.size() + "]"); + } + l.add(i, args[1]); + return; + } + throw new IllegalArgumentException( + "List requires (value) to append or (index(Integer), value) to insert"); + } + + throw new IllegalArgumentException("Target is neither Map nor List: " + typeName(target)); + } + + private static String typeName(Object o) { + return (o == null) ? "null" : o.getClass().getName(); + } +} diff --git a/tests/integration/java/app/src/main/java/org/example/Transpilable.java b/tests/integration/java/app/src/main/java/org/example/Transpilable.java new file mode 100644 index 0000000..fc08750 --- /dev/null +++ b/tests/integration/java/app/src/main/java/org/example/Transpilable.java @@ -0,0 +1,77 @@ +package org.example; +class Second +{ + public String myClassProperty = "classProp"; + public boolean myBoolProp = false; + + public Object stringifyNumber(Object arg) + { + return String.valueOf(arg); + } +} +class Test +{ + public void test() + { + Object a = 1; + Object b = 2; + Object c = Helpers.add(a, b); + System.out.println(c); // should print 3 + Object s1 = "a"; + Object s2 = "b"; + Object s3 = Helpers.add(s1, s2); + Object stringVar = null; + stringVar = "hello"; + System.out.println(stringVar); // should print "hello" + System.out.println(s3); // should print "ab" + Object x = false; + if (Helpers.isTrue(x)) + { + System.out.println("x is true"); + } else + { + System.out.println("x is false"); // should print "x is false" + } + var instance = new Second(); + System.out.println(instance.stringifyNumber(4)); // should print 4 + System.out.println(instance.myClassProperty); // should print "classProp" + if (Helpers.isTrue(Helpers.isEqual(instance.myBoolProp, false))) + { + System.out.println("myBoolProp is false"); // should print "myBoolProp is false" + } + Object arr = new java.util.ArrayList(java.util.Arrays.asList(1, 2, 3, 4)); + System.out.println(Helpers.getArrayLength(arr)); // should print 4 + Object first = Helpers.GetValue(arr, 0); + System.out.println(first); // should print 1 + Object dict = new java.util.HashMap() {{ + put( "a", "b" ); + }}; + System.out.println(Helpers.GetValue(dict, "a")); // should print "b" + Object i = 0; + for (var w = 0; Helpers.isLessThan(w, 10); w++) + { + i = Helpers.add(i, 1); + } + System.out.println(String.valueOf(i)); // should print 10 + Object list2 = new java.util.ArrayList(java.util.Arrays.asList(1, 2, 3, 4, 5)); + java.util.Collections.reverse((java.util.List)list2); + System.out.println(Helpers.GetValue(list2, 0)); // should print 5 + //should delete key from dict + Object dict2 = new java.util.HashMap() {{ + put( "a", 1 ); + put( "b", 2 ); + }}; + ((java.util.Map)dict2).remove((String)"a"); + Object dictKeys = new java.util.ArrayList(((java.util.Map)dict2).keySet()); + System.out.println(Helpers.getArrayLength(dictKeys)); // should print 1 + System.out.println(Helpers.GetValue(dictKeys, 0)); // should print "b" + Object firstConcat = new java.util.ArrayList(java.util.Arrays.asList("a", "b")); + Object secondConcat = new java.util.ArrayList(java.util.Arrays.asList("c", "d")); + Object both = Helpers.concat(firstConcat, secondConcat); + System.out.println(Helpers.getArrayLength(both)); // should print 4 + System.out.println(Helpers.GetValue(both, 2)); // should print "c" + Object baseString = "aabba"; + Object replacedAllString = Helpers.replaceAll((String)baseString, (String)"a", (String)""); + System.out.println(replacedAllString); // should print "bb" + } +} diff --git a/tests/integration/java/app/src/test/java/org/example/AppTest.java b/tests/integration/java/app/src/test/java/org/example/AppTest.java new file mode 100644 index 0000000..f14c54a --- /dev/null +++ b/tests/integration/java/app/src/test/java/org/example/AppTest.java @@ -0,0 +1,9 @@ +/* + * This source file was generated by the Gradle 'init' task + */ +package org.example; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class AppTest {} diff --git a/tests/integration/java/gradle.properties b/tests/integration/java/gradle.properties new file mode 100644 index 0000000..377538c --- /dev/null +++ b/tests/integration/java/gradle.properties @@ -0,0 +1,5 @@ +# This file was generated by the Gradle 'init' task. +# https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties + +org.gradle.configuration-cache=true + diff --git a/tests/integration/java/gradle/libs.versions.toml b/tests/integration/java/gradle/libs.versions.toml new file mode 100644 index 0000000..aae5ec4 --- /dev/null +++ b/tests/integration/java/gradle/libs.versions.toml @@ -0,0 +1,10 @@ +# This file was generated by the Gradle 'init' task. +# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format + +[versions] +guava = "33.3.1-jre" +junit-jupiter = "5.11.1" + +[libraries] +guava = { module = "com.google.guava:guava", version.ref = "guava" } +junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" } diff --git a/tests/integration/java/gradle/wrapper/gradle-wrapper.jar b/tests/integration/java/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..a4b76b9 Binary files /dev/null and b/tests/integration/java/gradle/wrapper/gradle-wrapper.jar differ diff --git a/tests/integration/java/gradle/wrapper/gradle-wrapper.properties b/tests/integration/java/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..cea7a79 --- /dev/null +++ b/tests/integration/java/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/tests/integration/java/gradlew b/tests/integration/java/gradlew new file mode 100755 index 0000000..f3b75f3 --- /dev/null +++ b/tests/integration/java/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original 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. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# 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 ;; #( + MSYS* | 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 + if ! command -v java >/dev/null 2>&1 + then + 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 +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# 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"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/tests/integration/java/gradlew.bat b/tests/integration/java/gradlew.bat new file mode 100644 index 0000000..9d21a21 --- /dev/null +++ b/tests/integration/java/gradlew.bat @@ -0,0 +1,94 @@ +@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 +@rem SPDX-License-Identifier: Apache-2.0 +@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=. +@rem This is normally unused +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% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +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% equ 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! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/tests/integration/java/settings.gradle.kts b/tests/integration/java/settings.gradle.kts new file mode 100644 index 0000000..0b4c4fe --- /dev/null +++ b/tests/integration/java/settings.gradle.kts @@ -0,0 +1,14 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * For more detailed information on multi-project builds, please refer to https://docs.gradle.org/8.12/userguide/multi_project_builds.html in the Gradle documentation. + */ + +plugins { + // Apply the foojay-resolver plugin to allow automatic download of JDKs + id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" +} + +rootProject.name = "java" +include("app") diff --git a/tests/integration/source/transpilable.ts b/tests/integration/source/transpilable.ts index d2407a8..b4f1203 100644 --- a/tests/integration/source/transpilable.ts +++ b/tests/integration/source/transpilable.ts @@ -10,6 +10,30 @@ class Second { class Test { + public functionWithOptionals(a: string, c: number | undefined = undefined, d = 1) { + console.log(a); + if (c !== undefined) { + console.log(c); + } + if (d !== undefined) { + console.log(d); + } + } + + public getValue(x) { + return x; + } + + public testJavaScope() { + const newObject = { + 'a': this.getValue(5), + 'b': this.getValue(this.getValue(this.getValue(2))) + }; + + console.log(newObject['a']); // should print 5 + console.log(newObject['b']); // should print 2 + } + public test() { var a = 1; var b = 2; @@ -75,6 +99,20 @@ class Test { const baseString = "aabba"; const replacedAllString = baseString.replaceAll("a", ""); console.log(replacedAllString); // should print "bb" + + this.functionWithOptionals("hello"); + this.functionWithOptionals("hello", 5); + this.functionWithOptionals("hello", 5, 1); + + const list3 = ["empty"] + list3[0] = "first" + console.log(list3[0]) // should print "first" + + const dict3 = {} + dict3["key"] = "value" + console.log(dict3["key"]) // should print "value" + + this.testJavaScope(); } } diff --git a/tests/integration/test.ts b/tests/integration/test.ts index 687af3e..def2634 100644 --- a/tests/integration/test.ts +++ b/tests/integration/test.ts @@ -9,6 +9,7 @@ const PY_TRANSPILABLE_FILE = "./tests/integration/py/transpilable.py"; const PHP_TRANSPILABLE_FILE = "./tests/integration/php/transpilable.php"; const CS_TRANSPILABLE_FILE = "./tests/integration/cs/transpilable.cs"; const GO_TRANSPILABLE_FILE = "./tests/integration/go/transpilable.go"; +const JAVA_TRANSPILABLE_FILE = "./tests/integration/java/app/src/main/java/org/example/Transpilable.java"; const TS_FILE = "./tests/integration/source/init.ts"; @@ -16,14 +17,15 @@ const PY_FILE = "./tests/integration/py/init.py"; const PHP_FILE = "./tests/integration/php/init.php"; const CS_FILE = "./tests/integration/cs"; const GO_FILE = "./tests/integration/go"; +const JAVA_FILE = "./tests/integration/java/" + const langConfig = [ { language: "csharp", async: true }, - - { + { language: "python", async: true }, @@ -35,6 +37,9 @@ const langConfig = [ language: "go", async: true }, + { + language: "java", + }, ] function transpileTests() { @@ -56,6 +61,8 @@ function transpileTests() { let csharp = 'namespace tests;\n' + result[0].content; csharp = csharp.replace('class Test', 'partial class Test'); + let java = `package org.example;\n` + result[4].content; + java = java.replaceAll(/public class (\w+)/g, 'class $1'); const goImports = [ '\n', @@ -70,6 +77,7 @@ function transpileTests() { writeFileSync(PY_TRANSPILABLE_FILE, pythonAsync); writeFileSync(CS_TRANSPILABLE_FILE, csharp); writeFileSync(GO_TRANSPILABLE_FILE, go); + writeFileSync(JAVA_TRANSPILABLE_FILE, java); } function runCommand(command) { @@ -94,6 +102,26 @@ function runCommand(command) { }); } +function runCommandJava(command) { + return new Promise((resolve, reject) => { + exec(command, (error, stdout, stderr) => { + if (stderr !== undefined || stderr !== null) { + stderr = stderr.replace('Debugger attached.\nWaiting for the debugger to disconnect...\n', ''); + } + if (stderr.startsWith("Debugger listening") && stderr.includes("For help, see: https://nodejs.org/en/docs/inspector")) { + stderr = undefined; + } + if (error) { + reject(error); + return; + } + if (stderr) console.error(stderr); + + resolve(stdout.trim()); + }); + }); +} + async function runTS() { const command = "node --no-warnings --loader ts-node/esm " + TS_FILE; const result = await runCommand(command); @@ -133,6 +161,22 @@ async function runGO() { return result; } +async function runJava() { + try { + // ./tests/integration/java/gradlew -p ./tests/integration/java/ run + const buildCommand = JAVA_FILE + "gradlew -p" + JAVA_FILE + " build"; + await runCommandJava(buildCommand); + const run = JAVA_FILE + "gradlew -p" + JAVA_FILE + " -q --console=plain run"; + const result = await runCommandJava(run); + console.log(blue("Executed JAVA")) + return result; + } catch (e) { + console.error(red("Error running JAVA:"), e); + throw e; + } + +} + async function main() { transpileTests(); @@ -141,10 +185,11 @@ async function main() { runPHP(), runPy(), runCS(), - runGO() + runGO(), + runJava(), ]; const results = await Promise.all(promises); - const [ts, php, py, cs, go]: any = results; + const [ts, php, py, cs, go, java]: any = results; let success = true; if (php !== ts) { @@ -165,10 +210,14 @@ async function main() { compareOutputs("GO", ts, go); } + if (java !== ts) { + success = false; + compareOutputs("JAVA", ts, java); + } + if (success) { console.log(green("Integration tests passed!")); } - }